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)