diff --git a/twitchdl/cli.py b/twitchdl/cli.py index 0529efa..e198634 100644 --- a/twitchdl/cli.py +++ b/twitchdl/cli.py @@ -8,6 +8,7 @@ import click from twitchdl import __version__ from twitchdl.entities import DownloadOptions +from twitchdl.naming import DEFAULT_OUTPUT_TEMPLATE from twitchdl.twitch import ClipsPeriod, VideosSort, VideosType # Tweak the Click context @@ -229,7 +230,7 @@ def clips( "-o", "--output", help="Output file name template. See docs for details.", - default="{date}_{id}_{channel_login}_{title_slug}.{format}", + default=DEFAULT_OUTPUT_TEMPLATE, ) @click.option( "-q", diff --git a/twitchdl/commands/download.py b/twitchdl/commands/download.py index 75d5182..dce43c0 100644 --- a/twitchdl/commands/download.py +++ b/twitchdl/commands/download.py @@ -7,7 +7,7 @@ import subprocess import tempfile from os import path from pathlib import Path -from typing import Dict, List, Optional +from typing import List, Optional from urllib.parse import urlencode, urlparse import click @@ -18,6 +18,7 @@ from twitchdl.download import download_file from twitchdl.entities import DownloadOptions from twitchdl.exceptions import ConsoleError from twitchdl.http import download_all +from twitchdl.naming import clip_filename, video_filename from twitchdl.output import blue, bold, green, print_log, yellow from twitchdl.playlists import ( enumerate_vods, @@ -26,7 +27,7 @@ from twitchdl.playlists import ( parse_playlists, select_playlist, ) -from twitchdl.twitch import Chapter, Clip, ClipAccessToken, Video +from twitchdl.twitch import Chapter, ClipAccessToken, Video def download(ids: List[str], args: DownloadOptions): @@ -93,65 +94,6 @@ def _concat_vods(vod_paths: List[str], target: str): raise ConsoleError(f"Joining files failed: {result.stderr}") -def get_video_placeholders(video: Video, format: str) -> Dict[str, str]: - date, time = video["publishedAt"].split("T") - game = video["game"]["name"] if video["game"] else "Unknown" - - return { - "channel": video["creator"]["displayName"], - "channel_login": video["creator"]["login"], - "date": date, - "datetime": video["publishedAt"], - "format": format, - "game": game, - "game_slug": utils.slugify(game), - "id": video["id"], - "time": time, - "title": utils.titlify(video["title"]), - "title_slug": utils.slugify(video["title"]), - } - - -def _video_target_filename(video: Video, args: DownloadOptions): - subs = get_video_placeholders(video, args.format) - - try: - return args.output.format(**subs) - except KeyError as e: - supported = ", ".join(subs.keys()) - raise ConsoleError(f"Invalid key {e} used in --output. Supported keys are: {supported}") - - -def _clip_target_filename(clip: Clip, args: DownloadOptions): - date, time = clip["createdAt"].split("T") - game = clip["game"]["name"] if clip["game"] else "Unknown" - - url = clip["videoQualities"][0]["sourceURL"] - _, ext = path.splitext(url) - ext = ext.lstrip(".") - - subs = { - "channel": clip["broadcaster"]["displayName"], - "channel_login": clip["broadcaster"]["login"], - "date": date, - "datetime": clip["createdAt"], - "format": ext, - "game": game, - "game_slug": utils.slugify(game), - "id": clip["id"], - "slug": clip["slug"], - "time": time, - "title": utils.titlify(clip["title"]), - "title_slug": utils.slugify(clip["title"]), - } - - try: - return args.output.format(**subs) - except KeyError as e: - supported = ", ".join(subs.keys()) - raise ConsoleError(f"Invalid key {e} used in --output. Supported keys are: {supported}") - - def _crete_temp_dir(base_uri: str) -> str: """Create a temp dir to store downloads if it doesn't exist.""" path = urlparse(base_uri).path.lstrip("/") @@ -220,7 +162,7 @@ def _download_clip(slug: str, args: DownloadOptions) -> None: duration = utils.format_duration(clip["durationSeconds"]) click.echo(f"Found: {green(title)} by {yellow(user)}, playing {blue(game)} ({duration})") - target = _clip_target_filename(clip, args) + target = clip_filename(clip, args.output) click.echo(f"Target: {blue(target)}") if not args.overwrite and path.exists(target): @@ -252,7 +194,7 @@ def _download_video(video_id: str, args: DownloadOptions) -> None: click.echo(f"Found: {blue(video['title'])} by {yellow(video['creator']['displayName'])}") - target = _video_target_filename(video, args) + target = video_filename(video, args.format, args.output) click.echo(f"Output: {blue(target)}") if not args.overwrite and path.exists(target): diff --git a/twitchdl/commands/info.py b/twitchdl/commands/info.py index f4f3a15..6159baa 100644 --- a/twitchdl/commands/info.py +++ b/twitchdl/commands/info.py @@ -4,8 +4,8 @@ import click import m3u8 from twitchdl import twitch, utils -from twitchdl.commands.download import get_video_placeholders from twitchdl.exceptions import ConsoleError +from twitchdl.naming import video_placeholders from twitchdl.output import bold, print_clip, print_json, print_log, print_table, print_video from twitchdl.playlists import parse_playlists from twitchdl.twitch import Chapter, Clip, Video @@ -67,7 +67,7 @@ def video_info(video: Video, playlists: str, chapters: List[Chapter]): duration = utils.format_time(chapter["durationMilliseconds"] // 1000) click.echo(f'{start} {bold(chapter["description"])} ({duration})') - placeholders = get_video_placeholders(video, format="mkv") + placeholders = video_placeholders(video, format="mkv") placeholders = [[f"{{{k}}}", v] for k, v in placeholders.items()] click.echo("") print_table(["Placeholder", "Value"], placeholders) diff --git a/twitchdl/naming.py b/twitchdl/naming.py new file mode 100644 index 0000000..2f4d5dd --- /dev/null +++ b/twitchdl/naming.py @@ -0,0 +1,69 @@ +import os +from typing import Dict + +from twitchdl import utils +from twitchdl.entities import Clip, Video +from twitchdl.exceptions import ConsoleError + +DEFAULT_OUTPUT_TEMPLATE = "{date}_{id}_{channel_login}_{title_slug}.{format}" + + +def video_filename(video: Video, format: str, output: str) -> str: + subs = video_placeholders(video, format) + return _format(output, subs) + + +def video_placeholders(video: Video, format: str) -> Dict[str, str]: + date, time = video["publishedAt"].split("T") + game = video["game"]["name"] if video["game"] else "Unknown" + + return { + "channel": video["creator"]["displayName"], + "channel_login": video["creator"]["login"], + "date": date, + "datetime": video["publishedAt"], + "format": format, + "game": game, + "game_slug": utils.slugify(game), + "id": video["id"], + "time": time, + "title": utils.titlify(video["title"]), + "title_slug": utils.slugify(video["title"]), + } + + +def clip_filename(clip: Clip, output: str): + subs = clip_placeholders(clip) + return _format(output, subs) + + +def clip_placeholders(clip: Clip) -> Dict[str, str]: + date, time = clip["createdAt"].split("T") + game = clip["game"]["name"] if clip["game"] else "Unknown" + + url = clip["videoQualities"][0]["sourceURL"] + _, ext = os.path.splitext(url) + ext = ext.lstrip(".") + + return { + "channel": clip["broadcaster"]["displayName"], + "channel_login": clip["broadcaster"]["login"], + "date": date, + "datetime": clip["createdAt"], + "format": ext, + "game": game, + "game_slug": utils.slugify(game), + "id": clip["id"], + "slug": clip["slug"], + "time": time, + "title": utils.titlify(clip["title"]), + "title_slug": utils.slugify(clip["title"]), + } + + +def _format(output: str, subs: Dict[str, str]) -> str: + try: + return output.format(**subs) + except KeyError as e: + supported = ", ".join(subs.keys()) + raise ConsoleError(f"Invalid key {e} used in --output. Supported keys are: {supported}")