@ -14,6 +14,9 @@ from common.database import DBManager, query
from common . googleapis import GoogleAPIClient
from common . googleapis import GoogleAPIClient
PlaylistEntry = namedtuple ( " PlaylistEntry " , [ " entry_id " , " video_id " ] )
class PlaylistManager ( object ) :
class PlaylistManager ( object ) :
def __init__ ( self , dbmanager , api_client , upload_locations , playlist_tags ) :
def __init__ ( self , dbmanager , api_client , upload_locations , playlist_tags ) :
@ -30,7 +33,7 @@ class PlaylistManager(object):
if playlist_id is None :
if playlist_id is None :
# playlist_state represents our mirrored view of the list of items in each playlist.
# playlist_state represents our mirrored view of the list of items in each playlist.
# If a playlist is not present, it means we need to refresh our view of it.
# If a playlist is not present, it means we need to refresh our view of it.
# {playlist_id: [ video_id ]}
# {playlist_id: [ PlaylistEntry ]}
self . playlist_state = { }
self . playlist_state = { }
else :
else :
self . playlist_state . pop ( playlist_id , None )
self . playlist_state . pop ( playlist_id , None )
@ -112,7 +115,7 @@ class PlaylistManager(object):
# If we have nothing to add, short circuit without doing any API calls to save quota.
# If we have nothing to add, short circuit without doing any API calls to save quota.
matching_video_ids = { video . video_id for video in matching }
matching_video_ids = { video . video_id for video in matching }
playlist_video_ids = set ( self . get_playlist ( playlist_id ) )
playlist_video_ids = { entry . video_id for entry in self . get_playlist ( playlist_id ) }
if not ( matching_video_ids - playlist_video_ids ) :
if not ( matching_video_ids - playlist_video_ids ) :
logging . debug ( " All videos already in playlist, nothing to do " )
logging . debug ( " All videos already in playlist, nothing to do " )
return
return
@ -120,7 +123,7 @@ class PlaylistManager(object):
self . refresh_playlist ( playlist_id )
self . refresh_playlist ( playlist_id )
# Get an updated list of new videos
# Get an updated list of new videos
matching_video_ids = { video . video_id for video in matching }
matching_video_ids = { video . video_id for video in matching }
playlist_video_ids = set ( self . get_playlist ( playlist_id ) )
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
# 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 v : v . start_time )
new_videos = sorted ( matching_video_ids - playlist_video_ids , key = lambda v : v . start_time )
@ -154,8 +157,11 @@ class PlaylistManager(object):
query . fetch_all ( )
query . fetch_all ( )
# Update saved copy with video ids
# Update saved copy with video ids
self . playlist_state [ playlist_id ] = [
self . playlist_state [ playlist_id ] = [
item [ ' snippet ' ] [ ' resourceId ' ] . get ( ' videoId ' ) # api implies it's possible that non-videos are added
PlaylistEntry (
for item in query . items
item [ ' id ' ] ,
# api implies it's possible that non-videos are added, so videoId might not exist
item [ ' snippet ' ] [ ' resourceId ' ] . get ( ' videoId ' ) ,
) for item in query . items
]
]
def find_insert_index ( self , videos , playlist , new_video ) :
def find_insert_index ( self , videos , playlist , new_video ) :
@ -168,7 +174,7 @@ class PlaylistManager(object):
# item that should be after us in sort order.
# item that should be after us in sort order.
# Note that we treat unknown items (videos we don't know) as being before us
# Note that we treat unknown items (videos we don't know) as being before us
# in sort order, so that we always insert after them.
# in sort order, so that we always insert after them.
for n , video_id in enumerate ( playlist ) :
for n , ( _ , video_id ) in enumerate ( playlist ) :
if video_id not in videos :
if video_id not in videos :
# ignore unknowns
# ignore unknowns
continue
continue
@ -185,9 +191,9 @@ class PlaylistManager(object):
Makes the API call then also updates our mirrored copy .
Makes the API call then also updates our mirrored copy .
"""
"""
logging . info ( f " Inserting { video_id } at index { index } of { playlist_id } " )
logging . info ( f " Inserting { video_id } at index { index } of { playlist_id } " )
self . api . insert_into_playlist ( playlist_id , video_id , index )
entry_id = self . api . insert_into_playlist ( playlist_id , video_id , index )
# Update our copy
# Update our copy
self . playlist_state . setdefault ( playlist_id , [ ] ) . insert ( index , video_id)
self . playlist_state . setdefault ( playlist_id , [ ] ) . insert ( index , PlaylistEntry( entry_id , video_id)
class YoutubeAPI ( object ) :
class YoutubeAPI ( object ) :
@ -218,6 +224,7 @@ class YoutubeAPI(object):
raise Exception ( " Failed to insert {video_id} at index {index} of {playlist} with {resp.status_code} : {resp.content} " . format (
raise Exception ( " Failed to insert {video_id} at index {index} of {playlist} with {resp.status_code} : {resp.content} " . format (
playlist = playlist_id , video_id = video_id , index = index , resp = resp ,
playlist = playlist_id , video_id = video_id , index = index , resp = resp ,
) )
) )
# TODO return entry_id from resp
def list_playlist ( self , playlist_id ) :
def list_playlist ( self , playlist_id ) :
""" Fetches the first page of playlist contents and returns a ListQuery object.
""" Fetches the first page of playlist contents and returns a ListQuery object.