mirror of
https://github.com/ihabunek/twitch-dl
synced 2024-08-30 18:32:25 +00:00
Add twitch-dl info command
This commit is contained in:
parent
2380dc5a35
commit
548a9350ba
@ -1,9 +1,11 @@
|
||||
from .clips import clips
|
||||
from .download import download
|
||||
from .info import info
|
||||
from .videos import videos
|
||||
|
||||
__all__ = [
|
||||
clips,
|
||||
download,
|
||||
info,
|
||||
videos,
|
||||
]
|
||||
|
@ -55,7 +55,7 @@ def _clip_target_filename(clip):
|
||||
name = "_".join([
|
||||
date,
|
||||
clip["id"],
|
||||
clip["broadcaster"]["channel"]["name"],
|
||||
clip["broadcaster"]["login"],
|
||||
utils.slugify(clip["title"]),
|
||||
])
|
||||
|
||||
@ -96,6 +96,7 @@ def clips(args):
|
||||
print_out("<yellow>Showing clips {}-{} of ??</yellow>".format(first, last))
|
||||
|
||||
for clip in clips["edges"]:
|
||||
print_out()
|
||||
print_clip(clip["node"])
|
||||
|
||||
if not args.pager:
|
||||
|
@ -68,13 +68,13 @@ def _join_vods(playlist_path, target, overwrite):
|
||||
|
||||
|
||||
def _video_target_filename(video, format):
|
||||
match = re.search(r"^(\d{4})-(\d{2})-(\d{2})T", video['published_at'])
|
||||
match = re.search(r"^(\d{4})-(\d{2})-(\d{2})T", video['publishedAt'])
|
||||
date = "".join(match.groups())
|
||||
|
||||
name = "_".join([
|
||||
date,
|
||||
video['_id'][1:],
|
||||
video['channel']['name'],
|
||||
video['id'][1:],
|
||||
video['creator']['login'],
|
||||
utils.slugify(video['title']),
|
||||
])
|
||||
|
||||
@ -92,7 +92,7 @@ def _clip_target_filename(clip):
|
||||
name = "_".join([
|
||||
date,
|
||||
clip["id"],
|
||||
clip["broadcaster"]["channel"]["name"],
|
||||
clip["broadcaster"]["login"],
|
||||
utils.slugify(clip["title"]),
|
||||
])
|
||||
|
||||
@ -127,32 +127,16 @@ def _crete_temp_dir(base_uri):
|
||||
return temp_dir
|
||||
|
||||
|
||||
VIDEO_PATTERNS = [
|
||||
r"^(?P<id>\d+)?$",
|
||||
r"^https://(www.)?twitch.tv/videos/(?P<id>\d+)(\?.+)?$",
|
||||
]
|
||||
|
||||
CLIP_PATTERNS = [
|
||||
r"^(?P<slug>[A-Za-z0-9]+)$",
|
||||
r"^https://(www.)?twitch.tv/\w+/clip/(?P<slug>[A-Za-z0-9]+)(\?.+)?$",
|
||||
r"^https://clips.twitch.tv/(?P<slug>[A-Za-z0-9]+)(\?.+)?$",
|
||||
]
|
||||
|
||||
|
||||
def download(args):
|
||||
for pattern in VIDEO_PATTERNS:
|
||||
match = re.match(pattern, args.video)
|
||||
if match:
|
||||
video_id = match.group('id')
|
||||
return _download_video(video_id, args)
|
||||
video_id = utils.parse_video_identifier(args.video)
|
||||
if video_id:
|
||||
return _download_video(video_id, args)
|
||||
|
||||
for pattern in CLIP_PATTERNS:
|
||||
match = re.match(pattern, args.video)
|
||||
if match:
|
||||
clip_slug = match.group('slug')
|
||||
return _download_clip(clip_slug, args)
|
||||
clip_slug = utils.parse_clip_identifier(args.video)
|
||||
if clip_slug:
|
||||
return _download_clip(clip_slug, args)
|
||||
|
||||
raise ConsoleError("Invalid video: {}".format(args.video))
|
||||
raise ConsoleError("Invalid input: {}".format(args.video))
|
||||
|
||||
|
||||
def _get_clip_url(clip, args):
|
||||
@ -216,7 +200,7 @@ def _download_video(video_id, args):
|
||||
video = twitch.get_video(video_id)
|
||||
|
||||
print_out("Found: <blue>{}</blue> by <yellow>{}</yellow>".format(
|
||||
video['title'], video['channel']['display_name']))
|
||||
video['title'], video['creator']['displayName']))
|
||||
|
||||
print_out("<dim>Fetching access token...</dim>")
|
||||
access_token = twitch.get_access_token(video_id)
|
||||
|
78
twitchdl/commands/info.py
Normal file
78
twitchdl/commands/info.py
Normal file
@ -0,0 +1,78 @@
|
||||
import m3u8
|
||||
|
||||
from twitchdl import utils, twitch
|
||||
from twitchdl.exceptions import ConsoleError
|
||||
from twitchdl.output import print_video, print_clip, print_json, print_out, print_log
|
||||
|
||||
|
||||
def info(args):
|
||||
video_id = utils.parse_video_identifier(args.identifier)
|
||||
if video_id:
|
||||
print_log("Fetching video...")
|
||||
video = twitch.get_video(video_id)
|
||||
|
||||
print_log("Fetching access token...")
|
||||
access_token = twitch.get_access_token(video_id)
|
||||
|
||||
print_log("Fetching playlists...")
|
||||
playlists = twitch.get_playlists(video_id, access_token)
|
||||
|
||||
if video:
|
||||
if args.json:
|
||||
video_json(video, playlists)
|
||||
else:
|
||||
video_info(video, playlists)
|
||||
return
|
||||
|
||||
raise ConsoleError("Video #{} not found".format(video_id))
|
||||
|
||||
clip_slug = utils.parse_clip_identifier(args.identifier)
|
||||
if clip_slug:
|
||||
print_log("Fetching clip...")
|
||||
clip = twitch.get_clip(clip_slug)
|
||||
if clip:
|
||||
if args.json:
|
||||
print_json(clip)
|
||||
else:
|
||||
clip_info(clip)
|
||||
return
|
||||
|
||||
raise ConsoleError("Clip {} not found".format(clip_slug))
|
||||
|
||||
raise ConsoleError("Invalid input: {}".format(args.video))
|
||||
|
||||
|
||||
def video_info(video, playlists):
|
||||
print_out()
|
||||
print_video(video)
|
||||
|
||||
print_out()
|
||||
print_out("Playlists:")
|
||||
for p in m3u8.loads(playlists).playlists:
|
||||
print_out("<b>{}</b> {}".format(p.stream_info.video, p.uri))
|
||||
|
||||
|
||||
def video_json(video, playlists):
|
||||
playlists = m3u8.loads(playlists).playlists
|
||||
|
||||
video["playlists"] = [
|
||||
{
|
||||
"bandwidth": p.stream_info.bandwidth,
|
||||
"resolution": p.stream_info.resolution,
|
||||
"codecs": p.stream_info.codecs,
|
||||
"video": p.stream_info.video,
|
||||
"uri": p.uri
|
||||
} for p in playlists
|
||||
]
|
||||
|
||||
print_json(video)
|
||||
|
||||
|
||||
def clip_info(clip):
|
||||
print_out()
|
||||
print_clip(clip)
|
||||
print_out()
|
||||
print_out("Download links:")
|
||||
|
||||
for q in clip["videoQualities"]:
|
||||
print_out("<b>{quality}p{frameRate}</b> {sourceURL}".format(**q))
|
@ -51,6 +51,7 @@ def videos(args):
|
||||
print_out("<yellow>Showing videos {}-{} of {}</yellow>".format(first, last, total))
|
||||
|
||||
for video in videos["edges"]:
|
||||
print_out()
|
||||
print_video(video["node"])
|
||||
|
||||
if not args.pager:
|
||||
|
@ -171,6 +171,21 @@ COMMANDS = [
|
||||
})
|
||||
],
|
||||
),
|
||||
Command(
|
||||
name="info",
|
||||
description="Print information for a given Twitch URL, video ID or clip slug",
|
||||
arguments=[
|
||||
(["identifier"], {
|
||||
"help": "identifier",
|
||||
"type": str,
|
||||
}),
|
||||
(["-j", "--json"], {
|
||||
"help": "Show results as JSON",
|
||||
"action": "store_true",
|
||||
"default": False,
|
||||
}),
|
||||
],
|
||||
)
|
||||
]
|
||||
|
||||
COMMON_ARGUMENTS = [
|
||||
|
@ -62,10 +62,16 @@ def print_err(*args, **kwargs):
|
||||
print(*args, file=sys.stderr, **kwargs)
|
||||
|
||||
|
||||
def print_log(*args, **kwargs):
|
||||
args = ["<dim>{}</dim>".format(a) for a in args]
|
||||
args = [colorize(a) if USE_ANSI_COLOR else strip_tags(a) for a in args]
|
||||
print(*args, file=sys.stderr, **kwargs)
|
||||
|
||||
|
||||
def print_video(video):
|
||||
published_at = video["publishedAt"].replace("T", " @ ").replace("Z", "")
|
||||
length = utils.format_duration(video["lengthSeconds"])
|
||||
channel = video["creator"]["channel"]["displayName"]
|
||||
channel = video["creator"]["displayName"]
|
||||
playing = (
|
||||
"playing <blue>{}</blue>".format(video["game"]["name"])
|
||||
if video["game"] else ""
|
||||
@ -74,7 +80,7 @@ def print_video(video):
|
||||
# Can't find URL in video object, strange
|
||||
url = "https://www.twitch.tv/videos/{}".format(video["id"])
|
||||
|
||||
print_out("\n<b>{}</b>".format(video["id"]))
|
||||
print_out("<b>Video {}</b>".format(video["id"]))
|
||||
print_out("<green>{}</green>".format(video["title"]))
|
||||
print_out("<blue>{}</blue> {}".format(channel, playing))
|
||||
print_out("Published <blue>{}</blue> Length: <blue>{}</blue> ".format(published_at, length))
|
||||
@ -84,13 +90,13 @@ def print_video(video):
|
||||
def print_clip(clip):
|
||||
published_at = clip["createdAt"].replace("T", " @ ").replace("Z", "")
|
||||
length = utils.format_duration(clip["durationSeconds"])
|
||||
channel = clip["broadcaster"]["channel"]["displayName"]
|
||||
channel = clip["broadcaster"]["displayName"]
|
||||
playing = (
|
||||
"playing <blue>{}</blue>".format(clip["game"]["name"])
|
||||
if clip["game"] else ""
|
||||
)
|
||||
|
||||
print_out("\n<b>{}</b>".format(clip["slug"]))
|
||||
print_out("Clip <b>{}</b>".format(clip["slug"]))
|
||||
print_out("<green>{}</green>".format(clip["title"]))
|
||||
print_out("<blue>{}</blue> {}".format(channel, playing))
|
||||
print_out(
|
||||
@ -98,3 +104,8 @@ def print_clip(clip):
|
||||
" Length: <blue>{}</blue>"
|
||||
" Views: <blue>{}</blue>".format(published_at, length, clip["viewCount"]))
|
||||
print_out("<i>{}</i>".format(clip["url"]))
|
||||
|
||||
|
||||
def print_clip_urls(clip):
|
||||
from pprint import pprint
|
||||
pprint(clip)
|
@ -61,7 +61,7 @@ def gql_query(query):
|
||||
return response
|
||||
|
||||
|
||||
def get_video(video_id):
|
||||
def get_video_legacy(video_id):
|
||||
"""
|
||||
https://dev.twitch.tv/docs/v5/reference/videos#get-video
|
||||
"""
|
||||
@ -70,29 +70,61 @@ def get_video(video_id):
|
||||
return kraken_get(url).json()
|
||||
|
||||
|
||||
VIDEO_FIELDS = """
|
||||
id
|
||||
title
|
||||
publishedAt
|
||||
broadcastType
|
||||
lengthSeconds
|
||||
game {
|
||||
name
|
||||
}
|
||||
creator {
|
||||
login
|
||||
displayName
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
def get_video(video_id):
|
||||
query = """
|
||||
{{
|
||||
video(id: "{video_id}") {{
|
||||
{fields}
|
||||
}}
|
||||
}}
|
||||
"""
|
||||
|
||||
query = query.format(video_id=video_id, fields=VIDEO_FIELDS)
|
||||
|
||||
response = gql_query(query)
|
||||
return response["data"]["video"]
|
||||
|
||||
|
||||
def get_clip(slug):
|
||||
query = """
|
||||
{{
|
||||
clip(slug: "{}") {{
|
||||
id
|
||||
slug
|
||||
title
|
||||
createdAt
|
||||
viewCount
|
||||
durationSeconds
|
||||
game {{
|
||||
name
|
||||
}}
|
||||
broadcaster {{
|
||||
login
|
||||
displayName
|
||||
channel {{
|
||||
name
|
||||
}}
|
||||
}}
|
||||
url
|
||||
videoQualities {{
|
||||
frameRate
|
||||
quality
|
||||
sourceURL
|
||||
}}
|
||||
game {{
|
||||
id
|
||||
name
|
||||
}}
|
||||
broadcaster {{
|
||||
displayName
|
||||
login
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
"""
|
||||
@ -139,10 +171,8 @@ def get_channel_clips(channel_id, period, limit, after=None):
|
||||
name
|
||||
}}
|
||||
broadcaster {{
|
||||
channel {{
|
||||
name
|
||||
displayName
|
||||
}}
|
||||
displayName
|
||||
login
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
@ -213,9 +243,8 @@ def get_channel_videos(channel_id, limit, sort, type="archive", game_ids=[], aft
|
||||
name
|
||||
}}
|
||||
creator {{
|
||||
channel {{
|
||||
displayName
|
||||
}}
|
||||
login
|
||||
displayName
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
|
@ -61,3 +61,31 @@ def slugify(value):
|
||||
value = unicodedata.normalize('NFKC', value)
|
||||
value = re_pattern.sub('', value).strip().lower()
|
||||
return re_spaces.sub('_', value)
|
||||
|
||||
|
||||
VIDEO_PATTERNS = [
|
||||
r"^(?P<id>\d+)?$",
|
||||
r"^https://(www.)?twitch.tv/videos/(?P<id>\d+)(\?.+)?$",
|
||||
]
|
||||
|
||||
CLIP_PATTERNS = [
|
||||
r"^(?P<slug>[A-Za-z0-9]+)$",
|
||||
r"^https://(www.)?twitch.tv/\w+/clip/(?P<slug>[A-Za-z0-9]+)(\?.+)?$",
|
||||
r"^https://clips.twitch.tv/(?P<slug>[A-Za-z0-9]+)(\?.+)?$",
|
||||
]
|
||||
|
||||
|
||||
def parse_video_identifier(identifier):
|
||||
"""Given a video ID or URL returns the video ID, or null if not matched"""
|
||||
for pattern in VIDEO_PATTERNS:
|
||||
match = re.match(pattern, identifier)
|
||||
if match:
|
||||
return match.group("id")
|
||||
|
||||
|
||||
def parse_clip_identifier(identifier):
|
||||
"""Given a clip slug or URL returns the clip slug, or null if not matched"""
|
||||
for pattern in CLIP_PATTERNS:
|
||||
match = re.match(pattern, identifier)
|
||||
if match:
|
||||
return match.group("slug")
|
||||
|
Loading…
Reference in New Issue
Block a user