a have problems in audio network (i work ip audio) - time time have short gaps in audio stream. have logger record streams. i've write small script python , ffmpeg (and borrowed javascript visualizing :)) find gaps in logger mp3 files. it's better nothing have lot of false detections - it's quite annoying to check results manually - script finds 20 200 gaps per hour , 1-10 gaps caused fault - others short term low audio level in songs, speech, etc. i'm looking high level machine learning/data mining mechanism check gaps automatically leave want. can provide lot of "true" gaps (array data) , "false" gaps teach machine , after want feed data stamp gap inside compare "true" gap or not. can recommend fastest solution? note python thing can write little. :/ @ moment code of gap searching following. finds gaps duration more gap_min ms , less gap_max ms in mp3 file or folder files.
import numpy np import subprocess, os, sys import ntpath tolerance=150#100 gap_min=0.007#0.021 gap_max=0.035#0.03 sample_rate=48000 gap_file_duration=3#duration of output mp3 files gaps ffmpeg_path=r'/applications/ffmpeg' temp_folder=r'/users/user/downloads/' result_folder=r'/users/user/downloads/tmp/' target_lufs=-9#in lufs def samples_to_timecode(samples): return '{0:02d}:{1:02d}:{2:02d}.{3:02d}'.format(int(samples / (3600*sample_rate)), int(samples / (60*sample_rate) % 60), int(samples / sample_rate % 60), int(samples % sample_rate)) def timecode_to_samples(timecode): return sum(f * int(t) f,t in zip((3600*sample_rate, 60*sample_rate, sample_rate, 1), timecode.split(':'))) def seconds_to_timecode(seconds): return '{0:02d}:{1:02d}:{2:03f}'.format(int(seconds / (3600)), int(seconds / (60) % 60), seconds % 60)#, #int(seconds % 1000 % 60)) def analyze_bin_file(source_file): print('analizing start...') data = np.memmap(source_file, dtype='h', mode='r') zero_indexes=np.where(np.logical_and(data>=-tolerance, data<=tolerance)) gap_start=none gaps_array=[] in range(len(zero_indexes[0])-1): if zero_indexes[0][i+1]-zero_indexes[0][i] == 1: if not gap_start: gap_start=i else: if gap_start: if ((zero_indexes[0][i]-zero_indexes[0][gap_start]) >= (gap_min*sample_rate)) , ((zero_indexes[0][i]-zero_indexes[0][gap_start]) <= (gap_max*sample_rate)): gaps_array.append([float(zero_indexes[0][gap_start])/sample_rate, float(zero_indexes[0][i])/sample_rate, samples_to_timecode(zero_indexes[0][gap_start]), round(float(zero_indexes[0][i]-zero_indexes[0][gap_start])/sample_rate,3)]) print('gaps found: %s' % len(gaps_array)) gap_start=none os.remove(source_file)#for reasons works badly in windows. comment line if cause problem. should delete temporary bin files manually after that. print('analizing done!') return gaps_array def execute_cmd(cmd): p = subprocess.popen(cmd , shell=true, stdout=subprocess.pipe, stderr=subprocess.pipe) out, err = p.communicate() return out.rstrip(), err.rstrip(), p.returncode def prepare_bin_file(source_file): print('start preparing binary file...') result_file_path=temp_folder+ntpath.basename(source_file)+'.bin' result=execute_cmd('{0} -i {1} -ar {4} -af volume={3} -ac 1 -map 0:a -c:a pcm_s16le -y -f data {2}'.format(ffmpeg_path, source_file, result_file_path, volume, sample_rate)) if result[2] == 0: print('preparing done!') return result_file_path else: print('error occures while preparing!') def cut_gaps(mp3_file,gaps_array): print('cutting file {0} start...'.format(mp3_file)) result_files=[] path_list = mp3_file.split(os.sep) gap in range(len(gaps_array)): gap_start=seconds_to_timecode(gaps_array[gap][0]-float(gap_file_duration)/2) gap_duration=gap_file_duration+gaps_array[gap][3] result=execute_cmd('{0} -y -i {1} -ss {2} -t {3} -c:a copy {4}'.format(ffmpeg_path, mp3_file, gap_start, gap_duration, result_folder+path_list[-2]+os.sep+'mp3'+os.sep+ntpath.basename(mp3_file)+'.{0:03d}'.format(gap)+'.mp3')) #print('save bin data file {0} of {1} {2}'.format(gap+1, len(gaps_array), 'ok' if (result_bin[-1] == 0) else 'error')) #print(result_bin) result_files.append(ntpath.basename(mp3_file)+'.{0:03d}'.format(gap)+'.mp3') print('cutting file {0} of {1} {2}'.format(gap+1, len(gaps_array), 'ok' if (result[-1] == 0) else 'error')) print('cutting done!') return result_files def make_report(source_file, gaps_array, cut_files): path_list = source_file.split(os.sep) report=open(result_folder+path_list[-2]+os.sep+ntpath.basename(source_file)+'.html','w') report.write('<!doctype html><html lang=""><head></head><html><body><script src="https://cdnjs.cloudflare.com/ajax/libs/wavesurfer.js/1.1.2/wavesurfer.min.js"></script>') report.write('<div>file {0} analizing report<br>'.format(source_file)) report.write('searching parameters:<br>gap minimum {0} second<br>gap maximum {1} second<br>tolerance value {2}<br>analyze volume {3} db<hr><hr></div>'.format(gap_min, gap_max, tolerance, volume)) if len(gaps_array) > 0: gap_no in range(len(gaps_array)): report.write('<div>gap no {0}<br>gap start {1}<br>gap duration {2}ms</div>'.format(gap_no, gaps_array[gap_no][2], gaps_array[gap_no][3]*1000)) html=""" <div id='waveform""" + str(gap_no) + """'></div> <div style='text-align: center'> <button class='btn btn-primary' onclick='wavesurfer""" + str(gap_no) + """.playpause()'> <i class='glyphicon glyphicon-play'></i> play </button> <p class='row'> <div class='col-xs-1'> <i class='glyphicon glyphicon-zoom-in'></i> </div> <div class='col-xs-10'> <input id='slider""" + str(gap_no) + """' type='range' min='1' max='4000' value='1' style='width: 100%' /> </div> <div class='col-xs-1'> <i class='glyphicon glyphicon-zoom-out'></i> </div> </p> </div> """ report.write(html) script=""" <script> var wavesurfer""" + str(gap_no) + """ = wavesurfer.create({ container: '#waveform""" + str(gap_no) + """', wavecolor: 'red', progresscolor: 'purple' }); wavesurfer""" + str(gap_no) + """.load('./mp3/""" + cut_files[gap_no] + """'); var slider""" + str(gap_no) + """ = document.queryselector('#slider""" + str(gap_no) + """'); slider""" + str(gap_no) + """.oninput = function () { var zoomlevel = number(slider""" + str(gap_no) + """.value); wavesurfer""" + str(gap_no) + """.zoom(zoomlevel); }; </script> """ report.write(script) else: report.write('<div>no gaps found!</div>') report.write('</body></html>') report.close() def normalize_file(source): print('analizing integrated loudness...') result = execute_cmd('{0} -nostats -i {1} -filter_complex ebur128 -f null -'.format(ffmpeg_path, source)) if result[-1] == 0: summary_index=str(result[1][-255:]).rfind('summary:') summary_list=str(result[1][-255:][summary_index:]).split() i_lufs = float(summary_list[summary_list.index('i:') + 1]) gainlog = -(i_lufs - target_lufs) volume = 10 ** (gainlog / 20) print('analizing complete. i= {0} lufs. volume change value={1}.'.format(i_lufs, volume)) else: print('error!') return volume def run(source): if os.path.isfile(source) or os.path.isdir(source): path_list = source.split(os.sep) if not os.path.isdir(result_folder+path_list[-2]): os.makedirs(result_folder+path_list[-2]) if not os.path.isdir(result_folder+path_list[-2]+os.sep+'mp3'): os.makedirs(result_folder+path_list[-2]+os.sep+'mp3') else: print('error! file of folder {0} not found!'.format(source)) if os.path.isfile(source): global volume volume=normalize_file(source) bin_file=prepare_bin_file(source) gaps_array=analyze_bin_file(bin_file) if len(gaps_array): cut_files=cut_gaps(source, gaps_array) make_report(source, gaps_array, cut_files) else: make_report(source, gaps_array, cut_files=[]) elif os.path.isdir(source): file in os.listdir(source): if file.endswith(".mp3"): print(source ,file) run(source+os.sep+file) src=r'/users/user/downloads/2016-08-02' if len(sys.argv) > 1: run(sys.argv[1]) else: run(src)
the result html file waveforms. result works in firefox browser. false gaps: example of false gap 1 true gaps: example of true gap 1
update. because algorithm sensitive volume level, i've added volume normalization before analyzing data. doesn't apply output files - it's normalize data before being analyzed.
Comments
Post a Comment