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"]); | ||||||
|         if (action_argument === undefined) |     } | ||||||
|         { |     video_ids = video_ids.join(","); | ||||||
|             action_function(card.dataset['ytid'], receive_action_response); | 
 | ||||||
|         } |     if (action_argument === undefined) | ||||||
|         else |     { | ||||||
|         { |         action_function(video_ids, receive_action_response); | ||||||
|             action_function(card.dataset['ytid'], action_argument, receive_action_response); |     } | ||||||
|         } |     else | ||||||
|  |     { | ||||||
|  |         action_function(video_ids, action_argument, receive_action_response); | ||||||
|     } |     } | ||||||
|     deselect_all(); |     deselect_all(); | ||||||
| } | } | ||||||
|  | @ -299,25 +343,29 @@ 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']; | ||||||
|     var state = response['state']; |     for (var index = 0; index < video_ids.length; index += 1) | ||||||
|     var card = document.getElementById("video_card_" + video_id); |  | ||||||
|     if (state == 'pending') |  | ||||||
|     { |     { | ||||||
|         card.classList = ["video_card", "video_card_pending"].join(" "); |         var video_id = video_ids[index]; | ||||||
|         card.style.backgroundColor = "#ffffaa"; |         var state = response['state']; | ||||||
|  |         var card = document.getElementById("video_card_" + video_id); | ||||||
|  |         if (state == 'pending') | ||||||
|  |         { | ||||||
|  |             card.classList = ["video_card", "video_card_pending"].join(" "); | ||||||
|  |             card.style.backgroundColor = "#ffffaa"; | ||||||
|  |         } | ||||||
|  |         else if (state == 'ignored') | ||||||
|  |         { | ||||||
|  |             card.classList = ["video_card", "video_card_ignored"].join(" "); | ||||||
|  |             card.style.backgroundColor = "#ffc886"; | ||||||
|  |         } | ||||||
|  |         else if (state == 'downloaded') | ||||||
|  |         { | ||||||
|  |             card.classList = ["video_card", "video_card_downloaded"].join(" "); | ||||||
|  |             card.style.backgroundColor = "#aaffaa"; | ||||||
|  |         } | ||||||
|  |         give_action_buttons(card); | ||||||
|     } |     } | ||||||
|     else if (state == 'ignored') |  | ||||||
|     { |  | ||||||
|         card.classList = ["video_card", "video_card_ignored"].join(" "); |  | ||||||
|         card.style.backgroundColor = "#ffc886"; |  | ||||||
|     } |  | ||||||
|     else if (state == 'downloaded') |  | ||||||
|     { |  | ||||||
|         card.classList = ["video_card", "video_card_downloaded"].join(" "); |  | ||||||
|         card.style.backgroundColor = "#aaffaa"; |  | ||||||
|     } |  | ||||||
|     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 | ||||||
|  |  | ||||||
							
								
								
									
										46
									
								
								ycdl/ycdl.py
									
									
									
									
									
								
							
							
						
						
									
										46
									
								
								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,7 +197,8 @@ 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]) | ||||||
|         self.sql.commit() |         if commit: | ||||||
|  |             self.sql.commit() | ||||||
| 
 | 
 | ||||||
|     def get_channel(self, channel_id): |     def get_channel(self, channel_id): | ||||||
|         self.cur.execute('SELECT * FROM channels WHERE id == ?', [channel_id]) |         self.cur.execute('SELECT * FROM channels WHERE id == ?', [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