Improve handling of livestreams, premieres with live_broadcast attr.
This commit is contained in:
		
							parent
							
								
									885bc2b711
								
							
						
					
					
						commit
						fa363a33c7
					
				
					 6 changed files with 92 additions and 14 deletions
				
			
		|  | @ -226,6 +226,7 @@ https://stackoverflow.com/a/35153397 | ||||||
|                 onclick="return action_button_passthrough(event, api.videos.mark_state, 'pending');" |                 onclick="return action_button_passthrough(event, api.videos.mark_state, 'pending');" | ||||||
|                 >Revert to Pending</button> |                 >Revert to Pending</button> | ||||||
| 
 | 
 | ||||||
|  |                 {% if video.live_broadcast is none %} | ||||||
|                 <button |                 <button | ||||||
|                 {% if video.state == "pending" %} |                 {% if video.state == "pending" %} | ||||||
|                 class="video_action_download" |                 class="video_action_download" | ||||||
|  | @ -234,6 +235,9 @@ https://stackoverflow.com/a/35153397 | ||||||
|                 {% endif %} |                 {% endif %} | ||||||
|                 onclick="return action_button_passthrough(event, api.videos.start_download);" |                 onclick="return action_button_passthrough(event, api.videos.start_download);" | ||||||
|                 >Download</button> |                 >Download</button> | ||||||
|  |                 {% else %} | ||||||
|  |                 <button disabled>Video is {{video.live_broadcast}}</button> | ||||||
|  |                 {% endif %} | ||||||
| 
 | 
 | ||||||
|                 <button |                 <button | ||||||
|                 {% if video.state == "pending" %} |                 {% if video.state == "pending" %} | ||||||
|  |  | ||||||
|  | @ -249,6 +249,43 @@ def upgrade_7_to_8(ycdldb): | ||||||
|     # Redundant due to (state, published) |     # Redundant due to (state, published) | ||||||
|     ycdldb.sql.execute('DROP INDEX IF EXISTS index_video_state') |     ycdldb.sql.execute('DROP INDEX IF EXISTS index_video_state') | ||||||
| 
 | 
 | ||||||
|  | def upgrade_8_to_9(ycdldb): | ||||||
|  |     ''' | ||||||
|  |     In this version, the `live_broadcast` column was added to the videos table. | ||||||
|  |     ''' | ||||||
|  |     m = Migrator(ycdldb) | ||||||
|  | 
 | ||||||
|  |     m.tables['videos']['create'] = ''' | ||||||
|  |     CREATE TABLE IF NOT EXISTS videos( | ||||||
|  |         id TEXT, | ||||||
|  |         published INT, | ||||||
|  |         author_id TEXT, | ||||||
|  |         title TEXT, | ||||||
|  |         description TEXT, | ||||||
|  |         duration INT, | ||||||
|  |         views INT, | ||||||
|  |         thumbnail TEXT, | ||||||
|  |         live_broadcast TEXT, | ||||||
|  |         state TEXT | ||||||
|  |     ); | ||||||
|  |     ''' | ||||||
|  |     m.tables['videos']['transfer'] = ''' | ||||||
|  |     INSERT INTO videos SELECT | ||||||
|  |         id, | ||||||
|  |         published, | ||||||
|  |         author_id, | ||||||
|  |         title, | ||||||
|  |         description, | ||||||
|  |         duration, | ||||||
|  |         views, | ||||||
|  |         thumbnail, | ||||||
|  |         NULL, | ||||||
|  |         state | ||||||
|  |     FROM videos_old; | ||||||
|  |     ''' | ||||||
|  | 
 | ||||||
|  |     m.go() | ||||||
|  | 
 | ||||||
| def upgrade_all(data_directory): | def upgrade_all(data_directory): | ||||||
|     ''' |     ''' | ||||||
|     Given the directory containing a ycdl database, apply all of the |     Given the directory containing a ycdl database, apply all of the | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| from voussoirkit import sqlhelpers | from voussoirkit import sqlhelpers | ||||||
| 
 | 
 | ||||||
| DATABASE_VERSION = 8 | DATABASE_VERSION = 9 | ||||||
| DB_VERSION_PRAGMA = f''' | DB_VERSION_PRAGMA = f''' | ||||||
| PRAGMA user_version = {DATABASE_VERSION}; | PRAGMA user_version = {DATABASE_VERSION}; | ||||||
| ''' | ''' | ||||||
|  | @ -32,6 +32,7 @@ CREATE TABLE IF NOT EXISTS videos( | ||||||
|     duration INT, |     duration INT, | ||||||
|     views INT, |     views INT, | ||||||
|     thumbnail TEXT, |     thumbnail TEXT, | ||||||
|  |     live_broadcast TEXT, | ||||||
|     state TEXT |     state TEXT | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -99,20 +99,33 @@ class Channel(Base): | ||||||
|             if not (force or status['new']): |             if not (force or status['new']): | ||||||
|                 break |                 break | ||||||
| 
 | 
 | ||||||
|  |         # Now we will refresh some other IDs that may not have been refreshed | ||||||
|  |         # by the previous loop. | ||||||
|  |         refresh_ids = set() | ||||||
|  | 
 | ||||||
|  |         # 1. Videos which have become unlisted, therefore not returned by the | ||||||
|  |         # get_playlist_videos call. Take the set of all known ids minus those | ||||||
|  |         # refreshed by the earlier  loop, the difference will be unlisted, | ||||||
|  |         # private, or deleted videos. At this time we have no special handling | ||||||
|  |         # for deleted videos, but they simply won't come back from ytapi. | ||||||
|         if force: |         if force: | ||||||
|             # If some videos have become unlisted, then they will not have been |  | ||||||
|             # refreshed by the previous loop. So, take the set of all known ids |  | ||||||
|             # minus those refreshed by the loop, and try to refresh them. |  | ||||||
|             # Of course, it's possible they were deleted. |  | ||||||
|             known_ids = {v.id for v in self.ycdldb.get_videos(channel_id=self.id)} |             known_ids = {v.id for v in self.ycdldb.get_videos(channel_id=self.id)} | ||||||
|             refresh_ids = list(known_ids.difference(seen_ids)) |             refresh_ids.update(known_ids.difference(seen_ids)) | ||||||
|             if refresh_ids: | 
 | ||||||
|                 self.ycdldb.log.debug( |         # 2. Premieres or live events which may now be over but were not | ||||||
|                     '%d ids did not come back from the generator, fetching them separately.', |         # included in the requested batch of IDs because they are not the most | ||||||
|                     len(refresh_ids), |         # recent. | ||||||
|                 ) |         query = 'SELECT * FROM videos WHERE live_broadcast IS NOT NULL' | ||||||
|             for video in self.ycdldb.youtube.get_videos(refresh_ids): |         videos = self.ycdldb.get_videos_by_sql(query) | ||||||
|                 self.ycdldb.insert_video(video, commit=False) |         refresh_ids.update(v.id for v in videos) | ||||||
|  | 
 | ||||||
|  |         if refresh_ids: | ||||||
|  |             self.ycdldb.log.debug('Refreshing %d ids separately.', len(refresh_ids)) | ||||||
|  | 
 | ||||||
|  |         # We call ingest_video instead of insert_video so that | ||||||
|  |         # premieres / livestreams which have finished can be automarked. | ||||||
|  |         for video_id in self.ycdldb.youtube.get_videos(refresh_ids): | ||||||
|  |             self.ycdldb.ingest_video(video_id, commit=False) | ||||||
| 
 | 
 | ||||||
|         if commit: |         if commit: | ||||||
|             self.ycdldb.commit() |             self.ycdldb.commit() | ||||||
|  | @ -186,6 +199,7 @@ class Video(Base): | ||||||
|         self.duration = db_row['duration'] |         self.duration = db_row['duration'] | ||||||
|         self.views = db_row['views'] |         self.views = db_row['views'] | ||||||
|         self.thumbnail = db_row['thumbnail'] |         self.thumbnail = db_row['thumbnail'] | ||||||
|  |         self.live_broadcast = db_row['live_broadcast'] | ||||||
|         self.state = db_row['state'] |         self.state = db_row['state'] | ||||||
| 
 | 
 | ||||||
|     def __repr__(self): |     def __repr__(self): | ||||||
|  |  | ||||||
|  | @ -433,6 +433,13 @@ class YCDLDBVideoMixin: | ||||||
|             return status |             return status | ||||||
| 
 | 
 | ||||||
|         if author.automark == 'downloaded': |         if author.automark == 'downloaded': | ||||||
|  |             if video.live_broadcast is not None: | ||||||
|  |                 self.log.debug( | ||||||
|  |                     'Not downloading %s because live_broadcast=%s.', | ||||||
|  |                     video.id, | ||||||
|  |                     video.live_broadcast, | ||||||
|  |                 ) | ||||||
|  |                 return status | ||||||
|             # download_video contains a call to mark_state. |             # download_video contains a call to mark_state. | ||||||
|             self.download_video(video.id, commit=False) |             self.download_video(video.id, commit=False) | ||||||
|         else: |         else: | ||||||
|  | @ -452,9 +459,11 @@ class YCDLDBVideoMixin: | ||||||
| 
 | 
 | ||||||
|         try: |         try: | ||||||
|             existing = self.get_video(video.id) |             existing = self.get_video(video.id) | ||||||
|  |             existing_live_broadcast = existing.live_broadcast | ||||||
|             download_status = existing.state |             download_status = existing.state | ||||||
|         except exceptions.NoSuchVideo: |         except exceptions.NoSuchVideo: | ||||||
|             existing = None |             existing = None | ||||||
|  |             existing_live_broadcast = None | ||||||
|             download_status = 'pending' |             download_status = 'pending' | ||||||
| 
 | 
 | ||||||
|         data = { |         data = { | ||||||
|  | @ -466,6 +475,7 @@ class YCDLDBVideoMixin: | ||||||
|             'duration': video.duration, |             'duration': video.duration, | ||||||
|             'views': video.views, |             'views': video.views, | ||||||
|             'thumbnail': video.thumbnail['url'], |             'thumbnail': video.thumbnail['url'], | ||||||
|  |             'live_broadcast': video.live_broadcast, | ||||||
|             'state': download_status, |             'state': download_status, | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -484,7 +494,16 @@ class YCDLDBVideoMixin: | ||||||
|         if commit: |         if commit: | ||||||
|             self.commit() |             self.commit() | ||||||
| 
 | 
 | ||||||
|         return {'new': not existing, 'video': video} |         # For the benefit of ingest_video, which will only apply the channel's | ||||||
|  |         # automark to newly released videos, let's consider the video to be | ||||||
|  |         # new if live_broadcast has changed to be None since last time. | ||||||
|  |         # This way, premieres and livestreams can be automarked by the next | ||||||
|  |         # refresh after they've ended. | ||||||
|  |         is_new = ( | ||||||
|  |             (existing is None) or | ||||||
|  |             (existing_live_broadcast is not None and video.live_broadcast is None) | ||||||
|  |         ) | ||||||
|  |         return {'new': is_new, 'video': video} | ||||||
| 
 | 
 | ||||||
| class YCDLDB( | class YCDLDB( | ||||||
|         YCDLDBCacheManagerMixin, |         YCDLDBCacheManagerMixin, | ||||||
|  |  | ||||||
|  | @ -32,6 +32,9 @@ class Video: | ||||||
|         # Something like '2016-10-01T21:00:01' |         # Something like '2016-10-01T21:00:01' | ||||||
|         self.published_string = snippet['publishedAt'] |         self.published_string = snippet['publishedAt'] | ||||||
|         self.published = isodate.parse_datetime(self.published_string).timestamp() |         self.published = isodate.parse_datetime(self.published_string).timestamp() | ||||||
|  |         self.live_broadcast = snippet['liveBroadcastContent'] | ||||||
|  |         if self.live_broadcast == 'none': | ||||||
|  |             self.live_broadcast = None | ||||||
|         self.tags = snippet.get('tags', []) |         self.tags = snippet.get('tags', []) | ||||||
| 
 | 
 | ||||||
|         self.duration = isodate.parse_duration(content_details['duration']).seconds |         self.duration = isodate.parse_duration(content_details['duration']).seconds | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue