]> git.parisson.com Git - telecaster-server.git/commitdiff
add new full mastering script
authorGuillaume Pellerin <guillaume.pellerin@free.fr>
Tue, 27 Feb 2024 13:54:13 +0000 (14:54 +0100)
committerGuillaume Pellerin <guillaume.pellerin@free.fr>
Tue, 27 Feb 2024 13:54:13 +0000 (14:54 +0100)
bin/mastering/mastering.py [new file with mode: 0755]

diff --git a/bin/mastering/mastering.py b/bin/mastering/mastering.py
new file mode 100755 (executable)
index 0000000..873c239
--- /dev/null
@@ -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')
+
+
+