cdosea-play/cdoseaplay/subtitleserver.py

278 lines
7.7 KiB
Python
Raw Normal View History

2017-10-04 11:02:13 +00:00
from glob import glob
from threading import Thread
import json
import logging
import mimetypes
import os
import time
import asyncio
2017-10-04 11:02:13 +00:00
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
2017-10-27 20:47:45 +00:00
def load_srt(srt):
if os.path.exists(srt):
return ox.srt.load(srt)
return []
2017-10-04 11:02:13 +00:00
class Subtitles():
path = None
next_path = None
playlist = None
player = None
last = 0
def __init__(self, player, playlist):
self.player = player
2017-10-13 07:33:29 +00:00
self.load_playlist(playlist)
self.player.observe_property('time-pos', self.update)
def load_playlist(self, playlist):
2017-10-04 11:02:13 +00:00
with open(playlist, 'r') as fd:
self.playlist = fd.read().strip().split('\n')
2017-10-13 07:33:29 +00:00
self.path = None
self.next_path = None
if self.player.path:
path = self.player.path
if isinstance(path, bytes):
path = path.decode()
2017-10-13 07:33:29 +00:00
self.path = path
self.update_next()
self.load_subtitles()
data = {}
data['subtitles'] = {}
data['subtitles'][os.path.basename(self.path)] = self.current
data['subtitles'][os.path.basename(self.next_path)] = self.next
self.trigger(data)
2017-10-04 11:02:13 +00:00
def update(self, *args):
pos = args[0]
if pos == 'time-pos' and len(args) > 1:
pos = args[1]
2017-10-04 11:02:13 +00:00
if pos is None:
return
if self.player and self.playlist and self.player.path:
self.position = pos
path = self.player.path
if isinstance(path, bytes):
path = path.decode()
2017-10-04 11:02:13 +00:00
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):
2017-10-27 20:47:45 +00:00
srt = self.path.replace('.mp4', '.srt')
self.current = load_srt(srt)
srt = self.next_path.replace('.mp4', '.srt')
self.next = load_srt(srt)
2017-10-04 11:02:13 +00:00
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, main):
2017-10-04 11:02:13 +00:00
self.sub = sub
self.main = main
2017-10-04 11:02:13 +00:00
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
self.main.add_callback(lambda: self.write_message(message))
2017-10-04 11:02:13 +00:00
class StaticFileHandler(RequestHandler):
def initialize(self, root):
self.root = root
def head(self):
self.get(include_body=False)
@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))
self.flush()
2017-10-04 11:02:13 +00:00
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))
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):
2017-10-13 07:33:29 +00:00
sub = None
main = None
2017-10-04 11:02:13 +00:00
def __init__(self, player, playlist):
Thread.__init__(self)
self.daemon = True
self.player = player
self.playlist = playlist
self.start()
def run(self):
self.main = asyncio.new_event_loop()
asyncio.set_event_loop(self.main)
2017-10-13 07:33:29 +00:00
main(self.player, self.playlist, self)
2017-10-04 11:02:13 +00:00
def join(self):
if self.main:
self.main.close()
self.main.stop()
2017-10-04 11:02:13 +00:00
IOLoop.instance().stop()
return Thread.join(self)
2017-10-13 07:33:29 +00:00
def load_playlist(self, playlist):
if self.sub:
self.sub.load_playlist(playlist)
2017-10-04 11:02:13 +00:00
2017-10-13 07:33:29 +00:00
def main(player, playlist, parent=None):
2017-10-04 11:02:13 +00:00
sub = Subtitles(player, playlist)
2017-10-13 07:33:29 +00:00
if parent:
parent.sub = sub
2017-10-04 11:02:13 +00:00
options = {
'debug': DEBUG,
'gzip': False
}
main = IOLoop.instance()
2017-10-04 11:02:13 +00:00
handlers = [
(r'/favicon.ico', NotFoundHandler),
(r'/ws/', Socket, dict(sub=sub, main=main)),
2017-10-04 11:02:13 +00:00
(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))
2017-10-13 07:33:29 +00:00
if parent:
parent.server = http_server
2017-10-04 11:02:13 +00:00
def shutdown():
http_server.stop()
#signal.signal(signal.SIGTERM, shutdown)
http_server.listen(PORT, ADDRESS)
try:
main.start()
except:
pass
shutdown()