add subtitle preview server

This commit is contained in:
j 2017-10-04 13:02:13 +02:00
parent 651607e354
commit 2ce50f2971
7 changed files with 322 additions and 4 deletions

1
MANIFEST.in Normal file
View file

@ -0,0 +1 @@
recursive-include cdoseaplay/static *

View file

@ -5,6 +5,7 @@ import os
import sys import sys
from .utils import update_playlist, get_player from .utils import update_playlist, get_player
from .subtitleserver import SubtitleServer
import logging import logging
logger = logging.getLogger('cdosea') logger = logging.getLogger('cdosea')
@ -40,6 +41,7 @@ def main():
player.register_key_binding('q', q_binding) player.register_key_binding('q', q_binding)
update_playlist(args.playlist, args.prefix) update_playlist(args.playlist, args.prefix)
sub = SubtitleServer(player, args.playlist)
player.loadlist(args.playlist) player.loadlist(args.playlist)
while True: while True:
@ -48,6 +50,7 @@ def main():
except: except:
sys.exit() sys.exit()
del player del player
sub.join()
if __name__ == '__main__': if __name__ == '__main__':

View file

@ -0,0 +1 @@
<script src="subtitles.js"></script>

View file

@ -0,0 +1,77 @@
'use strict'
var app = {}
app.status = {
currentTime: 0,
path: null,
subtitles: {}
}
app.render = function() {
var html = ''
html += 'Current Time: ' + app.status.currentTime + '<br>'
if (app.status.subtitles[app.status.current]) {
app.status.subtitles[app.status.current].forEach(function(sub) {
if (app.status.currentTime >= sub['in'] && app.status.currentTime < sub.out) {
html += '<br><br><b>' + sub['in'] + ' ' + sub.out + ': ' + sub.value + '</b>'
} else {
html += '<br><br>' + sub['in'] + ' ' + sub.out + ': ' + sub.value
}
})
}
if (app.status.subtitles[app.status.next]) {
html += '<br><br>Next: '
app.status.subtitles[app.status.next].forEach(function(sub) {
html += '<br><br>' + sub['in'] + ' ' + sub.out + ': ' + sub.value
})
}
document.body.innerHTML = html
}
app.connectWS = function() {
app.ws = new WebSocket('ws://' + document.location.host + '/ws/')
app.ws.onopen = function() {
//console.log('open')
}
app.ws.onerror = function(event) {
//console.log('error')
ws.close()
}
app.ws.onclose = function(event) {
//console.log('closed')
setTimeout(app.connectWS, 1000)
}
app.ws.onmessage = function(event) {
var request
try {
request = JSON.parse(event.data)
} catch(e) {
request = {
'error': {},
'debug': event.data
}
}
//console.log('message', request)
app.status.currentTime = request.currentTime
if (request.current) {
app.status.current = request.current
}
if (request.next) {
app.status.next = request.next
}
if (request.subtitles) {
Object.keys(request.subtitles).forEach(function(name) {
app.status.subtitles[name] = request.subtitles[name]
})
}
app.render()
}
}
app.connectWS()
window.addEventListener('load', app.render, false)

View file

@ -0,0 +1,235 @@
from glob import glob
from threading import Thread
import json
import logging
import mimetypes
import os
import time
from tornado.httpserver import HTTPServer
from tornado import gen
from tornado.ioloop import IOLoop
from tornado.web import Application, RequestHandler, HTTPError
from tornado.websocket import WebSocketHandler
import requests
import tornado.web
import ox.srt
logger = logging.getLogger('cdosea.subtitles')
sockets = []
STATIC_ROOT = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'static')
ADDRESS = '0.0.0.0'
PORT = 8080
DEBUG = False
class Subtitles():
path = None
next_path = None
playlist = None
player = None
last = 0
def __init__(self, player, playlist):
self.player = player
with open(playlist, 'r') as fd:
self.playlist = fd.read().strip().split('\n')
self.player.observe_property('time-pos', self.update)
def update(self, pos):
if pos is None:
return
if self.player and self.playlist and self.player.path:
self.position = pos
path = self.player.path.decode()
trigger_path = path != self.path
self.path = path
self.update_next()
self.load_subtitles()
data = {
'current': os.path.basename(self.path),
'next': os.path.basename(self.next_path),
'currentTime': pos
}
if trigger_path:
data['subtitles'] = {}
data['subtitles'][os.path.basename(self.next_path)] = self.next
self.last = 0
if abs(pos - self.last) > 0.5 or trigger_path:
self.trigger(data)
self.last = pos
def update_next(self):
index = self.playlist.index(self.path) + 1
if index == len(self.playlist):
index = 0
self.next_path = self.playlist[index]
def load_subtitles(self):
srt = self.path.replace('.mp4', '.srt')
self.current = ox.srt.load(srt)
srt = self.next_path.replace('.mp4', '.srt')
self.next = ox.srt.load(srt)
def trigger(self, data):
logger.debug('trigger %s', data)
for socket in sockets:
socket.post(data)
def status(self):
data = {
'curent': os.path.basename(self.path) if self.path else '',
'next': os.path.basename(self.next_path) if self.next_path else '',
'currentTime': self.position
}
data['subtitles'] = {}
data['subtitles'][os.path.basename(self.path)] = self.current
data['subtitles'][os.path.basename(self.next_path)] = self.next
return data
class Socket(WebSocketHandler):
def initialize(self, sub):
self.sub = sub
def check_origin(self, origin):
# bypass same origin check
return True
def open(self):
if self not in sockets:
sockets.append(self)
self.post(self.sub.status())
# websocket calls
def on_close(self):
if self in sockets:
sockets.remove(self)
def on_message(self, message):
pass
#logger.debug('got message %s', message)
def post(self, data):
try:
message = json.dumps(data)
except:
logger.debug('failed to serialize data %s', data)
return
main = IOLoop.instance()
main.add_callback(lambda: self.write_message(message))
class StaticFileHandler(RequestHandler):
def initialize(self, root):
self.root = root
def head(self):
self.get(include_body=False)
@tornado.web.asynchronous
@gen.coroutine
def get(self, include_body=True):
path = self.root + self.request.path
if self.request.path == '/':
path += 'index.html'
mimetype = mimetypes.guess_type(path)[0]
if mimetype is None:
mimetype = 'text/html'
self.set_header('Content-Type', mimetype)
size = os.stat(path).st_size
self.set_header('Accept-Ranges', 'bytes')
chunk_size = 4096
if include_body:
if 'Range' in self.request.headers:
self.set_status(206)
r = self.request.headers.get('Range').split('=')[-1].split('-')
start = int(r[0])
end = int(r[1]) if r[1] else (size - 1)
length = end - start + 1
self.set_header('Content-Length', str(length))
self.set_header('Content-Range', 'bytes %s-%s/%s' % (start, end, size))
with open(path, 'rb') as fd:
fd.seek(start)
p = 0
while p < length:
chunk = max(chunk_size, length-p)
self.write(fd.read(chunk))
yield gen.Task(self.flush, include_footers=False)
p += chunk
else:
self.set_header('Content-Length', str(size))
with open(path, 'rb') as fd:
p = 0
length = size
while p < length:
chunk = max(chunk_size, length-p)
self.write(fd.read(chunk))
yield gen.Task(self.flush, include_footers=False)
self.flush()
p += chunk
self.finish()
else:
self.set_header('Content-Length', str(size))
class NotFoundHandler(RequestHandler):
def get(self):
raise HTTPError(404)
class SubtitleServer(Thread):
def __init__(self, player, playlist):
Thread.__init__(self)
self.daemon = True
self.player = player
self.playlist = playlist
self.start()
def run(self):
main(self.player, self.playlist)
def join(self):
IOLoop.instance().stop()
return Thread.join(self)
def main(player, playlist):
sub = Subtitles(player, playlist)
options = {
'debug': DEBUG,
'gzip': False
}
handlers = [
(r'/favicon.ico', NotFoundHandler),
(r'/ws/', Socket, dict(sub=sub)),
(r'/.*', StaticFileHandler, dict(root=STATIC_ROOT))
]
if DEBUG:
log_format = '%(asctime)s:%(levelname)s:%(name)s:%(message)s'
logging.basicConfig(level=logging.DEBUG, format=log_format)
http_server = HTTPServer(Application(handlers, **options))
main = IOLoop.instance()
def shutdown():
http_server.stop()
#signal.signal(signal.SIGTERM, shutdown)
http_server.listen(PORT, ADDRESS)
try:
main.start()
except:
pass
shutdown()

View file

@ -25,6 +25,8 @@ def get_player(fullscreen=True):
#player.observe_property('time-pos', lambda pos: print('Now playing at {:.2f}s'.format(pos))) #player.observe_property('time-pos', lambda pos: print('Now playing at {:.2f}s'.format(pos)))
player.fullscreen = fullscreen player.fullscreen = fullscreen
player.loop = 'inf' player.loop = 'inf'
player.loop_file = 'no'
return player return player

View file

@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from setuptools import setup from setuptools import setup, find_packages
setup( setup(
name="cdoseaplay", name="cdoseaplay",
@ -12,9 +12,8 @@ setup(
scripts=[ scripts=[
'cdosea-play', 'cdosea-play',
], ],
packages=[ packages=find_packages(exclude=['tests', 'tests.*']),
'cdoseaplay' include_package_data=True,
],
install_requires=[ install_requires=[
'ox >= 2.1.541,<3', 'ox >= 2.1.541,<3',
'requests >= 1.1.0', 'requests >= 1.1.0',