3838# If no extenstion is specified it will target the default 'wav' extension. (Not case sensitive)
3939options = [ ]
4040ARGV . options do |opts |
41- opts . on ( '-p' , '--Policy=val' , String ) { |val | POLICY_FILE = val }
41+ opts . on ( '-a' , '--all' ) { options += [ 'meta' , 'bext' , 'signal' , 'dropouts' , 'md5' ] }
42+ opts . on ( '-b' , '--bext-scan' ) { options << 'bext' }
43+ opts . on ( '-c' , '--checksum' ) { options << 'md5' }
44+ opts . on ( '-d' , '--dropout-scan' ) { options << 'dropouts' }
4245 opts . on ( '-e' , '--Extension=val' , String ) { |val | TARGET_EXTENSION = val . downcase }
43- opts . on ( '-q' , '--Quiet' ) { options << 'quiet' }
4446 opts . on ( '-m' , '--meta-scan' ) { options << 'meta' }
45- opts . on ( '-b ' , '--bext-scan' ) { options << 'bext' }
47+ opts . on ( '-p ' , '--Policy=val' , String ) { | val | POLICY_FILE = val }
4648 opts . on ( '-s' , '--signal-scan' ) { options << 'signal' }
47- opts . on ( '-d' , '--dropout-scan' ) { options << 'dropouts' }
48- opts . on ( '-a' , '--all' ) { options += [ 'meta' , 'bext' , 'signal' , 'dropouts' ] }
49+ opts . on ( '-q' , '--Quiet' ) { options << 'quiet' }
4950 opts . parse!
5051end
5152
@@ -110,79 +111,7 @@ class QcTarget
110111 @input_path = value
111112 @warnings = [ ]
112113 end
113-
114- # Function to scan file for mediaconch compliance
115- def media_conch_scan ( policy )
116- if File . file? ( policy )
117- @qc_results = [ ]
118- policy_path = File . path ( policy )
119- command = 'mediaconch --Policy=' + '"' + policy_path + '" ' + '"' + @input_path + '"'
120- media_conch_out = `#{ command } `
121- media_conch_out . strip!
122- media_conch_out . split ( '/n' ) . each { |qcline | @qc_results << qcline }
123- @qc_results = @qc_results . to_s
124- if File . exist? ( policy )
125- if @qc_results . include? ( 'pass!' )
126- @qc_results = 'PASS'
127- else
128- @warnings << 'MEDIACONCH FAIL'
129- end
130- end
131- else
132- @qc_results = policy
133- end
134- end
135-
136- # Functions to scan audio stream characteristics
137- # Function to get ffprobe json info
138- def get_ffprobe
139- ffprobe_command = 'ffprobe -print_format json -threads auto -show_entries frame_tags=lavfi.astats.Overall.Number_of_samples,lavfi.astats.Overall.Peak_level,lavfi.astats.Overall.Max_difference,lavfi.astats.Overall.Mean_difference,lavfi.astats.Overall.Peak_level,lavfi.aphasemeter.phase -f lavfi -i "amovie=' + "\\ '" + @input_path + "\\ '" + ',astats=reset=1:metadata=1,aphasemeter=video=0"'
140- @ffprobe_out = JSON . parse ( `#{ ffprobe_command } ` )
141- @total_frame_count = @ffprobe_out [ 'frames' ] . size
142- end
143-
144- def normalize_time ( time_source )
145- Time . at ( time_source ) . utc . strftime ( '%H:%M:%S:%m' )
146- end
147-
148- def get_mediainfo
149- @mediainfo_out = JSON . parse ( `mediainfo --Output=JSON "#{ @input_path } "` )
150- @duration_normalized = Time . at ( @mediainfo_out [ 'media' ] [ 'track' ] [ 0 ] [ 'Duration' ] . to_f ) . utc . strftime ( '%H:%M:%S' )
151- end
152-
153- def qc_encoding_history
154- if TARGET_EXTENSION == 'wav'
155- @enc_hist_error = [ ]
156- unless @mediainfo_out [ 'media' ] [ 'track' ] [ 0 ] [ 'extra' ] . nil?
157- if @mediainfo_out [ 'media' ] [ 'track' ] [ 0 ] [ 'extra' ] [ 'bext_Present' ] == 'Yes' && @mediainfo_out [ 'media' ] [ 'track' ] [ 0 ] [ 'Encoded_Library_Settings' ]
158- signal_chain_count = @mediainfo_out [ 'media' ] [ 'track' ] [ 0 ] [ 'Encoded_Library_Settings' ] . scan ( /A=/ ) . count
159- if @mediainfo_out [ 'media' ] [ 'track' ] [ 1 ] [ 'Channels' ] == "1"
160- unless @mediainfo_out [ 'media' ] [ 'track' ] [ 0 ] [ 'Encoded_Library_Settings' ] . scan ( /mono/i ) . count == signal_chain_count
161- @enc_hist_error << "BEXT Coding History channels don't match file"
162- end
163- end
164-
165- if @mediainfo_out [ 'media' ] [ 'track' ] [ 1 ] [ 'Channels' ] == "2"
166- @stereo_count = @mediainfo_out [ 'media' ] [ 'track' ] [ 0 ] [ 'Encoded_Library_Settings' ] . scan ( /stereo/i ) . count
167- @dual_count = @mediainfo_out [ 'media' ] [ 'track' ] [ 0 ] [ 'Encoded_Library_Settings' ] . scan ( /dual/i ) . count
168- unless @stereo_count + @dual_count == signal_chain_count
169- @enc_hist_error << "BEXT Coding History channels don't match file"
170- end
171- end
172- end
173- else
174- @enc_hist_error << "Encoding history not present"
175- end
176- @warnings << @enc_hist_error if @enc_hist_error . size > 0
177- end
178- end
179-
180- def check_metaedit
181- scan_output = `bwfmetaedit "#{ @input_path } " 2>&1` . chomp . chomp
182- @wave_conformance = scan_output . split ( ':' ) . last . strip if scan_output . include? ( 'invalid' )
183- @warnings << "Invalid Wave Detected" unless @wave_conformance . nil?
184- end
185-
114+
186115 def check_dropouts
187116 @sample_ratios = [ ]
188117 @possible_drops = [ ]
@@ -201,6 +130,28 @@ class QcTarget
201130 @warnings << "Possible Dropouts Detected" if @possible_drops . length > 0
202131 end
203132
133+ def check_md5
134+ puts "Verifying embedded MD5 for #{ @input_path } "
135+ md5_line = `bwfmetaedit --MD5-Verify -v "#{ @input_path } " 2>&1` . chomp . chomp . split ( "\n " ) [ 0 ]
136+ if md5_line . include? ( 'MD5, no existing MD5 chunk' )
137+ @warnings << 'No MD5'
138+ elsif md5_line . include? ( 'MD5, failed verification' )
139+ @warnings << 'Failed MD5 Verification'
140+ elsif ! md5_line . include? ( 'MD5, verified' )
141+ @warnings << 'MD5 check unable to be performed'
142+ end
143+ end
144+
145+ def check_metaedit
146+ scan_output = `bwfmetaedit "#{ @input_path } " 2>&1` . chomp . chomp
147+ @wave_conformance = scan_output . split ( ':' ) . last . strip if scan_output . include? ( 'invalid' )
148+ if @wave_conformance . nil?
149+ @wave_conformance = ' '
150+ else
151+ @warnings << "Invalid Wave Detected" unless @wave_conformance . nil?
152+ end
153+ end
154+
204155 def find_peaks_n_phase
205156 high_db_frames = [ ]
206157 out_of_phase_frames = [ ]
@@ -217,12 +168,14 @@ class QcTarget
217168 phase_limit = Configurations [ 'generic_audio_phase_limit' ]
218169 end
219170 @ffprobe_out [ 'frames' ] . each do |frames |
220- peaklevel = frames [ 'tags' ] [ 'lavfi.astats.Overall.Peak_level' ] . to_f
171+ peaklevel = frames [ 'tags' ] [ 'lavfi.astats.Overall.Peak_level' ]
221172 audiophase = frames [ 'tags' ] [ 'lavfi.aphasemeter.phase' ] . to_f
222173 phase_frames << audiophase
223174 out_of_phase_frames << audiophase if audiophase < phase_limit
224- high_db_frames << peaklevel if peaklevel > Configurations [ 'high_level_warning' ]
225- @levels << peaklevel
175+ if peaklevel != '-inf'
176+ high_db_frames << peaklevel . to_f if peaklevel . to_f > Configurations [ 'high_level_warning' ]
177+ @levels << peaklevel . to_f
178+ end
226179 end
227180 @max_level = @levels . max . round ( 2 )
228181 @high_level_count = high_db_frames . size
@@ -238,23 +191,88 @@ class QcTarget
238191 @warnings << 'PHASE WARNING' if @phasey_frame_count > 50
239192 end
240193
194+ def get_ffprobe
195+ ffprobe_command = 'ffprobe -print_format json -threads auto -show_entries frame_tags=lavfi.astats.Overall.Number_of_samples,lavfi.astats.Overall.Peak_level,lavfi.astats.Overall.Max_difference,lavfi.astats.Overall.Mean_difference,lavfi.astats.Overall.Peak_level,lavfi.aphasemeter.phase -f lavfi -i "amovie=' + "\\ '" + @input_path + "\\ '" + ',astats=reset=1:metadata=1,aphasemeter=video=0"'
196+ @ffprobe_out = JSON . parse ( `#{ ffprobe_command } ` )
197+ @total_frame_count = @ffprobe_out [ 'frames' ] . size
198+ end
199+
200+ def get_mediainfo
201+ @mediainfo_out = JSON . parse ( `mediainfo --Output=JSON "#{ @input_path } "` )
202+ @duration_normalized = Time . at ( @mediainfo_out [ 'media' ] [ 'track' ] [ 0 ] [ 'Duration' ] . to_f ) . utc . strftime ( '%H:%M:%S' )
203+ end
204+
205+ # Function to scan file for mediaconch compliance
206+ def media_conch_scan ( policy )
207+ if File . file? ( policy )
208+ @qc_results = [ ]
209+ policy_path = File . path ( policy )
210+ command = 'mediaconch --Policy=' + '"' + policy_path + '" ' + '"' + @input_path + '"'
211+ media_conch_out = `#{ command } `
212+ media_conch_out . strip!
213+ media_conch_out . split ( '/n' ) . each { |qcline | @qc_results << qcline }
214+ @qc_results = @qc_results . to_s
215+ if File . exist? ( policy )
216+ if @qc_results . include? ( 'pass!' )
217+ @qc_results = 'PASS'
218+ else
219+ @warnings << 'MEDIACONCH FAIL'
220+ end
221+ end
222+ else
223+ @qc_results = policy
224+ end
225+ end
226+
227+ def normalize_time ( time_source )
228+ Time . at ( time_source ) . utc . strftime ( '%H:%M:%S:%m' )
229+ end
230+
241231 def output_csv_line ( options )
242232 line = [ @input_path , @warnings . flatten . join ( ', ' ) , @duration_normalized ]
243- if options . include? 'dropouts'
233+ if options . include? ( 'dropouts' )
244234 line << @possible_drops
245235 end
246- if options . include? 'signal'
236+ if options . include? ( 'signal' )
247237 line += [ @average_levels , @max_level , @high_level_count , @average_phase , @phasey_frame_count ]
248238 end
249- if options . include? 'meta'
239+ if options . include? ( 'meta' )
250240 line += [ @wave_conformance ] unless TARGET_EXTENSION != 'wav'
251241 line += [ @qc_results ]
252242 end
253243 return line
254244 end
245+
255246 def output_warnings
256247 @warnings
257248 end
249+
250+ def qc_encoding_history
251+ if TARGET_EXTENSION == 'wav'
252+ @enc_hist_error = [ ]
253+ unless @mediainfo_out [ 'media' ] [ 'track' ] [ 0 ] [ 'extra' ] . nil?
254+ if @mediainfo_out [ 'media' ] [ 'track' ] [ 0 ] [ 'extra' ] [ 'bext_Present' ] == 'Yes' && @mediainfo_out [ 'media' ] [ 'track' ] [ 0 ] [ 'Encoded_Library_Settings' ]
255+ signal_chain_count = @mediainfo_out [ 'media' ] [ 'track' ] [ 0 ] [ 'Encoded_Library_Settings' ] . scan ( /A=/ ) . count
256+ if @mediainfo_out [ 'media' ] [ 'track' ] [ 1 ] [ 'Channels' ] == "1"
257+ unless @mediainfo_out [ 'media' ] [ 'track' ] [ 0 ] [ 'Encoded_Library_Settings' ] . scan ( /mono/i ) . count == signal_chain_count
258+ @enc_hist_error << "BEXT Coding History channels don't match file"
259+ end
260+ end
261+
262+ if @mediainfo_out [ 'media' ] [ 'track' ] [ 1 ] [ 'Channels' ] == "2"
263+ @stereo_count = @mediainfo_out [ 'media' ] [ 'track' ] [ 0 ] [ 'Encoded_Library_Settings' ] . scan ( /stereo/i ) . count
264+ @dual_count = @mediainfo_out [ 'media' ] [ 'track' ] [ 0 ] [ 'Encoded_Library_Settings' ] . scan ( /dual/i ) . count
265+ unless @stereo_count + @dual_count == signal_chain_count
266+ @enc_hist_error << "BEXT Coding History channels don't match file"
267+ end
268+ end
269+ end
270+ else
271+ @enc_hist_error << "Encoding history not present"
272+ end
273+ @warnings << @enc_hist_error if @enc_hist_error . size > 0
274+ end
275+ end
258276end
259277
260278# Make list of inputs
@@ -284,27 +302,30 @@ end
284302file_inputs . each do |fileinput |
285303 target = QcTarget . new ( File . expand_path ( fileinput ) )
286304 target . get_mediainfo
287- if options . include? 'meta'
305+ if options . include? ( 'meta' )
288306 if defined? POLICY_FILE
289307 target . media_conch_scan ( POLICY_FILE )
290308 else
291309 target . media_conch_scan ( 'Valid Policy File Not Found' )
292310 end
293311 target . check_metaedit unless TARGET_EXTENSION != 'wav'
294312 end
295- if options . include? 'bext'
313+ if options . include? ( 'bext' )
296314 target . qc_encoding_history
297315 end
316+ if options . include? ( 'md5' )
317+ target . check_md5
318+ end
298319 if options . include? ( 'signal' ) || options . include? ( 'dropouts' )
299320 target . get_ffprobe
300- if options . include? 'signal'
321+ if options . include? ( 'signal' )
301322 target . find_peaks_n_phase
302323 end
303- if options . include? 'dropouts'
324+ if options . include? ( 'dropouts' )
304325 target . check_dropouts
305326 end
306327 end
307- if options . include? 'quiet'
328+ if options . include? ( 'quiet' )
308329 if target . output_warnings . empty?
309330 puts 'QC Pass!'
310331 exit 0
@@ -323,15 +344,15 @@ output_csv = ENV['HOME'] + "/Desktop/audioqc-out_#{timestamp}.csv"
323344
324345CSV . open ( output_csv , 'wb' ) do |csv |
325346 headers = [ 'Filename' , 'Warnings' , 'Duration' ]
326- if options . include? 'dropouts'
347+ if options . include? ( 'dropouts' )
327348 headers << 'Possible Drops'
328349 end
329350
330- if options . include? 'signal'
351+ if options . include? ( 'signal' )
331352 headers += [ 'Average Level' , 'Peak Level' , 'Number of Frames w/ High Levels' , 'Average Phase' , 'Number of Phase Warnings' ]
332353 end
333354
334- if options . include? 'meta'
355+ if options . include? ( 'meta' )
335356 headers << 'Wave Conformance Errors' unless TARGET_EXTENSION != 'wav'
336357 headers << 'MediaConch Policy Compliance'
337358 end
0 commit comments