forked from platypush/platypush
Black/LINT pass for media handler routes.
This commit is contained in:
parent
11c3b7820d
commit
f7fe844296
2 changed files with 89 additions and 30 deletions
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue