Раньше каждый кусок монтажа вырезался в отдельный файл, и только потом
список этих файлов шёл на склейку. Кэш весил столько же, сколько готовый
фильм — замерено 110 ГБ на часе материала, — а диск писал всё дважды.
Теперь каждый кусок режется своим ffmpeg прямо в MPEG-TS на стандартный
вывод, все потоки подряд переливаются в один принимающий ffmpeg, а тот
пишет готовый файл. Место куску задаёт -output_ts_offset, считаемый в
кадрах от начала. На диск ложится только результат и звуковая дорожка.
Через кэш по-прежнему собирается то, что потоком не выходит: кодек, не
переносимый в MPEG-TS, и пересборка всего ролика под площадку.
Сверено на четырёх монтажах, включая настоящий материал с дрона
(HEVC 2688x1512, 55 Мбит/с, файлы с шестью потоками): картинка совпала
попиксельно, PSNR бесконечен на каждом кадре, длина и размер те же.
Сверка шла и с прошлым выпуском, а не только нового пути со старым.
На отрывке в 17.5 с: 14.2 с и 421 КБ на диске против 17.5 с и 152 МБ.
По дороге найдены две ловушки, обе записаны в README:
-avoid_negative_ts make_zero отменяет -output_ts_offset, а -fflags
+igndts у приёмника сминает кадры на стыках.
Две беды у всех кнопок нижней полосы.
Всплывающая подсказка появлялась прямо поверх кнопки, о которой
рассказывает, и перехватывала щелчок: нажать кнопку было нельзя.
Подсказка переехала в строку состояния — она в той же полосе, ничего не
закрывает и меняется сразу после щелчка, не дожидаясь нового наведения.
Строка состояния теперь занимает всё свободное место справа, пока рядом
не показан ход работы, а самые длинные подсказки укорочены: раньше их
обрезало на полуслове.
Второй щелчок подряд виджетсет отдавал как двойной, и кнопка его не
видела: быстрая очередь щелчков срабатывала через раз. У кнопки убран
csDoubleClicks, а на случай, если двойной всё же дойдёт, он считается
за обычный.
Проверено: tests/lfmcheck, раздел «Кнопки нижней панели», и
tests/buttonclicks.sh мышью на невидимом экране — по всем шести
переключателям очереди из 1-5 щелчков отрабатываются полностью.
Окно кадрирования брало кадр на десятой части от начала исходного файла,
не глядя, какой клип правят. Клип обычно вырезан из середины долгой
съёмки, и в окне была совсем другая сцена: прицелиться увеличением было
нечем.
Теперь для клипа берётся его собственное время: место курсора, если он
внутри клипа, иначе середина клипа. Кадрирование файла целиком из списка
работает как раньше.
Проверено на съёмке с дрона: клип из 900-й секунды, окно показало кадр
именно оттуда (совпал с эталонным кадром на 900 с), раньше показывало
кадр на пятой секунде.
Перекодированный кусочек от точки реза до опорного кадра шёл без ключа
битрейта, и NVENC брал своё умолчание. Замерено в готовом ролике
пользователя: 5.7-6.8 Мбит/с в огрызке против 54-60 в скопированном
материале рядом — мутное пятно на полсекунды.
Раньше это задевало только редкие точные резы, а теперь точны все: в том
ролике 98 кусков, то есть 98 таких пятен.
После правки на том же материале: огрызок 86.8 Мбит/с (потолок в полтора
битрейта проекта, для куска в 0.7 с с опорным кадром это нормально),
соседний скопированный 61.6. Сверка кадров и звука проходит.
Режимов выделения стало два, переключаются клавишей D и кнопкой внизу,
режим показан её значком.
Рамкой по клипам — как было. По времени — протяжка отмечает промежуток по
всем дорожкам сразу, он остаётся виден пунктиром, и S режет все дорожки по
обоим его краям. Выделенной после этого остаётся ровно середина: следующим
действием её обычно удаляют, а выделение на старых клипах убрало бы не то.
Курсор теперь идёт за резом: после S встаёт на место реза (в режиме по
времени — на начало промежутка), после удаления куска — на место стыка,
после схлопывания дыры — туда же. Раньше в это место приходилось попадать
мышью заново.
Рез кладётся на кадровую сетку: между кадрами времени нет, и рез на доле
кадра оставлял бы в монтаже время, которого не бывает.
Выделение рамкой требует настоящего перекрытия, а не касания краями:
сосед, кончающийся ровно на краю рамки, попадал в выделение из-за разницы
в последнем знаке дробей.
Кнопка журнала и строка состояния сдвинуты вправо: переключатель выделения
встал в один ряд с прилипанием и схлопыванием.
Значки — icons8, набор ios-glyphs, авторство дописано в attribution.txt.
Набора «ios26» на их раздаче нет (404), взят ближайший по виду к нынешним.
Проверки: рез по курсору и по краям промежутка, кадровая сетка, выделение
середины, курсор после удаления и после схлопывания.
Меню «Проект» → «Проверить и починить проект...». Показывает найденное и
чинит только по согласию, отменяется через Ctrl+Z.
Ищутся клипы мимо кадровой сетки, клипы на дорожках чужого вида и
повторяющиеся имена дорожек. Первое — наследство: в старых проектах
времена записаны как попало (2.457599 с при 59.94 к/с — это 147.31 кадра),
и пока монтаж просит доли кадра, предпросмотр и сборка округляют их
каждый по-своему. Сдвиг при починке не больше полкадра.
Края отмеченного участка при сборке тоже кладутся на сетку: подрезка
возвращала в крайние куски доли кадра, которые уплощение только что
убрало.
Проверено на копии настоящего проекта: 82 клипа, после починки мимо сетки
ни одного, клипы остались встык, общая длина изменилась на 0.028 с за два
часа; Ctrl+Z возвращает как было. Плюс проверка в lfmcheck, включая клип
короче кадра — он не должен схлопнуться в ничто.
Рез всегда точный. Раньше начало куска притягивалось к ближайшему
опорному кадру, а звук брался из непритянутой точки: по журналам сборки
средняя притяжка 0.24 с, худшая 0.99 с, и на длинном куске этот сдвиг
держится все его минуты. Плата за точность — доли секунды перекодирования
на рез. Второй вид реза (Shift+S) стал тем же самым и убран.
Разбор заголовка MP4 не учитывал список правок (elst): метки опорных
кадров уезжали на задержку показа, на пробе два кадра. Сверка быстрого
пути с ffprobe теперь совпадает точно, в том числе на записи с B-кадрами.
Куску заказывается точное число кадров, а не только время: при
копировании ffmpeg отмеряет конец по времени декодирования, и у записи с
B-кадрами кусок выходил длиннее.
Склейке длительность каждого куска задаётся явно. Последний пакет
скопированного куска показывается позже прочих, контейнер считал его в
длительность, и время картинки растягивалось — 0.05 с на кусок.
Звука в кусках картинки больше нет: он не используется, а в MPEG-TS ещё и
сбивает выравнивание склейки на 36 мс.
Границы кусков ложатся на кадровую сетку: картинка выходит целым числом
кадров, звук набирается до отсчёта, и доли кадра разводили их в стороны.
Разогрев кодировщика звука компенсируется: сжатый AAC начинается со
служебных отсчётов, MP4 помечает их пропускаемыми, Matroska — нет, и звук
ехал на 21.3 мс позже. Величина меряется пробным сжатием.
Продолжение прерванной сборки чинится заодно: готовность проверяется у
каждой части, а не у куска целиком. При точном резе частей две, и вторая
при продолжении терялась.
В журнал сборки добавлена проверка синхронности: сколько кадров вышло и
сколько просил монтаж, с вердиктом и худшей частью.
Замер (tests/syncprobe.sh, исходник со вспышкой и щелчком на каждой
секунде): было до 0.6 с и росло по ходу ролика, стало 0.02-0.04 с без
накопления при точности измерения 0.017 с.
Сохранение теперь отвечает в строке состояния: «Проект сохранён: имя,
время». Раньше об этом знал только журнал, а он по умолчанию закрыт —
нажал Ctrl+S и не знаешь, дошло ли до файла.
Проект пишется во временный файл рядом и подставляется целиком; прежний
остаётся как .bak. Оборванная запись больше не может оставить огрызок
вместо проекта.
Автосохранение: пока есть несохранённое, раз в полминуты рядом с
проектом пишется копия .autosave. При открытии программа сравнивает её
время с временем проекта и, если копия новее, показывает оба времени и
предлагает её открыть. После сохранения проекта копия убирается.
Журнал рядом с программой больше не переписывается при каждом запуске:
запуски дописываются с заголовком, файл урезается при переросте. Прошлый
запуск — как раз тот, в котором обычно и надо разбираться.
Клавиша \ подтягивает окно к курсору и ставит его посередине: её жмут
после реза или выделения. Переключатель «курсор посередине» остался
кнопкой с целью.
Рамка выделения: протяжка по пустому месту дорожек обтягивает клипы,
Delete убирает всё, что она задела. Щелчок без движения по-прежнему
ставит курсор.
Рез по курсору (S) режет все дорожки, а не ту, что под указателем мыши:
стоило указателю оказаться над пустой дорожкой, и клавиша молча не
делала ничего. Теперь рез отвечает числом разрезанных клипов.
Курсор после правки монтажа улетал в конец: при перезагрузке списка
кусков mpv сбрасывает отсчёт времени, программа ждала перемотки, не
дожидалась и подтягивала курсор к плееру. На паузе курсор теперь
остаётся там, куда его поставил человек. Это и делало Ctrl+Shift+R
неудобным: после каждого схлопывания приходилось целиться заново.
Схлопывание дыры ищет её с допуском и по всем дорожкам, а не только под
мышью, и называет дорожку в строке состояния.
Ответ на команду держится в строке состояния несколько секунд: раньше
его через долю секунды затирало сообщение о пересборке предпросмотра.
Проверки: рамка, рез всех дорожек, допуск при схлопывании (lfmcheck).
Отмену вставок просили мы сами, когда правка сделала работу ненужной, а
журнал сообщал «Не удалось подготовить вставки: Отменено» — неправда там,
где всё шло по плану. Теперь отмена докладывается как отмена и сразу
планирует пересборку предпросмотра, а не ждёт таймера.
Вчерашнее дробление дыр не дошло до звуковых дорожек: они уплощаются
отдельной функцией. Кусок тишины в три минуты ссылался на минутный файл, и
плеер жаловался «uses 192.5 seconds, but file has only 60». Теперь длинная
тишина набирается повторами, а одинаковые файлы не заказываются дважды.
Проверено живьём на материале 2688x1512: отмена ловится, обе строки в
журнале верные, предупреждений о коротких файлах ноль. Проверка 15 в
flattest следит за дроблением тишины.
Отбивку или текстовый блок проще размножить копией, чем заводить заново
через меню и настраивать оформление с нуля.
Копируются выделенные клипы, вставляются по курсору. Связанные берутся
вместе с парой — у картинки со звуком иначе уехала бы половина, — но
связка у копий своя: копия и подлинник не должны ездить вместе. Взаимное
расположение сохраняется, набор сдвигается к курсору.
Дорожка выбирается сама: сначала та, откуда копировали, если это время на
ней свободно; потом любая подходящая по виду; в крайнем случае заводится
новая. Наложить копию на соседа по дорожке нельзя — это означало бы
переход, которого никто не заказывал.
В модели появился TClip.CopyAllFrom: полная копия, кроме места на
монтаже. Проверка в lfmcheck перебирает перенесённые свойства и отдельно
убеждается, что место и связка не переносятся.
При выключенном «курсоре посередине» окно всё равно подтягивалось к
курсору, стоило тому выйти за край видимой части. Со стороны это выглядело
так: Shift с колесом, первый поворот прокручивает, второй возвращает
обратно. Человек отматывает осознанно, и возвращать его силой нельзя.
Теперь выключенный переключатель означает именно неподвижное окно.
Подтягивание при пуске воспроизведения осталось: там курсор надо привести
в поле зрения.
Проверено на невидимом экране при идущем воспроизведении: оба поворота
колеса двигают линейку (1651 и 1581 изменившихся точек), и к исходному
виду она не возвращается.
Дыра в сорок минут требовала двухчасового чёрного файла: лестница готовых
длин доходила до восьми часов, и NVENC кодировал такое минутами. Отсюда
«Делаю чёрную вставку 1 из 3», которое не кончается. Теперь длины две, 15
и 60 секунд, длинная дыра набирается повторами минутного файла, а само
чёрное кодируется мегабитом вместо полусотни.
Промежуток короче половины кадра дырой больше не считается: между
клипами, сдвинутыми встык мышью, оставались микросекунды. Мышь при этом
кладёт клипы на кадровую сетку — между кадрами времени не существует.
Правка монтажа прекращает генерацию, ставшую ненужной: раньше закрытая
дыра не отменяла уже начатую работу.
Ctrl+Shift+R работает без мыши: дыра ищется на активной дорожке, если
указатель не над дорожками, и команда отвечает в строке состояния, а не
молчит.
Вжигание текста режет кусок на части и пересобирает только середину.
Надпись в тридцать секунд внутри сорокаминутного куска стоила сорока
минут пересборки. Часть после надписи помечается точным резом, иначе
копия пошла бы от опорного кадра и картинка сдвинулась бы.
Проверки: flattest 13 и 14, схлопывание без мыши в lfmcheck, сборка с
надписью внутри длинного куска — 1.000000 с пересборки вместо 6.
Из истории убраны собранный ролик, скачанные зависимости под Windows,
отладочные символы macOS, пробники замеров, изначальная постановка и
рабочие бумаги — планы с логами сессий. Всё это осталось на диске, но
больше не отслеживается и закрыто .gitignore: они попадали в коммиты
через git add -A по корню.
Картинка собиралась в промежуточный файл, а потом сшивалась со звуком в
конечный. На часовом ролике это 91 ГБ в кэше плюс 91 ГБ рядом и лишний
полный проход копирования — ради того, чтобы приписать звуковую дорожку.
Теперь звук собирается раньше картинки, а склейка сразу кладёт всё вместе
в конечный файл. Тем же путём пошла и сборка с перекодированием.
Промежуточного файла нет вовсе, проход на один меньше.
Куски после удачной сборки удаляются, и в журнале сказано, сколько места
освободилось: на часовой съёмке их набирается больше сотни гигабайт, а
нужны они только чтобы продолжить прерванную сборку. После неудачной
сборки они остаются — с них и продолжают. Настройка keep_parts оставляет
их всегда.
Копирование оформления: Ctrl+Shift+C берёт с выделенного клипа
кадрирование, поворот, увеличение, смещение, прозрачность, ключ,
затухания и громкость, Ctrl+Shift+V применяет их ко всем выделенным. Вид
клипа и текст не переносятся: свойства копируют между разными клипами.
Порядок дорожек: слева в заголовке две стрелки, у крайних дорожек
соответствующая нарисована тускло и не нажимается. Двойной щелчок по
названию переименовывает дорожку — на монтаже из десятка дорожек «карта»
и «титры» читаются лучше, чем V1 и Т1. То же есть и в меню по правому
щелчку. Ввод имени — своим тёмным окном: системный InputQuery выглядел бы
белым пятном.
Окно сборки показывает, сколько прошло и сколько примерно осталось.
Оценка по пройденной доле; скорость на деле меняется, поэтому «примерно»
и пересчёт на каждом шаге.
Прерванная сборка продолжается с готовых кусков. Каждый доведённый до
конца кусок помечается в списке рядом с ними: одного наличия файла мало —
сборку могли прервать посреди записи. Первая строка списка — отпечаток
плана; не совпал, значит монтаж менялся, и всё собирается заново.
Проверено: четыре куска взяты готовыми, два пересобраны, склейка не
потеряла ни одного кадра; при изменённом монтаже готовые куски отвергнуты.
Миникарта была видна в окне, но не в ролике. Предпросмотр рисовал оверлей
поверх всего, не глядя на порядок дорожек, а сборка живёт по правилу
«верхний клип побеждает целиком»: фотография лежала выше оверлея и в
ролике его закрывала. Теперь окно считает так же — оверлей показывается
только там, где его не закрывает сплошной клип с дорожки выше, а закрытый
посередине разбивается на несколько окон показа.
Текст не появлялся в окне. Разметка лежит в файле с одним именем на весь
проект, а плеер сравнивал имена: имя прежнее — перечитывать нечего.
Правка доходила до окна только вместе с перезагрузкой списка кусков.
Теперь сравнивается сама разметка, и при изменении файл перечитывается.
Отдельно проверено, что цепочка lavfi-complex субтитрам не мешает.
Две дорожки Т2. Имя новой дорожке давалось по счёту дорожек своего вида
плюс единица, и после удаления средней счёт совпадал с занятым именем.
Имя подбирается свободное, а повторы из старых проектов разводятся при
чтении.
Проверки 11 и 12 в nlvetool flattest.
Вжигание не доходило до сгенерированных вставок. Картинки и чёрное
отбирались первой же веткой сборки — до проверки на пересборку — и
копировались целиком вместе с отметкой о разметке. Галка вжигания стояла,
кусок в журнале был помечен «с текстом», а в ролике надписи не было.
Причину показал журнал сборки рядом с файлом: помечено «пересобирается»,
а команда — копирование.
Теперь вставка с разметкой идёт общей веткой пересборки, и время у неё
берётся своё: она начинается с нуля, а не с середины монтажа.
У линейки появился верхний этаж в двенадцать точек — про отметки.
Протяжка по нему отмечает участок, даже если отметок не было; щелчок без
движения их снимает. Уголки по краям тянутся, как и раньше.
Заголовок дорожки: правый щелчок открывает меню — поднять, опустить,
удалить (с клипами — с подтверждением). Заголовок можно и потащить:
порядок дорожек есть порядок перекрытия, и менять его перетаскиванием
привычнее, чем через меню.
В живом проекте фотография оказалась на текстовой дорожке, а надпись — на
дорожке картинки. Уплощение обходит дорожки по видам, и такой клип не
видит ни сборка, ни предпросмотр: на монтаже он нарисован, в готовом
ролике его нет, и ни одной жалобы по дороге. Именно это и было причиной
пропавших фотографии и текста, а не ошибка со стоп-кадром.
Теперь перетаскивание на дорожку чужого вида не проходит, а файл из
списка, брошенный на такую дорожку, ложится на подходящую — с записью в
журнал, куда именно. Новая дорожка, заведённая перетаскиванием вниз,
заводится того вида, какой нужен клипу.
При открытии проекта такие клипы пересчитываются, и программа предлагает
перенести их на подходящие дорожки, сохранив время. Молча не переносим:
клип мог оказаться там осознанно, и внезапный переезд — сюрприз хуже
пропажи.
Проверка 10 в nlvetool flattest: картинка на текстовой дорожке в
уплощение не попадает, после переноса попадает и остаётся на своём месте.
Отметки участка стали видимыми уголками на линейке — жёлтые треугольники
по краям, за них можно тянуть. Клавишами это выходило долго: поставил,
посмотрел, промахнулся, ставь заново. Край прилипает к границам клипов,
как и курсор, и края не пропускаются друг за друга.
Клавиша \ и кнопка с целью переключают, держать ли курсор посередине
монтажа при воспроизведении. Раньше монтаж ехал под курсором всегда, а
это мешает, когда разглядываешь конкретное место.
В окне сборки вместо надписи про участок появилась галка «только
отмеченный участок». Надпись легко проглядеть, а вопрос из тех, где
ошибка стоит часа работы: соберёшь кусок вместо ролика или наоборот.
Снятая галка собирает весь монтаж, отметки при этом остаются.
Журнал сборки пишется рядом с готовым файлом и под тем же именем. Общий
журнал программы перезаписывается следующим запуском, а разбираться,
почему в ролике не то, приходится потом. В журнале: время, проект,
участок, кадр и битрейт, что с текстом, и полный список кусков — с
пометками, где слои, где текст и что пересобирается. Строка пишется
сразу, чтобы после падения осталась последняя.
Отмеченный участок. Клавиши I и O ставят начало и конец по курсору,
полоса под линейкой показывает отмеченное, сборка собирает только его.
Без этого проверить правку на длинном монтаже нельзя: каждая проверка
стоит столько же, сколько идёт весь ролик. Отметки живут в проекте.
Разметка текста при этом берётся с начала участка, иначе надписи легли бы
со сдвигом.
Фотография пропадала из ролика. Кусок с наложением подаёт слои в ffmpeg
как есть, а одиночная картинка даёт ровно один кадр: кусок в три секунды
получался длиной 0.016667 с. Теперь у слоя есть признак «это картинка», и
она повторяется ключом -loop с частотой кадров проекта.
Текст пропадал там же. Вжигание помечает кусок разметкой, но ветка
наложения про неё не знала. Пока оверлей лежит на всём ролике, этой
веткой идут все куски — и текста не было нигде. Теперь разметка вжигается
последним звеном цепочки, поверх сведённой картинки.
Качество пересобранных кусков. Битрейт не задавался вовсе, и кодировщик
брал своё значение по умолчанию: 5.9 Мбит/с там, где исходник 56. Теперь
пересборка целится в наибольший битрейт среди файлов проекта, с потолком
в полтора раза выше и буфером вдвое. Тем же битрейтом делаются вставки из
фотографий.
Проверки: обрезка списка кусков по участку (nlvetool flattest, 9),
сборка фотографии под наложением и вжигание текста поверх наложения
(nlvetool render ... burn).
После разреза хвост оставался без цветового ключа: перенос настроек был
написан перечислением полей, а ключ появился позже и в перечень не попал.
Хвост переставал быть наложением, становился обычным победителем и
закрывал собой всё, что ниже — вместо компаса в углу окно показывало
пурпурную плашку во весь кадр.
Перечисление заменено на CopySettingsFrom: одно место, где сказано, что
переносится при копировании клипа, а что делает клип этим клипом. Иначе
та же ловушка досталась бы следующему свойству.
Проверка в lfmcheck: рез клипа с ключом, прозрачностью, громкостью и
кадрированием — всё остаётся у обеих половин, связки у них разные и
непустые.
Проекты, разрезанные прежней сборкой, правка задним числом не чинит:
у хвоста ключ надо включить заново.
Оверлей показывался как есть, со своим фоном: чтобы увидеть, что выйдет
после сборки, приходилось собирать. Теперь выбитый цвет и приглушение
считаются в той же цепочке плеера, на лету.
Строки фильтров стали общими со сборкой (ColorKeyFilter, OpacityFilter
в uTransform): окно и готовый ролик больше не могут разойтись по тому,
как записан цвет или порог.
Переключатель в меню «Вид», состояние переживает перезапуск. Замер на
2688x1512, 600 кадров: основа 2.07 с, с наложением 2.43 с, с ключом и
прозрачностью 2.66 с — вчетверо быстрее реального времени, но на слабой
машине это заметнее, поэтому выключить можно.
В бэклог записано окно настроек: переключателей, которые стоят машинного
времени, набралось много, и живут они кто где — кнопками внизу, в меню,
а выбор кодировщика вообще нигде.
Курсор воспроизведения по щелчку вставал ровно туда, куда попала мышь.
В стык двух кусков так не попасть: на общем плане одна точка экрана —
это секунды материала, а ставят курсор чаще всего именно на стык.
Теперь он притягивается к границам клипов на любой дорожке и к началу
монтажа. Работает и при щелчке по клипу, и по линейке, и по пустому
месту, и при протяжке. Shift меняет решение на противоположное — как у
прилипания клипов.
Переключатель свой, со значком клея, рядом с магнитом: клипы липнут при
перетаскивании, курсор — при щелчке, это разные привычки, и одно бывает
нужно без другого. Состояние переживает перезапуск.
Расчёт открыт наружу и проверяется числами: семь проверок в lfmcheck,
от «стык рядом — притянуло» до «выключено — не притягивает».
Внутри проект — обычный JSON, и система считала его просто текстом:
открывала редактором и рисовала значок текстового файла.
Теперь у него свой тип application/x-nlve-project, свой значок (лист с
загнутым углом и знаком программы) и открытие двойным щелчком. Тип
объявлен наследником application/json, но не простого текста — иначе
редактор снова оказался бы программой по умолчанию.
Всё, что нужно для встраивания в рабочий стол, лежит в packaging и
ставится скриптом tools/install-desktop.sh в домашний каталог, без прав
администратора. Раньше пункт меню и значки существовали только на одной
машине и нигде не были записаны.
Клипу задаётся цвет, который при сборке становится прозрачным. Окно
ключа показывает кадр: цвет берётся щелчком по нему, выбитое показано
клеткой, допуск и смягчение края — ползунками. Открывается из окна
кадрирования и из меню «Правка», на монтаже клип помечен квадратиком
выбиваемого цвета.
Предпросмотр перестал сводить такие участки в файлы. Плеер играет две
дорожки одна поверх другой: оверлей во весь ролик больше не заставляет
пересобирать весь фильм после каждой правки. Замерено: наложение стоит
0.4 с на 10 с материала 2688x1512, вчетверо быстрее реального времени.
Починено в сборке: уменьшенный клип обкладывался чёрными полями, и они
закрывали собой нижнее видео — оставалась видна только дырка, выбитая
ключом. Теперь верхний слой кладётся по координатам, тем же, по которым
его показывает предпросмотр.
Смещение по горизонтали и вертикали работает и там, где картинка меньше
кадра проекта: раньше уменьшенный клип нельзя было сдвинуть в угол.
Проверки форм теперь создают каждое окно из файла формы: элемент,
забытый в классе, вскрывается на сборке, а не при открытии окна.
Схлопывание при удалении. При включённом автосхлопывании удаление куска
сдвигает всё, что правее, влево до первого касания — дыра не остаётся, и
сборке не приходится заполнять её чернотой.
Сдвиг считается по всем дорожкам сразу, а не по каждой отдельно: иначе
развалилось бы выверенное соответствие картинки и звука. Насколько можно
сдвинуть, решает та дорожка, где запас меньше всего. Дыра не обязана быть
пустой везде: клип, который её перекрывает, никуда не едет, и запас на
его дорожке считается от его конца, поэтому сдвиг сам останавливается там,
где упёрся.
Проверено: клип 0..4 с парным звуком разрезан на 2 с, удалён первый
кусок. Хвост уехал в ноль, следующий клип встал вплотную, дыры нет.
Внутренние ошибки. Окно «Access violation» без единого слова о том, где
именно, не даёт ничего, а при воспроизведении оно повторяется на каждый
кадр и его невозможно закрыть. Теперь ошибка перехватывается: в журнал
пишется разбор стека с именами файлов и номерами строк — сборка идёт с
отладочными сведениями. Окно показывается только на первую ошибку,
дальнейшие лишь считаются.
Звуковые дорожки предпросмотра хранятся номерами, а не указателями.
Указатели были ловушкой: отмена и открытие проекта пересоздают все
дорожки, а список продолжал показывать на освобождённую память —
и обращаются к нему на каждое обновление времени, то есть десятки раз в
секунду.
Del удалял весь исходный клип вместо выделенного куска, и звук вместе с
ним. Причина: при резе хвост наследовал номер связки головы, а удаление
расширяет выделение на всю связку — обе половины оказывались одной
связкой и умирали вместе. Дальнейшие резы наследовали тот же номер, так
что от удаления одного куска исчезали все.
Теперь хвост получает новый номер связки. Один на весь рез: все хвосты,
отрезанные от одной связки, остаются связанными между собой — картинка
со своим звуком, как и было задумано.
Проверено на монтаже: клип 0..4 с парным звуком разрезан на 2 с, удалён
первый кусок. Было — исчезал весь клип и весь его звук. Стало — остались
хвост 2..4 со своим звуком в новой связке и нетронутый соседний клип
4..8.
Проекты, разрезанные прежней сборкой, останутся со сцепленными
половинами: у них один номер связки на всё. Развязать их можно Ctrl+G.
Окно сборки теперь спрашивает не только имя файла: куда собирать, в каком
контейнере и что делать с текстом — это один вопрос, ответ на любую часть
меняет допустимое в остальных.
При MP4 вжигание становится обязательным, и галка запирается. Причина
измерена, а не выдумана: отдельной дорожкой субтитров со всем оформлением
умеет только Matroska, в MP4 их кладут как mov_text, а он не хранит ни
цвета, ни обводки, ни положения. Переключение на MKV возвращает прежний
ответ человека — тот, что он давал до того, как MP4 решил за него.
Вжигание. Куски, которые задевает надпись, помечаются к пересборке, и
каждому подкладывается своя разметка: времена сдвинуты к началу куска и
обрезаны по его длине. Иначе никак — фильтр subtitles рисует по времени
того файла, который ему дали, а кусок начинается со своего нуля. Текст
дорисовывается последним, уже по кадру проекта: раньше он попал бы под
масштабирование и уехал бы вместе с картинкой.
Отдельная дорожка. Разметка вкладывается в готовый файл кодеком ass при
сшивании, ничего не перекодируется.
Предупреждение о пересборке теперь считается после подготовки текста:
раньше оно показывалось до неё и не знало о вжигаемых участках.
Проверено сборкой двух роликов из одного монтажа с надписью на 2..6
секунде. С вжиганием: кадры на 1 и 7 секундах чистые, на 3 и 5 — с
надписью, предупреждение назвало 8 секунд из 15.5. Отдельной дорожкой: в
файле три потока, третий ass с названием «Надписи», картинка чистая.
Своя ошибка, найденная по дороге: установка галки кодом вызывает тот же
обработчик, что и щелчок, и навязанное при MP4 значение записывалось как
осознанный выбор — при возврате на MKV галка оставалась стоять.
Окно хода сборки переименовано в «Ход сборки»: два окна с заголовком
«Сборка ролика» путали и меня, и человека.
ffmpeg умеет рисовать текст, но не тем фильтром, о котором думают.
drawtext в местной сборке отсутствует: с версии 7.1 ему нужен harfbuzz.
Зато есть libass — и он для задачи лучше. Проверено отрисовкой: цвет,
обводка, тень, шрифт по имени, размер, поля и девять положений. Девять
положений — родное понятие ASS, ровно та сетка, которую просили.
Чего libass не делает — не уменьшает шрифт, чтобы текст влез. Это считаем
мы: сами переносим по словам и сами подбираем размер, а libass получает
готовые переводы строк, чтобы нарисовать ровно то, что мы померили.
Размер ищется делением отрезка пополам. Прикидка «уменьшить во столько же
раз, во сколько не влезли» давала слишком мелко: после переноса строк
становится меньше, и места остаётся вдоволь — на длинной надписи вместо
29 помещался 37. Замер готового блока: ровно 144 точки при разрешённых
144, то есть пятая часть кадра 720p.
Текст живёт на своей дорожке, как звук: правило «верхний клип побеждает»
на него не распространяется, он лежит поверх и ничего не закрывает.
Предпросмотр ничего не стоит. mpv рисует внешний файл ASS поверх нашего
виртуального списка кусков — правка текста видна сразу, без пересборки
участка, в отличие от прозрачности. Подключение сделано в двух местах:
при смене файла и при загрузке нового списка кусков, потому что тогда mpv
забывает все внешние дорожки.
Редактор: текст, шрифт из установленных в системе, размер, три цвета
образцами, толщина обводки и тени, сетка три на три, доля высоты. Рядом
сказано, во что превратится надпись, — считает тот же код, что и сборка
разметки.
Замер ширины подключается снаружи: точный требует холста, а холст —
оконного набора. Служебная программа без экрана обходится оценкой, окно
ставит точный. Из-за этого же из модели убран TColor: она не должна
тянуть за собой оконную библиотеку.
Сборки ролика с текстом пока нет — это следующий этап, он расписан в
IMPLEMENTATION_PLAN.md.
Ссылка в ~/.local/bin и ярлык в меню запускают программу не по её
настоящему пути. ExtractFilePath(ParamStr(0)) при этом указывал на
каталог ссылки, и рядом с ней оказались бы и журнал, и файл
зависимостей, и поиск ffmpeg «рядом с программой».
Появился ProgramDir в uAppTypes: он раскрывает ссылки, пока они не
кончатся, с ограничением на случай закольцованных. Под Windows ссылок
такого рода нет, и путь берётся как есть.
Заменены все четыре места, где каталог программы вычислялся вручную:
журнал, поиск инструментов, каталог зависимостей и путь заготовки в
проекте.
Проверено: запуск через ~/.local/bin/nlve из домашнего каталога кладёт
журнал в каталог программы, а не рядом со ссылкой.
./build.sh macos делает две сборки: nlve_intel для Intel и nlve_arm для
Apple Silicon. Имя цели в проекте одно на все системы, и сборка под
darwin затирает линуксовую — поэтому каждый файл переименовывается сразу,
а сборка под эту систему в конце делается заново.
Обёртку .app, которую делает lazbuild, удаляем: внутри неё ссылка на файл
с именем nlve, которого после переименования уже нет.
BACKLOG.md — то, что известно и сознательно не сделано. Про macOS там
записано главное: собирается, но предпросмотра не будет, пока встраивание
mpv не научится работать через NSView; окно зависимостей советует маку
apt вместо brew; проверить нечем, потому что мака нет. Заодно туда
переехали лицензионный вопрос с libmpv и три мелочи: значок exe, кэш
наложений и четыре секунды на открытие окна кадрирования.
Падение. Окно зависимостей роняло программу под Wine: «stack overflow»,
шестнадцать мегабайт стека съедались целиком. Причина — качели между
таблицей и своими полосами прокрутки: появление вертикальной полосы
сужает таблицу, от этого перестаёт помещаться содержимое по ширине,
появляется горизонтальная, от неё убавляется высота — и решение о
вертикальной принимается заново. В комментарии стояло обещание, что
второго круга не будет; под Wine круг не сходился никогда.
Теперь глубина пересчёта ограничена тремя кругами. Лишняя полоса
прокрутки не беда, падение — беда.
Искалось на невидимом экране в отдельном префиксе Wine: программа сама
писала след по шагам, и он показал, что окно создаётся целиком, а падает
на показе. Дальше отключением по одному проверены обработчики
перерисовки рамки, подновление полосы меню по таймеру и значок окна —
ни при чём.
Напоминание. Если не хватает ffmpeg или libmpv, при запуске появляется
окно: чего именно нет, и три кнопки — открыть окно зависимостей,
отложить, больше не напоминать. Последнее запоминается в настройках.
Для этого появился ThemedChoiceDlg: диалог со своими кнопками, готовых
«Да / Нет / Отмена» тут не хватало.
Высота надписи в обоих диалогах считалась по общей ширине текста и не
видела переводов строк — сообщение обрезалось снизу. Теперь каждая
строка считается отдельно.
Текст переносится сам, по ширине окна, и переливается заново при
изменении размера. Считать переносы заранее нельзя: текст когда-нибудь
поедет в переводы, а там длина строки другая.
Появился uArticleView — страница текста со ссылками. Журнал для этого не
годился: он показывает сообщения программы, где перенос сбил бы
выравнивание, и строк не переносит принципиально.
Разметка нарочно бедная: «# » — заголовок, пустая строка — отступ, два
пробела — отступ списка, http:// и https:// — ссылка. Больше ничего не
нужно: это окно «О программе», а не браузер.
Ссылки нажимаются: под указателем меняются цвет и курсор, по нажатию
открывается браузер. Ссылка никогда не переносится — из двух половин её
не собрать.
Вкладки названы «Ресурсы» и «Возможности».
Тексты переписаны короче: строка — одна мысль, без рассуждений.
Значки заменены на присланные. Они вшиты в программу модулем uIconData,
который собирается из icons_online командой tools/make_icons.py — правятся
исходные картинки, а не он. Размер выбран под кнопку: двадцать четыре
точки в клетке двадцать восемь, уменьшение делает convert, пропорции
сохранены — все исходники квадратные.
Цвет значку задаётся при отрисовке: берётся прозрачность точки, а цвет —
тот, каким сейчас рисуется кнопка. Поэтому один значок годится и для
тёмной кнопки, и для оранжевой, и для недоступной. Перекрашенные
запоминаются: цветов ровно три.
Значок программы стал значком окна. Значок самого файла под Windows
задаётся не отсюда, а ресурсом при сборке — это осталось как было.
Окно «О программе» (Справка → О программе). Две вкладки: «Библиотеки и
авторство» и «Что умеет». Первая обязательна — бесплатная лицензия
Icons8 требует ссылку на каждый значок; заодно там перечислено всё, на
чём программа держится, и под какой лицензией. Списки разные по
системам: под Linux ffmpeg и libmpv ставит пакетный менеджер, под
Windows их скачивает сама программа, и это разные сборки.
Вкладки свои, а не TPageControl: системные вкладки под Windows остались
бы светлой полосой поверх тёмного окна.
Текст показывается нашим журналом — его можно выделить и скопировать,
так что ссылку из списка получится взять в браузер. Перенос по словам
свой: ссылки не рвутся, отступ списка сохраняется на продолжениях.
Свои рисованные значки из icons/ убраны: они больше не используются, и
держать рядом два набора значило бы путать.
Главное. Каждый щелчок по нашей кнопке срабатывал дважды. Click вызывает
сама LCL при отпускании внутри кнопки — TControl.WMLButtonUp, с той же
проверкой «внутри», какую я написал руками в MouseUp. Мой вызов
оказывался вторым. На кнопке-переключателе это значит «ничего не
произошло»: первое срабатывание меняло состояние, второе возвращало
обратно. Свой вызов убран.
Найдено на невидимом экране: на один щелчок мышью в журнал легли две
записи об одном обработчике, тогда как то же действие с клавиатуры давало
одну. После правки три щелчка дают три переключения, а магнит послушно
меняет цвет туда и обратно.
Второе. Журнал, спрятанный до первого показа окна, не получал своего
виджета вовсе, и вернуть его было нечем: свойство переключалось, а на
экране ничего не менялось. Теперь состояние применяется на показе окна,
когда дескриптор уже есть.
Третье. Правка файла формы не сработала молча — строка не совпала, а я
это не проверил. Кнопка прилипания осталась широкой, с надписью, и
наехала на соседние. Исправлено. Заодно выяснилось, что файл формы
пересобирается только вместе со своим модулем: правки одного лишь lfm в
двоичный файл не попадают.
Значок прилипания переделан в подкову магнита: линия со стрелками смысла
не передавала.
Переключение журнала добавлено в меню «Вид» и на Ctrl+L.
Двойной щелчок по кнопке журнала. Причина оказалась не в кнопке: журнал
лежал ниже полосы состояния. Спрятали журнал — полоса уехала вниз на сто
тридцать пикселей, кнопка ушла из-под курсора, и следующий щелчок в то же
место попадал уже мимо. Порядок исправлен: полоса состояния теперь самая
нижняя, журнал над ней, и при переключении ничего не движется.
Прилипание, схлопывание и журнал стали квадратными кнопками со значками и
собраны в один набор. Надписи переехали в подсказки — целиком, вместе с
состоянием: у значка нет другого способа рассказать о себе. Подсказка
всплывает через десятую долю секунды и держится пятнадцать.
Значки рисуются кодом, а не берутся из файла: программа не должна
зависеть от того, лежит ли рядом с ней картинка. Рисунки придуманы в
icons/*.svg — оттуда их можно перегнать в png чем угодно, проверено
командой convert.
Свойства из файлов форм теперь проверяются отдельной программой по
сведениям о типах. Файл формы разбирается при запуске, а не при сборке:
опечатка в имени свойства или в значении перечисления даёт не ошибку
компиляции, а падение окна на старте. Проверка включена в build.sh.
Полоска выполнения теперь показывает всё, что делается в стороне от
окна: сборку наложений и затуханий, разбор звука, миниатюры. Раньше она
знала только про звук и миниатюры, а самое долгое — пересборку участков
с прозрачностью — не показывала вовсе.
Как она растёт. Настоящая доля известна только вехами: вставка готова,
файл разобран. Между вехами не происходит ничего видимого, и полоска,
стоящая на месте, выглядит как зависшая программа. Поэтому между вехами
она ползёт сама — к следующей вехе, но никогда её не достигая: каждый
шаг покрывает малую долю оставшегося расстояния. Работа идёт как
задумано — веха приходит быстро и полоска прыгает к ней; затянулась —
шаги мельчают, и заполнение само собой замедляется. Назад не идёт
никогда, полной без настоящего конца не становится.
Расчёт вынесен в uAppTypes отдельной функцией именно затем, чтобы его
можно было прогнать числами, и прогоняется: проверки в
tests/progress/progtest.lpr включены в build.sh. Замер: до половины пути
десять шагов таймера, до трёх четвертей — ещё десять.
Участок монтажа, который сейчас пересобирается, помечается косой
штриховкой во всю высоту дорожек. Сплошная заливка закрыла бы клипы, а
рамка на узком участке была бы неразличима; штрих наклонный, чтобы его
не спутать с курсором и границами клипов — те все вертикальные.
Кнопка «Журнал» прячет и показывает нижнюю полосу с сообщениями,
состояние переживает перезапуск.
Системный TStatusBar не взят намеренно: его рисует система, и под
Windows он остался бы светлой полосой — ровно та беда, из-за которой уже
переписаны кнопки, полосы прокрутки, журнал и меню. Всё сделано на своей
нижней панели, теми же средствами.
Плеер играет список кусков, а список накладывать не умеет: там побеждает
верхний клип целиком. Поэтому участки с прозрачностью и переходами
заранее сводятся в отдельные файлы кэша, и предпросмотр играет уже их.
Файл считается один раз, имя выводится из описания слоёв — при следующем
открытии ждать нечего.
Считает их тот же код, что и сборка ролика. Разными путями считать одно
и то же нельзя: предпросмотр разошёлся бы с результатом.
Для сборки подстановка не применяется: там наложение считается заново с
параметрами вывода, и промежуточный файл был бы лишней потерей качества.
Проверено на монтаже с клипом в 50% прозрачности, где оба слоя из одного
файла: кадр наложения ближе к полусумме двух слоёв (отклонение 13.8), чем
к любому из них по отдельности (26.0 и 18.4).
Попутно найдено и исправлено: наложения кодировались процессорным x265,
потому что ffmpeg выбирает кодировщик по имени кодека, а мы передавали
«hevc». Замерено: 9.6 с материала за 11.2 с работы. Теперь и здесь
берётся предпочтительный кодировщик, аппаратный.
В служебную программу добавлена команда preview: список кусков ровно
такой, каким его видит предпросмотр, вместе с готовыми наложениями.
Затухания. Механика была написана давно и работала в сборке, но увидеть
или задать её было нечем. Теперь у клипа два верхних угла — ручки:
левая тянет появление, правая уход. Взятое затухание рисуется срезанным
углом, так это устроено в монтажных программах. Ручки перехватывают
нажатие раньше растягивания за край: они мелкие, промахнуться мимо них
легче, а взяться за край можно ниже.
Перекрытие двух клипов одной дорожки — это переход, и он теперь помечен
косым крестом во всю высоту. Раньше о нём можно было узнать только по
предупреждению перед сборкой.
Сборка с перекодированием. Отдельный пункт меню и отдельное окно:
размер кадра от 720p до 4K, поток в килобитах, кодек HEVC или H.264.
Перекодирование идёт одним проходом на весь ролик — пересобирать куски
по отдельности нельзя, на стыках была бы видна смена качества.
Кодирование на видеокарте, и распаковка тоже: замерено на паре кусков
2688x1512 — 9.18 с на процессоре против 1.77 с с аппаратной распаковкой,
впятеро. Полностью карточный путь через scale_cuda оказался медленнее
(5.74 с) и не взят. Если аппаратная распаковка не пойдёт, сборка
повторяется без неё: медленно лучше, чем никак. Окно честно
предупреждает, когда аппаратного кодировщика в ffmpeg нет вовсе.
Журнал в окне сборки заменён на свой: у системного поля оставались
светлые полосы прокрутки и не работала прокрутка вбок с Shift. Это было
последнее такое место.
До этого места не дотянуться ни отрисовкой пунктов (это не пункт), ни
подложкой через SetMenuInfo: под Wine пункты выходят тёмными, а полоса
остаётся светлой. Полоса живёт в неклиентской части окна, поэтому
закрашиваем прямо туда: правый край последнего названия берётся из
GetMenuItemRect, границы полосы из GetMenuBarInfo, всё что правее —
заливается нашей кистью.
Закраска подновляется на перерисовке рамки окна и по таймеру: система
трогает полосу и по другим поводам, а рисуем мы поверх неё.
Прямоугольник и цвет те же, поэтому подновление не видно.
Заодно закраска сама отчитывается в журнал: какие прямоугольники выдала
система и куда мы рисовали. Полоса упорно остаётся светлой, а гадать по
снимкам экрана дальше некуда.
Журнал закрытия показал место точно: последняя запись «Закрытие: закрываю
плеер», дальше ничего. Значит, зависание внутри mpv_terminate_destroy —
она не просто отпускает клиента, а ждёт, пока ядро mpv свернёт работу и
добьёт свои потоки. Под Linux это мгновенно, под Wine не кончалось вовсе,
причём даже когда ничего не открывали.
Теперь плеер отсоединяется через mpv_destroy: она отпускает клиента и
возвращает управление сразу, а ядро без клиентов сворачивается само.
Команда «завершиться» перед этим намеренно не посылается — её тоже
выполняет ядро, и встать она могла бы точно так же. Если в библиотеке
mpv_destroy не окажется, остаётся прежний путь с ожиданием, и в журнале
будет видно, что пошли по нему.
Отсюда следствие: libmpv больше не выгружается при выходе. Потоки ядра в
этот момент могут ещё жить внутри неё, и выдёргивать её из-под них
нельзя. Программа всё равно закрывается, память освободит система.
Плеер получил свою строчку в журнале — теперь видно и отсоединение, и
завершение, а не только шаги окна.
Звук пропадал кусками, и причина оказалась не в громкости и не в
раскладке дорожек: обе были верны. Дыры на звуковой дорожке заполнялись
тем же чёрным клипом, что и дыры на видеодорожке, а опорные кадры в нём
стоят через 4.171 с. Перемотка внутри такой вставки откатывается к
ближайшему опорному кадру, дорожка начинает отдавать звук на несколько
секунд раньше соседних, смешивание равняется по ней — и содержимое
соседней дорожки в это окно не попадает.
Замерено на файле, без запуска окна. Смешивание трёх дорожек проекта:
старт 0 с: -23.3 dB старт 24 с: -91.0 dB (полная тишина)
старт 12 с: -10.1 dB старт 30 с: -18.0 dB
При этом та же дорожка A2 отдельно на 24-й секунде даёт -11.7 dB, то
есть содержимое на месте. Смешивание A2 с A4 работает, A1 с A2 — нет:
у A1 в этой точке идёт длинная чёрная вставка, и перемотка в ней
откатывается на 3.1 с назад.
Теперь у звуковых дорожек своя вставка — anullsrc в звуковом кодеке
проекта, без видеопотока. В звуке опорным является каждый кадр, и
отката нет. После правки во всех проверенных точках звук есть:
0 с -24.1 7 с -19.0 12 с -11.9 18 с -17.5 24 с -11.7
25 с -9.6 30 с -9.5 40 с -15.6 60 с -19.5 72 с -15.9
Сборка ролика от этого не страдает: она декодирует куски в отсчёты, и
файл без картинки ей подходит так же.
Заодно в служебную программу добавлена команда audio: раскладка
звуковых дорожек проекта на куски вместе со строками edl://. Ею это и
было найдено.
Подложка. SetMenuInfo работает по дескриптору меню, а тот появляется
вместе с дескриптором окна — то есть после создания формы. Оба прежних
вызова попадали на несозданный дескриптор и молча ничего не делали.
Теперь вызов идёт из показа окна.
Громкости. В журнал пишется, какая дорожка какую громкость получила и
какая цепочка смешивания ушла в mpv. Снимками экрана это не разобрать:
в одном месте одного и того же клипа звук есть, в соседнем нет, и без
цепочки видно только следствие.
Закрытие. Каждый шаг отмечается в журнале: остановка предпросмотра,
ожидание потоков миниатюр и разбора звука, сохранение настроек,
закрытие плеера, выгрузка libmpv. Под Wine программа зависает при
выходе, а по молчаливому журналу шаг не найти.
Цвет подложки задаётся кистью через SetMenuInfo по дескриптору меню, а
дескриптор появляется только когда меню отдано окну. Вызов стоял до
присваивания, условие HandleAllocated не выполнялось, и подложка не
задавалась ни разу: пункты рисовались нашими руками, а полоса правее
последнего названия оставалась светлой.
Теперь вызов идёт дважды и оба раза нужны: до присваивания — расставить
обработчики отрисовки к первому показу, после — задать подложку. Полоса
меню живёт в неклиентской части окна и сама себя не перерисовывает,
поэтому следом идёт DrawMenuBar.
Самодельная полоса из кнопок стоила поведения: наведение на соседнее
название не перекидывало туда раскрытый список, не работал переход по
меню с Alt и стрелками. Подражать настоящему меню своими руками пришлось
бы до бесконечности, а причина отказа была одна — цвет.
Цвет решается иначе. Под gtk2 своими руками меню не рисуется, и это
записано в самом LCL: OwnerDraw, OnDrawItem и OnMeasureItem там помечены
как неподдерживаемые (interfaces/gtk2/issues.xml), вызывает DoDrawItem
только win32. Зато gtk принимает описание стиля текстом —
gtk_rc_parse_string, разбирается один раз до создания окон. Под win32
наоборот: строк стиля нет, зато отрисовка пунктов работает, в том числе
для названий в самой полосе.
Проверено отдельным пробником (probe/menu): настоящее TMainMenu под gtk2
берёт наши цвета и в полосе, и в раскрытом списке, включая цвет
выделенного пункта.
Отрисовка пунктов теперь отличает полосу от раскрытого списка: в полосе
не нужно место под галочку и сочетание клавиш, иначе название разошлось
бы с шириной, которую мы же и посчитали.
Остаток полосы правее последнего названия под Windows по-прежнему рисует
система — это уступка, а не решение.
Общая громкость плеера выставлялась по громкости единственной звуковой
дорожки и обратно уже не возвращалась. Клип с выключенным звуком —
обычное дело, и стоило монтажу на миг остаться с одной такой дорожкой,
как плеер немел до конца работы: на всём монтаже, сколько бы дорожек
потом ни появилось. Теперь в ветке смешивания громкость всегда полная —
громкость каждой дорожки задана внутри цепочки. Случай «дорожек нет
вовсе» тоже снимает и цепочку, и приглушение.
Замер: одна дорожка с громкостью 0 даёт точный ноль, после возврата
дорожек звук возвращается на -11.4 dB. Раньше он не возвращался.
Список меню открывается через таймер, а не прямо из обработчика щелчка.
Пока обработчик не завершился, указатель захвачен кнопкой, и раскрытому
списку захват не достаётся. Внешне это выглядело как «меню открывается
через раз»: замерено 175 мс, не открылось, 175 мс, не открылось. Показ
по нажатию, как в настоящих полосах меню, под gtk2 не открывает список
ни разу. Теперь восемь щелчков подряд — восемь открытий по 168 мс, в том
числе во время фонового разбора звука.
Рамка фокуса больше не рисуется на кнопках, которые фокус не берут:
на пунктах меню она выглядела как испорченный пункт.
Фоновая работа видна в нижней полосе: надпись с числом файлов и своя
полоска выполнения. Разбор звука читает файл целиком, на шести
гигабайтах это больше десяти секунд, и молчать об этом нельзя.