diff --git a/static/common.css b/frontends/ycdl_flask/static/common.css
similarity index 100%
rename from static/common.css
rename to frontends/ycdl_flask/static/common.css
diff --git a/static/common.js b/frontends/ycdl_flask/static/common.js
similarity index 100%
rename from static/common.js
rename to frontends/ycdl_flask/static/common.js
diff --git a/static/favicon.png b/frontends/ycdl_flask/static/favicon.png
similarity index 100%
rename from static/favicon.png
rename to frontends/ycdl_flask/static/favicon.png
diff --git a/static/favicon.svg b/frontends/ycdl_flask/static/favicon.svg
similarity index 100%
rename from static/favicon.svg
rename to frontends/ycdl_flask/static/favicon.svg
diff --git a/templates/channel.html b/frontends/ycdl_flask/templates/channel.html
similarity index 93%
rename from templates/channel.html
rename to frontends/ycdl_flask/templates/channel.html
index 5292a4b..e53f299 100644
--- a/templates/channel.html
+++ b/frontends/ycdl_flask/templates/channel.html
@@ -77,15 +77,15 @@
onclick="refresh_channel('{{channel['id']}}', false, function(){location.reload()})">Refresh new videos
- All
- Pending
- Ignored
- Downloaded
+ All
+ Pending
+ Ignored
+ Downloaded
{% else %}
- All
- Pending
- Ignored
- Downloaded
+ All
+ Pending
+ Ignored
+ Downloaded
{% endif %}
{{videos|length}} items
diff --git a/templates/channels.html b/frontends/ycdl_flask/templates/channels.html
similarity index 100%
rename from templates/channels.html
rename to frontends/ycdl_flask/templates/channels.html
diff --git a/templates/header.html b/frontends/ycdl_flask/templates/header.html
similarity index 100%
rename from templates/header.html
rename to frontends/ycdl_flask/templates/header.html
diff --git a/templates/root.html b/frontends/ycdl_flask/templates/root.html
similarity index 100%
rename from templates/root.html
rename to frontends/ycdl_flask/templates/root.html
diff --git a/frontends/ycdl_flask/ycdl_flask/__init__.py b/frontends/ycdl_flask/ycdl_flask/__init__.py
new file mode 100644
index 0000000..e7fe26c
--- /dev/null
+++ b/frontends/ycdl_flask/ycdl_flask/__init__.py
@@ -0,0 +1,3 @@
+from . import ycdl_flask
+
+site = ycdl_flask.site
diff --git a/ycdl_site.py b/frontends/ycdl_flask/ycdl_flask/ycdl_flask.py
similarity index 80%
rename from ycdl_site.py
rename to frontends/ycdl_flask/ycdl_flask/ycdl_flask.py
index 05bdf38..c345fcc 100644
--- a/ycdl_site.py
+++ b/frontends/ycdl_flask/ycdl_flask/ycdl_flask.py
@@ -2,22 +2,36 @@
Do not execute this file directly.
Use ycdl_launch.py to start the server with gevent.
'''
+import logging
+logging.getLogger('googleapicliet.discovery_cache').setLevel(logging.ERROR)
+
import datetime
import flask
from flask import request
import json
import mimetypes
import os
+import traceback
import bot
-import helpers
import ycdl
-import ytapi
-youtube_core = ytapi.Youtube(bot.YOUTUBE_KEY)
+from voussoirkit import pathclass
+
+root_dir = pathclass.Path(__file__).parent.parent
+
+TEMPLATE_DIR = root_dir.with_child('templates')
+STATIC_DIR = root_dir.with_child('static')
+FAVICON_PATH = STATIC_DIR.with_child('favicon.png')
+
+youtube_core = ycdl.ytapi.Youtube(bot.YOUTUBE_KEY)
youtube = ycdl.YCDL(youtube_core)
-site = flask.Flask(__name__)
+site = flask.Flask(
+ __name__,
+ template_folder=TEMPLATE_DIR.absolute_path,
+ static_folder=STATIC_DIR.absolute_path,
+)
site.config.update(
SEND_FILE_MAX_AGE_DEFAULT=180,
TEMPLATES_AUTO_RELOAD=True,
@@ -91,7 +105,7 @@ def send_file(filepath):
if request.method == 'HEAD':
outgoing_data = bytes()
else:
- outgoing_data = helpers.read_filebytes(filepath, range_min=range_min, range_max=range_max)
+ outgoing_data = ycdl.helpers.read_filebytes(filepath, range_min=range_min, range_max=range_max)
response = flask.Response(
outgoing_data,
@@ -112,8 +126,8 @@ def root():
@site.route('/favicon.ico')
@site.route('/favicon.png')
def favicon():
- filename = os.path.join('static', 'favicon.png')
- return flask.send_file(filename)
+ return flask.send_file(FAVICON_PATH.absolute_path)
+
@site.route('/channels')
def get_channels():
@@ -123,24 +137,30 @@ def get_channels():
return flask.render_template('channels.html', channels=channels)
@site.route('/videos')
+@site.route('/watch')
@site.route('/videos/')
@site.route('/channel/')
@site.route('/channel//')
def get_channel(channel_id=None, download_filter=None):
if channel_id is not None:
- youtube.add_channel(channel_id)
+ try:
+ youtube.add_channel(channel_id)
+ except Exception:
+ traceback.print_exc()
channel = youtube.get_channel(channel_id)
- if channel is None:
- flask.abort(404)
else:
channel = None
videos = youtube.get_videos(channel_id=channel_id, download_filter=download_filter)
- search_term = request.args.get('q', None)
- if search_term is not None:
- search_term = search_term.lower()
- videos = [v for v in videos if search_term in v['title'].lower()]
+ search_terms = request.args.get('q', '').lower().strip().replace('+', ' ').split()
+ if search_terms:
+ videos = [v for v in videos if all(term in v['title'].lower() for term in search_terms)]
+
+ video_id = request.args.get('v', '')
+ if video_id:
+ youtube.insert_video(video_id)
+ videos = [youtube.get_video(video_id)]
limit = request.args.get('limit', None)
if limit is not None:
@@ -155,14 +175,12 @@ def get_channel(channel_id=None, download_filter=None):
published = datetime.datetime.utcfromtimestamp(published)
published = published.strftime('%Y %m %d')
video['_published_str'] = published
- return flask.render_template('channel.html', channel=channel, videos=videos)
-
-@site.route('/static/')
-def get_static(filename):
- filename = filename.replace('\\', os.sep)
- filename = filename.replace('/', os.sep)
- filename = os.path.join('static', filename)
- return flask.send_file(filename)
+ return flask.render_template(
+ 'channel.html',
+ channel=channel,
+ videos=videos,
+ query_string='?' + request.query_string.decode('utf-8'),
+ )
@site.route('/mark_video_state', methods=['POST'])
def post_mark_video_state():
@@ -184,7 +202,7 @@ def post_mark_video_state():
@site.route('/refresh_all_channels', methods=['POST'])
def post_refresh_all_channels():
force = request.form.get('force', False)
- force = helpers.truthystring(force)
+ force = ycdl.helpers.truthystring(force)
youtube.refresh_all_channels(force=force)
return make_json_response({})
@@ -204,7 +222,7 @@ def post_refresh_channel():
flask.abort(404)
force = request.form.get('force', False)
- force = helpers.truthystring(force)
+ force = ycdl.helpers.truthystring(force)
youtube.refresh_channel(channel_id, force=force)
return make_json_response({})
@@ -215,7 +233,7 @@ def post_start_download():
video_id = request.form['video_id']
try:
youtube.download_video(video_id)
- except ytapi.VideoNotFound:
+ except ycdl.ytapi.VideoNotFound:
flask.abort(404)
return make_json_response({'video_id': video_id, 'state': 'downloaded'})
diff --git a/ycdl_launch.py b/frontends/ycdl_flask/ycdl_flask_launch.py
similarity index 67%
rename from ycdl_launch.py
rename to frontends/ycdl_flask/ycdl_flask_launch.py
index 1b21718..05fac91 100644
--- a/ycdl_launch.py
+++ b/frontends/ycdl_flask/ycdl_flask_launch.py
@@ -1,3 +1,6 @@
+import logging
+logging.getLogger('googleapicliet.discovery_cache').setLevel(logging.ERROR)
+
import gevent.monkey
gevent.monkey.patch_all()
@@ -5,7 +8,7 @@ import gevent.pywsgi
import gevent.wsgi
import sys
-import ycdl_site
+import ycdl_flask
if len(sys.argv) == 2:
port = int(sys.argv[1])
@@ -15,14 +18,14 @@ else:
if port == 443:
http = gevent.pywsgi.WSGIServer(
listener=('', port),
- application=ycdl_site.site,
+ application=ycdl_flask.site,
keyfile='https\\flasksite.key',
certfile='https\\flasksite.crt',
)
else:
http = gevent.pywsgi.WSGIServer(
- listener=('', port),
- application=ycdl_site.site,
+ listener=('0.0.0.0', port),
+ application=ycdl_flask.site,
)
diff --git a/ycdl_repl.py b/frontends/ycdl_repl.py
similarity index 72%
rename from ycdl_repl.py
rename to frontends/ycdl_repl.py
index 660359c..ff6e021 100644
--- a/ycdl_repl.py
+++ b/frontends/ycdl_repl.py
@@ -5,7 +5,6 @@ session with these variables preloaded.
import bot
import ycdl
-import ytapi
-youtube_core = ytapi.Youtube(bot.YOUTUBE_KEY)
+youtube_core = ycdl.ytapi.Youtube(bot.YOUTUBE_KEY)
youtube = ycdl.YCDL(youtube_core)
diff --git a/download_thumbnails.py b/utilities/download_thumbnails.py
similarity index 100%
rename from download_thumbnails.py
rename to utilities/download_thumbnails.py
diff --git a/refresh_channels.py b/utilities/refresh_channels.py
similarity index 100%
rename from refresh_channels.py
rename to utilities/refresh_channels.py
diff --git a/utilities/ytqueue.py b/utilities/ytqueue.py
new file mode 100644
index 0000000..7ae3e1b
--- /dev/null
+++ b/utilities/ytqueue.py
@@ -0,0 +1,45 @@
+'''
+I was having trouble making my Flask server perform the youtube-dl without
+clogging up the other site activities. So instead I'll just have the server
+export ytqueue files, which this script will download as a separate process.
+
+Rather than maintaining a text file or database of IDs to be downloaded,
+I'm fine with creating each ID as a file and letting the filesystem act
+as the to-do list.
+'''
+
+import argparse
+import os
+import sys
+import time
+
+YOUTUBE_DL = 'youtube-dlw https://www.youtube.com/watch?v={id}'
+
+def ytqueue(only_once=False):
+ while True:
+ print(time.strftime('%H:%M:%S'), 'Looking for files.')
+ queue = [f for f in os.listdir() if f.endswith('.ytqueue')]
+ for filename in queue:
+ yt_id = filename.split('.')[0]
+ command = YOUTUBE_DL.format(id=yt_id)
+ exit_code = os.system(command)
+ if exit_code == 0:
+ os.remove(filename)
+ if only_once:
+ break
+ time.sleep(10)
+
+def ytqueue_argparse(args):
+ return ytqueue(only_once=args.once)
+
+def main(argv):
+ parser = argparse.ArgumentParser()
+
+ parser.add_argument('--once', dest='once', action='store_true')
+ parser.set_defaults(func=ytqueue_argparse)
+
+ args = parser.parse_args(argv)
+ args.func(args)
+
+if __name__ == '__main__':
+ main(sys.argv[1:])
diff --git a/ycdl.py b/ycdl/__init__.py
similarity index 96%
rename from ycdl.py
rename to ycdl/__init__.py
index 4c52785..b4ac022 100644
--- a/ycdl.py
+++ b/ycdl/__init__.py
@@ -2,10 +2,11 @@ import logging
import os
import sqlite3
-import ytapi
+from . import helpers
+from . import ytapi
def YOUTUBE_DL_COMMAND(video_id):
- path = 'C:\\Incoming\\ytqueue\\{id}.ytqueue'.format(id=video_id)
+ path = 'D:\\Incoming\\ytqueue\\{id}.ytqueue'.format(id=video_id)
open(path, 'w')
logging.basicConfig(level=logging.DEBUG)
@@ -207,6 +208,12 @@ class YCDL:
channels.sort(key=lambda x: x['name'].lower())
return channels
+ def get_video(self, video_id):
+ self.cur.execute('SELECT * FROM videos WHERE id == ?', [video_id])
+ video = self.cur.fetchone()
+ video = {key: video[SQL_VIDEO[key]] for key in SQL_VIDEO}
+ return video
+
def get_videos(self, channel_id=None, download_filter=None):
wheres = []
bindings = []
diff --git a/helpers.py b/ycdl/helpers.py
similarity index 100%
rename from helpers.py
rename to ycdl/helpers.py
diff --git a/ytapi.py b/ycdl/ytapi.py
similarity index 99%
rename from ytapi.py
rename to ycdl/ytapi.py
index b205f5b..442d0f6 100644
--- a/ytapi.py
+++ b/ycdl/ytapi.py
@@ -1,7 +1,7 @@
import apiclient.discovery
import datetime
-import helpers
+from . import helpers
class VideoNotFound(Exception):
pass
diff --git a/ytqueue.py b/ytqueue.py
deleted file mode 100644
index f4f0861..0000000
--- a/ytqueue.py
+++ /dev/null
@@ -1,24 +0,0 @@
-'''
-I was having trouble making my Flask server perform the youtube-dl without
-clogging up the other site activities. So instead I'll just have the server
-export ytqueue files, which this script will download as a separate process.
-
-Rather than maintaining a text file or database of IDs to be downloaded,
-I'm fine with creating each ID as a file and letting the filesystem act
-as the to-do list.
-'''
-import os
-import time
-
-YOUTUBE_DL = 'youtube-dlw https://www.youtube.com/watch?v={id}'
-
-while True:
- print(time.strftime('%H:%M:%S'), 'Looking for files.')
- queue = [f for f in os.listdir() if f.endswith('.ytqueue')]
- for filename in queue:
- yt_id = filename.split('.')[0]
- command = YOUTUBE_DL.format(id=yt_id)
- exit_code = os.system(command)
- if exit_code == 0:
- os.remove(filename)
- time.sleep(10)