Batch requests & filterbox & update metadata
- Download / Ignore on multiple videos is batched into a single request, like it should have been all along. - Text box at the top of the page lets you search for terms live instead of loading various ?q urls. - Doing a force refresh will actually update the metadata of old videos instead of skipping them.
This commit is contained in:
parent
f1f12423b1
commit
c667ebd872
4 changed files with 132 additions and 58 deletions
|
@ -12,10 +12,11 @@
|
||||||
{
|
{
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 1440px;
|
min-width: 200px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1440px;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
max-width: 100%;
|
|
||||||
}
|
}
|
||||||
.video_card
|
.video_card
|
||||||
{
|
{
|
||||||
|
@ -89,6 +90,9 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<span>{{videos|length}} items</span>
|
<span>{{videos|length}} items</span>
|
||||||
|
|
||||||
|
<center><input type="text" id="search_filter"/></center>
|
||||||
|
<center><span id="search_filter_count">{{videos|length}}</span> items</center>
|
||||||
|
|
||||||
<div id="video_cards">
|
<div id="video_cards">
|
||||||
{% for video in videos %}
|
{% for video in videos %}
|
||||||
<div id="video_card_{{video['id']}}"
|
<div id="video_card_{{video['id']}}"
|
||||||
|
@ -97,7 +101,7 @@
|
||||||
class="video_card video_card_{{video['download']}}"
|
class="video_card video_card_{{video['download']}}"
|
||||||
>
|
>
|
||||||
<img src="http://i3.ytimg.com/vi/{{video['id']}}/default.jpg" height="100px">
|
<img src="http://i3.ytimg.com/vi/{{video['id']}}/default.jpg" height="100px">
|
||||||
<a href="https://www.youtube.com/watch?v={{video['id']}}">{{video['_published_str']}} - {{video['title']}}</a>
|
<a class="video_title" href="https://www.youtube.com/watch?v={{video['id']}}">{{video['_published_str']}} - {{video['title']}}</a>
|
||||||
<span>({{video['duration'] | seconds_to_hms}})</span>
|
<span>({{video['duration'] | seconds_to_hms}})</span>
|
||||||
{% if channel is none %}
|
{% if channel is none %}
|
||||||
<a href="/channel/{{video['author_id']}}">(Chan)</a>
|
<a href="/channel/{{video['author_id']}}">(Chan)</a>
|
||||||
|
@ -145,7 +149,38 @@
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var video_card_first_selected = null;
|
var video_card_first_selected = null;
|
||||||
var video_card_selections = [];
|
var video_card_selections = [];
|
||||||
var video_cards = Array.from(document.getElementById("video_cards").children);
|
|
||||||
|
var search_filter_box = document.getElementById("search_filter");
|
||||||
|
var search_filter_hook = function(event)
|
||||||
|
{
|
||||||
|
filter_video_cards(search_filter_box.value);
|
||||||
|
}
|
||||||
|
search_filter_box.addEventListener("keyup", search_filter_hook);
|
||||||
|
|
||||||
|
function filter_video_cards(search_term)
|
||||||
|
{
|
||||||
|
var count = 0;
|
||||||
|
video_cards = document.getElementById("video_cards");
|
||||||
|
video_cards.classList.add("hidden");
|
||||||
|
search_term = search_term.toLocaleLowerCase();
|
||||||
|
var cards = video_cards.children;
|
||||||
|
for (var index = 0; index < cards.length; index += 1)
|
||||||
|
{
|
||||||
|
var video_card = cards[index];
|
||||||
|
var title = video_card.getElementsByClassName("video_title")[0].innerText.toLocaleLowerCase();
|
||||||
|
if (search_term !== "" && title.indexOf(search_term) == -1)
|
||||||
|
{
|
||||||
|
video_card.classList.add("hidden");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
video_card.classList.remove("hidden");
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
video_cards.classList.remove("hidden");
|
||||||
|
document.getElementById("search_filter_count").innerText = count;
|
||||||
|
}
|
||||||
|
|
||||||
function toggle_embed_video(video_id)
|
function toggle_embed_video(video_id)
|
||||||
{
|
{
|
||||||
|
@ -197,6 +232,8 @@ function onclick_select(event)
|
||||||
video_card_first_selected = event.target;
|
video_card_first_selected = event.target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var video_cards = Array.from(document.getElementById("video_cards").children);
|
||||||
|
|
||||||
if (event.shiftKey === false && event.ctrlKey === false)
|
if (event.shiftKey === false && event.ctrlKey === false)
|
||||||
{
|
{
|
||||||
video_card_selections = [];
|
video_card_selections = [];
|
||||||
|
@ -217,6 +254,10 @@ function onclick_select(event)
|
||||||
|
|
||||||
for (var index = start_index; index <= end_index; index += 1)
|
for (var index = start_index; index <= end_index; index += 1)
|
||||||
{
|
{
|
||||||
|
if (video_cards[index].classList.contains("hidden"))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
video_card_selections.push(video_cards[index]);
|
video_card_selections.push(video_cards[index]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -263,17 +304,20 @@ function action_button_passthrough(event, action_function, action_argument)
|
||||||
// Button -> button toolbox -> video card
|
// Button -> button toolbox -> video card
|
||||||
elements = [this_card];
|
elements = [this_card];
|
||||||
}
|
}
|
||||||
|
var video_ids = [];
|
||||||
for (var index = 0; index < elements.length; index += 1)
|
for (var index = 0; index < elements.length; index += 1)
|
||||||
{
|
{
|
||||||
card = elements[index];
|
video_ids.push(elements[index].dataset["ytid"]);
|
||||||
|
}
|
||||||
|
video_ids = video_ids.join(",");
|
||||||
|
|
||||||
if (action_argument === undefined)
|
if (action_argument === undefined)
|
||||||
{
|
{
|
||||||
action_function(card.dataset['ytid'], receive_action_response);
|
action_function(video_ids, receive_action_response);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
action_function(card.dataset['ytid'], action_argument, receive_action_response);
|
action_function(video_ids, action_argument, receive_action_response);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
deselect_all();
|
deselect_all();
|
||||||
}
|
}
|
||||||
|
@ -299,7 +343,10 @@ function give_action_buttons(video_card_div)
|
||||||
|
|
||||||
function receive_action_response(response)
|
function receive_action_response(response)
|
||||||
{
|
{
|
||||||
var video_id = response['video_id'];
|
var video_ids = response['video_ids'];
|
||||||
|
for (var index = 0; index < video_ids.length; index += 1)
|
||||||
|
{
|
||||||
|
var video_id = video_ids[index];
|
||||||
var state = response['state'];
|
var state = response['state'];
|
||||||
var card = document.getElementById("video_card_" + video_id);
|
var card = document.getElementById("video_card_" + video_id);
|
||||||
if (state == 'pending')
|
if (state == 'pending')
|
||||||
|
@ -318,6 +365,7 @@ function receive_action_response(response)
|
||||||
card.style.backgroundColor = "#aaffaa";
|
card.style.backgroundColor = "#aaffaa";
|
||||||
}
|
}
|
||||||
give_action_buttons(card);
|
give_action_buttons(card);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function refresh_channel(channel_id, force, callback)
|
function refresh_channel(channel_id, force, callback)
|
||||||
|
@ -329,20 +377,20 @@ function refresh_channel(channel_id, force, callback)
|
||||||
return post(url, data, callback);
|
return post(url, data, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
function mark_video_state(video_id, state, callback)
|
function mark_video_state(video_ids, state, callback)
|
||||||
{
|
{
|
||||||
var url = "/mark_video_state";
|
var url = "/mark_video_state";
|
||||||
data = new FormData();
|
data = new FormData();
|
||||||
data.append("video_id", video_id);
|
data.append("video_ids", video_ids);
|
||||||
data.append("state", state);
|
data.append("state", state);
|
||||||
return post(url, data, callback);
|
return post(url, data, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
function start_download(video_id, callback)
|
function start_download(video_ids, callback)
|
||||||
{
|
{
|
||||||
var url = "/start_download";
|
var url = "/start_download";
|
||||||
data = new FormData();
|
data = new FormData();
|
||||||
data.append("video_id", video_id);
|
data.append("video_ids", video_ids);
|
||||||
return post(url, data, callback);
|
return post(url, data, callback);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -79,7 +79,7 @@ function _new_channel_submit()
|
||||||
{
|
{
|
||||||
if (box.value !== "")
|
if (box.value !== "")
|
||||||
{
|
{
|
||||||
refresh_channel(box.value, false, function(){location.reload()});
|
refresh_channel(box.value, true, function(){location.reload()});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -187,21 +187,26 @@ def get_channel(channel_id=None, download_filter=None):
|
||||||
|
|
||||||
@site.route('/mark_video_state', methods=['POST'])
|
@site.route('/mark_video_state', methods=['POST'])
|
||||||
def post_mark_video_state():
|
def post_mark_video_state():
|
||||||
if 'video_id' not in request.form or 'state' not in request.form:
|
if 'video_ids' not in request.form or 'state' not in request.form:
|
||||||
flask.abort(400)
|
flask.abort(400)
|
||||||
video_id = request.form['video_id']
|
video_ids = request.form['video_ids']
|
||||||
state = request.form['state']
|
state = request.form['state']
|
||||||
try:
|
try:
|
||||||
youtube.mark_video_state(video_id, state)
|
video_ids = video_ids.split(',')
|
||||||
|
for video_id in video_ids:
|
||||||
|
youtube.mark_video_state(video_id, state, commit=False)
|
||||||
|
youtube.sql.commit()
|
||||||
|
|
||||||
except ycdl.NoSuchVideo:
|
except ycdl.NoSuchVideo:
|
||||||
|
youtube.rollback()
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
flask.abort(404)
|
flask.abort(404)
|
||||||
|
|
||||||
except ycdl.InvalidVideoState:
|
except ycdl.InvalidVideoState:
|
||||||
|
youtube.rollback()
|
||||||
flask.abort(400)
|
flask.abort(400)
|
||||||
|
|
||||||
return make_json_response({'video_id': video_id, 'state': state})
|
return make_json_response({'video_ids': video_ids, 'state': state})
|
||||||
|
|
||||||
@site.route('/refresh_all_channels', methods=['POST'])
|
@site.route('/refresh_all_channels', methods=['POST'])
|
||||||
def post_refresh_all_channels():
|
def post_refresh_all_channels():
|
||||||
|
@ -232,15 +237,20 @@ def post_refresh_channel():
|
||||||
|
|
||||||
@site.route('/start_download', methods=['POST'])
|
@site.route('/start_download', methods=['POST'])
|
||||||
def post_start_download():
|
def post_start_download():
|
||||||
if 'video_id' not in request.form:
|
if 'video_ids' not in request.form:
|
||||||
flask.abort(400)
|
flask.abort(400)
|
||||||
video_id = request.form['video_id']
|
video_ids = request.form['video_ids']
|
||||||
try:
|
try:
|
||||||
youtube.download_video(video_id)
|
video_ids = video_ids.split(',')
|
||||||
|
for video_id in video_ids:
|
||||||
|
youtube.download_video(video_id, commit=False)
|
||||||
|
youtube.sql.commit()
|
||||||
|
|
||||||
except ycdl.ytapi.VideoNotFound:
|
except ycdl.ytapi.VideoNotFound:
|
||||||
|
youtube.rollback()
|
||||||
flask.abort(404)
|
flask.abort(404)
|
||||||
|
|
||||||
return make_json_response({'video_id': video_id, 'state': 'downloaded'})
|
return make_json_response({'video_ids': video_ids, 'state': 'downloaded'})
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
pass
|
pass
|
||||||
|
|
44
ycdl/ycdl.py
44
ycdl/ycdl.py
|
@ -5,6 +5,9 @@ import sqlite3
|
||||||
from . import helpers
|
from . import helpers
|
||||||
from . import ytapi
|
from . import ytapi
|
||||||
|
|
||||||
|
from voussoirkit import sqlhelpers
|
||||||
|
|
||||||
|
|
||||||
def YOUTUBE_DL_COMMAND(video_id):
|
def YOUTUBE_DL_COMMAND(video_id):
|
||||||
path = 'D:\\Incoming\\ytqueue\\{id}.ytqueue'.format(id=video_id)
|
path = 'D:\\Incoming\\ytqueue\\{id}.ytqueue'.format(id=video_id)
|
||||||
open(path, 'w')
|
open(path, 'w')
|
||||||
|
@ -151,7 +154,7 @@ class YCDL:
|
||||||
return None
|
return None
|
||||||
return fetch[SQL_CHANNEL['directory']]
|
return fetch[SQL_CHANNEL['directory']]
|
||||||
|
|
||||||
def download_video(self, video, force=False):
|
def download_video(self, video, commit=True, force=False):
|
||||||
'''
|
'''
|
||||||
Execute the `YOUTUBE_DL_COMMAND`, within the channel's associated
|
Execute the `YOUTUBE_DL_COMMAND`, within the channel's associated
|
||||||
directory if applicable.
|
directory if applicable.
|
||||||
|
@ -194,6 +197,7 @@ class YCDL:
|
||||||
os.chdir(current_directory)
|
os.chdir(current_directory)
|
||||||
|
|
||||||
self.cur.execute('UPDATE videos SET download = "downloaded" WHERE id == ?', [video_id])
|
self.cur.execute('UPDATE videos SET download = "downloaded" WHERE id == ?', [video_id])
|
||||||
|
if commit:
|
||||||
self.sql.commit()
|
self.sql.commit()
|
||||||
|
|
||||||
def get_channel(self, channel_id):
|
def get_channel(self, channel_id):
|
||||||
|
@ -251,24 +255,36 @@ class YCDL:
|
||||||
if add_channel:
|
if add_channel:
|
||||||
self.add_channel(video.author_id, get_videos=False, commit=False)
|
self.add_channel(video.author_id, get_videos=False, commit=False)
|
||||||
self.cur.execute('SELECT * FROM videos WHERE id == ?', [video.id])
|
self.cur.execute('SELECT * FROM videos WHERE id == ?', [video.id])
|
||||||
|
|
||||||
fetch = self.cur.fetchone()
|
fetch = self.cur.fetchone()
|
||||||
if fetch is not None:
|
existing = fetch is not None
|
||||||
return {'new': False, 'row': fetch}
|
|
||||||
|
|
||||||
data = [None] * len(SQL_VIDEO)
|
download_status = 'pending' if not existing else fetch[SQL_VIDEO['download']]
|
||||||
data[SQL_VIDEO['id']] = video.id
|
|
||||||
data[SQL_VIDEO['published']] = video.published
|
data = {
|
||||||
data[SQL_VIDEO['author_id']] = video.author_id
|
'id': video.id,
|
||||||
data[SQL_VIDEO['title']] = video.title
|
'published': video.published,
|
||||||
data[SQL_VIDEO['description']] = video.description
|
'author_id': video.author_id,
|
||||||
data[SQL_VIDEO['duration']] = video.duration
|
'title': video.title,
|
||||||
data[SQL_VIDEO['thumbnail']] = video.thumbnail['url']
|
'description': video.description,
|
||||||
data[SQL_VIDEO['download']] = 'pending'
|
'duration': video.duration,
|
||||||
|
'thumbnail': video.thumbnail['url'],
|
||||||
|
'download': download_status,
|
||||||
|
}
|
||||||
|
|
||||||
|
if existing:
|
||||||
|
(qmarks, bindings) = sqlhelpers.update_filler(data, where_key='id')
|
||||||
|
query = f'UPDATE videos {qmarks}'
|
||||||
|
else:
|
||||||
|
(qmarks, bindings) = sqlhelpers.insert_filler(SQL_VIDEO_COLUMNS, data)
|
||||||
|
query = f'INSERT INTO videos VALUES({qmarks})'
|
||||||
|
|
||||||
|
self.cur.execute(query, bindings)
|
||||||
|
|
||||||
self.cur.execute('INSERT INTO videos VALUES(?, ?, ?, ?, ?, ?, ?, ?)', data)
|
|
||||||
if commit:
|
if commit:
|
||||||
self.sql.commit()
|
self.sql.commit()
|
||||||
return {'new': True, 'row': data}
|
|
||||||
|
return {'new': not existing, 'row': data}
|
||||||
|
|
||||||
def mark_video_state(self, video_id, state, commit=True):
|
def mark_video_state(self, video_id, state, commit=True):
|
||||||
'''
|
'''
|
||||||
|
|
Loading…
Reference in a new issue