|
|
|
@ -2,7 +2,6 @@
|
|
|
|
|
import logging
|
|
|
|
|
import json
|
|
|
|
|
import signal
|
|
|
|
|
from collections import namedtuple
|
|
|
|
|
|
|
|
|
|
import argh
|
|
|
|
|
import gevent
|
|
|
|
@ -97,7 +96,7 @@ class PlaylistManager(object):
|
|
|
|
|
# the next time.
|
|
|
|
|
conn = self.dbmanager.get_conn()
|
|
|
|
|
videos = query(conn, """
|
|
|
|
|
SELECT id, video_id, tags, COALESCE((video_ranges[1]).start, event_start) AS start_time
|
|
|
|
|
SELECT video_id, tags, COALESCE((video_ranges[1]).start, event_start) AS start_time
|
|
|
|
|
FROM events
|
|
|
|
|
WHERE state = 'DONE' AND upload_location = ANY (%s) AND public
|
|
|
|
|
""", self.upload_locations)
|
|
|
|
@ -118,14 +117,14 @@ class PlaylistManager(object):
|
|
|
|
|
""")
|
|
|
|
|
}
|
|
|
|
|
self.dbmanager.put_conn(conn)
|
|
|
|
|
duplicates = set(playlists) & set(self.static_playlist_tags)
|
|
|
|
|
duplicates = set(playlists) & set(self.static_playlists)
|
|
|
|
|
if duplicates:
|
|
|
|
|
raise ValueError(
|
|
|
|
|
"Some playlists are listed in both static and dynamic playlist sources: {}".format(", ".join(duplicates))
|
|
|
|
|
)
|
|
|
|
|
playlists.update({
|
|
|
|
|
id: PlaylistConfig(tags, None, None)
|
|
|
|
|
for id, tags in self.static_playlist_tags.items()
|
|
|
|
|
for id, tags in self.static_playlists.items()
|
|
|
|
|
})
|
|
|
|
|
return playlists
|
|
|
|
|
|
|
|
|
@ -158,22 +157,16 @@ class PlaylistManager(object):
|
|
|
|
|
self.reorder_in_playlist(playlist_id, entry, index)
|
|
|
|
|
|
|
|
|
|
# Get an updated list of new videos
|
|
|
|
|
playlist = self.get_playlist(playlist_id)
|
|
|
|
|
matching_video_ids = {video.video_id for video in matching}
|
|
|
|
|
playlist_video_ids = {entry.video_id for entry in playlist}
|
|
|
|
|
playlist_video_ids = {entry.video_id for entry in self.get_playlist(playlist_id)}
|
|
|
|
|
# It shouldn't matter, but just for clarity let's sort them by event order
|
|
|
|
|
new_videos = sorted(
|
|
|
|
|
matching_video_ids - playlist_video_ids,
|
|
|
|
|
key=lambda video_id: videos[video_id].start_time,
|
|
|
|
|
)
|
|
|
|
|
new_videos = sorted(matching_video_ids - playlist_video_ids, key=lambda v: v.start_time)
|
|
|
|
|
|
|
|
|
|
# Insert each new video one at a time
|
|
|
|
|
logging.debug(f"Inserting new videos for playlist {playlist_id}: {new_videos}")
|
|
|
|
|
for video_id in new_videos:
|
|
|
|
|
# Note we update the cached playlist after each loop as it is modified by insertions.
|
|
|
|
|
playlist = self.get_playlist(playlist_id)
|
|
|
|
|
index = self.find_insert_index(videos, playlist_config, playlist, videos[video_id])
|
|
|
|
|
self.insert_into_playlist(playlist_id, video_id, index)
|
|
|
|
|
for video in new_videos:
|
|
|
|
|
index = self.find_insert_index(videos, playlist_config, self.get_playlist(playlist_id), video)
|
|
|
|
|
self.insert_into_playlist(playlist_id, video.video_id, index)
|
|
|
|
|
except PlaylistOutdated:
|
|
|
|
|
logging.warning("Restarting playlist update as playlist is out of date")
|
|
|
|
|
continue
|
|
|
|
@ -192,11 +185,11 @@ class PlaylistManager(object):
|
|
|
|
|
if entry.video_id not in videos:
|
|
|
|
|
# Unknown videos should always remain in-place.
|
|
|
|
|
continue
|
|
|
|
|
video = videos[entry.video_id]
|
|
|
|
|
video = videos[video_id]
|
|
|
|
|
|
|
|
|
|
if video.id == playlist_config.first_event_id:
|
|
|
|
|
if video.id == playlist.first_event_id:
|
|
|
|
|
new_index = 0
|
|
|
|
|
elif video.id == playlist_config.last_event_id:
|
|
|
|
|
elif video.id == playlist.last_event_id:
|
|
|
|
|
new_index = len(playlist) - 1
|
|
|
|
|
else:
|
|
|
|
|
continue
|
|
|
|
@ -284,10 +277,8 @@ class PlaylistManager(object):
|
|
|
|
|
"""
|
|
|
|
|
logging.info(f"Inserting {video_id} at index {index} of {playlist_id}")
|
|
|
|
|
entry_id = self.api.insert_into_playlist(playlist_id, video_id, index)
|
|
|
|
|
# Update our copy, if we have one (which we should)
|
|
|
|
|
if playlist_id in self.playlist_state:
|
|
|
|
|
playlist = self.get_playlist(playlist_id)
|
|
|
|
|
playlist.insert(index, PlaylistEntry(entry_id, video_id))
|
|
|
|
|
# Update our copy
|
|
|
|
|
self.playlist_state.setdefault(playlist_id, []).insert(index, PlaylistEntry(entry_id, video_id)
|
|
|
|
|
|
|
|
|
|
def reorder_in_playlist(self, playlist_id, entry, new_index):
|
|
|
|
|
"""Take an existing entry in a given playlist and move it to the new index.
|
|
|
|
@ -443,7 +434,6 @@ def main(
|
|
|
|
|
playlists,
|
|
|
|
|
upload_location_allowlist="youtube",
|
|
|
|
|
interval=600,
|
|
|
|
|
once=False,
|
|
|
|
|
metrics_port=8007,
|
|
|
|
|
backdoor_port=0,
|
|
|
|
|
):
|
|
|
|
@ -459,7 +449,6 @@ def main(
|
|
|
|
|
upload_location.
|
|
|
|
|
|
|
|
|
|
interval is how often to check for new videos, default every 10min.
|
|
|
|
|
If --once is given, interval is ignored and we exit after one check.
|
|
|
|
|
"""
|
|
|
|
|
common.PromLogCountsHandler.install()
|
|
|
|
|
common.install_stacksampler()
|
|
|
|
@ -483,15 +472,12 @@ def main(
|
|
|
|
|
dbmanager = DBManager(dsn=dbconnect)
|
|
|
|
|
manager = PlaylistManager(dbmanager, client, upload_locations, playlists)
|
|
|
|
|
|
|
|
|
|
if once:
|
|
|
|
|
manager.run_once()
|
|
|
|
|
else:
|
|
|
|
|
while not stop.is_set():
|
|
|
|
|
try:
|
|
|
|
|
manager.run_once()
|
|
|
|
|
except Exception:
|
|
|
|
|
logging.exception("Failed to run playlist manager")
|
|
|
|
|
manager.reset()
|
|
|
|
|
stop.wait(interval) # wait for interval, or until stopping
|
|
|
|
|
while not stop.is_set():
|
|
|
|
|
try:
|
|
|
|
|
manager.run_once()
|
|
|
|
|
except Exception:
|
|
|
|
|
logging.exception("Failed to run playlist manager")
|
|
|
|
|
manager.reset()
|
|
|
|
|
stop.wait(interval) # wait for interval, or until stopping
|
|
|
|
|
|
|
|
|
|
logging.info("Stopped")
|
|
|
|
|