예제 #1
0
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."))
예제 #2
0
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()
예제 #3
0
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))
예제 #4
0
 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")
     ]
예제 #5
0
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"))
예제 #6
0
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"))
예제 #7
0
    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
예제 #8
0
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)
예제 #9
0
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)
예제 #10
0
def print_channels() -> None:
    channels = core.get_channels()
    if not channels:
        print(_("No channels added, yet."))
    else:
        for channel in channels:
            print(channel.displayname)
예제 #11
0
    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
예제 #12
0
def cleanup() -> None:
    print(_("Cleaning up database..."))
    core.cleanup()
예제 #13
0
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()
예제 #14
0
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")
]
예제 #15
0
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)
예제 #16
0
def update_archive() -> None:
    print(_("Checking archived channels for recent"))
    core.update_archive()
예제 #17
0
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)
예제 #18
0
 def handler(signum: Any, frame: Any) -> None:
     core.close()
     print()
     print(_("Bye..."))
     exit(1)
예제 #19
0
def update_all() -> None:
    print(_("Updating channels..."))
    core.update_all()
예제 #20
0
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