import hashlib import json import threading from flask import Response from platypush.backend.http.app.utils import get_remote_base_url, logger, \ send_message from platypush.backend.http.media.handlers import MediaHandler media_map = {} media_map_lock = threading.RLock() # Size for the bytes chunk sent over the media streaming infra STREAMING_CHUNK_SIZE = 4096 # Maximum range size to be sent through the media streamer if Range header # is not set STREAMING_BLOCK_SIZE = 3145728 def get_media_url(media_id): return '{url}/media/{media_id}'.format( url=get_remote_base_url(), media_id=media_id) def get_media_id(source): return hashlib.sha1(source.encode()).hexdigest() def register_media(source, subtitles=None): global media_map, media_map_lock media_id = get_media_id(source) media_url = get_media_url(media_id) with media_map_lock: if media_id in media_map: return media_map[media_id] subfile = None if subtitles: req = { 'type': 'request', 'action': 'media.subtitles.download', 'args': { 'link': subtitles, 'convert_to_vtt': True, } } try: subfile = (send_message(req).output or {}).get('filename') except Exception as e: logger().warning('Unable to load subtitle {}: {}' .format(subtitles, str(e))) with media_map_lock: media_hndl = MediaHandler.build(source, url=media_url, subtitles=subfile) media_map[media_id] = media_hndl media_hndl.media_id = media_id logger().info('Streaming "{}" on {}'.format(source, media_url)) return media_hndl def unregister_media(source): global media_map, media_map_lock if source is None: raise KeyError('No media_id specified') media_id = get_media_id(source) media_info = {} with media_map_lock: if media_id not in media_map: raise FileNotFoundError('{} is not a registered media_id'. format(source)) media_info = media_map.pop(media_id) logger().info('Unregistered {} from {}'.format(source, media_info.get('url'))) return media_info def stream_media(media_id, req): global STREAMING_BLOCK_SIZE, STREAMING_CHUNK_SIZE media_hndl = media_map.get(media_id) if not media_hndl: raise FileNotFoundError('{} is not a registered media_id'.format(media_id)) range_hdr = req.headers.get('range') content_length = media_hndl.content_length status_code = 200 headers = { 'Accept-Ranges': 'bytes', 'Content-Type': media_hndl.mime_type, } if 'download' in req.args: headers['Content-Disposition'] = 'attachment' + \ ('; filename="{}"'.format(media_hndl.filename) if media_hndl.filename else '') if range_hdr: headers['Accept-Ranges'] = 'bytes' from_bytes, to_bytes = range_hdr.replace('bytes=', '').split('-') from_bytes = int(from_bytes) if not to_bytes: to_bytes = content_length - 1 content_length -= from_bytes else: to_bytes = int(to_bytes) content_length = to_bytes - from_bytes status_code = 206 headers['Content-Range'] = 'bytes {start}-{end}/{size}'.format( start=from_bytes, end=to_bytes, size=media_hndl.content_length) else: from_bytes = 0 to_bytes = STREAMING_BLOCK_SIZE headers['Content-Length'] = content_length return Response(media_hndl.get_data( from_bytes=from_bytes, to_bytes=to_bytes, chunk_size=STREAMING_CHUNK_SIZE), status_code, headers=headers, mimetype=headers['Content-Type'], direct_passthrough=True) def add_subtitles(media_id, req): """ This route can be used to download and/or expose subtitles files associated to a media file """ media_hndl = media_map.get(media_id) if not media_hndl: raise FileNotFoundError('{} is not a registered media_id'.format(media_id)) subfile = None if req.data: subfile = json.loads(req.data.decode('utf-8')).get('filename') if not subfile: raise AttributeError('No filename specified in the request') if not subfile: if not media_hndl.path: raise NotImplementedError( 'Subtitles are currently only supported for local media files') req = { 'type': 'request', 'action': 'media.subtitles.get_subtitles', 'args': { 'resource': media_hndl.path, } } try: subtitles = send_message(req).output or [] except Exception as e: raise RuntimeError('Could not get subtitles: {}'.format(str(e))) if not subtitles: raise FileNotFoundError('No subtitles found for resource {}'. format(media_hndl.path)) req = { 'type': 'request', 'action': 'media.subtitles.download', 'args': { 'link': subtitles[0].get('SubDownloadLink'), 'media_resource': media_hndl.path, 'convert_to_vtt': True, } } subfile = (send_message(req).output or {}).get('filename') media_hndl.set_subtitles(subfile) return { 'filename': subfile, 'url': get_remote_base_url() + '/media/subtitles/' + media_id + '.vtt', } def remove_subtitles(media_id): media_hndl = media_map.get(media_id) if not media_hndl: raise FileNotFoundError('{} is not a registered media_id'. format(media_id)) if not media_hndl.subtitles: raise FileNotFoundError('{} has no subtitles attached'. format(media_id)) media_hndl.remove_subtitles() return {} # vim:sw=4:ts=4:et: