问题
In my python script I'm getting the video ID of my latest video.
This is the code, playlistId
being my channel's playlist ID that contains all my videos:
def get_latest_video_id(youtube, playlistId):
id_request = youtube.playlistItems().list(
part = 'snippet',
playlistId = playlistId
)
id_response = id_request.execute()
video_id = id_response['items'][0]['snippet']['resourceId']['videoId']
return video_id
The problem now is, my live streams also get saved into this playlist. I couldn't find out if there is a playlist with all my uploads excluding my saved live streams.
The workaround I thought of is to get a list of all my livestreams and compare their ID to the ID I got from the method above.
My question is, isn't there a better way to do this? Is there by chance a API call that does what I need, without high quota cost?
回答1:
You'll have to iterate your call to PlaylistItems.list API endpoint (using pagination) for to filter out manually the videos that are live streams.
def get_non_livestream_videos(youtube, video_ids):
assert len(video_ids) <= 50
response = youtube.videos().list(
fields = 'items(id,liveStreamingDetails)',
part = 'id,liveStreamingDetails',
maxResults = len(video_ids),
id = ','.join(video_ids),
).execute()
items = response.get('items', [])
assert len(items) <= len(video_ids)
not_live = lambda video: \
not video.get('liveStreamingDetails')
video_id = lambda video: video['id']
return map(video_id, filter(not_live, items))
def get_latest_video_id(youtube, playlistId):
request = youtube.playlistItems().list(
fields = 'nextPageToken,items/snippet/resourceId',
playlistId = playlistId,
maxResults = 50,
part = 'snippet'
)
is_video = lambda item: \
item['snippet']['resourceId']['kind'] == 'youtube#video'
video_id = lambda item: \
item['snippet']['resourceId']['videoId']
while request:
response = request.execute()
items = response.get('items', [])
assert len(items) <= 50
videos = map(video_id, filter(is_video, items))
if videos:
videos = get_non_livestream_videos(youtube, videos)
if videos: return videos[0]
request = youtube.playlistItems().list_next(
request, response)
return None
Note that above I used the fields request parameter for to get from the APIs only the info that's actually needed.
Also note that you may have to elaborate a bit the function get_non_livestream_videos
, since the Videos.list API endpoint queried with its id parameter as a comma-separated list of video IDs may well alter the order of the items it returns w.r.t. the given order of the IDs in video_ids
.
Yet an important note: if you're running the code above under Python 3 (your question does not mention this), then make sure you have the following configuration code inserted at the top of your script:
if sys.version_info[0] >= 3:
from builtins import map as builtin_map
map = lambda *args: list(builtin_map(*args))
This is needed since, under Python 3, the builtin function map returns an iterator, whereas under Python 2, map returns a list.
Here is the code that solves the issue I mentioned above w.r.t. the case of Videos.list
altering the order of items returned relative to the order of the IDs given by the argument video_ids
of function get_non_livestream_videos
:
import sys
if sys.version_info[0] >= 3:
from builtins import map as builtin_map
map = lambda *args: list(builtin_map(*args))
class MergeVideoListsError(Exception): pass
def merge_video_lists(video_ids, video_res):
pair0 = lambda pair: pair[0]
pair1 = lambda pair: pair[1]
video_ids = sorted(
enumerate(video_ids), key = pair1)
video_res.sort(
key = lambda video: video['id'])
def error(video_id):
raise MergeVideoListsError(
"unexpected video resource of ID '%s'" % video_id)
def do_merge():
N = len(video_ids)
R = len(video_res)
assert R <= N
l = []
i, j = 0, 0
while i < N and j < R:
v = video_ids[i]
r = video_res[j]
s = v[1]
d = r['id']
if s == d:
l.append((v[0], r))
i += 1
j += 1
elif s < d:
i += 1
else:
error(d)
if j < R:
error(video_res[j]['id'])
return l
video_res = do_merge()
video_res.sort(key = pair0)
return map(pair1, video_res)
def println(*args):
for a in args:
sys.stdout.write(str(a))
sys.stdout.write('\n')
def test_merge_video_lists(ids, res, val):
try:
println("ids: ", ids)
println("res: ", res)
r = merge_video_lists(ids, res)
println("merge: ", r)
except MergeVideoListsError as e:
println("error: ", e)
r = str(e)
finally:
println("test: ", "OK" \
if val == r \
else "failed")
TESTS = ((
['c', 'b', 'a'],
[{'id': 'c'}, {'id': 'a'}, {'id': 'b'}],
[{'id': 'c'}, {'id': 'b'}, {'id': 'a'}]
),(
['c', 'b', 'a'],
[{'id': 'b'}, {'id': 'c'}],
[{'id': 'c'}, {'id': 'b'}]
),(
['c', 'b', 'a'],
[{'id': 'a'}, {'id': 'c'}],
[{'id': 'c'}, {'id': 'a'}]
),(
['c', 'b', 'a'],
[{'id': 'a'}, {'id': 'b'}],
[{'id': 'b'}, {'id': 'a'}]
),(
['c', 'b', 'a'],
[{'id': 'z'}, {'id': 'b'}, {'id': 'c'}],
"unexpected video resource of ID 'z'"
),(
['c', 'b', 'a'],
[{'id': 'a'}, {'id': 'z'}, {'id': 'c'}],
"unexpected video resource of ID 'z'"
),(
['c', 'b', 'a'],
[{'id': 'a'}, {'id': 'b'}, {'id': 'z'}],
"unexpected video resource of ID 'z'"
))
def main():
for i, t in enumerate(TESTS):
if i: println()
test_merge_video_lists(*t)
if __name__ == '__main__':
main()
# $ python merge-video-lists.py
# ids: ['c', 'b', 'a']
# res: [{'id': 'c'}, {'id': 'a'}, {'id': 'b'}]
# merge: [{'id': 'c'}, {'id': 'b'}, {'id': 'a'}]
# test: OK
#
# ids: ['c', 'b', 'a']
# res: [{'id': 'b'}, {'id': 'c'}]
# merge: [{'id': 'c'}, {'id': 'b'}]
# test: OK
#
# ids: ['c', 'b', 'a']
# res: [{'id': 'a'}, {'id': 'c'}]
# merge: [{'id': 'c'}, {'id': 'a'}]
# test: OK
#
# ids: ['c', 'b', 'a']
# res: [{'id': 'a'}, {'id': 'b'}]
# merge: [{'id': 'b'}, {'id': 'a'}]
# test: OK
#
# ids: ['c', 'b', 'a']
# res: [{'id': 'z'}, {'id': 'b'}, {'id': 'c'}]
# error: unexpected video resource of ID 'z'
# test: OK
#
# ids: ['c', 'b', 'a']
# res: [{'id': 'a'}, {'id': 'z'}, {'id': 'c'}]
# error: unexpected video resource of ID 'z'
# test: OK
#
# ids: ['c', 'b', 'a']
# res: [{'id': 'a'}, {'id': 'b'}, {'id': 'z'}]
# error: unexpected video resource of ID 'z'
# test: OK
The code above is a standalone program (running both under Python v2 and v3) that implements a merging function merge_video_lists
.
You'll have to use this function within the function get_non_livestream_videos
by replacing the line:
return map(video_id, filter(not_live, items))
with:
return map(video_id, merge_video_lists(
video_ids, filter(not_live, items)))
for Python 2. For Python 3 the replacement would be:
return map(video_id, merge_video_lists(
video_ids, list(filter(not_live, items))))
Instead of replacing the return
statement, just have that statement preceded by this one:
items = merge_video_lists(video_ids, items)
This latter variant is better, since it also validates the video IDs returned by the API: if there is an ID that is not in video_ids
, then merge_video_lists
throws a MergeVideoListsError
exception indicating the culprit ID.
来源:https://stackoverflow.com/questions/65741116/youtube-data-api-get-latest-video-id-from-channel-excluding-live-streams