Black/LINT pass for media handler routes.

This commit is contained in:
Fabio Manganiello 2023-11-04 16:09:35 +01:00
parent 11c3b7820d
commit f7fe844296
Signed by untrusted user: blacklight
GPG key ID: D90FBA7F76362774
2 changed files with 89 additions and 30 deletions

View file

@ -1,7 +1,12 @@
from abc import ABC, abstractmethod
import hashlib
import logging import logging
from typing import Optional
from platypush.message import JSONAble
class MediaHandler: class MediaHandler(JSONAble, ABC):
""" """
Abstract class to manage media handlers that can be streamed over the HTTP Abstract class to manage media handlers that can be streamed over the HTTP
server through the `/media` endpoint. server through the `/media` endpoint.
@ -9,17 +14,26 @@ class MediaHandler:
prefix_handlers = [] prefix_handlers = []
def __init__(self, source, filename=None, def __init__(
mime_type='application/octet-stream', name=None, url=None, self,
subtitles=None): source: str,
matched_handlers = [hndl for hndl in self.prefix_handlers *_,
if source.startswith(hndl)] filename: Optional[str] = None,
mime_type: str = 'application/octet-stream',
name: Optional[str] = None,
url: Optional[str] = None,
subtitles: Optional[str] = None,
**__,
):
matched_handlers = [
hndl for hndl in self.prefix_handlers if source.startswith(hndl)
]
if not matched_handlers: if not matched_handlers:
raise AttributeError(('No matched handlers found for source "{}" ' + raise AttributeError(
'through {}. Supported handlers: {}').format( f'No matched handlers found for source "{source}" '
source, self.__class__.__name__, f'through {self.__class__.__name__}. Supported handlers: {self.prefix_handlers}'
self.prefix_handlers)) )
self.name = name self.name = name
self.path = None self.path = None
@ -32,7 +46,7 @@ class MediaHandler:
self._matched_handler = matched_handlers[0] self._matched_handler = matched_handlers[0]
@classmethod @classmethod
def build(cls, source, *args, **kwargs): def build(cls, source: str, *args, **kwargs) -> 'MediaHandler':
errors = {} errors = {}
for hndl_class in supported_handlers: for hndl_class in supported_handlers:
@ -42,32 +56,68 @@ class MediaHandler:
logging.exception(e) logging.exception(e)
errors[hndl_class.__name__] = str(e) errors[hndl_class.__name__] = str(e)
raise AttributeError(('The source {} has no handlers associated. ' + raise AttributeError(
'Errors: {}').format(source, errors)) f'The source {source} has no handlers associated. Errors: {errors}'
)
def get_data(self, from_bytes=None, to_bytes=None, chunk_size=None): @abstractmethod
def get_data(
self,
from_bytes: Optional[int] = None,
to_bytes: Optional[int] = None,
chunk_size: Optional[int] = None,
) -> bytes:
raise NotImplementedError() raise NotImplementedError()
def set_subtitles(self, subtitles_file): @property
def media_id(self) -> str:
"""
:returns: The unique ID of the media handler.
"""
return self.get_media_id(self.source)
def set_subtitles(self, subtitles_file: Optional[str]):
self.subtitles = subtitles_file self.subtitles = subtitles_file
def remove_subtitles(self): def remove_subtitles(self):
self.subtitles = None self.subtitles = None
def __iter__(self): def __iter__(self):
for attr in ['name', 'source', 'mime_type', 'url', 'subtitles', """
'prefix_handlers', 'media_id']: Iterate over the attributes of the media handler.
"""
for attr in [
'name',
'source',
'mime_type',
'url',
'subtitles',
'prefix_handlers',
'media_id',
]:
if hasattr(self, attr): if hasattr(self, attr):
yield attr, getattr(self, attr) yield attr, getattr(self, attr)
@staticmethod
def get_media_id(source: str) -> str:
"""
:returns: The ID of a media file given its source.
"""
return hashlib.sha1(source.encode()).hexdigest()
from .file import FileHandler def to_json(self):
"""
:returns: A dictionary representation of the media handler.
"""
return dict(self)
from .file import FileHandler # noqa
__all__ = ['MediaHandler', 'FileHandler'] __all__ = ['MediaHandler', 'FileHandler']
supported_handlers = [eval(hndl) for hndl in __all__ supported_handlers = [eval(hndl) for hndl in __all__ if hndl != MediaHandler.__name__]
if hndl != MediaHandler.__name__]
# vim:sw=4:ts=4:et: # vim:sw=4:ts=4:et:

View file

@ -8,30 +8,38 @@ from . import MediaHandler
class FileHandler(MediaHandler): class FileHandler(MediaHandler):
"""
Handler for local media files.
"""
prefix_handlers = ['file://'] prefix_handlers = ['file://']
def __init__(self, source, *args, **kwargs): def __init__(self, source, *args, **kwargs):
super().__init__(source, *args, **kwargs) super().__init__(source, *args, **kwargs)
self.path = os.path.abspath(os.path.expanduser( self.path = os.path.abspath(
self.source[len(self._matched_handler):])) os.path.expanduser(self.source[len(self._matched_handler) :])
)
self.filename = self.path.split('/')[-1] self.filename = self.path.split('/')[-1]
if not os.path.isfile(self.path): if not os.path.isfile(self.path):
raise FileNotFoundError('{} is not a valid file'. raise FileNotFoundError(f'{self.path} is not a valid file')
format(self.path))
self.mime_type = get_mime_type(source) self.mime_type = get_mime_type(source)
if self.mime_type[:5] not in ['video', 'audio', 'image'] and self.mime_type != 'application/octet-stream': assert self.mime_type, f'Could not detect mime type for {source}'
raise AttributeError('{} is not a valid media file (detected format: {})'. if (
format(source, self.mime_type)) self.mime_type[:5] not in ['video', 'audio', 'image']
and self.mime_type != 'application/octet-stream'
):
raise AttributeError(
f'{source} is not a valid media file (detected format: {self.mime_type})'
)
self.extension = mimetypes.guess_extension(self.mime_type) self.extension = mimetypes.guess_extension(self.mime_type)
if self.url: if self.url:
self.url += self.extension self.url += self.extension
self.content_length = os.path.getsize(self.path) self.content_length = os.path.getsize(self.path)
def get_data(self, from_bytes=None, to_bytes=None, chunk_size=None): def get_data(self, from_bytes=None, to_bytes=None, chunk_size=None):
if from_bytes is None: if from_bytes is None:
from_bytes = 0 from_bytes = 0
@ -42,8 +50,9 @@ class FileHandler(MediaHandler):
with open(self.path, 'rb') as f: with open(self.path, 'rb') as f:
f.seek(from_bytes) f.seek(from_bytes)
for chunk in iter(functools.partial( for chunk in iter(
f.read, min(to_bytes-from_bytes, chunk_size)), b''): functools.partial(f.read, min(to_bytes - from_bytes, chunk_size)), b''
):
yield chunk yield chunk