Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
birk
madek-broadcaster
Commits
34ff2df9
Commit
34ff2df9
authored
Feb 07, 2017
by
birk
Browse files
preloading and local caching of media files - video causes crashes
parent
660145a3
Changes
7
Hide whitespace changes
Inline
Side-by-side
player/content/api.py
View file @
34ff2df9
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
=
1
5.9
,
read_timeout
=
1
80
,
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
:
...
...
player/content/apidata.py
View file @
34ff2df9
...
...
@@ -149,6 +149,7 @@ class KeywordData(MetaDatum):
class
PeopleData
(
MetaDatum
):
@
classmethod
def
get_instance
(
cls
,
json_
:
dict
):
i
=
cls
.
find
(
json_
[
'id'
])
...
...
player/content/dispatcher.py
View file @
34ff2df9
...
...
@@ -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_
)
...
...
player/content/mediaentry.py
View file @
34ff2df9
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
player/content/program.py
View file @
34ff2df9
...
...
@@ -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
...
...
player/display/mediadisplay.py
View file @
34ff2df9
...
...
@@ -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
):
...
...
player/main.py
View file @
34ff2df9
...
...
@@ -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
:
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment