1
+
2
+ import os
3
+ import logging
4
+ import subprocess as sp
5
+
6
+ from ..encodingUtils import getFreeNameForFileAndLog
7
+ from ..encodingUtils import logffmpegEncodeProgress
8
+ from ..encodingUtils import isRquestCancelled
9
+
10
+ from ..optimisers .nelderMead import encodeTargetingSize as encodeTargetingSize_nelder_mead
11
+ from ..optimisers .linear import encodeTargetingSize as encodeTargetingSize_linear
12
+
13
+ def encoder (inputsList , outputPathName ,filenamePrefix , filtercommand , options , totalEncodedSeconds , totalExpectedEncodedSeconds , statusCallback ,requestId = None ,encodeStageFilter = 'null' ,globalOptions = {},packageglobalStatusCallback = print ):
14
+
15
+ audoBitrate = 8
16
+ for abr in ['48' ,'64' ,'96' ,'128' ,'192' ]:
17
+ if abr in options .get ('audioChannels' ,'' ):
18
+ audoBitrate = int (abr )* 1024
19
+
20
+ audio_mp = 8
21
+ video_mp = 1024 * 1024
22
+ initialBr = globalOptions .get ('initialBr' ,16777216 )
23
+ dur = totalExpectedEncodedSeconds - totalEncodedSeconds
24
+
25
+ if options .get ('maximumSize' ) == 0.0 :
26
+ sizeLimitMax = float ('inf' )
27
+ sizeLimitMin = float ('-inf' )
28
+ initialBr = globalOptions .get ('initialBr' ,16777216 )
29
+ else :
30
+ sizeLimitMax = options .get ('maximumSize' )* 1024 * 1024
31
+ sizeLimitMin = sizeLimitMax * (1.0 - globalOptions .get ('allowableTargetSizeUnderrun' ,0.25 ))
32
+ targetSize_guide = (sizeLimitMin + sizeLimitMax )/ 2
33
+ initialBr = ( ((targetSize_guide )/ dur ) - ((audoBitrate / 1024 / audio_mp )/ dur ) )* 8
34
+
35
+ videoFileName ,logFilePath ,tempVideoFilePath ,videoFilePath = getFreeNameForFileAndLog (filenamePrefix , 'mp4' , requestId )
36
+
37
+ def encoderStatusCallback (text ,percentage ,** kwargs ):
38
+ statusCallback (text ,percentage ,** kwargs )
39
+ packageglobalStatusCallback (text ,percentage )
40
+
41
+ def encoderFunction (br ,passNumber ,passReason ,passPhase = 0 , requestId = None ,widthReduction = 0.0 ,bufsize = None ):
42
+
43
+ ffmpegcommand = []
44
+ ffmpegcommand += ['ffmpeg' ,'-y' ]
45
+ ffmpegcommand += inputsList
46
+
47
+
48
+
49
+ if widthReduction > 0.0 :
50
+ encodefiltercommand = filtercommand + ',[outv]scale=iw*(1-{widthReduction}):ih*(1-{widthReduction}):flags=bicubic[outvfinal]' .format (widthReduction = widthReduction )
51
+ else :
52
+ encodefiltercommand = filtercommand + ',[outv]null[outvfinal]' .format (widthReduction = widthReduction )
53
+
54
+ if options .get ('audioChannels' ) == 'No audio' :
55
+ ffmpegcommand += ['-filter_complex' ,encodefiltercommand + ',[outa]anullsink' ]
56
+ ffmpegcommand += ['-map' ,'[outvfinal]' ]
57
+ elif 'Copy' in options .get ('audioChannels' ,'' ):
58
+ ffmpegcommand += ['-filter_complex' ,encodefiltercommand ]
59
+ ffmpegcommand += ['-map' ,'[outvfinal]' ,'-map' ,'a:0' ]
60
+ else :
61
+ ffmpegcommand += ['-filter_complex' ,encodefiltercommand ]
62
+ ffmpegcommand += ['-map' ,'[outvfinal]' ,'-map' ,'[outa]' ]
63
+
64
+
65
+ if passPhase == 1 :
66
+ ffmpegcommand += ['-pass' , '1' , '-passlogfile' , logFilePath ]
67
+ elif passPhase == 2 :
68
+ ffmpegcommand += ['-pass' , '2' , '-passlogfile' , logFilePath ]
69
+
70
+ if bufsize is None :
71
+ bufsize = 3000000
72
+ if sizeLimitMax != 0.0 :
73
+ bufsize = str (min (2000000000.0 ,br * 2 ))
74
+
75
+ threadCount = globalOptions .get ('encoderStageThreads' ,4 )
76
+ metadataSuffix = globalOptions .get ('titleMetadataSuffix' ,' WmG' )
77
+
78
+
79
+ audioCodec = ["-c:a" ,"libopus" ]
80
+ if 'Copy' in options .get ('audioChannels' ,'' ):
81
+ audioCodec = []
82
+
83
+ ffmpegcommand += ["-shortest" , "-slices" , "8" , "-copyts"
84
+ ,"-start_at_zero" , "-c:v" ,"libaom-av1" ] + audioCodec + [
85
+ "-stats" ,"-pix_fmt" ,"yuv420p" ,"-bufsize" , str (bufsize )
86
+ ,"-threads" , str (threadCount ),"-crf" ,'25' ,'-g' , '300'
87
+ ,'-psnr' ,'-cpu-used' ,'0' ,'-row-mt' , '1' , '-preset' , '8'
88
+ ,"-metadata" , 'title={}' .format (filenamePrefix .replace ('-' ,' -' ) + metadataSuffix ) ]
89
+
90
+ if sizeLimitMax == 0.0 :
91
+ ffmpegcommand += ["-b:v" ,"0" ,"-qmin" ,"0" ,"-qmax" ,"10" ]
92
+ else :
93
+ ffmpegcommand += ["-b:v" ,str (br )]
94
+
95
+ if 'No audio' in options .get ('audioChannels' ,'' ) or passPhase == 1 :
96
+ ffmpegcommand += ["-an" ]
97
+ elif 'Stereo' in options .get ('audioChannels' ,'' ):
98
+ ffmpegcommand += ["-ac" ,"2" ]
99
+ ffmpegcommand += ["-ar" ,'48k' ]
100
+ ffmpegcommand += ["-b:a" ,str (audoBitrate )]
101
+ elif 'Mono' in options .get ('audioChannels' ,'' ):
102
+ ffmpegcommand += ["-ac" ,"1" ]
103
+ ffmpegcommand += ["-ar" ,'48k' ]
104
+ ffmpegcommand += ["-b:a" ,str (audoBitrate )]
105
+ elif 'Copy' in options .get ('audioChannels' ,'' ):
106
+ ffmpegcommand += ["-c:a" ,"copy" ]
107
+ else :
108
+ ffmpegcommand += ["-an" ]
109
+
110
+ ffmpegcommand += ["-sn" ]
111
+
112
+ if passPhase == 1 :
113
+ ffmpegcommand += ['-f' , 'null' , os .devnull ]
114
+ else :
115
+ ffmpegcommand += [tempVideoFilePath ]
116
+
117
+ logging .debug ("Ffmpeg command: {}" .format (' ' .join (ffmpegcommand )))
118
+ proc = sp .Popen (ffmpegcommand ,stderr = sp .PIPE ,stdin = sp .DEVNULL ,stdout = sp .DEVNULL )
119
+ encoderStatusCallback (None ,None , lastEncodedBR = br , lastEncodedSize = None , lastBuff = bufsize , lastWR = widthReduction )
120
+ psnr = logffmpegEncodeProgress (proc ,'Pass {} {} {}' .format (passNumber ,passReason ,tempVideoFilePath ),totalEncodedSeconds ,totalExpectedEncodedSeconds ,encoderStatusCallback ,passNumber = passPhase ,requestId = requestId )
121
+ if isRquestCancelled (requestId ):
122
+ return 0 , psnr
123
+ if passPhase == 1 :
124
+ return 0 , psnr
125
+ else :
126
+ finalSize = os .stat (tempVideoFilePath ).st_size
127
+ encoderStatusCallback (None ,None ,lastEncodedSize = finalSize )
128
+ return finalSize , psnr
129
+
130
+ encoderStatusCallback ('Encoding final ' + videoFileName ,(totalEncodedSeconds )/ totalExpectedEncodedSeconds )
131
+
132
+ optimiser = encodeTargetingSize_linear
133
+ if 'Nelder-Mead' in options .get ('optimizer' ):
134
+ optimiser = encodeTargetingSize_nelder_mead
135
+
136
+ finalFilenameConfirmed = optimiser (encoderFunction = encoderFunction ,
137
+ tempFilename = tempVideoFilePath ,
138
+ outputFilename = videoFilePath ,
139
+ initialDependentValue = initialBr ,
140
+ twoPassMode = True ,
141
+ allowEarlyExitWhenUndersize = globalOptions .get ('allowEarlyExitIfUndersized' ,True ),
142
+ sizeLimitMin = sizeLimitMin ,
143
+ sizeLimitMax = sizeLimitMax ,
144
+ maxAttempts = globalOptions .get ('maxEncodeAttempts' ,6 ),
145
+ dependentValueMaximum = options .get ('maximumBitrate' ,0 ),
146
+ requestId = requestId ,
147
+ optimiserName = options .get ('optimizer' ))
148
+
149
+ encoderStatusCallback ('Encoding final ' + videoFileName ,(totalEncodedSeconds )/ totalExpectedEncodedSeconds )
150
+
151
+ encoderStatusCallback ('Encoding complete ' + videoFilePath ,1 ,finalFilename = finalFilenameConfirmed )
0 commit comments