Esempio n. 1
0
def print_meta(video: MappedVideo, stream: TextIO = sys.stdout) -> None:
    with StdOutOverride(stream):

        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(_("In playlist(s): "))
        printtln(", ".join(v.name for v in video.playlists), bold=True)

        description = video.description
        if 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()
Esempio n. 2
0
    def run(self) -> None:
        selectable = VideoSelection(config.tui.alphabet, self.videos)
        printer = TablePrinter()
        printer.filter = ["key", *config.ytcc.video_attrs]

        while True:
            remaining_tags = list(selectable.keys())

            # Clear display and set cursor to (1,1). Allows scrolling back in some terminals
            terminal.clear_screen()
            printer.print(selectable)

            tag, hook_triggered = self.command_line(remaining_tags)
            video = selectable.get(tag)

            if video is None and not hook_triggered:
                break

            if video is not None:
                if self.action is Action.MARK_WATCHED:
                    self.core.mark_watched(video)
                    del selectable[tag]
                elif self.action is Action.DOWNLOAD_AUDIO:
                    print()
                    self.download_video(video, True)
                    del selectable[tag]
                elif self.action is Action.DOWNLOAD_VIDEO:
                    print()
                    self.download_video(video, False)
                    del selectable[tag]
                elif self.action is Action.PLAY_AUDIO:
                    print()
                    self.play(video, True)
                    del selectable[tag]
                elif self.action is Action.PLAY_VIDEO:
                    print()
                    self.play(video, False)
                    del selectable[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()
                self.core.update()
                self.videos = list(self.core.list_videos())
                self.run()
                break
Esempio n. 3
0
File: cli.py Progetto: gruberma/ytcc
def play(video: Video, audio_only: bool) -> None:
    print(_('Playing "{video.title}" by "{video.channel.displayname}"...').format(video=video))
    maybe_print_description(video.description)
    if not ytcc_core.play_video(video, audio_only):
        print()
        print(_("WARNING: The video player terminated with an error.\n"
                "         The last video is not marked as watched!"))
        print()
Esempio n. 4
0
File: cli.py Progetto: gruberma/ytcc
def add_channel(name: str, channel_url: str) -> None:
    try:
        ytcc_core.add_channel(name, 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))
Esempio n. 5
0
File: cli.py Progetto: gruberma/ytcc
 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),
         ytcc_core.get_youtube_video_url(video.yt_videoid),
         _("Yes") if video.watched else _("No")
     ]
Esempio n. 6
0
File: cli.py Progetto: gruberma/ytcc
def import_channels(file: TextIO) -> None:
    print(_("Importing..."))
    try:
        ytcc_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"))
Esempio n. 7
0
 def download_video(self,
                    video: MappedVideo,
                    audio_only: bool = False) -> None:
     print(
         _('Downloading "{video.title}" in playlist(s) "{playlists}"...').
         format(video=video,
                playlists=", ".join(v.name for v in video.playlists)))
     if self.core.download_video(video=video, audio_only=audio_only):
         self.core.mark_watched(video)
     else:
         print(_("An Error occured while downloading the video"))
Esempio n. 8
0
File: cli.py Progetto: gruberma/ytcc
def mark_watched(video_ids: List[int]) -> None:
    ytcc_core.set_video_id_filter(video_ids)
    videos = ytcc_core.list_videos()
    if not videos:
        print(_("No videos were marked as watched"))
        return

    for video in videos:
        video.watched = True

    print(_("Following videos were marked as watched:"))
    print()
    print_videos(videos)
Esempio n. 9
0
File: cli.py Progetto: gruberma/ytcc
    def get_prompt_text(self) -> str:
        if self.action == Action.MARK_WATCHED:
            return _("Mark as watched")
        if self.action == Action.DOWNLOAD_AUDIO:
            return _("Download audio")
        if self.action == Action.DOWNLOAD_VIDEO:
            return _("Download video")
        if self.action == Action.PLAY_AUDIO:
            return _("Play audio")
        if self.action == Action.PLAY_VIDEO:
            return _("Play video")

        return ""
Esempio n. 10
0
File: cli.py Progetto: gruberma/ytcc
def print_channels() -> None:
    channels = ytcc_core.get_channels()
    if not channels:
        print(_("No channels added, yet."))
    else:
        for channel in channels:
            print(channel.displayname)
Esempio n. 11
0
File: cli.py Progetto: gruberma/ytcc
    def command_line(self, tags: List[str], alphabet: Set[str]) -> Tuple[str, bool]:
        prompt_format = "{prompt_text} >"
        prompt = prompt_format.format(prompt_text=self.get_prompt_text())
        print()
        print(_("Type a valid TAG. <F1> for help."))
        print(prompt, end=" ", flush=True)

        tag = ""
        hook_triggered = False
        while tag not in tags:
            char = getkey.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

            prompt = prompt_format.format(prompt_text=self.get_prompt_text())
            # Clear line, reset cursor, print prompt and tag
            print(f"\033[2K\r{prompt}", tag, end="", flush=True)

        print()
        return tag, hook_triggered
Esempio n. 12
0
File: cli.py Progetto: gruberma/ytcc
def watch(video_ids: Iterable[int]) -> None:
    ytcc_core.set_video_id_filter(video_ids)
    videos = ytcc_core.list_videos()

    if not videos:
        print(_("No videos to watch. No videos match the given criteria."))
    elif not INTERACTIVE_ENABLED:
        for video in videos:
            play(video, NO_VIDEO)
    else:
        Interactive(videos).run()
Esempio n. 13
0
File: cli.py Progetto: gruberma/ytcc
def maybe_print_description(description: Optional[str]) -> None:
    if DESCRIPTION_ENABLED and description is not None:
        columns = shutil.get_terminal_size().columns
        delimiter = "=" * columns
        lines = description.splitlines()

        print()
        print(_("Video description:"))
        print(delimiter)

        for line in lines:
            print(wrap.fill(line, width=columns))

        print(delimiter, end="\n\n")
Esempio n. 14
0
class Action(Enum):
    def __init__(self, text: str, hotkey: str, color: Callable[[], int]):
        self.text = text
        self.hotkey = hotkey
        self.color = color

    @staticmethod
    def from_config():
        return Action.__dict__.get(config.tui.default_action.value.upper(),
                                   Action.PLAY_VIDEO)

    SHOW_HELP = (None, FKeys.F1, None)
    PLAY_VIDEO = (_("Play video"), FKeys.F2,
                  lambda: config.theme.prompt_play_video)
    PLAY_AUDIO = (_("Play audio"), FKeys.F3,
                  lambda: config.theme.prompt_play_audio)
    MARK_WATCHED = (_("Mark as watched"), FKeys.F4,
                    lambda: config.theme.prompt_mark_watched)
    REFRESH = (None, FKeys.F5, None)
    DOWNLOAD_AUDIO = (_("Download audio"), FKeys.F7,
                      lambda: config.theme.prompt_download_audio)
    DOWNLOAD_VIDEO = (_("Download video"), FKeys.F6,
                      lambda: config.theme.prompt_download_video)
Esempio n. 15
0
    def command_line(self, tags: List[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: Optional[str] = terminal.getkey()

            if char in self.hooks:
                hook_triggered = True
                if self.hooks[char]():
                    break

                char = None

            if char in {"\x04", "\x03"}:  # Ctrl+d, Ctrl+d
                hook_triggered = False
                break

            if char in {"\r", ""} and tags:
                tag = tags[0]
                break

            if char == FKeys.DEL:
                tag = tag[:-1]
            elif char and char in config.tui.alphabet:
                tag += char

            print_prompt()
            printt(tag)

        print()
        return tag, hook_triggered
Esempio n. 16
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
Esempio n. 17
0
File: cli.py Progetto: gruberma/ytcc
from ytcc import core, arguments, getkey, _
from ytcc.database import Video
from ytcc.exceptions import BadConfigException, ChannelDoesNotExistException, \
    DuplicateChannelException, BadURLException
from ytcc.utils import unpack_optional

try:
    ytcc_core = core.Ytcc()  # pylint: disable=C0103
    COLUMN_FILTER = [ytcc_core.config.table_format.getboolean("ID"),
                     ytcc_core.config.table_format.getboolean("Date"),
                     ytcc_core.config.table_format.getboolean("Channel"),
                     ytcc_core.config.table_format.getboolean("Title"),
                     ytcc_core.config.table_format.getboolean("URL"),
                     ytcc_core.config.table_format.getboolean("Watched")]
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"), _("Watched")]
_REGISTERED_OPTIONS: Dict[str, "Option"] = dict()


def register_option(option_name, exit=False, is_action=True):  # pylint: disable=redefined-builtin
    def decorator(func):
        nargs = len(inspect.signature(func).parameters)
        _REGISTERED_OPTIONS[option_name] = Option(
Esempio n. 18
0
File: cli.py Progetto: gruberma/ytcc
 def handler(signum: Any, frame: Any) -> None:
     ytcc_core.close()
     print()
     print(_("Bye..."))
     exit(1)
Esempio n. 19
0
File: cli.py Progetto: gruberma/ytcc
def cleanup() -> None:
    print(_("Cleaning up database..."))
    ytcc_core.cleanup()
Esempio n. 20
0
File: cli.py Progetto: gruberma/ytcc
    def run(self) -> None:
        alphabet = ytcc_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
            print("\033[2J\033[1;1H", end="")
            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 == Action.MARK_WATCHED:
                    video.watched = True
                    del index[tag]
                elif self.action == Action.DOWNLOAD_AUDIO:
                    print()
                    download_video(video, True)
                    del index[tag]
                elif self.action == Action.DOWNLOAD_VIDEO:
                    print()
                    download_video(video, False)
                    del index[tag]
                elif self.action == Action.PLAY_AUDIO:
                    print()
                    play(video, True)
                    del index[tag]
                elif self.action == Action.PLAY_VIDEO:
                    print()
                    play(video, False)
                    del index[tag]
            elif self.action == Action.SHOW_HELP:
                self.action = self.previous_action
                print("\033[2J\033[1;1H", end="")
                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 == Action.REFRESH:
                self.action = self.previous_action
                print("\033[2J\033[1;1H", end="")
                update_all()
                self.videos = ytcc_core.list_videos()
                self.run()
                break
Esempio n. 21
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)
Esempio n. 22
0
File: cli.py Progetto: gruberma/ytcc
def list_videos() -> None:
    videos = ytcc_core.list_videos()
    if not videos:
        print(_("No videos to list. No videos match the given criteria."))
    else:
        print_videos(videos)
Esempio n. 23
0
File: cli.py Progetto: gruberma/ytcc
def update_all() -> None:
    print(_("Updating channels..."))
    ytcc_core.update_all()
Esempio n. 24
0
def get_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description=_("ytcc is a commandline YouTube client that keeps track of your favorite "
                      "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. URL is the "
                               "url of the channel's front page or the URL of any video published "
                               "by the channel"),
                        nargs=2,
                        metavar=("NAME", "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 'ID'"),
                        metavar="ID",
                        nargs='+',
                        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("-w", "--watch",
                        help=_("play the videos identified by 'ID'. Omitting the ID will play all "
                               "videos specified by the filter options"),
                        nargs='*',
                        type=int,
                        metavar="ID")

    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-watched",
                        help=_("mark videos identified by ID as watched. Omitting the ID will mark"
                               " all videos that match the criteria given by the filter options as "
                               "watched"),
                        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=ytcc.cli.TABLE_HEADER),
                        nargs='+',
                        metavar="COL",
                        choices=["all", *ytcc.cli.TABLE_HEADER])

    parser.add_argument("--no-header",
                        help=_("do not print the header of the table when listing videos"),
                        action="store_true")

    parser.add_argument("-x", "--no-video",
                        help=_("plays or downloads only the audio part of a video"),
                        action="store_true")

    parser.add_argument("-y", "--disable-interactive",
                        help=_("disables the interactive mode"),
                        action="store_true")

    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("--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()
Esempio n. 25
0
File: cli.py Progetto: gruberma/ytcc
def download_video(video: Video, audio_only: bool = False) -> None:
    print(_('Downloading "{video.title}" by "{video.channel.displayname}"...').format(video=video))
    success = ytcc_core.download_video(video=video, path=DOWNLOAD_PATH, audio_only=audio_only)
    if not success:
        print(_("An Error occured while downloading the video"))