bbb-utils/bbb-to-edl.py

173 lines
4.1 KiB
Python
Executable File

#!/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 <tnt@246tNt.com>
# 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()