diff --git a/main.py b/main.py index a6c6d4e..32939fd 100755 --- a/main.py +++ b/main.py @@ -83,7 +83,7 @@ def get_playlist_name(playlist_id: str) -> str: if plid == playlist_id), playlist_id) -def get_videos(yt_api, playlist_id): +def list_playlist(yt_api, playlist_id): playlist_name = get_playlist_name(playlist_id) videos = [] fetched = 0 @@ -172,8 +172,8 @@ def copy_playlist_items(yt_api, if limit < 0: limit = len(src_playlist) - src_videos = get_videos(yt_api, src_playlist) - dst_videos = get_videos(yt_api, dst_playlist) + src_videos = list_playlist(yt_api, src_playlist) + dst_videos = list_playlist(yt_api, dst_playlist) dst_video_map = {video['snippet']['resourceId']['videoId']: video['id'] for video in dst_videos} @@ -186,24 +186,41 @@ def copy_playlist_items(yt_api, remove_video_from_playlist(yt_api, src_video, src_playlist, dry_run) +def get_video_info(youtube, video_id: str): + try: + response = youtube.videos().list( + part="snippet", + id=video_id + ).execute() + return response['items'][0] + except HttpError as e: + exit_on_exceeded_quota(e) + print(f'Error getting video {video_id}: {e}') + return None + + def main(): parser = argparse.ArgumentParser() parser.add_argument('-n', '--dry-run', action='store_true', help='Dry run, do not send changes to YoutubeAPI') subparsers = parser.add_subparsers(title='commands', dest='command') - parser_move = subparsers.add_parser('move', help='Move videos from one playlist to another') - parser_move.add_argument('src_playlist', help='Source playlist ID') - parser_move.add_argument('dst_playlist', help='Destination playlist ID') - parser_move.add_argument('-l', '--limit', type=int, default=-1, - help='Limit number of videos to process') + parser_add = subparsers.add_parser('add', help='Add videos to a playlist') + parser_add.add_argument('playlist', help='Playlist name/ID') + parser_add.add_argument('video_ids', nargs='+', help='Video IDs to add') parser_copy = subparsers.add_parser('copy', help='Copy videos from one playlist to another') - parser_copy.add_argument('src_playlist', help='Source playlist ID') - parser_copy.add_argument('dst_playlist', help='Destination playlist ID') + parser_copy.add_argument('src_playlist', help='Source playlist name/ID') + parser_copy.add_argument('dst_playlist', help='Destination playlist name/ID') parser_copy.add_argument('-l', '--limit', type=int, default=-1, help='Limit number of videos to process') + parser_move = subparsers.add_parser('move', help='Move videos from one playlist to another') + parser_move.add_argument('src_playlist', help='Source playlist name/ID') + parser_move.add_argument('dst_playlist', help='Destination playlist name/ID') + parser_move.add_argument('-l', '--limit', type=int, default=-1, + help='Limit number of videos to process') + args = parser.parse_args() # Disable OAuthlib's HTTPS verification when running locally. @@ -217,12 +234,14 @@ def main(): if args.command is None: parser.print_help() return 1 + elif args.command == 'copy': src_playlist = get_playlist_id(args.src_playlist) dst_playlist = get_playlist_id(args.dst_playlist) copy_playlist_items(youtube, src_playlist, dst_playlist, delete_from_src=False, limit=args.limit, dry_run=args.dry_run) + elif args.command == 'move': src_playlist = get_playlist_id(args.src_playlist) dst_playlist = get_playlist_id(args.dst_playlist) @@ -230,6 +249,19 @@ def main(): delete_from_src=True, limit=args.limit, dry_run=args.dry_run) + elif args.command == 'add': + playlist = get_playlist_id(args.playlist) + pl_videos = {video['snippet']['resourceId']['videoId'] + for video in list_playlist(youtube, playlist)} + for video_id in args.video_ids: + if video_id in pl_videos: + print(f'Video {video_id} already in playlist {args.playlist}') + continue + video = get_video_info(youtube, video_id) + if video is None: + continue + add_video_to_playlist(youtube, video, playlist, args.dry_run) + if __name__ == '__main__': sys.exit(main())