ytpdl module
Entry module containing the logic and CLI. The program currently works by polling the Youtube API at the set rate in a separate thread.
""" Entry module containing the logic and CLI. The program currently works by polling the Youtube API at the set rate in a separate thread. """ from __future__ import print_function, unicode_literals import itertools import pickle import sys import threading import time import schedule import argparse import youtube import youtube_dl parser = argparse.ArgumentParser( description="Automated youtube playlist downloader") saved_playlists = [] poll_rate = 0.2 * 60 # In seconds done = False ydl_opts = { 'ignore-errors': True } class StoreDictKeyPair(argparse.Action): """ Custom argparse action to store arguments as key-value pair dict. """ def __call__(self, parser, namespace, values, option_string=None): my_dict = {} for kv in values.split(","): k, v = kv.split("=") my_dict[k] = v setattr(namespace, self.dest, my_dict) def init(poll_rate): """ Reads or creates the file that contains the saved playlists and scheldules the Youtube API poll. """ global saved_playlists try: with open('playlists.dat', 'r+b') as playlists: saved_playlists = pickle.load(playlists) except IOError: open('playlists.dat', 'w').close() schedule.every(poll_rate).seconds.do(job) new_thread(schedule_thread) def animate(): """ Loading animation. """ for c in itertools.cycle(['|', '/', '-', '\\']): if done: break print('\rloading ' + c, end='') sys.stdout.flush() time.sleep(0.1) print('\rDone ') def stop_anim(): """ Loading animation. """ global done done = True time.sleep(0.3) def save_playlist(item): """ Saves playlist. """ saved_playlists.append(item) print(item['title'], "added to update queue") def download(item, title): """ Downloads playlist and sets youtubedls download location to 'playlist_title/video_title' . """ ydl_opts['outtmpl'] = '{}\%(title)s.%(ext)s'.format(title) youtube_dl.YoutubeDL(ydl_opts).download([item['id']]) def job(): """ Job to be executed. """ map(update, saved_playlists) def update(playlist): """ Update logic. Checks for new items and downloads from the last known item. """ new_playlist = youtube.get_playlist_items(playlist['id']) new_vid = new_playlist[0] old_vid = playlist.get('latest') if old_vid and old_vid['id'] != new_vid['id']: old_vid_pos = next((index for (index, item) in enumerate(new_playlist) if item['id'] == old_vid['id']), None) map(download, new_playlist[:old_vid_pos], [playlist['title']]) playlist['latest'] = new_vid def schedule_thread(): """ Schedules the update thead. """ while True: schedule.run_pending() time.sleep(1) def new_thread(target): """ Creates a new thread. """ threading.Thread(target=target).start() def get_args(): """ Sets up argparse. :return: parsed arguments """ group = parser.add_mutually_exclusive_group() group.add_argument('-lk', '--liked', action='store_true', help='Add your liked videos') group.add_argument('-mp', '--my-playlists', action='store_true', help='Choose from your playlists') group.add_argument('-id', '--id', help='Add playlist by id') group.add_argument('-ch', '--channel', help='Add channel uploads, by channel id') parser.add_argument('-d', '--download', action='store_true', help='Add and download current playlist retroactively') parser.add_argument('-o', '--options', dest='ydl_opts', action=StoreDictKeyPair, metavar='OPT1=VAL1,OPT2=OPT2...', help='youtube-dl options') parser.add_argument('-ls', '--list', action='store_true', help='List saved playlists') return parser.parse_args() def main(args): """ Main logic and CLI handler. """ if len(sys.argv) > 1: youtube.init() init(poll_rate) new_thread(animate) if args.my_playlists: playlists = youtube.get_my_playlists() stop_anim() for (i, playlist) in enumerate(playlists): print(i, ') ', playlist['title'], sep='') integer = input("\nEnter playlist number: ") playlist = playlists[integer] save_playlist(playlist) if args.download: download(playlist, playlist['title']) elif args.id: stop_anim() playlist = youtube.get_playlist(args.id) save_playlist(playlist) elif args.channel: stop_anim() playlist = youtube.get_uploads_playlist(args.channel) save_playlist(playlist) elif args.list: stop_anim() print([playlist['title'].encode('utf-8') for playlist in saved_playlists]) with open('update_list.dat', 'wb') as update_list_dat: pickle.dump(saved_playlists, update_list_dat) else: parser.print_help() if __name__ == '__main__': main(get_args())
Module variables
var done
var parser
var poll_rate
var saved_playlists
var ydl_opts
Functions
def animate(
)
Loading animation.
def animate(): """ Loading animation. """ for c in itertools.cycle(['|', '/', '-', '\\']): if done: break print('\rloading ' + c, end='') sys.stdout.flush() time.sleep(0.1) print('\rDone ')
def download(
item, title)
Downloads playlist and sets youtubedls download location to 'playlist_title/video_title' .
def download(item, title): """ Downloads playlist and sets youtubedls download location to 'playlist_title/video_title' . """ ydl_opts['outtmpl'] = '{}\%(title)s.%(ext)s'.format(title) youtube_dl.YoutubeDL(ydl_opts).download([item['id']])
def get_args(
)
Sets up argparse. :return: parsed arguments
def get_args(): """ Sets up argparse. :return: parsed arguments """ group = parser.add_mutually_exclusive_group() group.add_argument('-lk', '--liked', action='store_true', help='Add your liked videos') group.add_argument('-mp', '--my-playlists', action='store_true', help='Choose from your playlists') group.add_argument('-id', '--id', help='Add playlist by id') group.add_argument('-ch', '--channel', help='Add channel uploads, by channel id') parser.add_argument('-d', '--download', action='store_true', help='Add and download current playlist retroactively') parser.add_argument('-o', '--options', dest='ydl_opts', action=StoreDictKeyPair, metavar='OPT1=VAL1,OPT2=OPT2...', help='youtube-dl options') parser.add_argument('-ls', '--list', action='store_true', help='List saved playlists') return parser.parse_args()
def init(
poll_rate)
Reads or creates the file that contains the saved playlists and scheldules the Youtube API poll.
def init(poll_rate): """ Reads or creates the file that contains the saved playlists and scheldules the Youtube API poll. """ global saved_playlists try: with open('playlists.dat', 'r+b') as playlists: saved_playlists = pickle.load(playlists) except IOError: open('playlists.dat', 'w').close() schedule.every(poll_rate).seconds.do(job) new_thread(schedule_thread)
def job(
)
Job to be executed.
def job(): """ Job to be executed. """ map(update, saved_playlists)
def main(
args)
Main logic and CLI handler.
def main(args): """ Main logic and CLI handler. """ if len(sys.argv) > 1: youtube.init() init(poll_rate) new_thread(animate) if args.my_playlists: playlists = youtube.get_my_playlists() stop_anim() for (i, playlist) in enumerate(playlists): print(i, ') ', playlist['title'], sep='') integer = input("\nEnter playlist number: ") playlist = playlists[integer] save_playlist(playlist) if args.download: download(playlist, playlist['title']) elif args.id: stop_anim() playlist = youtube.get_playlist(args.id) save_playlist(playlist) elif args.channel: stop_anim() playlist = youtube.get_uploads_playlist(args.channel) save_playlist(playlist) elif args.list: stop_anim() print([playlist['title'].encode('utf-8') for playlist in saved_playlists]) with open('update_list.dat', 'wb') as update_list_dat: pickle.dump(saved_playlists, update_list_dat) else: parser.print_help()
def new_thread(
target)
Creates a new thread.
def new_thread(target): """ Creates a new thread. """ threading.Thread(target=target).start()
def save_playlist(
item)
Saves playlist.
def save_playlist(item): """ Saves playlist. """ saved_playlists.append(item) print(item['title'], "added to update queue")
def schedule_thread(
)
Schedules the update thead.
def schedule_thread(): """ Schedules the update thead. """ while True: schedule.run_pending() time.sleep(1)
def stop_anim(
)
Loading animation.
def stop_anim(): """ Loading animation. """ global done done = True time.sleep(0.3)
def update(
playlist)
Update logic. Checks for new items and downloads from the last known item.
def update(playlist): """ Update logic. Checks for new items and downloads from the last known item. """ new_playlist = youtube.get_playlist_items(playlist['id']) new_vid = new_playlist[0] old_vid = playlist.get('latest') if old_vid and old_vid['id'] != new_vid['id']: old_vid_pos = next((index for (index, item) in enumerate(new_playlist) if item['id'] == old_vid['id']), None) map(download, new_playlist[:old_vid_pos], [playlist['title']]) playlist['latest'] = new_vid
Classes
class StoreDictKeyPair
Custom argparse action to store arguments as key-value pair dict.
class StoreDictKeyPair(argparse.Action): """ Custom argparse action to store arguments as key-value pair dict. """ def __call__(self, parser, namespace, values, option_string=None): my_dict = {} for kv in values.split(","): k, v = kv.split("=") my_dict[k] = v setattr(namespace, self.dest, my_dict)
Ancestors (in MRO)
- StoreDictKeyPair
- argparse.Action
- argparse._AttributeHolder
- __builtin__.object
Methods
def __init__(
self, option_strings, dest, nargs=None, const=None, default=None, type=None, choices=None, required=False, help=None, metavar=None)
def __init__(self, option_strings, dest, nargs=None, const=None, default=None, type=None, choices=None, required=False, help=None, metavar=None): self.option_strings = option_strings self.dest = dest self.nargs = nargs self.const = const self.default = default self.type = type self.choices = choices self.required = required self.help = help self.metavar = metavar