--- /dev/null
+#!/usr/bin/env python3
+
+
+import os, sys, string
+import psutil
+import logging
+import datetime
+import argparse
+import soundfile
+import librosa
+import numpy as np
+from scipy import signal
+
+
+class Logger:
+ """A logging object"""
+
+ def __init__(self, file):
+ self.logger = logging.getLogger('myapp')
+ self.hdlr = logging.FileHandler(file)
+ self.formatter = logging.Formatter('%(asctime)s %(message)s')
+ self.hdlr.setFormatter(self.formatter)
+ self.logger.addHandler(self.hdlr)
+ self.logger.setLevel(logging.INFO)
+
+
+class TeleCasterMastering(object):
+ """docstring for TelemetaTranscode"""
+
+ source_formats = ['webm',]
+ dest_formats = {
+ 'mp3' : {'in': '', 'out': '-vn -acodec libmp3lame -aq 6'},
+ 'mp4' : {'in': '', 'out': '-c:v libx264 -maxrate 1100k -c:a aac -b:a 128k'},
+ 'png' : {'in': '-ss 0:0:10', 'out': '-frames:v 1 -y'}
+ }
+ nvidia_formats = {'mp4': {'in': '', 'out': '-c:v h264_nvenc -maxrate 1100k -c:a aac -b:a 128k'}}
+ date_limit = datetime.datetime(year=2024, month=2, day=27)
+ tmp_dir = "/tmp/"
+
+ def __init__(self, root_dir,
+ log_file='/tmp/telecaster.log',
+ nvidia_mode=False,
+ auto_offset_mode=False,
+ dry_run=False):
+
+ self.log_file = log_file
+ self.logger = Logger(self.log_file)
+ self.root_dir = root_dir
+ self.nvidia_mode = nvidia_mode
+ if self.nvidia_mode:
+ for f in self.nvidia_formats:
+ self.dest_formats[f] = self.nvidia_formats[f]
+ self.auto_offset_mode = auto_offset_mode
+ self.dry_run = dry_run
+
+ def get_ext_in_dir(self, extension, root):
+ files = os.listdir(root)
+ exts = []
+ for f in files:
+ name, ext = os.path.splitext(f)
+ ext = ext[1:]
+ if not ext in exts:
+ exts.append(ext)
+ return extension in exts
+
+ def get_offset(self, within_file, find_file, window=10):
+ y_within, sr_within = librosa.load(within_file, sr=None, duration=60.0)
+ y_find, _ = librosa.load(find_file, sr=sr_within, duration=60.0)
+ c = signal.correlate(y_within, y_find[:sr_within*window], mode='valid', method='fft')
+ peak = np.argmax(c)
+ offset = round(peak / sr_within, 4)
+ return offset
+
+ def find_best_offset(self, files):
+ offsets = {}
+ for file in files:
+ others = files.copy()
+ others.remove(file)
+ offsets[file] = {}
+ offsets[file]['offsets'] = {}
+ offsets[file]['offsets_sum'] = 0
+ for other in others:
+ offset = self.get_offset(file, other)
+ offsets[file]['offsets'][other] = offset
+ offsets[file]['offsets_sum'] += offset
+
+ # print(offsets)
+ min_offset = sorted(offsets.items(), key=lambda x: x[1]['offsets_sum'])[0]
+ return min_offset
+
+ def double_remux(self, path):
+ filename, ext = os.path.splitext(path)
+ tmp_file = self.tmp_dir + os.sep + 'out' + '.' + ext
+ command = 'ffmpeg -loglevel 0 -i "' + path + '" -vcodec copy -acodec copy -y "' + tmp_file + '" > /dev/null'
+ os.system(command)
+ command = 'ffmpeg -loglevel 0 -i "' + tmp_file + '" -vcodec copy -acodec copy -y "' + path + '" > /dev/null'
+ os.system(command)
+
+ def touch_file(self, path):
+ file = open(path, 'w')
+ file.close()
+
+ def remux(self, file):
+ log = file + '.log'
+ if os.path.getsize(file) and not os.path.exists(log):
+ self.double_remux(file)
+ self.touch_file(log)
+
+ def transcode(self, file, offset=None):
+ filename, ext = os.path.splitext(file)
+ for dest_format in self.dest_formats:
+ ffmpeg_args = self.dest_formats[dest_format]
+ dest = filename + '.' + dest_format
+
+ if not os.path.exists(dest) or '--force' in self.args:
+ if dest_format == 'png':
+ offset_arg = ffmpeg_args['in']
+ elif offset:
+ offset_arg = '-ss ' + str(datetime.timedelta(seconds=offset))
+ else:
+ offset_arg = ''
+
+ command = 'ffmpeg -loglevel 0 ' + offset_arg + ' -i "' + file + '" ' + ffmpeg_args['out'] +' -y "' + dest + '"'
+
+ print(command)
+ self.logger.logger.info(command)
+
+ if not self.dry_run:
+ os.system(command)
+
+
+ def run(self):
+ for root, dirs, files in os.walk(self.root_dir):
+ for dir in dirs:
+ path = root + os.sep + dir
+ print(path)
+ dir_files = os.listdir(path)
+ source_files = []
+ for file in dir_files:
+ filename, ext = os.path.splitext(path + os.sep + file)
+ if ext[1:] in self.source_formats:
+ source_files.append(path + os.sep + file)
+
+ print(source_files)
+
+ if source_files:
+ offsets = {}
+ if len(source_files) > 1:
+ offsets = self.find_best_offset(source_files)
+ print(offsets)
+ for file in source_files:
+ path = os.path.abspath(file)
+ filename, ext = os.path.splitext(file)
+ ext = ext[1:]
+ date_dir = datetime.datetime.fromtimestamp(os.path.getmtime(file))
+ if ext in self.source_formats and date_dir > self.date_limit:
+ self.remux(file)
+ offset = None
+ if offsets:
+ if not file in offsets:
+ offset = offsets[1]['offsets'][file]
+ self.transcode(file, offset=offset)
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--dir', metavar='directory', type=str, help='media directory')
+ parser.add_argument('--log', metavar='log_file', type=str, help='log file', default='/tmp/telecaster.log')
+ parser.add_argument('--nvidia', metavar='nvidia', type=bool, help='use GPU nvidia encoder')
+ parser.add_argument('--auto_offset', metavar='auto_offset', type=bool, help='add auto offset based on audio correlation')
+ parser.add_argument('--dry_run', metavar='dry_run', type=bool, help='dry run')
+ args = parser.parse_args()
+ t = TeleCasterMastering(
+ args.dir,
+ log_file=args.log,
+ nvidia_mode=args.nvidia,
+ auto_offset_mode=args.auto_offset,
+ dry_run=args.dry_run
+ )
+ t.run()
+
+
+def get_pids(name, args=None):
+ """Get a process pid filtered by arguments and uid"""
+ pids = []
+ for proc in psutil.process_iter():
+ if proc.cmdline:
+ if name == proc.name:
+ if args:
+ if args in proc.cmdline:
+ pids.append(proc.pid)
+ else:
+ pids.append(proc.pid)
+ return pids
+
+
+path = os.path.abspath(__file__)
+pids = get_pids('python', args=path)
+
+
+if __name__ == '__main__':
+ print(datetime.datetime.now())
+ if len(pids) <= 1:
+ print('starting mastering...')
+ main()
+ print('mastering finished.\n')
+ else:
+ print('mastering already started !\n')
+
+
+