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');"
>Revert to Pending</button>
{% if video.live_broadcast is none %}
<button
{% if video.state == "pending" %}
class="video_action_download"
@ -234,6 +235,9 @@ https://stackoverflow.com/a/35153397
{% endif %}
onclick="return action_button_passthrough(event, api.videos.start_download);"
>Download</button>
{% else %}
<button disabled>Video is {{video.live_broadcast}}</button>
{% endif %}
<button
{% if video.state == "pending" %}

View file

@ -249,6 +249,43 @@ def upgrade_7_to_8(ycdldb):
# Redundant due to (state, published)
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):
'''
Given the directory containing a ycdl database, apply all of the

View file

@ -1,6 +1,6 @@
from voussoirkit import sqlhelpers
DATABASE_VERSION = 8
DATABASE_VERSION = 9
DB_VERSION_PRAGMA = f'''
PRAGMA user_version = {DATABASE_VERSION};
'''
@ -32,6 +32,7 @@ CREATE TABLE IF NOT EXISTS videos(
duration INT,
views INT,
thumbnail TEXT,
live_broadcast TEXT,
state TEXT
);

View file

@ -99,20 +99,33 @@ class Channel(Base):
if not (force or status['new']):
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 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)}
refresh_ids = list(known_ids.difference(seen_ids))
refresh_ids.update(known_ids.difference(seen_ids))
# 2. Premieres or live events which may now be over but were not
# included in the requested batch of IDs because they are not the most
# recent.
query = 'SELECT * FROM videos WHERE live_broadcast IS NOT NULL'
videos = self.ycdldb.get_videos_by_sql(query)
refresh_ids.update(v.id for v in videos)
if refresh_ids:
self.ycdldb.log.debug(
'%d ids did not come back from the generator, fetching them separately.',
len(refresh_ids),
)
for video in self.ycdldb.youtube.get_videos(refresh_ids):
self.ycdldb.insert_video(video, commit=False)
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:
self.ycdldb.commit()
@ -186,6 +199,7 @@ class Video(Base):
self.duration = db_row['duration']
self.views = db_row['views']
self.thumbnail = db_row['thumbnail']
self.live_broadcast = db_row['live_broadcast']
self.state = db_row['state']
def __repr__(self):

View file

@ -433,6 +433,13 @@ class YCDLDBVideoMixin:
return status
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.
self.download_video(video.id, commit=False)
else:
@ -452,9 +459,11 @@ class YCDLDBVideoMixin:
try:
existing = self.get_video(video.id)
existing_live_broadcast = existing.live_broadcast
download_status = existing.state
except exceptions.NoSuchVideo:
existing = None
existing_live_broadcast = None
download_status = 'pending'
data = {
@ -466,6 +475,7 @@ class YCDLDBVideoMixin:
'duration': video.duration,
'views': video.views,
'thumbnail': video.thumbnail['url'],
'live_broadcast': video.live_broadcast,
'state': download_status,
}
@ -484,7 +494,16 @@ class YCDLDBVideoMixin:
if 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(
YCDLDBCacheManagerMixin,

View file

@ -32,6 +32,9 @@ class Video:
# Something like '2016-10-01T21:00:01'
self.published_string = snippet['publishedAt']
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.duration = isodate.parse_duration(content_details['duration']).seconds