Пакетное преобразование видео для бытовых плееров

в 10:57, , рубрики: avi, batch, mkv, видео, конвертер, пакетная обработка, Работа с видео, метки: , , , , ,

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

Я столкнулся с этим по банальной причине бытового комфорта. Имея неплохую медиастанцию под Windows, с подключенной 60" плазмой и шестиканальным звуком, настроенную с душой, с написанными скриптами для AutoHotkey и переписанным под собственный USB ИК приемник WinLIRC, я понял что не могу смотреть скачанные киношки каждый вечер, так как делать это хочется не вылезая из под одеяла в спальне. Где висит простенький 40" комбайн, купленный на распродаже, по случаю, в магазине одной из крупных торговых сетей. Возможностей бытового плеера, втроенного в LCD-телевизор, как оказалось, хватило на чтение флешек и SD карт любого размера, но вот в форматах он оказался ограничен AVI и кодеками MPEG-4-ASP и более слабыми.

Решение было простым — перегнать пару сотен несмотренных фильмов в AVI, а в дальнейшем автоматически создавать Lite-версию при окончании загрузки торрента rtorrent-ом на домашнем сервере. Таким образом решение должно было работать как под Windows так и под Linux, так как сервер работает под управлением Gentoo.

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

Автоматизация создания скриптов Avidemux

На поверку CLI интерфейс Avidemux-а оказался довольно ущербным, но его спасает возможность записи конфигурации проекта в скрипт и последующее его выполнение опцией run:

avidemux2_cli --run script

Подглядеть формат скрипта довольно просто, для этого достаточно запустить GUI-версию Avidemux, сделать все необходимые настройки, а потом сохранить проект в файл. В этом файле хранятся все настройки кодеков и фильтров преобразования.

Собственно смену контейнера с помощью Avidemux можно делать без перекодирования видео, но в моем случае возникла необходимость изменять разрешение выходного файла и ограничивать его ширину 720 пикселями. При этом я не нашел иного способа установить выходное разрешение, кроме явного указания ширины и высоты в пикселях, поэтому идея использовать один скрипт для всех файлов отпала. Пришлось использовать MediaInfo, который также доступен для Linux, и получать из него размеры, а самое главное аспект, выводимого на экран изображения, и исходя из установленной ширины вычислять высоту. Дополнительно появилась идея слегка растягивать «широкоэкранные» фильмы по высоте, чтобы компенсировать визуальное сжатие висящего под небольшим углом телевизора.

Таким образом появился awk-скрипт (выбор awk обусловлен компактностью его реализации для windows, по сравнению, например с перлом). Скрипт ищет по маске *.mkv все файлы (строчку поиска легко изменить для любой платформы), затем для каждого найденного файла вызывает MediaInfo и создает скрипт для AviDemux, а также добавляет команду для вызова конвертера в пакетный файл. В дальнейшем этот скрипт легко переделывается для преобразования отдельного выбранного файла и подключению его менеджеру закачек.

Исходник скрипта

В приведенном исходнике удалены настройки выбранного видео-кодека, так как они занимают места больше чем весь скрипт и могут быть легко получены из проекта AviDemux. При этом следует экранировать все двойные кавычки символом обратной косой черты (в этом awk схож с си).

BEGIN {
# настройки
	# ширина и высота дисплея, на котором будут проигрываться полученные файлы
	WIDTH = 720
	HEIGHT = 404
	# следует ли образать края чтобы подогнать видео под размер экрана
	CROP = 1
	# макс. коэффициент растяжения для устранения/уменьшения ченых полос
	STRETCH = 0.15
	# имя командного файла,  в который будут записаны команды для каждого видео
	BATCH = "run.cmd"
	# имена исполняемых файлов
	AVIDEMUX = "P:\myProgs\AVIDemux.2.5.6\avidemux2_cli.exe"
	MEDIAINFO = "P:\myProgs\MediaInfo_CLI_0.7.59\MediaInfo.exe"
	# команда, формирующая список нужных файлов (dir, ls, find)
	LIST = "dir /b *.mkv"
# конец настроек

	ASPECT = WIDTH / HEIGHT
	STRETCH += 1
	while((LIST | getline fn) > 0) {
		printf("FILE: %srn", fn);
		InfoCommand = MEDIAINFO " --Output=Video;%Width%/%Height%/%AspectRatio%/%DisplayAspectRatio% ""fn"""
		if((InfoCommand | getline tmp) > 0) {
			if (match(tmp, /([0-9]+)/([0-9]+)/([0-9.]+)/([0-9.]+)/, m)) {
				w = int(m[1])
				h = int(m[2])
				a = 0.0 + m[3]
				d = 0.0 + m[4]
				if (a != d) {
					printf("tAspect/Display are not equal %d %drn", a, d);
				}
				# correct dimentions to square pixels
				cw = w
				ch = w / d
				# stretch to fit display
				if (cw / ch > ASPECT) {
					sh = cw / d * STRETCH
					printf("timage is wider than display, do vertical stretchrn")
					if (sh > cw / ASPECT) {
						sh = cw / ASPECT
					}
					sw = cw
					# crop to fit display
					if (CROP && (sw / sh > ASPECT)) {
						printf("timage is still wider than display, do horisontal croprn")
						sw = sh * ASPECT
					}
					fw = (sw < WIDTH) ? rint(sw, 8) : rint(WIDTH,  8)
					fh = rint(sh / sw * fw, 4)
				}
				else if (cw / ch < ASPECT) {
					sw = ch * d * STRETCH
					printf("timage is higher than display, do horisontal stretchrn")
					if (sw > ch * ASPECT) {
						sw = ch * ASPECT
					}
					sh = ch
					# crop
					if (CROP && (sw / sh > ASPECT)) {
						printf("timage is still higher than display, do horisontal croprn")
						sh = sw / ASPECT
					}
					fh = (sh < HEIGHT) ? rint(sh, 4) : rint(HEIGHT,  4)
					fw = rint(sw / sh * fh, 8)
				}
				rw = cw - sw
				rh = ch - sh
				printf("t%dx%d -stretch-crop-> %d(%+d)x%d(%+d) -display-fit-> %dx%drn", w, h, sw, -rw, sh, -rh, fw, fh);
				# calc 4 crop values
				if (rw < 0) rw = 0
				if (rh < 0) rh = 0
				rwl = int(rw / 2)
				rwr = int(rw - rwl)
				rht = int(rh / 2)
				rhb = int(rh - rht)
				# make new filenames
				rsfn = fn
				avifn = fn
				sub(/.[A-Za-z0-9_]+$/, ".rs", rsfn)
				if (rsfn == fn) rsfn = rsfn ".rs"
				sub(/.[A-Za-z0-9_]+$/, ".avi", avifn)
				if (avifn == fn) avifn = avifn ".avi"
				# avidemux sript does not like "" in path
				gsub(/\/, "/", fn)
				gsub(/\/, "/", avifn)
				# save run script
				printf("//AD  <- Needed to identify//rn")>rsfn
				printf("var app = new Avidemux();rnapp.load("%s");rnapp.video.setPostProc(3,3,0);rnapp.video.addFilter("crop","left=%d","right=%d","top=%d","bottom=%d");rnapp.video.addFilter("resize","w=%d","h=%d","algo=0");rn", fn, rwl, rwr, rht, rhb, fw, fh)>rsfn
				printf("app.video.codecPlugin("ЗДЕСЬ*ДОЛЖНЫ*БЫТЬ*НАСТРОЙКИ*КОДЕКА");rn")>rsfn
				printf("app.audio.reset();rnapp.audio.codec("Lame",128,20,"80 00 00 00 00 00 00 00 02 00 00 00 05 00 00 00 00 00 00 00 ");rnapp.audio.normalizeMode=1;rnapp.audio.normalizeValue=0;rnapp.audio.delay=0;rnapp.audio.mixer="STEREO";rnapp.audio.drc=true;rnapp.setContainer("AVI");rn")>rsfn
				printf("app.save("%s");rnapp.Exit();rn", avifn)>rsfn
				# add to batch
				printf("%s --nogui --run "%s"rn", AVIDEMUX, rsfn)>BATCH
			}
		}
		close(InfoCommand)
	}
	close(LIST)
}

function rint(v, b) {
	# floor value to specified base
	return int(int(v + 0.5) / b) * b
}

Автор: ma5ter

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js