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

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

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

Я столкнулся с этим по банальной причине бытового комфорта. Имея неплохую медиастанцию под 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: %s\r\n", 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 %d\r\n", 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 stretch\r\n")
					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 crop\r\n")
						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 stretch\r\n")
					if (sw > ch * ASPECT) {
						sw = ch * ASPECT
					}
					sh = ch
					# crop
					if (CROP && (sw / sh > ASPECT)) {
						printf("\timage is still higher than display, do horisontal crop\r\n")
						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%d\r\n", 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//\r\n")>rsfn
				printf("var app = new Avidemux();\r\napp.load(\"%s\");\r\napp.video.setPostProc(3,3,0);\r\napp.video.addFilter(\"crop\",\"left=%d\",\"right=%d\",\"top=%d\",\"bottom=%d\");\r\napp.video.addFilter(\"resize\",\"w=%d\",\"h=%d\",\"algo=0\");\r\n", fn, rwl, rwr, rht, rhb, fw, fh)>rsfn
				printf("app.video.codecPlugin(\"ЗДЕСЬ*ДОЛЖНЫ*БЫТЬ*НАСТРОЙКИ*КОДЕКА\");\r\n")>rsfn
				printf("app.audio.reset();\r\napp.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 \");\r\napp.audio.normalizeMode=1;\r\napp.audio.normalizeValue=0;\r\napp.audio.delay=0;\r\napp.audio.mixer=\"STEREO\";\r\napp.audio.drc=true;\r\napp.setContainer(\"AVI\");\r\n")>rsfn
				printf("app.save(\"%s\");\r\napp.Exit();\r\n", avifn)>rsfn
				# add to batch
				printf("%s --nogui --run \"%s\"\r\n", AVIDEMUX, rsfn)>BATCH
			}
		}
		close(InfoCommand)
	}
	close(LIST)
}

function rint(v, b) {
	# floor value to specified base
	return int(int(v + 0.5) / b) * b
}
Теги:
Хабы:
Всего голосов 9: ↑8 и ↓1+7
Комментарии0

Публикации