mirror of
https://github.com/ihabunek/twitch-dl
synced 2024-08-30 18:32:25 +00:00
parent
96f13e9cf7
commit
07f3a2fa48
@ -50,6 +50,13 @@ twitch-dl download 221837124
|
||||
twitch-dl download https://www.twitch.tv/videos/221837124
|
||||
```
|
||||
|
||||
Download a clip by slug or URL:
|
||||
|
||||
```
|
||||
twitch-dl download VenomousTameWormHumbleLife
|
||||
twitch-dl download https://www.twitch.tv/bananasaurus_rex/clip/VenomousTameWormHumbleLife
|
||||
```
|
||||
|
||||
Man page
|
||||
--------
|
||||
|
||||
|
@ -24,13 +24,13 @@ List recent videos from bananasaurus\_rex's channel:
|
||||
twitch-dl videos bananasaurus_rex
|
||||
```
|
||||
|
||||
Download by URL:
|
||||
Download video by URL:
|
||||
|
||||
```
|
||||
twitch-dl download https://www.twitch.tv/videos/377220226
|
||||
```
|
||||
|
||||
Download by ID:
|
||||
Download video by ID:
|
||||
|
||||
```
|
||||
twitch-dl download 377220226
|
||||
@ -48,6 +48,21 @@ Partial download by setting start and end time (hh:mm or hh:mm:ss):
|
||||
twitch-dl download --start=00:10 --end=02:15 377220226
|
||||
```
|
||||
|
||||
Download clip by URL:
|
||||
|
||||
```
|
||||
twitch-dl download https://www.twitch.tv/bananasaurus_rex/clip/VenomousTameWormHumbleLife
|
||||
```
|
||||
|
||||
Download clip by slug:
|
||||
|
||||
```
|
||||
twitch-dl download VenomousTameWormHumbleLife
|
||||
```
|
||||
|
||||
Note that clips are a single download, and don't benefit from the paralelism
|
||||
used when downloading videos.
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
youtube-dl(1)
|
||||
|
@ -7,10 +7,11 @@ import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from twitchdl import twitch, utils
|
||||
from twitchdl.download import download_files
|
||||
from twitchdl.download import download_file, download_files
|
||||
from twitchdl.exceptions import ConsoleError
|
||||
from twitchdl.output import print_out, print_video
|
||||
|
||||
@ -83,18 +84,6 @@ def _video_target_filename(video, format):
|
||||
return name + "." + format
|
||||
|
||||
|
||||
def _parse_video_id(video_id):
|
||||
"""This can be either a integer ID or an URL to the video on twitch."""
|
||||
if re.search(r"^\d+$", video_id):
|
||||
return int(video_id)
|
||||
|
||||
match = re.search(r"^https://www.twitch.tv/videos/(\d+)(\?.+)?$", video_id)
|
||||
if match:
|
||||
return int(match.group(1))
|
||||
|
||||
raise ConsoleError("Invalid video ID given, expected integer ID or Twitch URL")
|
||||
|
||||
|
||||
def _get_files(playlist, start, end):
|
||||
"""Extract files for download from playlist."""
|
||||
vod_start = 0
|
||||
@ -120,9 +109,69 @@ def _crete_temp_dir(base_uri):
|
||||
return directory
|
||||
|
||||
|
||||
def download(video_id, max_workers, format='mkv', start=None, end=None, keep=False, **kwargs):
|
||||
video_id = _parse_video_id(video_id)
|
||||
VIDEO_PATTERNS = [
|
||||
r"^(?P<id>\d+)?$",
|
||||
r"^https://www.twitch.tv/videos/(?P<id>\d+)(\?.+)?$",
|
||||
]
|
||||
|
||||
CLIP_PATTERNS = [
|
||||
r"^(?P<slug>[A-Za-z]+)$",
|
||||
r"^https://www.twitch.tv/\w+/clip/(?P<slug>[A-Za-z]+)(\?.+)?$",
|
||||
r"^https://clips.twitch.tv/(?P<slug>[A-Za-z]+)(\?.+)?$",
|
||||
]
|
||||
|
||||
|
||||
def download(video, **kwargs):
|
||||
for pattern in CLIP_PATTERNS:
|
||||
match = re.match(pattern, video)
|
||||
if match:
|
||||
clip_slug = match.group('slug')
|
||||
return _download_clip(clip_slug, **kwargs)
|
||||
|
||||
for pattern in VIDEO_PATTERNS:
|
||||
match = re.match(pattern, video)
|
||||
if match:
|
||||
video_id = match.group('id')
|
||||
return _download_video(video_id, **kwargs)
|
||||
|
||||
raise ConsoleError("Invalid video: {}".format(video_id))
|
||||
|
||||
|
||||
def _download_clip(slug, **kwargs):
|
||||
print_out("Looking up clip...")
|
||||
clip = twitch.get_clip(slug)
|
||||
|
||||
print_out("Found: <green>{}</green> by <yellow>{}</yellow>, playing <blue>{}</blue> ({})".format(
|
||||
clip["title"],
|
||||
clip["broadcaster"]["displayName"],
|
||||
clip["game"]["name"],
|
||||
utils.format_duration(clip["durationSeconds"])
|
||||
))
|
||||
|
||||
print_out("\nAvailable qualities:")
|
||||
qualities = clip["videoQualities"]
|
||||
for n, q in enumerate(qualities):
|
||||
print_out("{}) {} [{} fps]".format(n + 1, q["quality"], q["frameRate"]))
|
||||
|
||||
no = utils.read_int("Choose quality", min=1, max=len(qualities), default=1)
|
||||
selected_quality = qualities[no - 1]
|
||||
url = selected_quality["sourceURL"]
|
||||
|
||||
url_path = urlparse(url).path
|
||||
extension = Path(url_path).suffix
|
||||
filename = "{}_{}{}".format(
|
||||
clip["broadcaster"]["login"],
|
||||
utils.slugify(clip["title"]),
|
||||
extension
|
||||
)
|
||||
|
||||
print("Downloading clip...")
|
||||
download_file(url, filename)
|
||||
|
||||
print("Downloaded: {}".format(filename))
|
||||
|
||||
|
||||
def _download_video(video_id, max_workers, format='mkv', start=None, end=None, keep=False, **kwargs):
|
||||
if start and end and end <= start:
|
||||
raise ConsoleError("End time must be greater than start time")
|
||||
|
||||
|
@ -63,13 +63,13 @@ COMMANDS = [
|
||||
name="download",
|
||||
description="Download a video",
|
||||
arguments=[
|
||||
(["video_id"], {
|
||||
"help": "video ID",
|
||||
(["video"], {
|
||||
"help": "video ID, clip slug, or URL",
|
||||
"type": str,
|
||||
}),
|
||||
(["-w", "--max_workers"], {
|
||||
"help": "maximal number of threads for downloading vods "
|
||||
"concurrently (default 5)",
|
||||
"concurrently (default 20)",
|
||||
"type": int,
|
||||
"default": 20,
|
||||
}),
|
||||
|
@ -21,6 +21,19 @@ def authenticated_get(url, params={}, headers={}):
|
||||
return response
|
||||
|
||||
|
||||
def authenticated_post(url, data=None, json=None, headers={}):
|
||||
headers['Client-ID'] = CLIENT_ID
|
||||
|
||||
response = requests.post(url, data=data, json=json, headers=headers)
|
||||
if response.status_code == 400:
|
||||
data = response.json()
|
||||
raise ConsoleError(data["message"])
|
||||
|
||||
response.raise_for_status()
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def kraken_get(url, params={}, headers={}):
|
||||
"""
|
||||
Add accept header required by kraken API v5.
|
||||
@ -46,11 +59,40 @@ def get_video(video_id):
|
||||
"""
|
||||
https://dev.twitch.tv/docs/v5/reference/videos#get-video
|
||||
"""
|
||||
url = "https://api.twitch.tv/kraken/videos/%d" % video_id
|
||||
url = "https://api.twitch.tv/kraken/videos/{}".format(video_id)
|
||||
|
||||
return kraken_get(url).json()
|
||||
|
||||
|
||||
def get_clip(slug):
|
||||
url = "https://gql.twitch.tv/gql"
|
||||
|
||||
query = """
|
||||
{{
|
||||
clip(slug: "{}") {{
|
||||
title
|
||||
durationSeconds
|
||||
game {{
|
||||
name
|
||||
}}
|
||||
broadcaster {{
|
||||
login
|
||||
displayName
|
||||
}}
|
||||
videoQualities {{
|
||||
frameRate
|
||||
quality
|
||||
sourceURL
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
"""
|
||||
|
||||
payload = {"query": query.format(slug)}
|
||||
data = authenticated_post(url, json=payload).json()
|
||||
return data["data"]["clip"]
|
||||
|
||||
|
||||
def get_channel_videos(channel_id, limit, offset, sort):
|
||||
"""
|
||||
https://dev.twitch.tv/docs/v5/reference/channels#get-channel-videos
|
||||
@ -66,7 +108,7 @@ def get_channel_videos(channel_id, limit, offset, sort):
|
||||
|
||||
|
||||
def get_access_token(video_id):
|
||||
url = "https://api.twitch.tv/api/vods/%d/access_token" % video_id
|
||||
url = "https://api.twitch.tv/api/vods/{}/access_token".format(video_id)
|
||||
|
||||
return authenticated_get(url).json()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user