def get_event(self, event_type: str, xml: bytes): self.event = self._parse_event(event_type, xml) media_type = self._get_media_type() metadata = self._parse_metadata(media_type) timestamp = self._get_xpath_from_event("./vrt:timestamp") if event_type == "getMetadataResponse": correlation_id = self._get_xpath_from_event("./vrt:correlationId") status = self._get_xpath_from_event("./vrt:status") if status == "SUCCESS": return GetMetadataResponseEvent(event_type, metadata, timestamp, correlation_id, status, media_type) else: # TODO: report back to VRT raise InvalidEventException( f"getMetadataResponse status wasn't 'SUCCES': {status}") if event_type == "metadataUpdatedEvent": media_id = self._get_xpath_from_event("./vrt:mediaId") return MetadataUpdatedEvent(event_type, metadata, timestamp, media_id, media_type) raise InvalidEventException(f"Can't handle '{event_type}' events")
def _parse_event(self, event_type: str, xml: bytes): """Parse the input XML to a DOM""" try: tree = etree.parse(BytesIO(xml.strip())) except etree.XMLSyntaxError: raise InvalidEventException("Event is not valid XML.") try: return tree.xpath(f"/vrt:{event_type}", namespaces=NAMESPACES)[0] except IndexError: raise InvalidEventException(f"Event is not a '{event_type}'.")
def __timecode_to_frames(self, timecode: str, framerate: int) -> int: try: hours, minutes, seconds, frames = [ int(part) for part in timecode.split(":") ] except (TypeError, AttributeError, ValueError) as error: raise InvalidEventException(f"Invalid timecode in the event.", timecode=timecode) return (hours * 3600 + minutes * 60 + seconds) * framerate + frames
def _validate_metadata(self): if bool(self.soc) ^ bool(self.eoc): raise InvalidEventException( f"Only SOC or EOC is present. They should both be present or none at all." ) duration_frames = self.__timecode_to_frames(self.duration, self.framerate) som_frames = self.__timecode_to_frames(self.som, self.framerate) soc_frames = (self.__timecode_to_frames(self.soc, self.framerate) if self.soc else som_frames) eoc_frames = (self.__timecode_to_frames(self.eoc, self.framerate) if self.eoc else som_frames + duration_frames) eom_frames = (self.__timecode_to_frames(self.eom, self.framerate) if self.eom else eoc_frames) timecodes = [som_frames, soc_frames, eoc_frames, eom_frames] if timecodes != sorted(timecodes): raise InvalidEventException( f"Something is wrong with the SOM, SOC, EOC, EOM order.")
def _get_xpath_from_event(self, xpath, xml=False, optional: bool = False) -> str: try: if xml: return etree.tostring( self.event.xpath(xpath, namespaces=NAMESPACES)[0]).decode("utf-8") else: return self.event.xpath(xpath, namespaces=NAMESPACES)[0].text except IndexError: if optional: return "" else: raise InvalidEventException( f"'{xpath}' is not present in the event.")
def _calculate_resolution_xpath(self) -> str: """ Calculates the XPATH for the resolution information. It will use hires information if it is available. Otherwise, use lores information. Returns: The XPATH for the resolution information. Raises: InvalidEventException: If no hires and no lores are available. """ resolutions = ("hires", "lores") xpath_resolutions = [ f"//ebu:format[@formatDefinition='current'][./ebu:videoFormat[@videoFormatDefinition='{res}']]" for res in resolutions ] for xpath_resolution in xpath_resolutions: if self.event.xpath(xpath_resolution, namespaces=NAMESPACES): return xpath_resolution raise InvalidEventException("No hires/lores information available.")
def _get_media_type(self) -> str: is_video = bool( self._get_xpath_from_event( f"//ebu:format[@formatDefinition='current']/ebu:videoFormat", xml=True, optional=True, )) is_audio = bool( self._get_xpath_from_event( f"//ebu:format[@formatDefinition='current']/ebu:audioFormat", xml=True, optional=True, )) if is_video: return "video" if is_audio: return "audio" raise InvalidEventException("Unknown media type.")