diff --git a/twitchdl/commands/download.py b/twitchdl/commands/download.py index 5c85320..598535f 100644 --- a/twitchdl/commands/download.py +++ b/twitchdl/commands/download.py @@ -282,6 +282,9 @@ def _download_video(video_id, args) -> None: raise ConsoleError("Aborted") args.overwrite = True + # Chapter select or manual offset + start, end = _determine_time_range(video_id, args) + print_out("Fetching access token...") access_token = twitch.get_access_token(video_id, auth_token=args.auth_token) @@ -298,7 +301,7 @@ def _download_video(video_id, args) -> None: base_uri = re.sub("/[^/]+$", "/", playlist_uri) target_dir = _crete_temp_dir(base_uri) - vod_paths = _get_vod_paths(playlist, args.start, args.end) + vod_paths = _get_vod_paths(playlist, start, end) # Save playlists for debugging purposes with open(path.join(target_dir, "playlists.m3u8"), "w") as f: @@ -341,3 +344,40 @@ def _download_video(video_id, args) -> None: shutil.rmtree(target_dir) print_out("\nDownloaded: {}".format(target)) + + +def _determine_time_range(video_id, args): + if args.start or args.end: + return args.start, args.end + + if args.chapter is not None: + print_out("Fetching chapters...") + chapters = twitch.get_video_chapters(video_id) + + if not chapters: + raise ConsoleError("This video has no chapters") + + if args.chapter == 0: + chapter = _choose_chapter_interactive(chapters) + else: + try: + chapter = chapters[args.chapter - 1] + except IndexError: + raise ConsoleError(f"Chapter {args.chapter} does not exist. This video has {len(chapters)} chapters.") + + print_out(f'Selected chapter: {chapter["description"]}') + start = chapter["positionMilliseconds"] // 1000 + duration = chapter["durationMilliseconds"] // 1000 + return start, start + duration + + return None, None + + +def _choose_chapter_interactive(chapters): + print_out("\nChapters:") + for index, chapter in enumerate(chapters): + duration = utils.format_time(chapter["durationMilliseconds"] // 1000) + print_out(f'{index + 1}) {chapter["description"]} ({duration})') + index = utils.read_int("Select a chapter", 1, len(chapters)) + chapter = chapters[index - 1] + return chapter diff --git a/twitchdl/console.py b/twitchdl/console.py index 02839fd..92ee67b 100644 --- a/twitchdl/console.py +++ b/twitchdl/console.py @@ -233,6 +233,13 @@ COMMANDS = [ "Use 'k' and 'm' suffixes for kbps and mbps.", "type": rate, }), + (["-c", "--chapter"], { + "help": "Download a single chapter of the video. Specify the chapter number or " + "use the flag without a number to display a chapter select prompt.", + "type": int, + "nargs": "?", + "const": 0 + }), ], ), Command( diff --git a/twitchdl/utils.py b/twitchdl/utils.py index ea3bfcb..771435a 100644 --- a/twitchdl/utils.py +++ b/twitchdl/utils.py @@ -53,13 +53,16 @@ def format_time(total_seconds, force_hours=False): return f"{minutes:02}:{seconds:02}" -def read_int(msg, min, max, default): - msg = msg + " [default {}]: ".format(default) +def read_int(msg, min, max, default=None): + if default: + msg = msg + f" [default {default}]" + + msg += ": " while True: try: val = input(msg) - if not val: + if default and not val: return default if min <= int(val) <= max: return int(val)