⬆️ ⬇️

EBU R128 / BS.1770-3: Packet volume normalization of audio / video files, P2

In the previous post I described the idea of ​​batch normalizing the volume of audio / video files.



It is time to lay out the implementation of this idea. The solution was flexible and scalable.



To use it, you need to stock up with knowledge of the audio and video hardware , Sound on the eXchange , FFmpeg , BS1770GAIN manuals , as well as my favorite AutoIt package.

')

On Windows Server, as well as some other Small Soft platforms, ffmpeg version 3.1 and older no longer works.



The implementation is as follows:



A rather uncomplicated script (autoit) listens to an ini file, sections of which are folders to listen. Each section opens its own worker (an instance of the script), which reads the config before each pass through the media file. When removing a section, the worker closes. When closing the main worker, all open workers are closed.



The config of the worker is:



[\\host\path\] destination=\\host\path\ bak=\\host\path\ stmp=tmp\ tmp1=tmp\ tmp2=tmp\ otmp=tmp\ destinationExtension=.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 


Manual:



Read the manual

1) destination



Where to store the final files.

2) bak



Where to store the source files.

My source files are in the network basket, the files are backed up in the same basket, in a separate daddy.

3) temporary directories



It is understood that there are several physical media on the machine that processes the video files.

For example, I have the source files in one network basket, the finished files are in the second network basket, and the processing is done by the third machine.

In the third machine, I stuck three hard drives for temporary files.

stmp



 $sFile = $stmp & $tempFile & $sExtension 


The file from the network basket arrives on hard0

tmp1



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


If the sox_cmd value is present in the config, then the audio data is read from hard2 to and the result of processing is recorded hard1

When loudness is normalized, audio data is read from hard2 or hard1 ( sox_cmd? ) And written to hard1

tmp2



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


The original audio stream of the video file is read from hard0 to hard2

otmp



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


When assembling a finished video file, the video stream is read from hard0 ( stmp ), the audio stream is read from hard1 ( tmp1 ), and the result is written to hard2

4) destinationExtension



Result Video File Container

5) threads



Writes to the command line the assembly of the resulting video file threads = (threads) from the config.

6) prepare_ffmpeg_cmd



If not empty, then

ffmpeg -y -ss 0:0:0.0 -r 25 -i [bak] [prepare_ffmpeg_cmd] [stmp]

Otherwise, the source file is copied from bak to stmp.

7) ffmpeg_cmd



Reads the file name.

If at the end of the file name there is a construction of the form {HH MM SS mm ss}, then starting from the frame HH MM SS the tail of the file will be converted to the mm ss timing by the acceleration / deceleration method without distorting the audio track.

The resulting file is assembled with the command

ffmpeg -i [stmp(video)] -i [tmp1(normalized -23LUFS audio)] [ffmpeg_cmd] -map 0:v -map 1:a -threads [threads] [otmp] -y

sox_cmd



Audio filter will be applied before normalizing the sound.

sox [tmp2] [tmp1] [sox_cmd]



Here, in fact, the whole simple script. Very comfortably. Daddy creates himself, files tracks, processes, stores as necessary, buggy very rarely, almost never. Memory does not eat, behaves with dignity) Use for health!



Read script code
 #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)) ;AZ $aSpace[1] = Chr(Random(97, 122, 1)) ;az $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 


Source: https://habr.com/ru/post/333686/



All Articles