Source code for pycmx.event

# pycmx
# (c) 2023-2025 Jamie Hardt

from .statements import (StmtFrmc, StmtEvent, StmtClipName, StmtSourceFile,
                         StmtAudioExt, StmtUnrecognized, StmtEffectsName,
                         StmtCdlSop, StmtCdlSat)
from .edit import Edit

from typing import List, Generator, Optional, Tuple, Any


[docs] class Event: """ Represents a collection of :class:`~pycmx.edit.Edit` s, all with the same event number. """ def __init__(self, statements): self.statements = statements @property def number(self) -> int: """ Return the event number. """ return int(self._edit_statements()[0].event) @property def edits(self) -> List[Edit]: """ Returns the edits. Most events will have a single edit, a single event will have multiple edits when a dissolve, wipe or key transition needs to be performed. """ # FTR this is a totall bonkers way of doing this, I wrote this when # I was still learning Python and I'm sure there's easier ways to do # it. The job is complicated because multiple edits can occur in one # event and then other statements can modify the event in different # ways. edits_audio = list(self._statements_with_audio_ext()) clip_names = self._clip_name_statements() source_files = self._source_file_statements() # We first get the edit events combined with their extra audio # channel statements, if any. # The list the_zip contains one element for each initialization # parameter in Edit() the_zip: List[List[Any]] = [edits_audio] # If there are two Clip Name statements and two edits, we look for # "FROM" and "TO" clip name lines. Otherwise we just look for on # each per edit. if len(edits_audio) == 2: start_name: Optional[StmtClipName] = None end_name: Optional[StmtClipName] = None for clip_name in clip_names: if clip_name.affect == 'from': start_name = clip_name elif clip_name.affect == 'to': end_name = clip_name the_zip.append([start_name, end_name]) else: if len(edits_audio) == len(clip_names): the_zip.append(clip_names) else: the_zip.append([None] * len(edits_audio)) # if there's one source file statemnent per clip, we allocate them to # each edit in order. Otherwise if there's only one, we assign the one # to all the edits. If there's no source_file statements, we provide # None. if len(edits_audio) == len(source_files): the_zip.append(source_files) elif len(source_files) == 1: the_zip.append(source_files * len(edits_audio)) else: the_zip.append([None] * len(edits_audio)) # attach effects name to last event try: trans_statement = self._trans_name_statements()[0] trans_names: List[Optional[Any]] = [None] * (len(edits_audio) - 1) trans_names.append(trans_statement) the_zip.append(trans_names) except IndexError: the_zip.append([None] * len(edits_audio)) return [Edit(edit_statement=e1[0], audio_ext_statement=e1[1], clip_name_statement=n1, source_file_statement=s1, trans_name_statement=u1, asc_sop_statement=self._asc_sop_statement(), asc_sat_statement=self._asc_sat_statement(), frmc_statement=self._frmc_statement()) for (e1, n1, s1, u1) in zip(*the_zip)] @property def unrecognized_statements(self) -> Generator[StmtUnrecognized, None, None]: """ A generator for all the unrecognized statements in the event. """ for s in self.statements: if type(s) is StmtUnrecognized: yield s def _trans_name_statements(self) -> List[StmtEffectsName]: return [s for s in self.statements if type(s) is StmtEffectsName] def _edit_statements(self) -> List[StmtEvent]: return [s for s in self.statements if type(s) is StmtEvent] def _clip_name_statements(self) -> List[StmtClipName]: return [s for s in self.statements if type(s) is StmtClipName] def _source_file_statements(self) -> List[StmtSourceFile]: return [s for s in self.statements if type(s) is StmtSourceFile] def _statements_with_audio_ext(self) -> Generator[ Tuple[StmtEvent, Optional[StmtAudioExt]], None, None]: if len(self.statements) == 1 and type(self.statements[0]) is StmtEvent: yield (self.statements[0], None) else: for (s1, s2) in zip(self.statements, self.statements[1:]): if type(s1) is StmtEvent and type(s2) is StmtAudioExt: yield (s1, s2) elif type(s1) is StmtEvent: yield (s1, None) def _asc_sop_statement(self) -> Optional[StmtCdlSop]: return next((s for s in self.statements if type(s) is StmtCdlSop), None) def _asc_sat_statement(self) -> Optional[StmtCdlSat]: return next((s for s in self.statements if type(s) is StmtCdlSat), None) def _frmc_statement(self) -> Optional[StmtFrmc]: return next((s for s in self.statements if type(s) is StmtFrmc), None)