265 lines
7.4 KiB
Python
265 lines
7.4 KiB
Python
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
|
|
|
|
|
|
def load_srt(srt):
|
|
if os.path.exists(srt):
|
|
return ox.srt.load(srt)
|
|
return []
|
|
|
|
|
|
class Subtitles():
|
|
path = None
|
|
next_path = None
|
|
playlist = None
|
|
player = None
|
|
last = 0
|
|
|
|
def __init__(self, player, playlist):
|
|
self.player = player
|
|
self.load_playlist(playlist)
|
|
self.player.observe_property('time-pos', self.update)
|
|
|
|
def load_playlist(self, playlist):
|
|
with open(playlist, 'r') as fd:
|
|
self.playlist = fd.read().strip().split('\n')
|
|
self.path = None
|
|
self.next_path = None
|
|
if self.player.path:
|
|
path = self.player.path.decode()
|
|
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)
|
|
|
|
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 = load_srt(srt)
|
|
|
|
srt = self.next_path.replace('.mp4', '.srt')
|
|
self.next = load_srt(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):
|
|
sub = None
|
|
|
|
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, self)
|
|
|
|
def join(self):
|
|
IOLoop.instance().stop()
|
|
return Thread.join(self)
|
|
|
|
def load_playlist(self, playlist):
|
|
if self.sub:
|
|
self.sub.load_playlist(playlist)
|
|
|
|
|
|
def main(player, playlist, parent=None):
|
|
sub = Subtitles(player, playlist)
|
|
if parent:
|
|
parent.sub = sub
|
|
|
|
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))
|
|
if parent:
|
|
parent.server = http_server
|
|
main = IOLoop.instance()
|
|
|
|
def shutdown():
|
|
http_server.stop()
|
|
|
|
#signal.signal(signal.SIGTERM, shutdown)
|
|
http_server.listen(PORT, ADDRESS)
|
|
try:
|
|
main.start()
|
|
except:
|
|
pass
|
|
shutdown()
|