1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
#!/usr/bin/env python3
# pylint: disable=no-member,method-hidden
import os
import sys
from aiohttp import web
import socketio
import logging
import json
from ytdl import DownloadQueueNotifier, DownloadQueue
log = logging.getLogger('main')
class Config:
_DEFAULTS = {
'DOWNLOAD_DIR': '.',
'AUDIO_DOWNLOAD_DIR': '%%DOWNLOAD_DIR',
'CUSTOM_DIR': 'true',
'AUTO_CREATE_CUSTOM_DIR': 'false',
'STATE_DIR': '.',
'URL_PREFIX': '',
'OUTPUT_TEMPLATE': '%(title)s.%(ext)s',
'OUTPUT_TEMPLATE_CHAPTER': '%(title)s - %(section_number)s %(section_title)s.%(ext)s',
'YTDL_OPTIONS': '{}'
}
def __init__(self):
for k, v in self._DEFAULTS.items():
setattr(self, k, os.environ[k] if k in os.environ else v)
for k, v in self.__dict__.items():
if v.startswith('%%'):
setattr(self, k, getattr(self, v[2:]))
if not self.URL_PREFIX.endswith('/'):
self.URL_PREFIX += '/'
try:
self.YTDL_OPTIONS = json.loads(self.YTDL_OPTIONS)
assert isinstance(self.YTDL_OPTIONS, dict)
except (json.decoder.JSONDecodeError, AssertionError):
log.error('YTDL_OPTIONS is invalid')
sys.exit(1)
config = Config()
class ObjectSerializer(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, object):
return obj.__dict__
else:
return json.JSONEncoder.default(self, obj)
serializer = ObjectSerializer()
app = web.Application()
sio = socketio.AsyncServer(cors_allowed_origins='*')
sio.attach(app, socketio_path=config.URL_PREFIX + 'socket.io')
routes = web.RouteTableDef()
class Notifier(DownloadQueueNotifier):
async def added(self, dl):
await sio.emit('added', serializer.encode(dl))
async def updated(self, dl):
await sio.emit('updated', serializer.encode(dl))
async def completed(self, dl):
await sio.emit('completed', serializer.encode(dl))
async def canceled(self, id):
await sio.emit('canceled', serializer.encode(id))
async def cleared(self, id):
await sio.emit('cleared', serializer.encode(id))
dqueue = DownloadQueue(config, Notifier())
app.on_startup.append(lambda app: dqueue.initialize())
@routes.post(config.URL_PREFIX + 'add')
async def add(request):
post = await request.json()
url = post.get('url')
quality = post.get('quality')
if not url or not quality:
raise web.HTTPBadRequest()
format = post.get('format')
folder = post.get('folder')
status = await dqueue.add(url, quality, format, folder)
return web.Response(text=serializer.encode(status))
@routes.post(config.URL_PREFIX + 'delete')
async def delete(request):
post = await request.json()
ids = post.get('ids')
where = post.get('where')
if not ids or where not in ['queue', 'done']:
raise web.HTTPBadRequest()
status = await (dqueue.cancel(ids) if where == 'queue' else dqueue.clear(ids))
return web.Response(text=serializer.encode(status))
@sio.event
async def connect(sid, environ):
await sio.emit('all', serializer.encode(dqueue.get()), to=sid)
@routes.get(config.URL_PREFIX)
def index(request):
return web.FileResponse('ui/dist/metube/index.html')
if config.URL_PREFIX != '/':
@routes.get('/')
def index_redirect_root(request):
return web.HTTPFound(config.URL_PREFIX)
@routes.get(config.URL_PREFIX[:-1])
def index_redirect_dir(request):
return web.HTTPFound(config.URL_PREFIX)
routes.static(config.URL_PREFIX + 'favicon/', 'favicon')
routes.static(config.URL_PREFIX + 'download/', config.DOWNLOAD_DIR)
routes.static(config.URL_PREFIX, 'ui/dist/metube')
app.add_routes(routes)
# https://github.com/aio-libs/aiohttp/pull/4615 waiting for release
# @routes.options(config.URL_PREFIX + 'add')
async def add_cors(request):
return web.Response(text=serializer.encode({"status": "ok"}))
app.router.add_route('OPTIONS', config.URL_PREFIX + 'add', add_cors)
async def on_prepare(request, response):
if 'Origin' in request.headers:
response.headers['Access-Control-Allow-Origin'] = request.headers['Origin']
response.headers['Access-Control-Allow-Headers'] = 'Content-Type'
app.on_response_prepare.append(on_prepare)
if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG)
web.run_app(app, port=8081)
|