В предыдущем посте обрисовал идею с пакетной нормализацией громкости аудио/видео файлов.

Настала пора выложить реализацию этой идеи. Решение получилось гибкое и масштабируемое.

Для использования необходимо запастись знаниями матчасти звука и видео, мануалами по SoX — Sound eXchange, FFmpeg, BS1770GAIN, а так же моим любимым пакетом AutoIt.

На Windows Server, а так же некоторых других МелкоМягких платформах ffmpeg версии 3.1 и старше больше не работает.

Реализация состоит в следующем:

Довольно не сложный скрипт (autoit) слушает ini файл, секциями которого являются папки, которые нужно слушать. На каждую секцию открывается свой воркер (экземпляр скрипта), который считывает конфиг перед каждым прохдом по медиа файлу. При удалении секции, воркер закрывается. При закрытии главного воркера, все открытые воркеры закрываются.

Конфиг воркера имеет вид:

[\\host\path\]
destination=\\host\pathbak=\\host\pathstmp=tmptmp1=tmptmp2=tmpotmp=tmpdestinationExtension=.avi
threads=16
prepare_ffmpeg_cmd=-flags +ilme+ildct -deinterlace -c:v copy -c:a copy
ffmpeg_cmd=-flags +ilme+ildct -deinterlace -c:v copy -c:a copy
sox_cmd=compand 0.1,0.3 -90,-90,-70,-55,-50,-35,-31,-31,-21,-21,0,-20 0 0 0.1

Мануал:


Читать мануал

1) destination


Куда складировать итоговые файлы.

2) bak


Куда складировать исходные файлы.
У меня исходные файлы лежат в сетевой корзине, файлы бекапятся в ту же корзину, в отдельную папочку.

3) временные каталоги


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

stmp


$sFile = $stmp & $tempFile & $sExtension

Файл с сетевой корзины прилетает на хард0

tmp1


$audioInputSox = $tmp1 & $tempFile & "_sox.wav"
$audioOutput = $tmp1 & $tempFile & "_norm.wav"

Если в конфиге присутствует значение sox_cmd, то аудиоданные считываются с хард2 на и результат обработки записывается хард1
При нормализации громкости аудиоданные считываются с хард2 или хард1 (sox_cmd?) и записываются на хард1

tmp2


$audioInput = $tmp2 & $tempFile & ".wav"

Исходный аудио поток видеофайла читается с хард0 на хард2

otmp


$outFile = $otmp & $tempFile & "_out" & $destinationExtension

При сборке готового видеофайла видеопоток считывается с хард0 (stmp), аудиопоток считывется с хард1(tmp1), а результат записывается на хард2

4) destinationExtension


Контейнер результирующего видеофайла

5) threads


Записывает в командную строку сборки результирующего видеофайла threads=(threads) из конфига.

6) prepare_ffmpeg_cmd


Если не пусто, то выполняется
ffmpeg -y -ss 0:0:0.0 -r 25 -i [bak] [prepare_ffmpeg_cmd] [stmp]
Иначе, исходный файл копируется из bak в stmp

7) ffmpeg_cmd


Считывается имя файла.
Если в конце имени файла присутствует конструкция вида {HH MM SS mm ss}, то начиная с кадра HH MM SS хвост файла будет приведен к хронометражу mm ss методом ускорения/замедления без искажения аудиодорожки.
Выполняется сборка результирующего файла командой
ffmpeg -i [stmp(video)] -i [tmp1(normalized -23LUFS audio)] [ffmpeg_cmd] -map 0:v -map 1:a -threads [threads] [otmp] -y

sox_cmd


Перед нормализацией звука будет применен аудиофильтр
sox [tmp2] [tmp1] [sox_cmd]

Вот, собственно, и весь нехитрый скрипт. Очень удобно. Папочки создает сам, файлы отслеживает, обрабатывает, складирует как надо, глючит очень редко, почти никогда. Память не жрет, ведет себя достойно ) Пользуйтесь на здоровье!

Читать код скрипта
#Region ;**** Directives created by AutoIt3Wrapper_GUI ****
#AutoIt3Wrapper_Icon=C:\Program Files (x86)\AutoIt3\Icons\MyAutoIt3_Blue.ico
#AutoIt3Wrapper_Compile_Both=y
#AutoIt3Wrapper_UseX64=y
#EndRegion ;**** Directives created by AutoIt3Wrapper_GUI ****
#include <Array.au3>
#include <File.au3>

Func randomString($digits)
   Local $pwd = ""
   Local $aSpace[3]
   For $i = 1 To $digits
	  $aSpace[0] = Chr(Random(65, 90, 1)) ;A-Z
	  $aSpace[1] = Chr(Random(97, 122, 1)) ;a-z
	  $aSpace[2] = Chr(Random(48, 57, 1)) ;0-9
	  $pwd &= $aSpace[Random(0, 2, 1)]
   Next
   Return $pwd
EndFunc

$iniFile = "watch.ini"

Dim $run[0][2]
Dim $newRun[0]

Func TerminateChilds()
	For $i = 0 to UBound($run) - 1
		ProcessClose($run[$i][0])
	Next
EndFunc

Local $source
If $CmdLine[0] == 0 Then

	Local $i, $j, $exists, $pid
	OnAutoItExitRegister ( "TerminateChilds" )

	While 1
		$source = IniReadSectionNames($iniFile)
		For $i = 0 To UBound($run) - 1
			$exists = False
			For $j = 1 To $source[0]
				If $source[$j] == $run[$i][1] Then $exists = True
			Next
			If Not $exists Then
				ProcessClose($run[$i][0])
				_ArrayDelete($run, $i)
				ContinueLoop
			EndIf
		Next
		For $i = 1 To $source[0]
			$exists = False
			For $j = 0 To UBound($run) - 1
				If $source[$i] == $run[$j][1] Then $exists = True
			Next
			If Not $exists Then
				$pid = Run(@ScriptName & " """ & $source[$i] & """")
				Dim $temp[1][2] = [[$pid, $source[$i]]]
				_ArrayAdd($run, $temp)
				ContinueLoop
			EndIf
		Next
		For $i = 0 To UBound($run) - 1
			If ProcessExists($run[$i][0]) == 0 Then
				$pid = Run(@ScriptName & " """ & $run[$i][1] & """")
				$run[$i][0] = $pid
				ContinueLoop
			EndIf
		Next

		Sleep(1000)
	WEnd

EndIf

MsgBox($MB_SYSTEMMODAL, $CmdLine[1], "I am started " & @CRLF & $CmdLine[1], 10)
Func Terminated()
	MsgBox($MB_SYSTEMMODAL, $CmdLine[1], "I am terminated " & @CRLF & $CmdLine[1], 10)
EndFunc
OnAutoItExitRegister ( "Terminated" )
TraySetToolTip($CmdLine[1])

$tools = "bs1770gain-tools\"

Local $source = $CmdLine[1]
Local $destination = IniRead($iniFile, $source, "destination", Null)
Local $bak = IniRead($iniFile, $source, "bak", Null)
Local $stmp = IniRead($iniFile, $source, "stmp", Null)
Local $tmp1 = IniRead($iniFile, $source, "tmp1", Null)
Local $tmp2 = IniRead($iniFile, $source, "tmp2", Null)
Local $otmp = IniRead($iniFile, $source, "otmp", Null)
Local $ffmpeg_cmd = IniRead($iniFile, $source, "ffmpeg_cmd", Null)
Local $destinationExtension = IniRead($iniFile, $source, "destinationExtension", Null)
Local $threads = IniRead($iniFile, $source, "threads", Null)
Local $sox_cmd = IniRead($iniFile, $source, "sox_cmd", Null)

If Not FileExists($source) Then DirCreate($source)
If Not FileExists($bak) Then DirCreate($bak)
If Not FileExists($destination) Then DirCreate($destination)
If Not FileExists($stmp) Then DirCreate($stmp)
If Not FileExists($tmp1) Then DirCreate($tmp1)
If Not FileExists($tmp2) Then DirCreate($tmp2)
If Not FileExists($otmp) Then DirCreate($otmp)

Local $tempFile
Local $sFile
Local $descriptionFile
Local $audioInput
Local $audioOutput
Local $outFile
Local $sTitr
Local $eTitr

While 1
	Local $files = _FileListToArray($source, "*", $FLTA_FILES, False)

	Local $i = 1
	For $i = 1 To Ubound($files) - 1

		Local $f = $files[$i]
		Local $sDrive = "", $sDir = "", $sFileName = "", $sExtension = ""
		Local $aPathSplit = _PathSplit($f, $sDrive, $sDir, $sFileName, $sExtension)

		Local $h = FileOpen($source & $sFileName & $sExtension, $FO_APPEND)
		If $h == -1 Then ContinueLoop
		FileClose($h)
		Sleep(50)
		Local $h = FileOpen($source & $sFileName & $sExtension, $FO_APPEND)
		If $h == -1 Then ContinueLoop
		FileClose($h)
		Sleep(50)
		Local $h = FileOpen($source & $sFileName & $sExtension, $FO_APPEND)
		If $h == -1 Then ContinueLoop
		FileClose($h)
		Sleep(50)
		Local $h = FileOpen($source & $sFileName & $sExtension, $FO_APPEND)
		If $h == -1 Then ContinueLoop
		FileClose($h)

		$bak = IniRead($iniFile, $source, "bak", Null)
		$destination = IniRead($iniFile, $source, "destination", Null)
		$stmp = IniRead($iniFile, $source, "stmp", Null)
		$tmp1 = IniRead($iniFile, $source, "tmp1", Null)
		$tmp2 = IniRead($iniFile, $source, "tmp2", Null)
		$otmp = IniRead($iniFile, $source, "otmp", Null)
		$ffmpeg_cmd = IniRead($iniFile, $source, "ffmpeg_cmd", Null)
		$destinationExtension = IniRead($iniFile, $source, "destinationExtension", Null)
		$threads = IniRead($iniFile, $source, "threads", Null)
		$sox_cmd = IniRead($iniFile, $source, "sox_cmd", Null)
		$pre_cmd = IniRead($iniFile, $source, "prepare_ffmpeg_cmd", Null)

		$tempFile = randomString(8)
		$bak &= $sFileName & $sExtension
		$sFile = $stmp & $tempFile & $sExtension
		$descriptionFile = $tmp1 & $tempFile & $sExtension & ".ini"
		$audioInput = $tmp2 & $tempFile & ".wav"
		$audioInputSox = $tmp1 & $tempFile & "_sox.wav"
		$audioOutput = $tmp1 & $tempFile & "_norm.wav"
		$outFile = $otmp & $tempFile & "_out" & $destinationExtension

		If FileMove($source & $sFileName & $sExtension, $bak, $FC_OVERWRITE) == 0 Then ContinueLoop
		If Not $pre_cmd Then
			If FileCopy($bak, $sFile, $FC_OVERWRITE) == 0 Then ContinueLoop
		Else
			$cmd_pre  = $tools & "ffmpeg -y -ss 0:0:0.0 -r 25 -i """ & $bak & """ " & $pre_cmd & " " & $sFile
			RunWait($cmd_pre)
		EndIf
		Sleep(100)

		;$log = 	FileOpen($tempFile & ".bat", $FO_OVERWRITE + $FO_UTF8 + $FO_CREATEPATH)

		$cmd_info = "cmd /c """ & $tools & "ffprobe -v quiet -print_format ini -show_format -show_streams " & $sFile & " > """ & $descriptionFile & """"
		;FileWriteLine($log, $cmd_info)
		RunWait($cmd_info)

		$dur = Number(IniRead($descriptionFile, "streams.stream.0", "duration", Null))

		$cmd_AudioInput = $tools & "ffmpeg -ss 0:0:0 -i " & $sFile & " -t " & $dur & " -vn -c:a pcm_s16le -af ""pan=stereo| FL < FL + 0.5*FC + 0.6*BL + 0.6*SL | FR < FR + 0.5*FC + 0.6*BR + 0.6*SR"" -ac 2 " & $audioInput & " -y -threads " & $threads
		;FileWriteLine($log, $cmd_AudioInput)
		RunWait($cmd_AudioInput)
		Sleep(100)

		$audioOutput = "tmp\" & $tempFile & ".flac"

		If IsString($sox_cmd) And $sox_cmd <> "" Then

			$audioOutput = "tmp\" & $tempFile & "_sox.flac"

			$cmd_Sox = $tools & "sox  " & $audioInput & " " & $audioInputSox & " " & $sox_cmd
			;FileWriteLine($log, $cmd_Sox)
			RunWait($cmd_Sox)
			$audioInput = $audioInputSox
		EndIf

		$cmd_BS1770gain = "bs1770gain --ebu """ & $audioInput & """ -ao ""tmp"""
		;FileWriteLine($log, $cmd_BS1770gain)
		RunWait($cmd_BS1770gain)
		Sleep(100)

		$a = StringRegExp($sFileName, "^.+{(\d{2}) (\d{2}) (\d{2}) (\d{2}) (\d{2})}$", $STR_REGEXPARRAYGLOBALMATCH)

		If @error Then
			$cmd_Output = $tools & "ffmpeg -i " & $sFile & " -i " & $audioOutput & " " & $ffmpeg_cmd & " -map 0:v -map 1:a -threads " & $threads & " " & $outFile & " -y"
			;FileWriteLine($log, $cmd_Output)
			RunWait($cmd_Output)
		Else

			$titr_h = Number($a[0])
			$titr_m = Number($a[1])
			$titr_s = Number($a[2])

			$dur_m = Number($a[3])
			$dur_s = Number($a[4])

			$dur = $dur - ($titr_h*60*60 + $titr_m*60 + $titr_s)
			$dstDur = $dur_m*60 + $dur_s
			$outDur = $titr_h*60*60 + $titr_m*60 + $titr_s + $dur_m*60 + $dur_s
			$speed = $dstDur / $dur

			$codec = IniRead($descriptionFile, "streams.stream.0", "codec_name", Null)

			$sTitr = $tmp1 & $tempFile & "_stitr" & $sExtension
			$eTitr = $tmp1 & $tempFile & "_etitr" & $sExtension

			$cmd_ETirt  = $tools & "ffmpeg -y -ss " & $titr_h & ":" & $titr_m & ":" & $titr_s & " -i " & $sFile & " -filter:v ""setpts=" & $speed & "*PTS"" -t 00:" & $dur_m & ":" & $dur_s & " -c:v " & $codec & " -qscale:v 0 -flags +ilme+ildct -deinterlace -an " & $eTitr
			$cmd_STitr  = $tools & "ffmpeg -y -ss 0:0:0 -i " & $sFile & " -t " & $titr_h & ":" & $titr_m & ":" & $titr_s & " -c:v copy -an " & $sTitr
			$cmd_Output = $tools & "ffmpeg -y -i concat:""" & $sTitr & "|" & $eTitr & """ -i " & $audioOutput & " -t " & $outDur & " " & $ffmpeg_cmd & " -map 0:v -map 1:a -threads " & $threads & " " & $outFile
			;FileWriteLine($log, $cmd_ETirt)
			;FileWriteLine($log, $cmd_STitr)
			;FileWriteLine($log, $cmd_Output)
			RunWait($cmd_ETirt)
			RunWait($cmd_STitr)
			RunWait($cmd_Output)

		EndIf

		;FileClose($log)

		FileMove($outFile, $destination & $sFileName & $destinationExtension, $FC_OVERWRITE)

		Sleep(100)

		FileDelete($sFile)
		FileDelete($descriptionFile)
		FileDelete($sTitr)
		FileDelete($eTitr)
		FileDelete($tmp2 & $tempFile & ".wav")
		FileDelete($tmp1 & $tempFile & "_sox.wav")
		FileDelete($tmp1 & $tempFile & "_norm.wav")
		FileDelete($audioOutput)

		;Exit

	Next

	Sleep(1000)
WEnd

Поделиться с друзьями
-->

Комментарии (0)