"""Utils for checking and correcting wavforms.""" import logging import os # basic os operations like paths import shutil import subprocess import wave from integration_scripts.general_use import which from integration_scripts.file_utils import copy_file from integration_scripts import logger import config # app_logger = app_log.app_logger def setup_wav_check_logger(log_table, date, log_path): # Init logger logger = logging.getLogger('wave_checker') log_file_name = 'bulkassetingest_wav_checker_{}-{}_log.csv'.format( log_table, date) log_file_name = os.path.join(log_path, log_file_name) hdlr = logging.FileHandler(log_file_name) formatter = logging.Formatter('%(asctime)s| %(levelname)s| %(message)s') hdlr.setFormatter(formatter) logger.addHandler(hdlr) logger.setLevel(logging.INFO) if not os.path.exists(log_file_name): # Init log file header - Include fields added by logger. with open(log_file_name, 'w') as fp: fp.write( 'time| time_ms| level| upc_folder| track| message| channels| ' 'sampwidth| framerate| frames| comptype| compname| solution\n') return logger def check_wav(wav_file, wav_logger, overwrite=False, output_path=None): """Check wavs""" # Check if sox is installed if not which('sox'): raise FileNotFoundError( '\'sox\' (http://sox.sourceforge.net/) must be installed on the ' 'system in order to check wav files. Either install \'sox\' in ' 'your path, or disable CHECK_WAV in the environment.') # Reset vars # unable_to_open = False fixed_file = False error_msg = None wav_params = None logger_msg = ', , , , , , , , , ' logger_solution = [] # Create the output path # output_path = create_path_with_todays_date(config.OUTPUT_WAV_PATH) # or # output_path = os.path.join(os.path.split(wav_file)[0], 'converted') # Use the same folder if output_path is None: output_path = os.path.split(wav_file)[0] orig_wav_file = wav_file file_root, file_ext = os.path.splitext(wav_file) fix_output_file = os.path.join(output_path, file_root + '_new' + '.wav') tmp_output_file = os.path.join(output_path, file_root + '_tmp' + '.wav') # if cleanup_temp_wav: # if file_ext == '.wav': # print('[{}] {}: Removing file.'.format(folder_count, wav_file)) # os.remove(wav_file) # else: # print('[{}] {}: Skipping non-wav file.'.format( # folder_count, wav_file)) # continue if file_root[0] == '.': msg = '{}: Hidden file. Skipping'.format(wav_file) logger.info(msg) wav_logger.info( '{}| {}| {}| {}| {}| {}| {}| {}| {}| {}'.format( output_path, wav_file, 'Hidden file.', '', '', '', '', '', '', 'Ignored.')) return False if file_ext != '.wav': msg = '{}: Not a WAV file.'.format(wav_file) logger.info(msg) wav_logger.info( '{}| {}| {}| {}| {}| {}| {}| {}| {}| {}'.format( output_path, wav_file, 'Unknown file type.', '', '', '', '', '', '', 'Ignored.')) return False elif os.stat(wav_file).st_size == 0: msg = '{}: Empty file.'.format(wav_file) logger.info(msg) wav_logger.info( '{}| {}| {}| {}| {}| {}| {}| {}| {}| {}'.format( output_path, wav_file, 'Empty File.', '', '', '', '', '', '', 'Ignored.')) return False elif file_ext == '.done': msg = '{}: Donefile.'.format(wav_file) logger.info(msg) wav_logger.info( '{}| {}| {}| {}| {}| {}| {}| {}| {}| {}'.format( output_path, wav_file, 'Donefile.', '', '', '', '', '', '', 'Ignored.')) if output_path != fix_output_file: shutil.copy(wav_file, fix_output_file) return True elif file_ext == '.flac': # Log to console msg = '{}: Flac file. Converting.'.format(wav_file) logger.info(msg) # Log to file logger_msg = '{}| {}| {}| {}| {}| {}| {}| {}| {}| {}' \ .format(output_path, wav_file, 'File opened'.format(wav_file), '', '', '', '', '', '', '') # Update wav_file loop var # new_wav_file = file_root + '.wav' # Create target path for converted file # wav_file_output = os.path.join(output_path, new_wav_file) # Transcode the file subprocess.run( [ 'sox', '{}'.format(wav_file), '-t', 'wavpcm', '{}'.format(fix_output_file) ], check=True) # replace the wave file for the rest of the loop wav_file = fix_output_file # fix_output_file = os.path.join(fix_output_path, wav_file) # Update the release list # fixed_release = True # Create a listing for the release list check at the end # wav_params= None try: # Open wave file wav = wave.open(wav_file) except Exception as e: # Open and convert fails error_msg = str(e) # Log msg = 'Exception: Could not open {} - {}'.format(wav_file, str(e)) logger.info(msg) else: wav_params = wav.getparams() logger_msg = '{}| {}| {}| {}| {}| {}| {}| {}| {}| {}' \ .format(output_path, wav_file, 'File opened'.format(wav_file), wav_params.nchannels, wav_params.sampwidth, wav_params.framerate, wav_params.nframes, wav_params.comptype, wav_params.compname, '') msg = '{}: File opened.'.format(wav_file) logger.info(msg) if not error_msg and config.FIX_ANYWAY: msg = '{}: FIX_ANYWAY - Re-saving immediately on open with ' \ 'unmodified params.'.format(wav_file) logger.info(msg) # Open and write try: wav = wave.open(wav_file) wav_params = wav.getparams() with wave.open(tmp_output_file, 'wb') as f: params = ( wav_params.nchannels, wav_params.sampwidth, wav_params.framerate, wav_params.nframes, wav_params.comptype, wav_params.compname) f.setparams(params) f.writeframes(wav.readframes(wav_params.nframes)) except Exception as e: msg = 'Exception: {}'.format(str(e)) logger.info(msg) else: shutil.move(tmp_output_file, fix_output_file) wav_params = wave.open(fix_output_file, 'rb').getparams() fixed_file = True logger_msg = '{}| {}| {}| {}| {}| {}| {}| {}| {}'.format( output_path, wav_file, 'Fixed anyway.', '', '', '', '', '', '') logger_solution.append('Fixed anyway.') if error_msg: if 'file does not start with RIFF id' in error_msg: msg = '{}: File is not a WAV.'.format(wav_file) logger.info(msg) logger_msg = '{}| {}| {}| {}| {}| {}| {}| {}| {}'.format( output_path, wav_file, error_msg, '', '', '', '', '', '') logger_solution.append('Not a WAV - Skipped') # Convert with sox if 'unknown format: 65534' in error_msg: # Update user msg = '{}: Converting from WAVE_FORMAT_EXTENSIBLE to ' \ 'WAVE_FORMAT_PCM.'.format(wav_file) logger.info(msg) # Convert with sox try: subprocess.run( [ 'sox', '{}'.format(wav_file), '-t', 'wavpcm', '{}'.format(fix_output_file) ], check=True) except subprocess.CalledProcessError as e: msg = 'Exception: {}'.format(str(e)) logger.info(msg) else: fixed_file = True logger_msg = '{}| {}| {}| {}| {}| {}| {}| {}| {}'.format( output_path, wav_file, error_msg, '', '', '', '', '', '') logger_solution.append('Converted to WAV_FORMAT_PCM') # wav_logger.info(logger_msg) elif 'unknown format: 3' in error_msg: msg = '{}: Converting from 32-bit to 24 bit.'.format(wav_file) logger.info(msg) # Convert with sox try: subprocess.run( [ 'sox', '{}'.format(wav_file), '-b', '24', '-t', 'wavpcm', '{}'.format(fix_output_file) ], check=True) except subprocess.CalledProcessError as e: msg = 'Exception: {}'.format(str(e)) logger.info(msg) else: fixed_file = True logger_msg = '{}| {}| {}| {}| {}| {}| {}| {}| {}'.format( output_path, wav_file, error_msg, '', '', '', '', '', '') logger_solution.append('Converted to 24-bit') # Files that open or were fixed to open if not error_msg or fixed_file: # File was fixed for opening. Re-open. Get new wav_params. if fixed_file: wav_params = wave.open(fix_output_file, 'rb').getparams() logger_msg = '{}| {}| {}| {}| {}| {}| {}| {}| {}| {}' \ .format(output_path, wav_file, 'Converted format to open', wav_params.nchannels, wav_params.sampwidth, wav_params.framerate, wav_params.nframes, wav_params.comptype, wav_params.compname, '') wav_file = fix_output_file # Mono files if wav_params.nchannels < 2: msg = '{}: Mono file. Skipping.'.format(wav_file) logger.info(msg) # CONVERSION TO STEREO - UNTESTED, UNNEEDED # try: # stereo_proc = subprocess.run( # [ # 'sox', # '{}'.format(wav_file), # '-c', # '2', # '-t', # 'wavpcm', # '{}'.format(fix_output_path+'_tmp') # ], check=True) # except subprocess.CalledProcessError as e: # print('Exception: {}'.format(str(e))) # else: # fixed_file = True # shutil.move(fix_output_file + '_tmp', fix_output_file) # wav_file = fix_output_file # wav_params = wave.open(fix_output_file, 'rb').getparams() # wav_logger.info('{}| {}| {}| {}| {}| {}| {}| {}| {}'.format( # wav_folder_name, wav_file, str(e), '', '', '', # '', '', # 'Converted to 24-bit')) logger_msg = '{}| {}| {}| {}| {}| {}| {}| {}| {}| {}' \ .format(output_path, wav_file, 'File opened: Mono', wav_params.nchannels, wav_params.sampwidth, wav_params.framerate, wav_params.nframes, wav_params.comptype, wav_params.compname, '') logger_solution.append('Converted to Stereo SKIPPED.') # Bit depth > 24 bit if int(wav_params.sampwidth) > 3: msg = '{}: Converting from sampwidth {} to 3 (24-bit).'.format( wav_file, wav_params.sampwidth) logger.info(msg) # Convert with sox try: bit_depth_proc = subprocess.run( # noqa [ 'sox', '{}'.format(wav_file), '-b', '24', '-t', 'wavpcm', '{}'.format(tmp_output_file) ], check=True) except subprocess.CalledProcessError as e: msg = 'Exception: {}'.format(str(e)) logger.info(msg) else: shutil.move(tmp_output_file, fix_output_file) wav_file = fix_output_file wav_params = wave.open(fix_output_file, 'rb').getparams() fixed_file = True logger_msg = '{}| {}| {}| {}| {}| {}| {}| {}| {}'.format( output_path, wav_file, 'Bitdepth > 24 bit', '', '', '', '', '', '') logger_solution.append('Converted to 24-bit') # Bitrate > 48000 (96000, 192000, etc.) # if int(wav_params.framerate) > 48000: # print('[{}] {}: Converting from {} to 48000.'.format( # folder_count, wav_file, wav_params.framerate)) # # # Convert with sox # try: # bit_rate_proc = subprocess.run( # [ # 'sox', # '-v', # '0.98', # use -G or -norm to auto normalize. # '{}'.format(wav_file), # '-r', # '48k', # '-t', # 'wavpcm', # '{}'.format(tmp_output_file) # ], check=True) # except subprocess.CalledProcessError as e: # print('[{}] Exception: {}'.format(folder_count, str(e))) # else: # shutil.move(tmp_output_file, fix_output_file) # wav_file = fix_output_file # wav_params = wave.open(fix_output_file, 'rb').getparams() # fixed_file = True # logger_msg = '{}| {}| {}| {}| {}| {}| {}| {}| {}'\ # .format(wav_folder_name, wav_file, # 'Bitrate > 48Khz', '', '', '', '', '', '') # logger_solution.append('Converted to 48Khz sample rate') # if config.FIX_ANYWAY and not error_msg and not fixed_file: # print('[{}] {}: FIX_ANYWAY - Re-saving file at end.'.format( # folder_count, wav_file)) # # # Convert with sox # try: # # bit_depth_proc = subprocess.run( # # [ # # 'sox', # # '{}'.format(wav_file), # # '-t', # # 'wavpcm', # # '{}'.format(tmp_output_file) # # ], check=True) # # Open wave file # wav = wave.open(wav_file) # with wave.open(fix_output_file + '_tmp', 'wb') as f: # params = ( # wav_params.nchannels, wav_params.sampwidth, # wav_params.framerate, wav_params.nframes, # wav_params.comptype, wav_params.compname) # f.setparams(params) # f.writeframes(wav.readframes(wav_params.nframes)) # except subprocess.CalledProcessError as e: # print('[{}] Exception: {}'.format(folder_count, str(e))) # else: # shutil.move(tmp_output_file, fix_output_file) # wav_file = fix_output_file # wav_params = wave.open(fix_output_file, 'rb').getparams() # fixed_file = True # logger_msg = '{}| {}| {}| {}| {}| {}| {}| {}| {}'.format( # wav_folder_name, wav_file, 'Fixed anyway.', '', '', # '', '', '', '') # logger_solution.append('Fixed anyway.') if not fixed_file: logger_msg = '{}| {}| {}| {}| {}| {}| {}| {}| {}| {}' \ .format(output_path, wav_file, 'File opened.', wav_params.nchannels, wav_params.sampwidth, wav_params.framerate, wav_params.nframes, wav_params.comptype, wav_params.compname, '') logger_solution.append('No Error.') # print('{}: Copying untouched orig. file to output_staging.' # .format(wav_file)) # shutil.copy(wav_file, fix_output_file) wav_logger.info(logger_msg + ' | '.join(logger_solution)) if fixed_file and overwrite: msg = 'Overwriting {} with {}'.format(orig_wav_file, fix_output_file) logger.info(msg) copy_file(src=fix_output_file, dst=orig_wav_file) return fixed_file