Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions bbb_presentation_video/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ def __getattr__(self, attr: str) -> Any:
help="generate video for a specific pod instead of default pod",
default=DEFAULT_PRESENTATION_POD,
)
parser.add_argument(
"--ignore-record-status",
action="store_true",
help="ignore recording start/stop events and generate video for the entire meeting",
default=False,
)

args = parser.parse_args()

Expand Down Expand Up @@ -159,6 +165,7 @@ def __getattr__(self, attr: str) -> Any:
args.start,
args.end,
args.pod,
args.ignore_record_status,
)

renderer.render()
Expand Down
63 changes: 46 additions & 17 deletions bbb_presentation_video/renderer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
class Codec(Enum):
H264 = "h264"
VP9 = "vp9"
RAW_VIDEO = "rawvideo"


class Encoder:
Expand Down Expand Up @@ -60,7 +61,16 @@ def join(self) -> None:
self.queue.put(None)
self.thread.join()

def run(self) -> None:
def output_raw(self) -> None:
with open(self.output, "wb") as f:
while True:
buf = self.queue.get()
if buf is None:
break
f.write(buf)
self.ret_queue.put(buf)

def output_ffmpeg(self) -> None:
if self.codec == Codec.H264:
codec_opts = ["-c:v", "libx264", "-qp", "0", "-preset", "ultrafast"]
elif self.codec == Codec.VP9:
Expand Down Expand Up @@ -128,9 +138,16 @@ def run(self) -> None:
if ffmpeg.returncode != 0:
raise CalledProcessError(returncode=ffmpeg.returncode, cmd=ffmpeg_cmdline)

def run(self) -> None:
if self.codec == Codec.RAW_VIDEO:
self.output_raw()
else:
self.output_ffmpeg()


class Renderer:
events: EventsInfo
start_time: Fraction = Fraction(0)
length: Fraction
input: str
output: str
Expand All @@ -139,6 +156,7 @@ class Renderer:
framerate: Fraction
codec: Codec
pod_id: str
ignore_record_status: bool

frame: int
framestep: Fraction
Expand All @@ -154,9 +172,10 @@ def __init__(
height: int,
framerate: Fraction,
codec: Codec,
start_time: Fraction,
end_time: Fraction,
start_time: Fraction | None,
end_time: Fraction | None,
pod_id: str,
ignore_record_status: bool,
):
self.events = events
self.input = input
Expand All @@ -166,22 +185,28 @@ def __init__(
self.framerate = framerate
self.codec = codec
self.pod_id = pod_id
self.ignore_record_status = ignore_record_status

# Current video position state
self.frame = 1
self.framestep = 1 / framerate
self.pts = Fraction(0)
self.recording = False
if self.ignore_record_status:
print("\tRenderer: ignoring record status events")
self.recording = True
else:
self.recording = False

# Only the section of recording within the time range of start_time
# through end_time will be included in the final video
if events.length is None:
raise ValueError("Recording length cannot be determined from events.xml")
if start_time is not None:
self.start_time = start_time
else:
self.start_time = 0
assert events.length is not None
if end_time is not None and end_time < events.length:
self.length = end_time
else:
self.length = events.length

# Cairo rendering context
self.surface = cairo.ImageSurface(cairo.FORMAT_RGB24, self.width, self.height)
Expand All @@ -193,10 +218,15 @@ def __init__(
font_options.set_hint_style(cairo.HINT_STYLE_NONE)
self.ctx.set_font_options(font_options)

def update_record(self, event: RecordEvent) -> None:
def update_record(self, event: RecordEvent) -> bool:
if self.ignore_record_status:
return False

if self.recording != event["status"]:
self.recording = event["status"]
print(f"\tRenderer: recording: {self.recording}")
return True
return False

def render(self) -> None:
cursor = CursorRenderer(
Expand Down Expand Up @@ -225,8 +255,7 @@ def render(self) -> None:
shapes_changed = False
cursor_changed = False
recording_changed = False
assert self.events.length is not None
while self.pts < self.events.length:
while self.pts < self.length:
event_ts = Fraction(0)
while True:
if len(self.events.events) == 0:
Expand Down Expand Up @@ -276,8 +305,8 @@ def render(self) -> None:
elif name == "clear":
shapes.update_clear(cast(events.ClearEvent, event))
elif name == "record":
self.update_record(cast(events.RecordEvent, event))
recording_changed = True
if self.update_record(cast(events.RecordEvent, event)):
recording_changed = True
elif name == "presenter":
cursor.update_presenter(cast(events.PresenterEvent, event))
elif name == "join":
Expand Down Expand Up @@ -339,12 +368,12 @@ def render(self) -> None:
# Output a frame
encoder.put(bytearray(self.surface.get_data()))

presentation_changed = False
shapes_changed = False
cursor_changed = False
recording_changed = False

self.frame += 1
self.pts += self.framestep

presentation_changed = False
shapes_changed = False
cursor_changed = False
recording_changed = False

encoder.join()