twitch-dl/twitchdl/console.py

320 lines
9.9 KiB
Python
Raw Normal View History

2018-01-25 10:09:20 +00:00
# -*- coding: utf-8 -*-
2022-02-23 15:23:48 +00:00
import logging
2019-08-12 11:47:48 +00:00
import sys
2022-08-14 09:13:11 +00:00
import re
2019-08-12 11:47:48 +00:00
from argparse import ArgumentParser, ArgumentTypeError
2018-01-25 10:09:20 +00:00
from collections import namedtuple
from twitchdl.exceptions import ConsoleError
from twitchdl.output import print_err
2020-05-17 12:35:33 +00:00
from twitchdl.twitch import GQLError
from . import commands, __version__
2018-01-25 10:09:20 +00:00
Command = namedtuple("Command", ["name", "description", "arguments"])
CLIENT_WEBSITE = 'https://github.com/ihabunek/twitch-dl'
def time(value):
"""Parse a time string (hh:mm or hh:mm:ss) to number of seconds."""
parts = [int(p) for p in value.split(":")]
if not 2 <= len(parts) <= 3:
raise ArgumentTypeError()
hours = parts[0]
minutes = parts[1]
seconds = parts[2] if len(parts) > 2 else 0
if hours < 0 or not (0 <= minutes <= 59) or not (0 <= seconds <= 59):
raise ArgumentTypeError()
return hours * 3600 + minutes * 60 + seconds
def pos_integer(value):
try:
value = int(value)
except ValueError:
raise ArgumentTypeError("must be an integer")
if value < 1:
raise ArgumentTypeError("must be positive")
return value
2022-08-14 09:13:11 +00:00
def rate(value):
match = re.search(r"^([0-9]+)(k|m|)$", value, flags=re.IGNORECASE)
if not match:
raise ArgumentTypeError("must be an integer, followed by an optional 'k' or 'm'")
amount = int(match.group(1))
unit = match.group(2)
if unit == "k":
return amount * 1024
if unit == "m":
return amount * 1024 * 1024
return amount
2018-01-25 10:09:20 +00:00
COMMANDS = [
Command(
name="videos",
2022-02-23 20:54:56 +00:00
description="List videos for a channel.",
2018-01-25 10:09:20 +00:00
arguments=[
(["channel_name"], {
2022-02-23 20:54:56 +00:00
"help": "Name of the channel to list videos for.",
2018-01-25 10:09:20 +00:00
"type": str,
}),
2020-05-17 12:35:33 +00:00
(["-g", "--game"], {
"help": "Show videos of given game (can be given multiple times)",
"action": "append",
"type": str,
}),
(["-l", "--limit"], {
2022-02-23 20:54:56 +00:00
"help": "Number of videos to fetch. Defaults to 10.",
"type": pos_integer,
"default": 10,
}),
(["-a", "--all"], {
"help": "Fetch all videos, overrides --limit",
"action": "store_true",
"default": False,
}),
(["-s", "--sort"], {
2022-02-23 20:54:56 +00:00
"help": "Sorting order of videos. Defaults to `time`.",
"type": str,
"choices": ["views", "time"],
"default": "time",
}),
(["-t", "--type"], {
2022-02-23 20:54:56 +00:00
"help": "Broadcast type. Defaults to `archive`.",
"type": str,
"choices": ["archive", "highlight", "upload"],
"default": "archive",
}),
(["-j", "--json"], {
2022-02-23 20:54:56 +00:00
"help": "Show results as JSON. Ignores `--pager`.",
"action": "store_true",
"default": False,
}),
(["-p", "--pager"], {
2022-02-23 20:54:56 +00:00
"help": "Print videos in pages. Ignores `--limit`. Defaults to 10.",
"type": pos_integer,
"nargs": "?",
"const": 10,
}),
2022-08-18 08:03:40 +00:00
(["-c", "--compact"], {
"help": "Show videos in compact mode, one line per video",
"action": "store_true",
"default": False,
}),
2018-01-25 10:09:20 +00:00
],
),
2020-09-03 06:49:41 +00:00
Command(
name="clips",
2022-02-23 20:54:56 +00:00
description="List or download clips for a channel.",
2020-09-03 06:49:41 +00:00
arguments=[
(["channel_name"], {
2022-02-23 20:54:56 +00:00
"help": "Name of the channel to list clips for.",
2020-09-03 06:49:41 +00:00
"type": str,
}),
(["-l", "--limit"], {
"help": "Number of videos to fetch (default 10, max 100)",
"type": pos_integer,
2020-09-03 06:49:41 +00:00
"default": 10,
}),
(["-a", "--all"], {
"help": "Fetch all videos, overrides --limit",
"action": "store_true",
"default": False,
}),
2020-09-03 06:49:41 +00:00
(["-P", "--period"], {
2022-02-23 20:54:56 +00:00
"help": "Period from which to return clips. Defaults to `all_time`.",
2020-09-03 06:49:41 +00:00
"type": str,
"choices": ["last_day", "last_week", "last_month", "all_time"],
"default": "all_time",
}),
(["-j", "--json"], {
2022-02-23 20:54:56 +00:00
"help": "Show results as JSON. Ignores `--pager`.",
2020-09-03 06:49:41 +00:00
"action": "store_true",
"default": False,
}),
(["-p", "--pager"], {
"help": "Number of clips to show per page. Disabled by default.",
"type": pos_integer,
"nargs": "?",
"const": 10,
2020-11-10 08:21:37 +00:00
}),
(["-d", "--download"], {
"help": "Download all videos in given period (in source quality)",
"action": "store_true",
"default": False,
}),
2020-09-03 06:49:41 +00:00
],
),
2018-01-25 10:09:20 +00:00
Command(
name="download",
description="Download videos or clips.",
2018-01-25 10:09:20 +00:00
arguments=[
(["videos"], {
"help": "One or more video ID, clip slug or twitch URL to download.",
"type": str,
"nargs": "+",
2018-01-25 10:09:20 +00:00
}),
(["-w", "--max-workers"], {
"help": "Number of workers for downloading vods concurrently (default 5)",
2018-01-25 10:09:20 +00:00
"type": int,
"default": 5,
2018-01-25 10:09:20 +00:00
}),
(["-s", "--start"], {
"help": "Download video from this time (hh:mm or hh:mm:ss)",
"type": time,
"default": None,
}),
(["-e", "--end"], {
"help": "Download video up to this time (hh:mm or hh:mm:ss)",
"type": time,
"default": None,
}),
(["-f", "--format"], {
"help": "Video format to convert into, passed to ffmpeg as the "
2022-02-23 20:54:56 +00:00
"target file extension. Defaults to `mkv`.",
"type": str,
"default": "mkv",
}),
(["-k", "--keep"], {
"help": "Don't delete downloaded VODs and playlists after merging.",
"action": "store_true",
"default": False,
}),
(["-q", "--quality"], {
2020-09-29 06:26:40 +00:00
"help": "Video quality, e.g. 720p. Set to 'source' to get best quality.",
"type": str,
}),
2022-06-25 07:59:31 +00:00
(["-a", "--auth-token"], {
2022-06-25 02:15:17 +00:00
"help": "Authentication token, passed to Twitch to access subscriber only "
"VODs. Can be copied from the 'auth_token' cookie in any browser "
"logged in on Twitch.",
"type": str,
"default": None,
}),
(["--no-join"], {
"help": "Don't run ffmpeg to join the downloaded vods, implies --keep.",
"action": "store_true",
"default": False,
}),
2020-09-29 08:57:09 +00:00
(["--overwrite"], {
"help": "Overwrite the target file if it already exists without prompting.",
"action": "store_true",
"default": False,
2022-01-23 08:14:40 +00:00
}),
(["-o", "--output"], {
"help": "Output file name template. See docs for details.",
"type": str,
"default": "{date}_{id}_{channel_login}_{title_slug}.{format}"
2022-08-14 09:13:11 +00:00
}),
(["-r", "--rate-limit"], {
"help": "Limit the maximum download speed in bytes per second. "
"Use 'k' and 'm' suffixes for kbps and mbps.",
"type": rate,
}),
2018-01-25 10:09:20 +00:00
],
),
2021-01-14 20:38:56 +00:00
Command(
name="info",
2022-02-23 20:54:56 +00:00
description="Print information for a given Twitch URL, video ID or clip slug.",
2021-01-14 20:38:56 +00:00
arguments=[
2022-02-23 20:54:56 +00:00
(["video"], {
"help": "Video ID, clip slug, or URL",
2021-01-14 20:38:56 +00:00
"type": str,
}),
(["-j", "--json"], {
"help": "Show results as JSON",
"action": "store_true",
"default": False,
}),
],
),
Command(
name="env",
description="Print environment information for inclusion in bug reports.",
arguments=[],
2021-01-14 20:38:56 +00:00
)
2018-01-25 10:09:20 +00:00
]
COMMON_ARGUMENTS = [
(["--debug"], {
"help": "show debug log in console",
"action": 'store_true',
"default": False,
}),
(["--no-color"], {
"help": "disable ANSI colors in output",
"action": 'store_true',
"default": False,
})
]
def get_parser():
description = "A script for downloading videos from Twitch"
parser = ArgumentParser(prog='twitch-dl', description=description, epilog=CLIENT_WEBSITE)
parser.add_argument("--version", help="show version number", action='store_true')
2018-01-25 10:09:20 +00:00
subparsers = parser.add_subparsers(title="commands")
for command in COMMANDS:
2022-02-23 20:54:56 +00:00
sub = subparsers.add_parser(
command.name,
description=command.description,
epilog=CLIENT_WEBSITE
)
2018-01-25 10:09:20 +00:00
# Set the function to call to the function of same name in the "commands" package
sub.set_defaults(func=commands.__dict__.get(command.name))
for args, kwargs in command.arguments + COMMON_ARGUMENTS:
sub.add_argument(*args, **kwargs)
return parser
def main():
parser = get_parser()
args = parser.parse_args()
2022-02-27 11:23:22 +00:00
if "--debug" in sys.argv:
2022-02-23 15:23:48 +00:00
logging.basicConfig(level=logging.DEBUG)
if args.version:
print("twitch-dl v{}".format(__version__))
return
2018-01-25 10:09:20 +00:00
if "func" not in args:
parser.print_help()
return
try:
args.func(args)
except ConsoleError as e:
print_err(e)
2019-08-12 11:47:48 +00:00
sys.exit(1)
2021-05-18 12:00:50 +00:00
except KeyboardInterrupt:
print_err("\nOperation canceled")
2021-05-18 12:00:50 +00:00
sys.exit(1)
2020-05-17 12:35:33 +00:00
except GQLError as e:
print_err(e)
for err in e.errors:
print_err("*", err["message"])
sys.exit(1)