Как стать автором
Обновить

Мой восьмилетний квест по оцифровке 45 видеокассет. Часть 1

Время на прочтение9 мин
Количество просмотров43K
Всего голосов 27: ↑24 и ↓3+21
Комментарии75

Комментарии 75

Звучит как-то сложно. Есть ощущение что автор просто не разобрался с премьером или выбрал неправильные инструменты для решения задачи. Лично я сейчас для любительского монтажа использую GoPro Quick. Это позволяет сократить время работы над типичным материалом с дней до десятков минут. Один фиг, никто никогда не будет смотреть часы домашнего видео. Короткие ролики на 2-3 минуты еще есть шансы что кто-то глянет.
Ну вот запись свадьбы бить на ролики как то не комфильно :) А то будут вместо жениха и невесты смотреть на ужимки пьяных гостей (ну ведь прикольней же)
p.s. Цифровал на чем было — TV-тюнер «Manli Home TV» (подключение тюльпанами) + Fly2000TV + комп из разряда celeron 1.3 с диском 40Гб. Вот это было приключение :)
поддерживаю, тот же рассинхрон аудио надо было решить автоматизацией скорости аудио, хотя почти уверен, что проблема бы решилась граббингом видео на мощном компе. Да и выбор формата экспорта это первое, что нужно решить заранее, ну либо сохранять проект. В целом, вполне можно было бы хранить видео на ютюбе с приватными ссылками и их передавать родственникам
НЛО прилетело и опубликовало эту надпись здесь
Как её обходить?
Оцифровывать аппаратным устройством (обычно это dv видеокамера со сквозным каналом)
Кстати, как-то в прошлом был удивлён, что простенькая JVC miniDV камера (у которой как раз была возможность сквозного канала тюльпан-DV) оцифровала сигнал с видеомагнитофона лучше, чем специально предназначенный AverMedia.
НЛО прилетело и опубликовало эту надпись здесь
В ПО для видеозахвата аудиопоток делался ведомым, на который навешивалась плавающая частота кадров видео.

Интересно, сколько просмотров от родственников наберут эти видео? ;)

НЛО прилетело и опубликовало эту надпись здесь
Если фоточки доживут до следующего поколения, то наверняка к ним будет интерес — посмотреть на деда/бабушку/прадеда/прабабушку и других родственников.
недавно для себя обнаружил что нейросети могут очень хорошо улучшать качество старых снимков
возможно вам будет интересно, посмотрите например приложение для android remini
пример до/после
image


Проблема в том, что нейросети не улучшают, а додумывают

Скажите пожалуйста, с каким размером изображения работает эта программа? 20 мегапикселей обработает ли?
недавно сравнивал различные суперскейлеры, гигапиксель неплох, но требует видюху нвидиа, esrgan тоже норм
НЛО прилетело и опубликовало эту надпись здесь

А можете дать ссылки на то, чем пользовались?

https://play.google.com/store/apps/details?id=com.bigwinepot.nwdn.international


Сейчас ещё пытаюсь вот этот проект майкрософта завести локально https://github.com/microsoft/Bringing-Old-Photos-Back-to-Life хочу попробовать пакетную обработку всех фото запустить

Интересная ссылка, спасибо!
Локально запустилось, пока не обрабатывает тестовый файл с царапинами «а» по неясной причине и обычный «с» из-за нехватки памяти, карта GTX1660 6Gb.
После прочтения вашего комментария тоже попробовал и неудачно. У вас с тех пор что-то получилось? За это время они добавили обработку только на CPU.
У меня нет кассет, но редактирование видео хотя бы из путешествий, несомнено огромная проблема. Отсняты сотни гигабай и обработать все это очень трудно. Или почти не возможно.
Главное правило, которое я для себя сделал это ролик должен быть не более 8 минут. Иначе никто смотреть не будет. Да и сам не будешь смотреть. Вот до 8 минут еще можно.
Пожтому привозя видео из путешествий я делаю один ролик — один день.
Куда их потом девать тот еще вопрос… Диски ломаются, облако дорого…
Куда их потом девать тот еще вопрос…

Думается, винчестер на "холодном хранении" из современных технологий самое надёжное. (Стример — ещё дороже, флешки/ссд — на зарядку каждые два месяца, оптика сд/двд — помирает потихой и новая делается одноразовой, оптика бд — ещё не показала себя на масштабе десятилетий).


Глядя на огромные ящики miniDV кассет родителей и смонтированные с них двухчасовые диски, которые ни разу не отсматривались, а потом добавляющиеся ещё в очередь на оцифровку ящики фоток от бабушек, прабабушек и прапра-..., решил снимать для интернета немного, а остальное писать в голову. Всё же потом хочется вспоминать то, как был где-то и делал что-то, а не как смотрел на всё это через экран размером с почтовую марку, стараясь записать кадр получше...

НЛО прилетело и опубликовало эту надпись здесь
Обычные-то BD фиг купишь, не то что м-диск (в оффлайне конечно, на eBay любой каприз за бешеные деньги)… Да и приводы распространены гораздо меньше, чем dvd — через какое-то время можно остаться с горой дисков, которые не в чем играть.

Честно говоря, у меня БДшники купленные на начале эпохи уже почти все перемёрли, что в комп, что отдельные. СД/ДВДшная техника пока живёт и новодел и старая, так что ей как-то доверия больше. Всё же плотности меньше, допуски ширше, главное не покупать совсем уж мусорные болванки.

Еще раз упомяну GoPro Quick. Я просто закидываю видео с гоупрохи на планшет и собираю ролик буквально на коленке, где угодно. Скажем, получается что-то подобное в почти автоматическом режиме:
www.youtube.com/watch?v=77R8Dfu0FmA

У друзей были круглые глаза, когда я собрал ролик с поездки, пока ждали заказ в кафе.

Оригиналы хранить даже смысла не вижу. Ну или по крайней мере удалять спустя полгода — год

Сильную долю в аудио-треке оно само распознаёт как-то? Или надо как-то дорожку разметить, и оно потом будет помогать склейки расставлять по этой разметке?

Да, распознает. По крайней мере там где-то полсотни трэков уже идут с приложением, их можно использовать при монтаже. Мне пока этого хватает) Разметить тоже можно — «подсветить» интересные места

У меня для вас плохие новости — и до 8 минут никто смотреть не будет.


Впрочем, если у вас интересный материал — "клип о том, как меня сожрала анаконда но я смог выбраться с другой стороны" то это другое дело. Но такие записи редкость.

НЛО прилетело и опубликовало эту надпись здесь
Так же как и автор не являюсь понимающим в теме, но интересно: а в чем у автора была проблема и как её решают?
НЛО прилетело и опубликовало эту надпись здесь
Насколько я помню, была галочка наподобие «выставлять дробную частоту кадров для синхронизации с аудио». По умолчанию частота кадров в формирующемся видео задавалась равной 30Гц. А надо было незначительно меньше, причем каждый раз по-разному незначительно (зависело от длины видео). У меня это помогало.

Меня особенно удивило, что вместо поиска причины проблемы (сходить поспрошать на реддите хотя бы) автор маниакально пытался экспериментировать вслепую. "Чего там думать, прыгать надо".

Ох уж эти китайские видеозахватки в USB — прям как конфеты у Форрест Гампа, никогда не догадаешься, что внутри, даже если бренд японский. Настолько рандомные штуки, что поиск по ютубу "Как решить проблему с драйверами на Easycap 007" выдаёт видос, как кто-то заливает его бензином и поджигает.


Совет начинающим оцифровщикам — берите Canopus или на крайняк Datavideo, в пределах СНГ ещё применим очень бюджетный вариант с очень хорошим качеством — Behold TV и их тюнеры на любой вкус и размер. С учётом, что в регионах России почти любая "оцифровочная контора" вам вернёт видео разрешения 240х240 и жёваную плёнку со словами «Ну а что вы хотели, это же с кассеты!», сделать всё дома будет проще, дешевле и надёжнее.

НЛО прилетело и опубликовало эту надпись здесь

О, кажется, здесь хорошее место, чтобы немного поныть на эту тему. Я вот тоже хочу оцифровать свои кассеты, но не могу определиться, как именно это сделать.


Видео чересстрочное, и значит, наверное, нужно провести деинтерлейсинг, но мне попадалось инфа, что алгоритмы деинтерлейсинга могут вносить искажения, поэтому стрёмно. Или на самом деле всё это чепуха и можно зарядить yadif2x без опаски?


Если не проводить деинтерлейсинг (благо H.264 поддерживает чересстрочность), то возникает другая прооблема. Исходный видеопоток, отдаваемый видеозахваткой, закодирован в YUV 4:2:2, который аппаратно поддерживается примерно никем, а если его перекодировать YUV 4:2:0, то из-за смешивания цветоразностных компонентов соседних полей цвета убиваются в хлам:


Демонстрация проблемы

Я так понимаю, автор просто забил болт на эти проблемы, и эта его «профессиональная оцифровка» даже 60fps умудрилась продолбать? Может, кто-то на Хабре тоже раздумывал, чё с этим получше сделать? Или воткнуть yadif2x и не париться?




Хотя с деинтерлейсингом вспомнил ещё одну проблему. Имеется тупой (во всех смыслах) телевизор DEXP, который умеет вопроизводить видеофайлы с USB, и если видео имеет частоту больше 30 кадров в секунду, то телевизор, похоже, считает его чересстрочным и пытается проводить собственный деинтерлейсинг — в итоге видео получается с кучей искажений, мерцанием и почему-то замедленной скоростью. А деинтерлейсить в 25/30фпс — себя не уважать

Все правильно — захват в 60 fps.
Используя EasyCap и OBS (там есть настройка устранения чересстрочности, я ставил Смешивание или Смешивание 2х) можно получить вполне терпимый результат. Но нужно понимать — в VHS используется полукадровая запись — пишутся по очереди четные и нечетные строки каждого кадра, поэтому при резких движениях будет гребенка или размытие.
Ну а потом в VSDC FreeVideoEditor пожно подрезать и сжать в H264.
У меня вот на EasyCap (одной из кучи их нонейм версий) было качество лучше, чем на USB тюнере от Avermedia, дававшем чересстрочную ёлочку по-моему даже на статичной картинке, когда подавал сигнал dvd-плеера с заставкой.
Захват идет в 25/30 кадров/с (50 и 60 полуполей). Есть вариант превращения в 50/60 Гц, но способ требует дополнительных действий. Любой деинтерлейс — это потеря потеря оригинального изображения. Я предпочитал сохранять в формат, в котором интерлейс сохраняется в оригинале. Деинтерлейсом занимается плеер или устройство так как считает это правильным. Единственный нюанс — правильно выставить первое поле при кодировании.

В вебе, считайте, деинтерлейс отсутствует как класс. На ютубе довольно много видео, которое было залито интерлейсным, а затем с компа смотришь его уже как-бы как прогрессив, но с гребёнками.

Загрузка в веб — это частный случай видео с большими потерями качества, к которому надо подготовить видео. Берем архивный оригинал и прогоняем быстро с аппаратным ускорением через HandBrake в нужный формат.

Так и с приложениями бывают косяки, так что для конечного пользователя (условно — вашей бабушки) лучше сделать видео в прогрессиве, чтобы у неё точно показывало хорошо.

Да мне по барабану. Тебя одного это беспокоит.

Кажется, это было даже не про то, что кто-то увидит эти видосы, а про то, что это вообще делается:) Десятки часов, как мой брат грызет клавиатуру или играется с котом?.. На свалку.
А видео детского садика сына?;)

Туда же. Никто это смотреть всё равно не будет.

через 100 лет будут.
Нет. посмотрите фотографии дедушек с бабушками. непонятные лица в непонятных местах. Для них эти фотографии еще имеют ценность. Для вас это просто рандомные давно мертвые люди.
Не согласен и согласен. Пересказы через поколения терялись, потому что слова не записывали, а фото были редкостью. Сейчас же у вас есть возможность снимать историю своих корней. Через 500 лет ваши предки будут офигивать от того, как жили мы тогда. Суть очень простая. Чем больше информации сохраняется из прошлого, тем легче человечеству корректировать свои нормы. Да, и без вас будет полно роликов. Однако, развивать любовь к человечеству легче через родные фильмы.
На самом деле это не ваша история, а всеобщая. Многие старинные фото — это чьи то семейные и любительские. Окно в прошлое и материал для историков.
Эти фотографии бесполезны и безынтересны, когда вырваны из контекста.

Напротив, если сохранить контекст — указать имена, места, занятия, описать сюжет — такие фотографии приобретают огромную важность для всей Википедии (не только для энциклопедических статей).
Если фотография распечатана, то уже через 100 лет она будет ценным свидетельством материальной культуры: что носят, как носят, какие прически, боевая раскраска, модные татуировки, вообще всё что в кадр попадет из окружения и без всякого указания контекста.
Если же она осталась битами в облаке, то конечно, будет бесполезна и безынтересна из-за не существования ни в каком виде во времена заинтересованных лиц.
то уже через 100 лет она будет ценным свидетельством материальной культуры: что носят, как носят, какие прически


Даже 20 лет вполне достаточно. В то время снимали не так много, большая часть любительских снимков была низкого качества и уже утрачена по разным причинам.
Поэтому хорошо снятые фото того времени пользуются большой популярностью, причем не у историков (им еще рано), а у современников (ностальгия :)

Если же она осталась битами в облаке, то конечно, будет бесполезна и безынтересна


Только из опасения, что это фейк.

Решил проблему оцифровки на работе покупкой Matrox RT.X2, аудиокартой ESI Juli@ и магнитофоном S-VHS с авито за 5000р. Максимум который можно выжать — выжал. Рассинхрона звука нет, видеопоток в максимальном качестве. Сжатием занимается Квадра М4000. Вот со сжатием больше всего проблем выдалось — любое сжатие на интерлейсном видео выдает так себе результат — либо слишком большой файл, либо шакалы.

Блин, на авито бывшие телевизионщики оцифровывают буквально за бутылку на работе.
Личный опыт.
Не мучайтесь как автор.
А кто во что кодирует видео с miniDV кассет? А то «как есть» хранить накладно.
MPEG2 наиболее близок к нему. Главная особенность DV — все кадры ключевые.
А я взял любимую видеокамеру MiniDV Canon с захватом с композита и весь VHS с видика прекрасно перевёл в DV-формат. Оцифровывает весьма качественно.
Это и было самое простое решение — просто цифровые видеокамерры с захватом с композита решают все проблемы, и не надо специализированные карты захвата которые делают непонятно что и с непонятным качеством.
Проблема в том, что в Европу такие камеры почти не ввозились из-за дополнительного налога на «пишущие устройства» — т.е. аналога налога на болванки Михалкова. А через Европу много камер ввозилось в СНГ и поэтому камер со сквозным каналом на рынке было мало.
Ну вот мне повезло. :)
А через Европу много камер ввозилось в СНГ


Первую камеру я купил в 1997, вторую — в 2007.
Несмотря на десятилетнюю разницу — обе камеры попали к нам с Ближнего Востока (ОАЭ).
У меня обе камеры были без In — одна куплена в ЕС, вторая у местного поставщика, у которого вариантов с In в прайсе не было.
В начале тысячелетия использовали для захвата с VHS карту Pinnacle, рассинхрона не было. Сама карта еще поддерживала аппаратные эффекты. Они вроде живы до сих пор.
Еще лет 10 назад решили так же записать эксклюзивное видео с детства, обычный USB Capture 007 (вроде), sony vegas, и тоже не было проблем, кроме нарезки — снимали тогда все, у кого есть камера, и все подряд, а хотелось сделать что-то без 4 минут съемки потолка.

А кто то в курсе, какой хард/софт у коммерческих предложений?
Сейчас по-моему все это ушло в историю Попросили тут отцифровать кассетку, пол дня матерился с актуальным Адобом, потом плюнул, достал старый ноутбук ( Пентиум 3, XP) и все замечательно перегнал. И с оптическим распознаванием смены сцен кстати.
НЛО прилетело и опубликовало эту надпись здесь
К меня такая же проблема — ящик VHS кассет, которые нужно оцифровать. Спасибо автору, что поднял эту тему.
В 2008 году без каких либо проблем оцифровал все видео с обычных кассет через AverTV Hybrid MCE 316 Plus, и MiniDV с родной для них камеры HDR-HC7E.

Поскольку снимал видео правильно, выбирая интересные моменты и избегая долгих и монотонных планов -перемонтировать почти ничего не пришлось. Файлы видео записал на диски и раздал родственникам. Ну и себе, конечно, оставил, вместе с резервными копиями
Кассеты выбросил.

В 2020 году пришла мысль — вот на… зачем я выбросил все кассеты?
«Маргадон, один пистолет надо было зарядить!» (с)

(Часть кассет надо было оставить — так как сам процесс съемки и просмотра начал вызывать ностальгию :)
Цифровал одну кассету и на чужом видаке, проблема была не с рассинхроном видео, а с ВЧ-писком (фиговые провода, от видео сифонило). Проблема решилась вторым прогоном с отключенным тюльпаном видео и последующей заменой дорожки в редакторе.
У меня тоже был рассинхрон при оцифровке. Причина, видимо, контейнер .avi и VirtualDub, который в оригинале только с ним и работает. Написал тогда скрипт на Пайтоне, который, исходя из предположения о линейной зависимости между временем записи и отклонением от идеала, вычислял коэффициент и, в общем, достаточно точно исправлял запись, если я правильно задавал для контрольной точки время и отклонение.
Скрипт прилагается
#! python3.5

import bisect
import os
import re
import subprocess
import sys
import threading
import tkinter
from tkinter import ttk
import win32api
import win32com.client
import win32con
import win32console
import win32gui

import mylib


class Application(ttk.Frame):
    def __init__(self, master=None):
        global padx, pady
        super().__init__(master)
        master.title(os.path.split(sys.argv[0])[1])
        master.resizable(False, False)
        
        # Определим размер шрифта для расчета горизонтальных и вертикальных отступов
        ttk.Style().configure("TLabel", font=font)
        self.label = ttk.Label(self, text="0" * 2)
        self.label.pack()
        padx = self.label.winfo_reqwidth()
        pady = self.label.winfo_reqheight()
        self.label["text"] = self.label["text"][0]
        padx -= self.label.winfo_reqwidth()
        self.label.destroy()
        del self.label
        
        self.correction_value = tkinter.IntVar()
        self.correction_value.set(1100)
        self.correction_focused = None
        self.sign_factor = -1
        self.correction0_value = tkinter.IntVar()
        self.correction0_value.set(0)
        self.sign0_factor = -1
        self.correction0_focused = None
        self.saved_value = None
        self.grid()
        self.createWidgets()
    
    def update_application_window(self):
        if root.wm_state() == "withdrawn":
            x = (root.winfo_screenwidth() - root.winfo_reqwidth()) // 2
            y = (root.winfo_screenheight() - root.winfo_reqheight()) // 2
            root.wm_geometry("+{0:d}+{1:d}".format(x, y))
            root.deiconify()  # Делаем видимым основное окно
        
        for suffix in ("", "0"):
            correction = getattr(self, "correction{0}".format(suffix))
            correction_focused = "focus" in correction.state()
            if correction_focused and not getattr(self, "correction{0}_focused".format(suffix)):
                correction.select_range(0, "end")
            setattr(self, "correction{0}_focused".format(suffix), correction_focused)
        
        state = "disabled" if self.correction_value.get() == self.correction0_value.get() else "normal"
        self.hour["state"] = self.minute["state"] = (state, )
        
        self.update_idletasks()
        self.update()
        self.after(100, self.update_application_window)
    
    def change_sign(self, widget):
        def factor():
            return "sign{0}_factor".format("0" if correction0 else "")
        correction0 = self.nametowidget(widget) in (self.correction0, self.sign0_button)
        setattr(self, factor(), -getattr(self, factor()))
        button = getattr(self, "sign{0}_button".format("0" if correction0 else ""))
        button["text"] = "-" if getattr(self, factor()) < 0 else "+"
    
    def key_return(self, event):
        if event.widget == self.run_button:
            self.run()
        else:
            event.widget.tk_focusNext().focus()
            next_widget = root.focus_get()
            if next_widget == self.sign0_button:
                next_widget.tk_focusNext().focus()
                next_widget = root.focus_get()
            if next_widget in (self.correction, self.correction0, self.hour, self.minute):
                next_widget.select_range(0, "end")
    
    def validate_correction(self, widget, data, action):
        correction0 = self.nametowidget(widget) == self.correction0
        value = getattr(self, "correction{0}_value".format("0" if correction0 else ""))
        other_value = getattr(self, "correction{0}_value".format("" if correction0 else "0"))
        entry = getattr(self, "correction{0}".format("0" if correction0 else ""))
        valid = re.match(r'^\d+$', data) is not None
        if action == '0':
            self.saved_value = value.get()
        elif action == '1':
            if not valid:
                try:
                    value.get()
                except Exception:
                    value.set(self.saved_value)
                    entry.select_range(0, "end")
                if data in "-+":
                    self.change_sign(widget)
                elif data == " ":
                    other_value.set(value.get())
                    entry.select_range(0, "end")
        else:
            return False
        return valid
    
    def validate_time(self, widget, new_value, data, action):
        def process_widget(name, max_value):
            widget = getattr(self, name)
            if widget.get() == '':
                widget.set(self.saved_value)
                widget.select_range(0, "end")
            if data == "-":
                widget.set(str(max(0, int(widget.get()) - 1)))
            elif data == "+":
                widget.set(str(min(max_value, int(widget.get()) + 1)))
        
        if action == '0':
            self.saved_value = self.nametowidget(widget).get()
            return True
        elif action == '1':
            if widget == str(self.hour):
                valid = re.match(r'^\d$', new_value) is not None
                if not valid:
                    process_widget('hour', 9)
            elif widget == str(self.minute):
                valid = re.match(r'^[0-5]?\d$', new_value) is not None
                if not valid:
                    process_widget('minute', 59)
        else:
            return False
        return valid
    
    def run(self, event=None):
        config = """VirtualDub.audio.SetSource(1);
VirtualDub.audio.SetMode(1);
VirtualDub.audio.SetInterleave(1,500,1,0,{2});
VirtualDub.audio.SetClipMode(1,1);
VirtualDub.audio.SetConversion(0,0,0,0,1);
VirtualDub.audio.SetVolume();
VirtualDub.audio.SetCompressionWithHint(8192,48000,2,0,32000,1024,"AC3ACM");
VirtualDub.audio.EnableFilterGraph({0});
VirtualDub.video.SetInputFormat(0);
VirtualDub.video.SetOutputFormat(7);
VirtualDub.video.SetMode(0);
VirtualDub.video.SetSmartRendering(0);
VirtualDub.video.SetPreserveEmptyFrames(0);
VirtualDub.video.SetFrameRate2(0,0,1);
VirtualDub.video.SetIVTC(0, 0, 0, 0);
VirtualDub.video.SetCompression();
VirtualDub.video.filters.Clear();
VirtualDub.audio.filters.Clear();
VirtualDub.audio.filters.Add("input");
VirtualDub.audio.filters.Add("time stretch");
VirtualDub.audio.filters.Connect(0, 0, 1, 0);
VirtualDub.audio.filters.instance[1].SetDouble(0, {1:.8});
VirtualDub.audio.filters.Add("output");
VirtualDub.audio.filters.Connect(1, 0, 2, 0);
"""
        try:
            correction = self.sign_factor * int(self.correction.get())
        except ValueError:
            threading.Thread(target=mylib.MessageBox, args=('Не задана коррекция!',
                win32con.MB_ICONEXCLAMATION, 5)).start()
            return
        try:
            correction0 = self.sign0_factor * int(self.correction0.get())
        except ValueError:
            threading.Thread(target=mylib.MessageBox, args=('Не задана коррекция нуля!',
                win32con.MB_ICONEXCLAMATION, 5)).start()
            return
        try:
            time_span = (int(self.hour.get()) * 60 + int(self.minute.get())) * 60
        except ValueError:
            time_span = 0
        if not time_span:
            threading.Thread(target=mylib.MessageBox, args=('Не задано время!',
                win32con.MB_ICONEXCLAMATION, 5)).start()
            return
        if not os.path.exists(PROGRAM):
            threading.Thread(target=mylib.MessageBox, args=('Не обнаружен "{0}"!'.format(PROGRAM),
                win32con.MB_ICONEXCLAMATION, 5)).start()
            return
        correction -= correction0
        if not correction:
            config = re.sub(r'^.*\.audio\.filters\.(?!Clear).*$', '', config, flags=re.MULTILINE).rstrip()
        factor = 1.0 + correction / 1000 / time_span
        with open(CONFIG_NAME, "w") as config_file:
            config_file.write(config.format(1 if correction else 0, factor, correction0))
        subprocess.Popen('"' + PROGRAM + '" /s "{0}"'.format(CONFIG_NAME))
        self.quit()
    
    def createWidgets(self):
        self.config(padding=(padx, int(pady/4)))
    
        style = ttk.Style()
        style.configure("TLabel", font=font)
        style.configure("TButton", font=font)
        style.configure("C.TButton", font=font, foreground="red")
        style.configure("TCombobox", font=font)
        style.configure("TRadiobutton", font=font)
        
        root.bind('<Key-Return>', self.key_return)
        root.bind('<Control-Key-Return>', self.run)
        
        row = 0
        self.header = ttk.Label(self, text="Задай коррекцию и время")
        self.header.grid(row=row, column=0, columnspan=4)
        row += 1
        self.correction_label = ttk.Label(self, text="Коррекция(ms)*:")
        self.correction_label.grid(row=row, column=0, sticky="e")
        self.clear_button = ttk.Button(self, text="C", width=1, style="C.TButton")
        self.clear_button["command"] = lambda:self.correction_value.set(0)
        self.clear_button.grid(row=row, column=1)
        self.sign_button = ttk.Button(self, text="-", width=1)
        self.sign_button["command"] = (self.register(self.change_sign), self.sign_button)
        self.sign_button.grid(row=row, column=2)
        self.correction = ttk.Entry(self, width=5, textvariable=self.correction_value, validate="key",
            validatecommand=(self.register(self.validate_correction), '%W', '%S', '%d'))
        self.correction.grid(row=row, column=3)
        self.correction.focus()
        row += 1
        self.correction0_label = ttk.Label(self, text="Коррекция нуля(ms)*:")
        self.correction0_label.grid(row=row, column=0, columnspan=2, sticky="e")
        self.sign0_button = ttk.Button(self, text="-", width=1)
        self.sign0_button["command"] = (self.register(self.change_sign), self.sign0_button)
        self.sign0_button.grid(row=row, column=2)
        self.correction0 = ttk.Entry(self, width=5, textvariable=self.correction0_value, validate="key",
            validatecommand=(self.register(self.validate_correction), '%W', '%S', '%d'))
        self.correction0.grid(row=row, column=3)
        row += 1
        self.time_label = ttk.Label(self, text="Время(h:mm):")
        self.time_label.grid(row=row, column=0, sticky="e")
        self.hour = ttk.Combobox(self, width=1, validate="key",
            validatecommand=(self.register(self.validate_time), '%W', '%P', '%S', '%d'))
        self.hour["values"] = list(map(lambda x:"{0}".format(x), range(10)))
        self.hour.current(0)
        self.hour.grid(row=row, column=1)
        self.sc1 = ttk.Label(self, text=":")
        self.sc1.grid(row=row, column=2)
        self.minute = ttk.Combobox(self, width=2, validate="key",
            validatecommand=(self.register(self.validate_time), '%W', '%P', '%S', '%d'))
        self.minute["values"] = list(map(lambda x:"{0:>02}".format(x*5), range(12)))
        self.minute.current(10)
        self.minute.grid(row=row, column=3)
        row += 1
        self.space_label = ttk.Label(self, text="*Space-копировать")
        self.space_label.grid(row=row, column=0, sticky="w")
        self.run_button = ttk.Button(self, text="Запуск", width=8, command=self.run)
        self.run_button.grid(row=row, column=1, columnspan=3, pady=int(pady/4))
        
        self.after(100, self.update_application_window)


if __name__ == "__main__":
    FOLDER = os.path.split(sys.argv[0])[0]
    PROGRAM = os.path.join(FOLDER, "VirtualDub.exe")
    CONFIG_NAME = os.path.join(FOLDER, "config.vcf")
    MONIKER = r"winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2"
    SCRIPT_TITLE = os.path.split(sys.argv[0])[1]
    keep_output = False
    COMSPEC = os.getenv("comspec")
    if COMSPEC:  # Проверим на запуск в режиме сохраниеия вывода после завершения
        parent_process_moniker = (MONIKER + ":Win32_Process.Handle=" + str(os.getppid()))
        try:
            for i in range(2):
                parent_process = win32com.client.GetObject(parent_process_moniker)
                keep_output += re.match('("?)' + COMSPEC.replace("\\", "\\\\").replace(".", "\\.") + '\\1\s+/[ck]\s+',
                    parent_process.CommandLine, re.IGNORECASE) is not None
                parent_process_moniker = (MONIKER + ":Win32_Process.Handle=" + str(parent_process.ParentProcessId))
        except Exception:
            pass
        finally:
            parent_process = None
    
    main_hwnd = win32console.GetConsoleWindow()
    if main_hwnd:
        if win32api.GetConsoleTitle() != SCRIPT_TITLE:
            win32api.SetConsoleTitle(SCRIPT_TITLE)
        if not keep_output:
            win32gui.ShowWindow(main_hwnd, win32con.SW_HIDE)
    
    root = tkinter.Tk()
    root.withdraw()  # Скроем основное окно до прорисовки точно в центре экрана (делаем видимым методом update_application_window)
    
    # Важно! Шрифт моноширинный
    font_sizes = [(0, 8), (480, 10), (864, 12)]
    font = ("Lucida Console", font_sizes[bisect.bisect(font_sizes, 
        (root.winfo_screenheight(), float("inf"))) - 1][1], "normal")
    
    app = Application(master=root)
    app.mainloop()
    
    try:
        root.destroy()
    except tkinter.TclError:
        pass

Но он потом оказался не нужен. В конце концов пришёл тогда к такому варианту: видео захватывалось тюнером Beholder 8 через YPbPr с деинтерлейсом и корректируемыми в процессе записи фильтрами с шестиголовочного магнитофона и сохранялось в x264 loseless, звук ac3 в контейнер .asf. Затем сжималось ffmpeg в один проход в x264 -crf 18, а звук в aac 128k в контейнер .mkv. Только это довольно долго было на старом компе. На новом видеокарта поновее, сжимаю ей, когда есть необходимость. Это значительно быстрее. Затем полученный материал длительностью в несколько часов разбивал на части avidemux. При необходимости публиковал на веб-сервере (у меня был Django на Малинке, который отдавал по паролю видео хранящееся на NAS, сертификат тогда ещё купил, защита была на A+). Пользователь либо скачивал файл, либо список воспроизведения .wpl и смотрел через Media Player.
Н-да… автор прогулялся по всем граблям причем как-то умудрился найти их все. И правда квест — собери все грабли и получи потрать 750 USD.
Дважды оцифровывал видео. Один раз использовал плату Miro, второй раз — какую-то внешнюю коробочку кажется от Sony. Оба раза — никаких проблем.

Желающим оцифровывать я бы предложил такой пайплайн:


  1. Зарипать видео с кассеты, выход сохранить в interlaced режиме в ProRES или DNxHD, накрайняк h264 с высоким битрейтом (не меньше пары десятков мегабит в секунду), если при этом исходик не в YUV420 — то писать в YUV444. При этом следить за цветовых пространством (PAL/NTSC), и за частотой кадров (50i для PAL, 59.94i для NTSC) — всё должно быть как в исходнике. Звук — aac 256 кбит/с. Исходники не удалять, по крайней мере сразу.
  2. Намонтировать всё это в нормальные ролики, сделать деинтерлейс (в 25/29.97 кадров в секунду соответственно), цветокоррекцию, перевести в цветовое пространство BT.709. Всё сразу можно в Davinci Resolve, например.
  3. Выгнать в h264/h265 с битрейтом не менее 5/4 мбит/с, с двухпроходным кодированием. Звук aac с битрейтом не менее 196 кбит/с. Результат уже куда угодно заливать.
Я бы сделал иначе.

Я бы СРАЗУ пошел к профессионалам и выяснил бы у них, как они проводят оцифровку.

Если мне критична приватность, можно было бы либо договориться от личном присутствии при оцифровке, либо об аренде их оборудования с инструктажем.

Но я немного слышал, что самое лучшее качество получается, если просто купить цифровую видеокамеру с поддержкой композитного входа, и сделать оцифровку через нее. Без кривых китайских видеозахватов, без магических опций виртуалдаба, выбора формата, рассинхронов и так далее. Еще и камера бы осталась в доме.

В общем в статье первая часть — грустная. Часть третья — скорее бесполезная. Облачный стриминг для личного видео? Расшарить видео сейчас не слишком сложно.

А вот часть 2, про таблицу и скрипты — самая полезная. Возможно стоило поделиться наработками и примерами использования — вполне найдутся айтишники непрограммисты, которым могли бы пригодиться.

p.s. правда я бы нарезал через csv/excel + bash+ffmpeg
Вот как надо вгрызаться в код. Этот ребёнок — пример программистам.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий