checkpoint
This commit is contained in:
		
							parent
							
								
									bd263b2ed7
								
							
						
					
					
						commit
						90eedca0d7
					
				
					 9 changed files with 138 additions and 55 deletions
				
			
		
							
								
								
									
										13
									
								
								download_thumbnails.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								download_thumbnails.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | |||
| import os | ||||
| import ycdl_easy | ||||
| from voussoirkit import downloady | ||||
| 
 | ||||
| DIRECTORY = 'C:\\users\\owner\\youtube thumbnails' | ||||
| 
 | ||||
| videos = ycdl_easy.youtube.get_videos() | ||||
| for video in videos: | ||||
|     thumbnail_path = os.path.join(DIRECTORY, video['id']) + '.jpg' | ||||
|     if os.path.exists(thumbnail_path): | ||||
|         continue | ||||
|     result = downloady.download_file(video['thumbnail'], thumbnail_path) | ||||
|     print(result) | ||||
|  | @ -11,9 +11,14 @@ | |||
| #content_body | ||||
| { | ||||
|     display: flex; | ||||
|     flex-grow: 1; | ||||
|     flex-shrink: 0; | ||||
|     flex-basis: auto; | ||||
|     flex-direction: column; | ||||
|     width: 1440px; | ||||
|     margin: auto; | ||||
|     max-width: 100%; | ||||
| } | ||||
| 
 | ||||
| .video_card_downloaded, | ||||
| .video_card_ignored, | ||||
| .video_card_pending | ||||
|  | @ -67,8 +72,10 @@ | |||
| <body> | ||||
| {{header.make_header()}} | ||||
| <div id="content_body"> | ||||
|     <button class="refresh_button" onclick="refresh_channel('{{channel['id']}}', false, function(){location.reload()})">Refresh new videos</button> | ||||
|     <button class="refresh_button" onclick="refresh_channel('{{channel['id']}}', true, function(){location.reload()})">Refresh everything</button> | ||||
|     <button class="refresh_button" | ||||
|     onclick="refresh_channel('{{channel['id']}}', false, function(){location.reload()})">Refresh new videos</button> | ||||
|     <button class="refresh_button" | ||||
|     onclick="refresh_channel('{{channel['id']}}', true, function(){location.reload()})">Refresh everything</button> | ||||
|     <span><a href="/channel/{{channel['id']}}">All</a></span> | ||||
|     <span><a href="/channel/{{channel['id']}}/pending">Pending</a></span> | ||||
|     <span><a href="/channel/{{channel['id']}}/ignored">Ignored</a></span> | ||||
|  | @ -107,7 +114,6 @@ | |||
|             class="video_action_ignore" | ||||
|             onclick="mark_video_state('{{video['id']}}', 'ignored', receive_action_response);" | ||||
|             >Ignore</button> | ||||
| 
 | ||||
|             {% endif %} | ||||
|         </div> | ||||
|     </div> | ||||
|  | @ -208,17 +214,5 @@ function start_download(video_id, callback) | |||
|     data.append("video_id", video_id); | ||||
|     return post(url, data, callback);     | ||||
| } | ||||
| 
 | ||||
| function toggle_dropdown(dropdown) | ||||
| { | ||||
|     if (dropdown.style.display != "inline-flex") | ||||
|     { | ||||
|         dropdown.style.display = "inline-flex"; | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         dropdown.style.display = "none"; | ||||
|     } | ||||
| } | ||||
| </script> | ||||
| </html> | ||||
|  |  | |||
|  | @ -11,7 +11,13 @@ | |||
| #content_body | ||||
| { | ||||
|     display: flex; | ||||
|     flex-grow: 1; | ||||
|     flex-shrink: 0; | ||||
|     flex-basis: auto; | ||||
|     flex-direction: column; | ||||
|     width: 1440px; | ||||
|     margin: auto; | ||||
|     max-width: 100%; | ||||
| } | ||||
| #new_channel_textbox, | ||||
| #new_channel_button | ||||
|  | @ -85,6 +91,7 @@ function refresh_channel(channel_id, force, callback) | |||
|     data.append("force", force) | ||||
|     return post(url, data, callback);     | ||||
| } | ||||
| 
 | ||||
| function refresh_all_channels(force, callback) | ||||
| { | ||||
|     var url = "/refresh_all_channels"; | ||||
|  |  | |||
							
								
								
									
										90
									
								
								ycdl.py
									
									
									
									
									
								
							
							
						
						
									
										90
									
								
								ycdl.py
									
									
									
									
									
								
							|  | @ -4,16 +4,15 @@ import sqlite3 | |||
| 
 | ||||
| import ytapi | ||||
| 
 | ||||
| # AVAILABLE FORMATTERS: | ||||
| # url, id | ||||
| # Note that if the channel has a value in the `directory` column, the bot will | ||||
| # chdir there before executing. | ||||
| YOUTUBE_DL_COMMAND = 'touch C:\\Incoming\\ytqueue\\{id}.ytqueue' | ||||
| def YOUTUBE_DL_COMMAND(video_id): | ||||
|     path = 'C:\\Incoming\\ytqueue\\{id}.ytqueue'.format(id=video_id) | ||||
|     open(path, 'w') | ||||
| 
 | ||||
| logging.basicConfig(level=logging.DEBUG) | ||||
| log = logging.getLogger(__name__) | ||||
| logging.getLogger('googleapiclient.discovery').setLevel(logging.WARNING) | ||||
| logging.getLogger('requests.packages.urllib3.connectionpool').setLevel(logging.WARNING) | ||||
| logging.getLogger('requests.packages.urllib3.util.retry').setLevel(logging.WARNING) | ||||
| 
 | ||||
| SQL_CHANNEL_COLUMNS = [ | ||||
|     'id', | ||||
|  | @ -64,6 +63,7 @@ DEFAULT_DBNAME = 'ycdl.db' | |||
| 
 | ||||
| ERROR_DATABASE_OUTOFDATE = 'Database is out-of-date. {current} should be {new}' | ||||
| 
 | ||||
| 
 | ||||
| def verify_is_abspath(path): | ||||
|     ''' | ||||
|     TO DO: Determine whether this is actually correct. | ||||
|  | @ -71,8 +71,16 @@ def verify_is_abspath(path): | |||
|     if os.path.abspath(path) != path: | ||||
|         raise ValueError('Not an abspath') | ||||
| 
 | ||||
| 
 | ||||
| class InvalidVideoState(Exception): | ||||
|     pass | ||||
| 
 | ||||
| class NoSuchVideo(Exception): | ||||
|     pass | ||||
| 
 | ||||
| 
 | ||||
| class YCDL: | ||||
|     def __init__(self, youtube, database_filename=None): | ||||
|     def __init__(self, youtube, database_filename=None, youtube_dl_function=None): | ||||
|         self.youtube = youtube | ||||
|         if database_filename is None: | ||||
|             database_filename = DEFAULT_DBNAME | ||||
|  | @ -90,6 +98,11 @@ class YCDL: | |||
|                 print(message) | ||||
|                 raise SystemExit | ||||
| 
 | ||||
|         if youtube_dl_function: | ||||
|             self.youtube_dl_function = youtube_dl_function | ||||
|         else: | ||||
|             self.youtube_dl_function = YOUTUBE_DL_COMMAND | ||||
| 
 | ||||
|         statements = DB_INIT.split(';') | ||||
|         for statement in statements: | ||||
|             self.cur.execute(statement) | ||||
|  | @ -134,28 +147,46 @@ class YCDL: | |||
|         return fetch[SQL_CHANNEL['directory']] | ||||
| 
 | ||||
|     def download_video(self, video, force=False): | ||||
|         if not isinstance(video, ytapi.Video): | ||||
|             video = self.youtube.get_video(video) | ||||
|         ''' | ||||
|         Execute the `YOUTUBE_DL_COMMAND`, within the channel's associated | ||||
|         directory if applicable. | ||||
|         ''' | ||||
|         # This logic is a little hazier than I would like, but it's all in the | ||||
|         # interest of minimizing unnecessary API calls. | ||||
|         if isinstance(video, ytapi.Video): | ||||
|             video_id = video.id | ||||
|         else: | ||||
|             video_id = video | ||||
|         self.cur.execute('SELECT * FROM videos WHERE id == ?', [video_id]) | ||||
|         video_row = self.cur.fetchone() | ||||
|         if video_row is None: | ||||
|             # Since the video was not in the db, we may not know about the channel either. | ||||
|             if not isinstance(video, ytapi.Video): | ||||
|                 print('get video') | ||||
|                 video = self.youtube.get_video(video) | ||||
|             channel_id = video.author_id | ||||
|             self.cur.execute('SELECT * FROM channels WHERE id == ?', [channel_id]) | ||||
|             if self.cur.fetchone() is None: | ||||
|                 print('add channel') | ||||
|                 self.add_channel(channel_id, get_videos=False, commit=False) | ||||
|             video_row = self.insert_video(video, commit=False)['row'] | ||||
|         else: | ||||
|             channel_id = video_row[SQL_VIDEO['author_id']] | ||||
| 
 | ||||
|         self.add_channel(video.author_id, get_videos=False, commit=False) | ||||
|         status = self.insert_video(video, commit=True) | ||||
| 
 | ||||
|         if status['row'][SQL_VIDEO['download']] != 'pending' and not force: | ||||
|         if video_row[SQL_VIDEO['download']] != 'pending' and not force: | ||||
|             print('That video does not need to be downloaded.') | ||||
|             return | ||||
| 
 | ||||
|         download_directory = self.channel_directory(video.author_id) | ||||
|         download_directory = self.channel_directory(channel_id) | ||||
|         download_directory = download_directory or os.getcwd() | ||||
| 
 | ||||
|         current_directory = os.getcwd() | ||||
|         os.makedirs(download_directory, exist_ok=True) | ||||
|         os.chdir(download_directory) | ||||
|         url = 'https://www.youtube.com/watch?v={id}'.format(id=video.id) | ||||
|         command = YOUTUBE_DL_COMMAND.format(url=url, id=video.id) | ||||
|         os.system(command) | ||||
|         self.youtube_dl_function(video_id) | ||||
|         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() | ||||
| 
 | ||||
|     def get_channel(self, channel_id): | ||||
|  | @ -173,14 +204,29 @@ class YCDL: | |||
|         channels.sort(key=lambda x: x['name'].lower()) | ||||
|         return channels | ||||
| 
 | ||||
|     def get_videos(self, channel_id=None): | ||||
|     def get_videos(self, channel_id=None, download_filter=None): | ||||
|         wheres = [] | ||||
|         bindings = [] | ||||
|         if channel_id is not None: | ||||
|             self.cur.execute('SELECT * FROM videos WHERE author_id == ?', [channel_id]) | ||||
|             wheres.append('author_id') | ||||
|             bindings.append(channel_id) | ||||
| 
 | ||||
|         if download_filter is not None: | ||||
|             wheres.append('download') | ||||
|             bindings.append(download_filter) | ||||
| 
 | ||||
|         if wheres: | ||||
|             wheres = [x + ' == ?' for x in wheres] | ||||
|             wheres = ' WHERE ' + ' AND '.join(wheres) | ||||
|         else: | ||||
|             self.cur.execute('SELECT * FROM videos ') | ||||
|             wheres = '' | ||||
| 
 | ||||
|         query = 'SELECT * FROM videos' + wheres | ||||
|         self.cur.execute(query, bindings) | ||||
|         videos = self.cur.fetchall() | ||||
|         if not videos: | ||||
|             return [] | ||||
| 
 | ||||
|         videos = [{key: video[SQL_VIDEO[key]] for key in SQL_VIDEO} for video in videos] | ||||
|         videos.sort(key=lambda x: x['published'], reverse=True) | ||||
|         return videos | ||||
|  | @ -214,10 +260,10 @@ class YCDL: | |||
|         Mark the video as ignored, pending, or downloaded. | ||||
|         ''' | ||||
|         if state not in ['ignored', 'pending', 'downloaded']: | ||||
|             raise ValueError(state) | ||||
|             raise InvalidVideoState(state) | ||||
|         self.cur.execute('SELECT * FROM videos WHERE id == ?', [video_id]) | ||||
|         if self.cur.fetchone() is None: | ||||
|             raise KeyError(video_id) | ||||
|             raise NoSuchVideo(video_id) | ||||
|         self.cur.execute('UPDATE videos SET download = ? WHERE id == ?', [state, video_id]) | ||||
|         if commit: | ||||
|             self.sql.commit() | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ gevent.monkey.patch_all() | |||
| import gevent.pywsgi | ||||
| import gevent.wsgi | ||||
| import sys | ||||
| 
 | ||||
| import ycdl_site | ||||
| 
 | ||||
| if len(sys.argv) == 2: | ||||
|  | @ -25,5 +26,5 @@ else: | |||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| print('Starting server') | ||||
| print('Starting server on port %d' % port) | ||||
| http.serve_forever() | ||||
|  |  | |||
							
								
								
									
										2
									
								
								ycdl_refresh.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								ycdl_refresh.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,2 @@ | |||
| import ycdl_easy | ||||
| ycdl_easy.youtube.refresh_all_channels() | ||||
							
								
								
									
										27
									
								
								ycdl_site.py
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								ycdl_site.py
									
									
									
									
									
								
							|  | @ -128,9 +128,8 @@ def get_channel(channel_id, download_filter=None): | |||
|     channel = youtube.get_channel(channel_id) | ||||
|     if channel is None: | ||||
|         flask.abort(404) | ||||
|     videos = youtube.get_videos(channel_id=channel_id) | ||||
|     if download_filter is not None: | ||||
|         videos = [video for video in videos if video['download'] == download_filter] | ||||
|     videos = youtube.get_videos(channel_id=channel_id, download_filter=download_filter) | ||||
| 
 | ||||
|     for video in videos: | ||||
|         published = video['published'] | ||||
|         published = datetime.datetime.utcfromtimestamp(published) | ||||
|  | @ -153,10 +152,13 @@ def post_mark_video_state(): | |||
|     state = request.form['state'] | ||||
|     try: | ||||
|         youtube.mark_video_state(video_id, state) | ||||
|     except KeyError: | ||||
| 
 | ||||
|     except ycdl.NoSuchVideo: | ||||
|         flask.abort(404) | ||||
|     except ValueError: | ||||
| 
 | ||||
|     except ycdl.InvalidVideoState: | ||||
|         flask.abort(400) | ||||
| 
 | ||||
|     return make_json_response({'video_id': video_id, 'state': state}) | ||||
| 
 | ||||
| @site.route('/refresh_all_channels', methods=['POST']) | ||||
|  | @ -174,6 +176,13 @@ def post_refresh_channel(): | |||
|     channel_id = channel_id.strip() | ||||
|     if not channel_id: | ||||
|         flask.abort(400) | ||||
|     if not (len(channel_id) == 24 and channel_id.startswith('UC')): | ||||
|         # It seems they have given us a username instead. | ||||
|         try: | ||||
|             channel_id = youtube.youtube.get_user_id(username=channel_id) | ||||
|         except IndexError: | ||||
|             flask.abort(404) | ||||
| 
 | ||||
|     force = request.form.get('force', False) | ||||
|     force = helpers.truthystring(force) | ||||
|     youtube.refresh_channel(channel_id, force=force) | ||||
|  | @ -184,11 +193,11 @@ def post_start_download(): | |||
|     if 'video_id' not in request.form: | ||||
|         flask.abort(400) | ||||
|     video_id = request.form['video_id'] | ||||
|     video_info = youtube_core.get_video([video_id]) | ||||
|     if video_info == []: | ||||
|     try: | ||||
|         youtube.download_video(video_id) | ||||
|     except ytapi.VideoNotFound: | ||||
|         flask.abort(404) | ||||
|     for video in video_info: | ||||
|         youtube.download_video(video) | ||||
| 
 | ||||
|     return make_json_response({'video_id': video_id, 'state': 'downloaded'}) | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|  |  | |||
							
								
								
									
										20
									
								
								ytapi.py
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								ytapi.py
									
									
									
									
									
								
							|  | @ -3,6 +3,9 @@ import datetime | |||
| 
 | ||||
| import helpers | ||||
| 
 | ||||
| class VideoNotFound(Exception): | ||||
|     pass | ||||
| 
 | ||||
| class Video: | ||||
|     def __init__(self, snippet): | ||||
|         self.id = snippet['id'] | ||||
|  | @ -33,6 +36,10 @@ class Youtube: | |||
|         ) | ||||
|         self.youtube = youtube | ||||
| 
 | ||||
|     def get_user_id(self, username): | ||||
|         user = self.youtube.channels().list(part='snippet', forUsername=username).execute() | ||||
|         return user['items'][0]['id'] | ||||
| 
 | ||||
|     def get_user_name(self, uid): | ||||
|         user = self.youtube.channels().list(part='snippet', id=uid).execute() | ||||
|         return user['items'][0]['snippet']['title'] | ||||
|  | @ -67,15 +74,18 @@ class Youtube: | |||
|             video_ids = [video_ids] | ||||
|         else: | ||||
|             singular = False | ||||
|         video_ids = helpers.chunk_sequence(video_ids, 50) | ||||
| 
 | ||||
|         results = [] | ||||
|         for chunk in video_ids: | ||||
|         chunks = helpers.chunk_sequence(video_ids, 50) | ||||
|         for chunk in chunks: | ||||
|             chunk = ','.join(chunk) | ||||
|             data = self.youtube.videos().list(part='snippet', id=chunk).execute() | ||||
|             items = data['items'] | ||||
|             results += items | ||||
|             #print('Found %d more, %d total' % (len(items), len(results))) | ||||
|         results = [Video(snippet) for snippet in results] | ||||
|         if singular and len(results) == 1: | ||||
|             return results[0] | ||||
|         if singular: | ||||
|             if len(results) == 1: | ||||
|                 return results[0] | ||||
|             elif len(results) == 0: | ||||
|                 raise VideoNotFound(video_ids[0]) | ||||
|         return results | ||||
|  |  | |||
|  | @ -18,6 +18,7 @@ while True: | |||
|     for filename in queue: | ||||
|         yt_id = filename.split('.')[0] | ||||
|         command = YOUTUBE_DL.format(id=yt_id) | ||||
|         os.system(command) | ||||
|         os.remove(filename) | ||||
|         exit_code = os.system(command) | ||||
|         if exit_code == 0: | ||||
|             os.remove(filename) | ||||
|     time.sleep(10) | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue