#!/usr/bin/env python # # bbb-to-edl.py # # BBB EDL generator # Creates an EDL files from a BBB recording shapes.svg # # Copyright (c) 2023 Sylvain Munaut # SPDX-License-Identifier: MIT # import argparse import math import bbb class TimeCode: def __init__(self, framerate, frames): self._rate = framerate self._aframes = frames @property def hours(self): return self._aframes // (self._rate * 3600) @property def minutes(self): tm = self._aframes // (self._rate * 60) return tm % 60 @property def seconds(self): ts = self._aframes // self._rate return ts % 60 @property def frames(self): return self._aframes % self._rate @property def framerate(self): return self._rate def __str__(self): return f"{self.hours:02d}:{self.minutes:02d}:{self.seconds:02d}:{self.frames:02d}" def __int__(self): return self._aframes def __add__(self, other): if isinstance(other, int): fo = other elif isinstance(other, TimeCode): if other._rate != self._rate: raise ValueError("Can't combine timecode of different framerate") fo = other._aframes return TimeCode(self._rate, self._aframes + fo) def __sub__(self, other): if isinstance(other, int): fo = other elif isinstance(other, TimeCode): if other._rate != self._rate: raise ValueError("Can't combine timecode of different framerate") fo = other._aframes return TimeCode(self._rate, self._aframes - fo) @classmethod def parse(self, framerate, s): # HH:MM:SS:FF m = re.match('^([0-9]{2}):([0-9]{2}):([0-9]{2}):([0-9]{2})$', s) if m: h = int(m.group(1)) m = int(m.group(2)) s = int(m.group(3)) f = int(m.group(4)) return TimeCode(framerate, ((((h*60)+m)*60)+s)*framerate+f) # [[HH:]MM:]SS.s m = re.match('^((([0-9]{1,2}):)?([0-9]{1,2}):)?([0-9]{1,2}(.[0-9]+)?)$', s) if m: h = int(m.group(3)) m = int(m.group(4)) s = float(m.group(5)) f = int(round((((h*60)+m)*60+s)*framerate)) return TimeCode(framerate, f) # SSS.s m = re.match('^[0-9]+(.[0-9]+)?)$', s) if m: s = float(s) f = int(round(s*framerate)) return TimeCode(framerate, f) # No match raise ValueError("Unable to parse timecode") def generate_edl(opts): # Load shapes data = bbb.parse_shapes_svg(opts.input) # Scan events out = opts.output i = 1 for t_start, t_stop, desc in data: # Is this a deskshare segment ? if desc is None: desc = opts.deskshare is_still = False else: desc = f'{opts.presentation:s}/{desc[0]:s}/slide-{desc[1]:02d}.png' is_still = True # Event times t_start = math.floor(t_start * opts.framerate) t_stop = math.floor(t_stop * opts.framerate) # Source times if is_still: t_src_start = TimeCode(opts.framerate, 0) t_src_stop = TimeCode(opts.framerate, t_stop - t_start) else: t_src_start = TimeCode(opts.framerate, t_start) t_src_stop = TimeCode(opts.framerate, t_stop) # Recorder times t_rec_start = TimeCode(opts.framerate, t_start) t_rec_stop = TimeCode(opts.framerate, t_stop) # EDL gen out.write(f"{i:03d} AX V C {str(t_src_start):s} {str(t_src_stop):s} {str(t_rec_start):s} {str(t_rec_stop):s}\n") if is_still: out.write(f"M2 AX 000.0 00:00:00:00\n") out.write(f"* FROM CLIP NAME: {desc:s}\n") out.write("\n") # Next i = i + 1 def parse_opt(): parser = argparse.ArgumentParser( prog = 'bbb-to-edl.py', description = 'BBB recording EDL generator', ) parser.add_argument('-i', '--input', required=True, type=argparse.FileType('r'), help="Path to the shapes.svg file from BBB recording") parser.add_argument('-o', '--output', required=True, type=argparse.FileType('w'), help="Path to the output EDL file") parser.add_argument('-d', '--deskshare', required=True, help="Path to use for the deskshare segments") parser.add_argument('-p', '--presentation', required=True, help="Prefix Path for the presentation slides segments") parser.add_argument('-f', '--framerate', type=int, default=24, help="Framerate of the project") return parser.parse_args() def main(): opts = parse_opt() generate_edl(opts) if __name__ == '__main__': main()