Add copy playlist command
This commit is contained in:
parent
439e160cdd
commit
b71bfb0674
76
main.py
76
main.py
@ -22,6 +22,7 @@ def _truncate_title(title: str, length: int = 30) -> str:
|
|||||||
return title
|
return title
|
||||||
return title[:length-3] + (title[length:] and '...')
|
return title[:length-3] + (title[length:] and '...')
|
||||||
|
|
||||||
|
|
||||||
def get_yt_creds():
|
def get_yt_creds():
|
||||||
""" Get YouTube API credentials """
|
""" Get YouTube API credentials """
|
||||||
creds = None
|
creds = None
|
||||||
@ -70,7 +71,8 @@ def get_playlist_id(playlist_name: str) -> str:
|
|||||||
def get_playlist_name(playlist_id: str) -> str:
|
def get_playlist_name(playlist_id: str) -> str:
|
||||||
if not _playlists:
|
if not _playlists:
|
||||||
read_playlists_file()
|
read_playlists_file()
|
||||||
return next((name for name, id in _playlists.items() if id == playlist_id), playlist_id)
|
return next((name for name, plid in _playlists.items()
|
||||||
|
if plid == playlist_id), playlist_id)
|
||||||
|
|
||||||
|
|
||||||
def get_videos(yt_api, playlist_id):
|
def get_videos(yt_api, playlist_id):
|
||||||
@ -100,7 +102,8 @@ def get_videos(yt_api, playlist_id):
|
|||||||
return videos
|
return videos
|
||||||
|
|
||||||
|
|
||||||
def add_video_to_playlist(yt_api, video, playlist_id, dry_run: bool = False) -> bool:
|
def add_video_to_playlist(yt_api, video, playlist_id,
|
||||||
|
dry_run: bool = False) -> bool:
|
||||||
playlist_name = get_playlist_name(playlist_id)
|
playlist_name = get_playlist_name(playlist_id)
|
||||||
video_id = video['snippet']['resourceId']['videoId']
|
video_id = video['snippet']['resourceId']['videoId']
|
||||||
video_title = _truncate_title(video['snippet']['title'])
|
video_title = _truncate_title(video['snippet']['title'])
|
||||||
@ -127,25 +130,34 @@ def add_video_to_playlist(yt_api, video, playlist_id, dry_run: bool = False) ->
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def remove_video_from_playlist(yt_api, video, playlist_id, dry_run: bool = False) -> bool:
|
def remove_video_from_playlist(yt_api, video, playlist_id,
|
||||||
|
dry_run: bool = False) -> bool:
|
||||||
playlist_name = get_playlist_name(playlist_id)
|
playlist_name = get_playlist_name(playlist_id)
|
||||||
video_id = video['snippet']['resourceId']['videoId']
|
video_id = video['snippet']['resourceId']['videoId']
|
||||||
video_title = _truncate_title(video['snippet']['title'])
|
video_title = _truncate_title(video['snippet']['title'])
|
||||||
try:
|
try:
|
||||||
if dry_run:
|
if dry_run:
|
||||||
print(f"Would remove video '{video_title}' [{video_id}] from playlist {playlist_name}")
|
print(f"Would remove video '{video_title}' [{video_id}]"
|
||||||
|
f" from playlist {playlist_name}")
|
||||||
return True
|
return True
|
||||||
yt_api.playlistItems().delete(
|
yt_api.playlistItems().delete(
|
||||||
id=video['id']
|
id=video['id']
|
||||||
).execute()
|
).execute()
|
||||||
print(f"Removed video '{video_title}' [{video_id}] from playlist {playlist_name}")
|
print(f"Removed video '{video_title}' [{video_id}]"
|
||||||
|
f" from playlist {playlist_name}")
|
||||||
return True
|
return True
|
||||||
except HttpError as e:
|
except HttpError as e:
|
||||||
print(f"Error removing video '{video_title}' [{video_id}] from playlist {playlist_name}: {e}")
|
print(f"Error removing video '{video_title}' [{video_id}]"
|
||||||
|
f" from playlist {playlist_name}: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def move_all_videos(yt_api, src_playlist: str, dst_playlist: str, limit: int = -1, dry_run: bool = False):
|
def copy_playlist_items(yt_api,
|
||||||
|
src_playlist: str,
|
||||||
|
dst_playlist: str,
|
||||||
|
delete_from_src: bool = False,
|
||||||
|
limit: int = -1,
|
||||||
|
dry_run: bool = False):
|
||||||
if limit < 0:
|
if limit < 0:
|
||||||
limit = len(src_playlist)
|
limit = len(src_playlist)
|
||||||
|
|
||||||
@ -159,37 +171,53 @@ def move_all_videos(yt_api, src_playlist: str, dst_playlist: str, limit: int = -
|
|||||||
video_id = src_video['snippet']['resourceId']['videoId']
|
video_id = src_video['snippet']['resourceId']['videoId']
|
||||||
if video_id not in dst_video_map:
|
if video_id not in dst_video_map:
|
||||||
add_video_to_playlist(yt_api, src_video, dst_playlist, dry_run)
|
add_video_to_playlist(yt_api, src_video, dst_playlist, dry_run)
|
||||||
remove_video_from_playlist(yt_api, src_video, src_playlist, dry_run)
|
if delete_from_src:
|
||||||
|
remove_video_from_playlist(yt_api, src_video, src_playlist, dry_run)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('src_playlist', help='Source playlist ID')
|
parser.add_argument('-n', '--dry-run', action='store_true',
|
||||||
parser.add_argument('dst_playlist', help='Destination playlist ID')
|
help='Dry run, do not send changes to YoutubeAPI')
|
||||||
parser.add_argument(
|
subparsers = parser.add_subparsers(title='commands', dest='command')
|
||||||
'-l', '--limit', help='Limit number of videos to move', type=int, default=-1
|
|
||||||
)
|
parser_move = subparsers.add_parser('move', help='Move videos from one playlist to another')
|
||||||
parser.add_argument(
|
parser_move.add_argument('src_playlist', help='Source playlist ID')
|
||||||
'-n', '--dry-run', action='store_true',
|
parser_move.add_argument('dst_playlist', help='Destination playlist ID')
|
||||||
help='Dry run, do not send changes to YoutubeAPI'
|
parser_move.add_argument('-l', '--limit', type=int, default=-1,
|
||||||
)
|
help='Limit number of videos to process')
|
||||||
|
|
||||||
|
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('-l', '--limit', type=int, default=-1,
|
||||||
|
help='Limit number of videos to process')
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
# Disable OAuthlib's HTTPS verification when running locally.
|
# Disable OAuthlib's HTTPS verification when running locally.
|
||||||
# *DO NOT* leave this option enabled in production.
|
# *DO NOT* leave this option enabled in production.
|
||||||
os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"
|
os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"
|
||||||
|
|
||||||
src_playlist = get_playlist_id(args.src_playlist)
|
|
||||||
dst_playlist = get_playlist_id(args.dst_playlist)
|
|
||||||
|
|
||||||
api_service_name = "youtube"
|
api_service_name = "youtube"
|
||||||
api_version = "v3"
|
api_version = "v3"
|
||||||
|
|
||||||
creds = get_yt_creds()
|
creds = get_yt_creds()
|
||||||
youtube = build(api_service_name, api_version, credentials=creds)
|
youtube = build(api_service_name, api_version, credentials=creds)
|
||||||
|
|
||||||
move_all_videos(youtube, src_playlist, dst_playlist,
|
if args.command is None:
|
||||||
limit=args.limit, dry_run=args.dry_run)
|
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)
|
||||||
|
copy_playlist_items(youtube, src_playlist, dst_playlist,
|
||||||
|
delete_from_src=True,
|
||||||
|
limit=args.limit, dry_run=args.dry_run)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user