From 08967456feb4d52d6514d24a3af3a67420d2c4ec Mon Sep 17 00:00:00 2001 From: Guillaume Pellerin Date: Tue, 27 Feb 2024 14:54:13 +0100 Subject: [PATCH] add new full mastering script --- bin/mastering/mastering.py | 211 +++++++++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100755 bin/mastering/mastering.py diff --git a/bin/mastering/mastering.py b/bin/mastering/mastering.py new file mode 100755 index 0000000..873c239 --- /dev/null +++ b/bin/mastering/mastering.py @@ -0,0 +1,211 @@ +#!/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') + + + -- 2.39.5