Commit 34ff2df9 authored by birk's avatar birk
Browse files

preloading and local caching of media files - video causes crashes

parent 660145a3
import asyncio
import collections
import tempfile
import pyglet
import simplejson as json
import urllib
import aiohttp
import sys
from uritemplate import expand
from system.config import Config
from content.apidata import PeopleData, KeywordData, MetaDatum
from content.collections import CollectionData
from content.mediaentry import *
......@@ -112,10 +115,10 @@ class ApiClient(object):
return self.__active
async def send_request(self, path_,
retries=3,
retries=5,
interval=0.9,
back_off=3,
read_timeout=15.9,
read_timeout=180,
http_status_codes_to_retry=HTTP_STATUS_CODES_TO_RETRY):
"""
This internal function handles all requests to the server and returns
......@@ -145,8 +148,7 @@ class ApiClient(object):
while attempt != 0:
if raised_exc:
print('caught "{}" url:{} remaining tries {}, sleeping {} secs'.format(raised_exc,
url, attempt, back_off_interval))
print('caught {} remaining tries {}, sleeping {} secs'.format(raised_exc, attempt, back_off_interval))
await asyncio.sleep(back_off_interval)
# bump interval for the next possible attempt
back_off_interval = back_off_interval * back_off
......@@ -154,20 +156,23 @@ class ApiClient(object):
with aiohttp.Timeout(timeout=read_timeout):
async with getattr(self.__session, 'get')(url) as response:
if response.status == 200:
try:
data = await response.json()
except json.JSONDecodeError as exc:
print('failed to decode response code:{} url:{} error:{} response:{}'.format(
response.status, url, exc,
response.reason)
)
raise aiohttp.errors.HttpProcessingError(
code=response.status, message=exc.msg)
else:
if self.debug:
print('... url:{} code:{} response:{}'.format(url, response.status, response.reason))
raised_exc = None
return data
if response.content_type == 'application/json-roa+json':
try:
data = await response.json()
except json.JSONDecodeError as exc:
print('failed to decode response code:{} url:{} error:{} response:{}'.format(
response.status, url, exc,
response.reason)
)
raise aiohttp.errors.HttpProcessingError(
code=response.status, message=exc.msg)
else:
if self.debug:
print('... url:{} code:{} response:{}'.format(url, response.status, response.reason))
raised_exc = None
return data
elif response.content_type == 'image/jpeg' or response.content_type == 'video/mp4':
return await response.read()
elif response.status in http_status_codes_to_retry:
print('received invalid response code:{} url:{} response:{}'.format(
response.status, url, response.reason))
......@@ -304,6 +309,8 @@ class ApiClient(object):
mf = await self.get_media_file(roa['relations']['media-file']['href'])
if mf:
m.set_media_file(mf)
# TODO: Decide whether or not to cache the media file
await self.cache_media_file(m)
else:
# a media entry without media files is invalid
print('No MediaFile for MediaEntry {}'.format(m.id))
......@@ -399,14 +406,26 @@ class ApiClient(object):
tasks = []
# look for previews
for r in j['previews']:
task = asyncio.ensure_future(self.get_preview(roa['collection']['relations'][r['id']]['href']))
tasks.append(task)
task = asyncio.ensure_future(self.get_preview(roa['collection']['relations'][r['id']]['href']))
tasks.append(task)
for p in await asyncio.gather( * tasks):
mf.add_preview(p)
if mf.get_preview():
return mf
return None
async def cache_media_file(self, media_entry_):
bytes = await self.send_request(media_entry_.get_media_url)
if media_entry_.is_image:
with tempfile.NamedTemporaryFile(suffix='.jpg') as cache_file:
cache_file.write(bytes)
media_entry_.set_image(pyglet.image.load(cache_file.name))
elif media_entry_.is_video:
t = tempfile.NamedTemporaryFile(suffix='.mp4')
t.write(bytes)
media_entry_.set_video(t)
return None
async def get_preview(self, path):
j = await self.send_request(path)
if j:
......
......@@ -149,6 +149,7 @@ class KeywordData(MetaDatum):
class PeopleData(MetaDatum):
@classmethod
def get_instance(cls, json_:dict):
i = cls.find(json_['id'])
......
......@@ -102,7 +102,6 @@ class Dispatcher(EventDispatcher):
"""
print('play_media_on_screen {} - {} - {} sec.'.format(screen_, media_entry_, media_entry_.duration))
media_display_ = MediaDisplay(media_entry_, screen_, self._program, index_)
media_display_.load_file()
media_display_.push_handlers(on_end=self.on_screen_ready)
screen_.set_media(media_display_)
......
import os
import random
from tempfile import NamedTemporaryFile
import pyglet
from content.apidata import ApiData
from system.config import Config
......@@ -38,6 +41,8 @@ class MediaEntryData(ApiData):
self.is_published = None
self.responsible_user_id = None
self.media_file = None
self.image = None
self.video = None
if Config().dev_mode:
self.duration = random.randint(3, 5)
else:
......@@ -88,12 +93,6 @@ class MediaEntryData(ApiData):
h = self.media_file.get_preview().height
return MediaEntryData.get_orientation(w, h)
@property
def file_name(self):
if self.media_file and len(self.media_file.previews) > 0:
return self.media_file.get_preview()
return None
@property
def get_media_url(self):
# TODO: Decide which data stream should be shown.
......@@ -117,7 +116,25 @@ class MediaEntryData(ApiData):
def media_width_height(self):
if self.media_file and len(self.media_file.previews) > 0:
return self.media_file.get_preview().width_height
return None
return None, None
def set_image(self, image_:pyglet.image.AbstractImage):
self.image = image_
if self.media_file and self.media_file.get_preview():
self.media_file.get_preview().set_size(self.image.width, self.image.height)
def set_video(self, video_:NamedTemporaryFile):
self.video = video_
self._source = pyglet.media.load(self.video.name)
self.set_media_size(self._source.video_format.width,
self._source.video_format.height)
if not Config().dev_mode:
self.duration = self._source.duration
self._source = None
def set_media_size(self, width_, height_):
if self.media_file and self.media_file.get_preview():
self.media_file.get_preview().set_size(width_, height_)
def __str__(self):
s = 'MediaEntryData {}'.format(self.uuid)
......@@ -190,11 +207,15 @@ class PreviewData():
self.height = json_['height']
self.created_at = json_['created_at']
self.updated_at = json_['updated_at']
# self.filename = json_['filename']
self.media_file_id = json_['media_file_id']
self.data_stream = '{}{}'.format(server_,json_[
self.data_stream = '{}'.format(json_[
'_json-roa']['relations']['data-stream']['href'])
@property
def width_height(self):
return self.width, self.height
def set_size(self, width_, height_):
# used to overwrite values from JSON with actual ones from cached file
self.width = width_
self.height = height_
\ No newline at end of file
......@@ -32,7 +32,7 @@ class Program(EventDispatcher):
def set_limit(self, limit_=0):
self._limit = limit_
async def load(self):
async def load(self, preload_media_=False):
print(self.start_url)
limit = max(self._limit, self._limit_selection)
self.__index = None
......
......@@ -19,22 +19,14 @@ class MediaDisplay(pyglet.event.EventDispatcher):
self.program = program_
self.index = index_
self.screen = screen_
self.loaded = False
self.file = None
self.texture = None # for images
self.texture = None
self.player = None # for videos
self.area = None
self.visible = False
self.cache_file = None
self._url = None
self._source = None
# set after loading preview
self._width = None
self._height = None
def find_position(self):
# Looks for a suitable position on the screen
w, h = self._width, self._height
w, h = self.media_entry.media_width_height
if w and h:
# First init the are with the original size ...
self.area = Area(0, 0, w, h)
......@@ -42,68 +34,21 @@ class MediaDisplay(pyglet.event.EventDispatcher):
self.area.scale_to(
self.screen.get_width, self.screen.get_height, 10, True, True)
def load_file(self):
# print('load_file() {} - image? {}'.format(self.media_entry, self.media_entry.is_image))
if not self.loaded:
if self.media_entry.is_image:
try:
response = requests.get(self.media_entry.get_media_url, stream=True, auth=Config().api_auth)
if response.status_code == 200:
fp = tempfile.NamedTemporaryFile(suffix='.jpg')
for block in response.iter_content(1024):
fp.write(block)
fp.seek(0)
self.file = pyglet.image.load(fp.name)
self._width = self.file.width
self._height = self.file.height
fp.close()
except:
print("Unexpected error:", sys.exc_info()[0])
raise
try:
self.texture = self.file.get_texture()
pyglet.gl.glBindTexture(
pyglet.gl.GL_TEXTURE_2D, self.texture.id)
pyglet.gl.glTexParameteri(
pyglet.gl.GL_TEXTURE_2D, pyglet.gl.GL_TEXTURE_MAG_FILTER, pyglet.gl.GL_NEAREST)
except:
print("... Unexpected error:", sys.exc_info()[0])
raise
elif self.media_entry.is_video:
self._url = self.media_entry.get_media_url
response = requests.get(self._url, stream=True, auth=Config().api_auth)
if response.status_code == 200:
self.cache_file = tempfile.NamedTemporaryFile(suffix='.mp4')
for block in response.iter_content(1024):
self.cache_file.write(block)
self._source = pyglet.media.load(self.cache_file.name)
self._width = self._source.video_format.width
self._height = self._source.video_format.height
# self._item.get_meta().set_duration(self._source.duration)
self.find_position()
self.loaded = True
def show(self):
# Called when the content appears on the screen.
self.find_position()
if self.media_entry.is_image:
self.texture = self.file.get_texture()
pyglet.clock.schedule_once(
self.on_timer_end, self.media_entry.duration)
self.texture = self.media_entry.image.get_texture()
elif self.media_entry.is_video:
self.player = self._source.play()
self.player.eos_action = self.player.EOS_PAUSE
# self.player.on_eos = self.on_content_end
@self.player.event
def on_eos():
self.on_video_end(self)
# self.player.push_handlers(on_eos)
print('video {}'.format(self.media_entry.video.name))
self.player = pyglet.media.load(self.media_entry.video.name).play()
self.texture = self.player.get_texture()
# self.player.eos_action = self.player.EOS_PAUSE
# @self.player.event
# def on_eos():
# self.on_video_end(self)
pyglet.clock.schedule_once(
self.on_timer_end, self.media_entry.duration)
self.set_width_height(self.area.width, self.area.height)
self.visible = True
self.dispatch_event('on_play', self)
......@@ -127,9 +72,9 @@ class MediaDisplay(pyglet.event.EventDispatcher):
def on_content_end(self):
# print('content end %s' % self.screen.index)
if self.cache_file and type(self.cache_file) is tempfile.NamedTemporaryFile:
self.cache_file.close()
self.cache_file = None
if self.media_entry.video and type(self.media_entry.video) is tempfile.NamedTemporaryFile:
self.media_entry.video.close()
self.media_entry.video = None
self.dispatch_event('on_end', self, self.screen)
def hide(self):
......
......@@ -89,7 +89,7 @@ class Main(object):
program = self._programs[self._program_index]
print('***** load_program {} *****'.format(program._name))
loop = self._api.start_session()
future = asyncio.ensure_future(program.load())
future = asyncio.ensure_future(program.load(True))
loop.run_until_complete(future)
self._api.complete_session()
if program.valid:
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment