2020-09-03 06:49:41 +00:00
|
|
|
import json
|
2024-05-31 10:57:51 +00:00
|
|
|
import sys
|
2022-02-23 18:07:27 +00:00
|
|
|
from itertools import islice
|
2024-04-23 16:09:30 +00:00
|
|
|
from typing import Any, Callable, Generator, List, Optional, TypeVar
|
2020-04-11 11:08:42 +00:00
|
|
|
|
2024-04-06 08:43:12 +00:00
|
|
|
import click
|
|
|
|
|
|
|
|
from twitchdl import utils
|
2024-08-24 18:16:22 +00:00
|
|
|
from twitchdl.entities import Clip, Video
|
2018-01-25 10:09:20 +00:00
|
|
|
|
2024-04-01 07:40:54 +00:00
|
|
|
T = TypeVar("T")
|
|
|
|
|
2018-01-25 10:09:20 +00:00
|
|
|
|
2024-05-31 10:57:51 +00:00
|
|
|
def clear_line():
|
|
|
|
sys.stdout.write("\033[1K")
|
|
|
|
sys.stdout.write("\r")
|
|
|
|
|
|
|
|
|
2022-08-20 09:35:07 +00:00
|
|
|
def truncate(string: str, length: int) -> str:
|
2022-08-18 08:03:40 +00:00
|
|
|
if len(string) > length:
|
2024-04-04 06:20:10 +00:00
|
|
|
return string[: length - 1] + "…"
|
2022-08-18 08:03:40 +00:00
|
|
|
|
|
|
|
return string
|
|
|
|
|
|
|
|
|
2022-08-20 09:35:07 +00:00
|
|
|
def print_json(data: Any):
|
2024-03-30 06:52:43 +00:00
|
|
|
click.echo(json.dumps(data))
|
2020-09-03 06:49:41 +00:00
|
|
|
|
|
|
|
|
2024-03-30 14:36:53 +00:00
|
|
|
def print_log(message: Any):
|
|
|
|
click.secho(message, err=True, dim=True)
|
2021-01-14 20:38:56 +00:00
|
|
|
|
|
|
|
|
2024-04-27 17:58:02 +00:00
|
|
|
def visual_len(text: str):
|
|
|
|
return len(click.unstyle(text))
|
|
|
|
|
|
|
|
|
|
|
|
def ljust(text: str, width: int):
|
|
|
|
diff = width - visual_len(text)
|
|
|
|
return text + (" " * diff) if diff > 0 else text
|
|
|
|
|
|
|
|
|
2024-04-23 16:09:30 +00:00
|
|
|
def print_table(headers: List[str], data: List[List[str]]):
|
2024-04-27 17:58:02 +00:00
|
|
|
widths = [[visual_len(cell) for cell in row] for row in data + [headers]]
|
2024-03-30 06:32:12 +00:00
|
|
|
widths = [max(width) for width in zip(*widths)]
|
|
|
|
underlines = ["-" * width for width in widths]
|
|
|
|
|
2024-04-23 16:09:30 +00:00
|
|
|
def print_row(row: List[str]):
|
2024-08-30 11:39:41 +00:00
|
|
|
parts = (ljust(cell, widths[idx]) for idx, cell in enumerate(row))
|
|
|
|
click.echo(" ".join(parts).strip())
|
2024-03-30 06:32:12 +00:00
|
|
|
|
|
|
|
print_row(headers)
|
|
|
|
print_row(underlines)
|
|
|
|
|
|
|
|
for row in data:
|
|
|
|
print_row(row)
|
|
|
|
|
|
|
|
|
2024-04-01 07:40:54 +00:00
|
|
|
def print_paged(
|
|
|
|
label: str,
|
|
|
|
generator: Generator[T, Any, Any],
|
|
|
|
print_fn: Callable[[T], None],
|
|
|
|
page_size: int,
|
2024-04-23 15:14:27 +00:00
|
|
|
total_count: Optional[int] = None,
|
2024-04-01 07:40:54 +00:00
|
|
|
):
|
|
|
|
iterator = iter(generator)
|
|
|
|
page = list(islice(iterator, page_size))
|
|
|
|
|
|
|
|
first = 1
|
|
|
|
last = first + len(page) - 1
|
|
|
|
|
|
|
|
while True:
|
|
|
|
click.echo("-" * 80)
|
|
|
|
|
|
|
|
click.echo()
|
|
|
|
for item in page:
|
|
|
|
print_fn(item)
|
|
|
|
|
|
|
|
last = first + len(page) - 1
|
|
|
|
|
|
|
|
click.echo("-" * 80)
|
|
|
|
click.echo(f"{label} {first}-{last} of {total_count or '???'}")
|
|
|
|
|
|
|
|
first = first + len(page)
|
|
|
|
last = first + 1
|
|
|
|
|
|
|
|
page = list(islice(iterator, page_size))
|
|
|
|
if not page or not prompt_continue():
|
|
|
|
break
|
|
|
|
|
|
|
|
|
2024-04-02 07:37:53 +00:00
|
|
|
def print_video(video: Video):
|
2020-05-17 09:56:21 +00:00
|
|
|
published_at = video["publishedAt"].replace("T", " @ ").replace("Z", "")
|
|
|
|
length = utils.format_duration(video["lengthSeconds"])
|
2022-02-05 08:47:55 +00:00
|
|
|
|
2024-04-04 06:20:10 +00:00
|
|
|
channel = blue(video["creator"]["displayName"]) if video["creator"] else ""
|
2024-03-31 19:39:54 +00:00
|
|
|
playing = f"playing {blue(video['game']['name'])}" if video["game"] else ""
|
2020-04-11 11:08:42 +00:00
|
|
|
|
2020-05-17 09:56:21 +00:00
|
|
|
# Can't find URL in video object, strange
|
2024-03-28 11:06:50 +00:00
|
|
|
url = f"https://www.twitch.tv/videos/{video['id']}"
|
2020-05-17 09:56:21 +00:00
|
|
|
|
2024-03-31 19:39:54 +00:00
|
|
|
click.secho(f"Video {video['id']}", bold=True)
|
2024-04-02 07:37:53 +00:00
|
|
|
click.secho(video["title"], fg="green")
|
2022-02-23 18:07:04 +00:00
|
|
|
|
|
|
|
if channel or playing:
|
2024-03-31 19:39:54 +00:00
|
|
|
click.echo(" ".join([channel, playing]))
|
2022-02-23 18:07:04 +00:00
|
|
|
|
2024-03-31 19:39:54 +00:00
|
|
|
click.echo(f"Published {blue(published_at)} Length: {blue(length)} ")
|
|
|
|
click.secho(url, italic=True)
|
2024-08-30 11:34:08 +00:00
|
|
|
|
|
|
|
if video["description"]:
|
|
|
|
click.echo(f"\nDescription:\n{video['description']}")
|
|
|
|
|
2024-04-01 07:40:54 +00:00
|
|
|
click.echo()
|
2020-09-03 06:49:41 +00:00
|
|
|
|
|
|
|
|
2024-04-02 07:37:53 +00:00
|
|
|
def print_video_compact(video: Video):
|
2022-08-18 08:03:40 +00:00
|
|
|
id = video["id"]
|
|
|
|
date = video["publishedAt"][:10]
|
|
|
|
game = video["game"]["name"] if video["game"] else ""
|
|
|
|
title = truncate(video["title"], 80).ljust(80)
|
2024-03-31 19:39:54 +00:00
|
|
|
click.echo(f"{bold(id)} {date} {green(title)} {blue(game)}")
|
2022-08-18 08:03:40 +00:00
|
|
|
|
|
|
|
|
2024-04-02 07:44:20 +00:00
|
|
|
def print_clip(clip: Clip):
|
2020-09-03 06:49:41 +00:00
|
|
|
published_at = clip["createdAt"].replace("T", " @ ").replace("Z", "")
|
|
|
|
length = utils.format_duration(clip["durationSeconds"])
|
2021-01-14 20:38:56 +00:00
|
|
|
channel = clip["broadcaster"]["displayName"]
|
2024-03-31 19:39:54 +00:00
|
|
|
playing = f"playing {blue(clip['game']['name'])}" if clip["game"] else ""
|
|
|
|
|
|
|
|
click.echo(f"Clip {bold(clip['slug'])}")
|
|
|
|
click.secho(clip["title"], fg="green")
|
|
|
|
click.echo(f"{blue(channel)} {playing}")
|
|
|
|
click.echo(
|
2024-04-04 06:20:10 +00:00
|
|
|
f"Published {blue(published_at)}"
|
|
|
|
+ f" Length: {blue(length)}"
|
|
|
|
+ f" Views: {blue(clip['viewCount'])}"
|
2024-03-28 11:06:50 +00:00
|
|
|
)
|
2024-03-31 19:39:54 +00:00
|
|
|
click.secho(clip["url"], italic=True)
|
2024-04-06 08:43:12 +00:00
|
|
|
click.echo()
|
|
|
|
|
|
|
|
|
|
|
|
def print_clip_compact(clip: Clip):
|
|
|
|
slug = clip["slug"]
|
|
|
|
date = clip["createdAt"][:10]
|
|
|
|
title = truncate(clip["title"], 50).ljust(50)
|
|
|
|
game = clip["game"]["name"] if clip["game"] else ""
|
|
|
|
game = truncate(game, 30).ljust(30)
|
|
|
|
|
|
|
|
click.echo(f"{date} {green(title)} {blue(game)} {bold(slug)}")
|
2022-02-23 18:07:27 +00:00
|
|
|
|
|
|
|
|
2024-04-01 07:40:54 +00:00
|
|
|
def prompt_continue():
|
2024-03-31 19:39:54 +00:00
|
|
|
enter = click.style("Enter", bold=True, fg="green")
|
|
|
|
ctrl_c = click.style("Ctrl+C", bold=True, fg="yellow")
|
|
|
|
click.echo(f"Press {enter} to continue, {ctrl_c} to break.")
|
2022-02-23 18:07:27 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
input()
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
2024-03-30 14:36:53 +00:00
|
|
|
|
|
|
|
|
|
|
|
# Shorthand functions for coloring output
|
|
|
|
|
2024-04-04 06:20:10 +00:00
|
|
|
|
2024-03-30 14:36:53 +00:00
|
|
|
def blue(text: Any) -> str:
|
|
|
|
return click.style(text, fg="blue")
|
|
|
|
|
|
|
|
|
|
|
|
def cyan(text: Any) -> str:
|
|
|
|
return click.style(text, fg="cyan")
|
|
|
|
|
|
|
|
|
|
|
|
def green(text: Any) -> str:
|
|
|
|
return click.style(text, fg="green")
|
|
|
|
|
|
|
|
|
|
|
|
def yellow(text: Any) -> str:
|
|
|
|
return click.style(text, fg="yellow")
|
|
|
|
|
|
|
|
|
|
|
|
def bold(text: Any) -> str:
|
|
|
|
return click.style(text, bold=True)
|
|
|
|
|
|
|
|
|
|
|
|
def dim(text: Any) -> str:
|
|
|
|
return click.style(text, dim=True)
|