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))
|
||||||
|
|
||||||
|
# 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:
|
if refresh_ids:
|
||||||
self.ycdldb.log.debug(
|
self.ycdldb.log.debug('Refreshing %d ids separately.', len(refresh_ids))
|
||||||
'%d ids did not come back from the generator, fetching them separately.',
|
|
||||||
len(refresh_ids),
|
# We call ingest_video instead of insert_video so that
|
||||||
)
|
# premieres / livestreams which have finished can be automarked.
|
||||||
for video in self.ycdldb.youtube.get_videos(refresh_ids):
|
for video_id in self.ycdldb.youtube.get_videos(refresh_ids):
|
||||||
self.ycdldb.insert_video(video, commit=False)
|
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