def rename_channel(oldname: str, newname: str) -> None: try: core.rename_channel(oldname, newname) except ChannelDoesNotExistException: print(_("Error: The given channel does not exist.")) except DuplicateChannelException: print(_("Error: The new name already exists."))
def print_meta(video: Video) -> None: def print_separator(text: Optional[str] = None, fat: bool = False) -> None: columns = shutil.get_terminal_size().columns sep = "━" if fat else "─" if not text: print(sep * columns) else: sep_len = (columns - len(text) - 2) padding = sep_len // 2 printt(sep * padding) printt(" ", text, " ", bold=fat) printtln(sep * (padding + (sep_len % 2))) print_separator("Playing now", fat=True) printt(_("Title: ")) printtln(video.title, bold=True) printt(_("Channel: ")) printtln(video.channel.displayname, bold=True) description = video.description if DESCRIPTION_ENABLED and description is not None: columns = shutil.get_terminal_size().columns lines = description.splitlines() print_separator(_("Video description")) for line in lines: print(wrap.fill(line, width=columns)) print_separator(fat=True) print()
def add_channel(name: str, dldir: str, channel_url: str) -> None: try: core.add_channel(name, dldir, channel_url) except BadURLException: print(_("{!r} is not a valid YouTube URL").format(channel_url)) except DuplicateChannelException: print(_("You are already subscribed to {!r}").format(name)) except ChannelDoesNotExistException: print(_("The channel {!r} does not exist").format(channel_url))
def video_to_list(video: Video) -> List[str]: timestamp = unpack_optional(video.publish_date, lambda: 0) return [ str(video.id), datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M"), str(video.channel.displayname), str(video.title), core.get_youtube_video_url(video.yt_videoid), _("Yes") if video.watched else _("No") ]
def import_channels(file: TextIO) -> None: print(_("Importing...")) try: core.import_channels(file) subscriptions = _("Subscriptions") print() print(subscriptions) print("=" * len(subscriptions)) print_channels() except core.InvalidSubscriptionFileError: print(_("The given file is not valid YouTube export file"))
def download_video(video: Video, audio_only: bool = False) -> None: print( _('Downloading "{video.title}" by "{video.channel.displayname}"...'). format(video=video)) publisherID = video.channel.yt_channelid success = core.download_video(video=video, path=DOWNLOAD_PATH, audio_only=audio_only, publisherID=publisherID, videotitle=video.title) if not success: print(_("An Error occured while downloading the video"))
def run(self) -> None: alphabet = core.config.quickselect_alphabet tags = self._prefix_codes(alphabet, len(self.videos)) index = OrderedDict(zip(tags, self.videos)) while index: remaining_tags = list(index.keys()) remaining_videos = index.values() # Clear display and set cursor to (1,1). Allows scrolling back in some terminals terminal.clear_screen() print_videos(remaining_videos, quickselect_column=remaining_tags) tag, hook_triggered = self.command_line(remaining_tags, alphabet) video = index.get(tag) if video is None and not hook_triggered: break if video is not None: if self.action is Action.MARK_WATCHED: video.watched = True del index[tag] elif self.action is Action.DOWNLOAD_AUDIO: print() download_video(video, True) del index[tag] elif self.action is Action.DOWNLOAD_VIDEO: print() download_video(video, False) del index[tag] elif self.action is Action.SHOW_HELP: self.action = self.previous_action terminal.clear_screen() print( _(" <F1> Display this help text.\n" " <F2> Set action: Play video.\n" " <F3> Set action: Play audio.\n" " <F4> Set action: Mark as watched.\n" " <F5> Refresh video list.\n" " <F6> Set action: Download video.\n" " <F7> Set action: Download audio.\n" " <Enter> Accept first video.\n" "<CTRL+D> Exit.\n")) input(_("Press Enter to continue")) elif self.action is Action.REFRESH: self.action = self.previous_action terminal.clear_screen() update_all() self.videos = core.list_videos() self.run() break
def mark_watched(video_ids: List[int]) -> None: core.set_video_id_filter(video_ids) videos = core.list_videos() if not videos: print(_("No videos were marked as downloaded")) return for video in videos: video.watched = True print(_("Following videos were marked as downloaded:")) print() print_videos(videos)
class Action(Enum): def __init__(self, text: str, hotkey: str, color: int): self.text = text self.hotkey = hotkey self.color = color SHOW_HELP = (None, terminal.Keys.F1, None) PLAY_VIDEO = (_("Play video"), terminal.Keys.F2, COLORS.prompt_play_video) PLAY_AUDIO = (_("Play audio"), terminal.Keys.F3, COLORS.prompt_play_audio) MARK_WATCHED = (_("Mark as downloaded"), terminal.Keys.F4, COLORS.prompt_mark_watched) REFRESH = (None, terminal.Keys.F5, None) DOWNLOAD_AUDIO = (_("Download audio"), terminal.Keys.F7, COLORS.prompt_download_audio) DOWNLOAD_VIDEO = (_("Download video"), terminal.Keys.F6, COLORS.prompt_download_video)
def print_channels() -> None: channels = core.get_channels() if not channels: print(_("No channels added, yet.")) else: for channel in channels: print(channel.displayname)
def command_line(self, tags: List[str], alphabet: Set[str]) -> Tuple[str, bool]: def print_prompt(): prompt_format = "{prompt_text} > " prompt = prompt_format.format(prompt_text=self.get_prompt_text()) printt(prompt, foreground=self.get_prompt_color(), bold=True, replace=True) print() print(_("Type a valid TAG. <F1> for help.")) print_prompt() tag = "" hook_triggered = False while tag not in tags: char = terminal.getkey() if char in self.hooks: hook_triggered = True if self.hooks[char](): break if char in {"\x04", "\x03"}: # Ctrl+d, Ctrl+d break if char in {"\r", ""}: tag = tags[0] break if char == "\x7f": # DEL tag = tag[:-1] elif char in alphabet: tag += char print_prompt() printt(tag) print() return tag, hook_triggered
def cleanup() -> None: print(_("Cleaning up database...")) core.cleanup()
def get_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description=_( "Command Line tool to monitor your favorite YouTube channels" "The --list, --watch, --download, --mark-watched options can be " "combined with filter options --channel-filter, --include-watched, --since," " --to")) parser.add_argument( "-a", "--add-channel", help= _("add a new channel. NAME is the name displayed by ytcc. DLDIR is the download directory under the folder defined in ytcc.conf URL is the " "url of the channel's front page or the URL of any video published " "by the channel"), nargs=3, metavar=("NAME", "DLDIR", "URL")) parser.add_argument("-c", "--list-channels", help=_("print a list of all subscribed channels"), action="store_true") parser.add_argument( "-r", "--delete-channel", help=_("unsubscribe from the channel identified by 'NAME'"), metavar="NAME", nargs='+', type=str) parser.add_argument("--rename", help=_("rename channel 'OLDNAME' to 'NEWNAME'"), metavar=("OLDNAME", "NEWNAME"), nargs=2, type=str) parser.add_argument("-u", "--update", help=_("update the video list"), action="store_true") parser.add_argument( "-l", "--list", help=_("print a list of videos that match the criteria given by the " "filter options"), action="store_true") parser.add_argument( "-d", "--download", help=_( "download the videos identified by 'ID'. The videos are saved " "in $HOME/Downloads by default. Omitting the ID will download " "all videos that match the criteria given by the filter options"), nargs="*", type=int, metavar="ID") parser.add_argument( "-m", "--mark-downloaded", help= _("mark videos identified by ID as downloaded. Omitting the ID will mark" " all videos that match the criteria given by the filter options as " "downloaded"), nargs='*', type=int, metavar="ID") parser.add_argument( "-f", "--channel-filter", help=_( "plays, lists, marks, downloads only videos from channels defined " "in the filter"), nargs='+', type=str, metavar="NAME") parser.add_argument( "-n", "--include-watched", help=_("include already watched videos to filter rules"), action="store_true") parser.add_argument( "-s", "--since", help=_("includes only videos published after the given date"), metavar="YYYY-MM-DD", type=is_date) parser.add_argument( "-t", "--to", help=_("includes only videos published before the given date"), metavar="YYYY-MM-DD", type=is_date) parser.add_argument("-p", "--path", help=_("set the download path to PATH"), metavar="PATH", type=is_directory) parser.add_argument( "-g", "--no-description", help=_("do not print the video description before playing the video"), action="store_true") parser.add_argument( "-o", "--columns", help=_( "specifies which columns will be printed when listing videos. COL " "can be any of {columns}. All columns can be enabled with " "'all'").format(columns=libs.cli.TABLE_HEADER), nargs='+', metavar="COL", choices=["all", *libs.cli.TABLE_HEADER]) parser.add_argument( "--import-from", help=_("import YouTube channels from YouTube's subscription export " "(available at https://www.youtube.com/subscription_manager)"), metavar="PATH", type=argparse.FileType("r")) parser.add_argument("--export-to", help=_("export YouTube channels in opml format"), metavar="PATH", type=argparse.FileType("wb")) parser.add_argument( "--cleanup", help=_( "removes old videos from the database and shrinks the size of the " "database file"), action="store_true") parser.add_argument("-v", "--version", help=_("output version information and exit"), action="store_true") parser.add_argument("--bug-report-info", help=_("print info to include in a bug report"), action="store_true") return parser.parse_args()
from libs.terminal import printt, printtln from libs.utils import unpack_optional try: core = core.YTCM() # pylint: disable=C0103 COLUMN_FILTER = [ core.config.table_format.getboolean("ID"), core.config.table_format.getboolean("Date"), core.config.table_format.getboolean("Channel"), core.config.table_format.getboolean("Title"), core.config.table_format.getboolean("URL"), core.config.table_format.getboolean("Downloaded") ] COLORS = core.config.color except BadConfigException: print(_("The configuration file has errors!")) exit(1) INTERACTIVE_ENABLED = True DESCRIPTION_ENABLED = True NO_VIDEO = False DOWNLOAD_PATH = "" HEADER_ENABLED = True TABLE_HEADER = [ _("ID"), _("Date"), _("Channel"), _("Title"), _("URL"), _("Downloaded") ]
def list_videos() -> None: videos = core.list_videos() if not videos: print(_("No videos to list. No videos match the given criteria.")) else: print_videos(videos)
def update_archive() -> None: print(_("Checking archived channels for recent")) core.update_archive()
def is_date(string: str) -> datetime: try: return datetime.strptime(string, "%Y-%m-%d") except ValueError: msg = _("{!r} is not a valid date").format(string) raise argparse.ArgumentTypeError(msg)
def handler(signum: Any, frame: Any) -> None: core.close() print() print(_("Bye...")) exit(1)
def update_all() -> None: print(_("Updating channels...")) core.update_all()
def is_directory(string: str) -> str: if not os.path.isdir(string): msg = _("{!r} is not a directory").format(string) raise argparse.ArgumentTypeError(msg) return string