Improve handling of livestreams, premieres with live_broadcast attr.

This commit is contained in:
voussoir 2021-03-31 17:24:57 -07:00
parent 885bc2b711
commit fa363a33c7
No known key found for this signature in database
GPG key ID: 5F7554F8C26DACCB
6 changed files with 92 additions and 14 deletions

View file

@ -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" %}

View file

@ -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

View file

@ -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
); );

View file

@ -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):

View file

@ -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,

View file

@ -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