2019-01-29 13:46:24 +00:00
|
|
|
import re
|
|
|
|
import unicodedata
|
2024-04-23 15:35:46 +00:00
|
|
|
from typing import Optional, Union
|
2019-01-29 13:46:24 +00:00
|
|
|
|
2024-03-30 14:36:53 +00:00
|
|
|
import click
|
|
|
|
|
2019-01-29 13:46:24 +00:00
|
|
|
|
2024-03-26 09:23:50 +00:00
|
|
|
def _format_size(value: float, digits: int, unit: str):
|
2020-04-11 12:05:23 +00:00
|
|
|
if digits > 0:
|
2024-03-28 11:06:50 +00:00
|
|
|
return f"{{:.{digits}f}}{unit}".format(value)
|
2020-04-11 12:05:23 +00:00
|
|
|
else:
|
2024-03-28 11:06:50 +00:00
|
|
|
return f"{int(value)}{unit}"
|
2020-04-11 12:05:23 +00:00
|
|
|
|
|
|
|
|
2024-04-23 15:14:27 +00:00
|
|
|
def format_size(bytes_: Union[int, float], digits: int = 1):
|
2020-04-11 11:08:42 +00:00
|
|
|
if bytes_ < 1024:
|
2020-04-11 12:05:23 +00:00
|
|
|
return _format_size(bytes_, digits, "B")
|
2020-04-11 11:08:42 +00:00
|
|
|
|
|
|
|
kilo = bytes_ / 1024
|
|
|
|
if kilo < 1024:
|
2020-04-11 12:05:23 +00:00
|
|
|
return _format_size(kilo, digits, "kB")
|
2020-04-11 11:08:42 +00:00
|
|
|
|
|
|
|
mega = kilo / 1024
|
|
|
|
if mega < 1024:
|
2020-04-11 12:05:23 +00:00
|
|
|
return _format_size(mega, digits, "MB")
|
2020-04-11 11:08:42 +00:00
|
|
|
|
2020-04-11 12:05:23 +00:00
|
|
|
return _format_size(mega / 1024, digits, "GB")
|
2020-04-11 11:08:42 +00:00
|
|
|
|
|
|
|
|
2024-04-23 15:14:27 +00:00
|
|
|
def format_duration(total_seconds: Union[int, float]) -> str:
|
2020-04-11 11:08:42 +00:00
|
|
|
total_seconds = int(total_seconds)
|
|
|
|
hours = total_seconds // 3600
|
|
|
|
remainder = total_seconds % 3600
|
|
|
|
minutes = remainder // 60
|
|
|
|
seconds = total_seconds % 60
|
|
|
|
|
|
|
|
if hours:
|
2024-03-28 11:06:50 +00:00
|
|
|
return f"{hours} h {minutes} min"
|
2020-04-11 11:08:42 +00:00
|
|
|
|
|
|
|
if minutes:
|
2024-03-28 11:06:50 +00:00
|
|
|
return f"{minutes} min {seconds} sec"
|
2020-04-11 11:08:42 +00:00
|
|
|
|
2024-03-28 11:06:50 +00:00
|
|
|
return f"{seconds} sec"
|
2020-04-11 11:08:42 +00:00
|
|
|
|
|
|
|
|
2024-04-23 15:14:27 +00:00
|
|
|
def format_time(total_seconds: Union[int, float], force_hours: bool = False) -> str:
|
2022-08-14 09:33:38 +00:00
|
|
|
total_seconds = int(total_seconds)
|
|
|
|
hours = total_seconds // 3600
|
|
|
|
remainder = total_seconds % 3600
|
|
|
|
minutes = remainder // 60
|
|
|
|
seconds = total_seconds % 60
|
|
|
|
|
2022-11-20 08:42:09 +00:00
|
|
|
if hours or force_hours:
|
2022-08-14 09:33:38 +00:00
|
|
|
return f"{hours:02}:{minutes:02}:{seconds:02}"
|
|
|
|
|
|
|
|
return f"{minutes:02}:{seconds:02}"
|
|
|
|
|
|
|
|
|
2024-04-23 15:14:27 +00:00
|
|
|
def read_int(msg: str, min: int, max: int, default: Optional[int] = None) -> int:
|
2020-04-11 11:08:42 +00:00
|
|
|
while True:
|
|
|
|
try:
|
2024-03-30 14:36:53 +00:00
|
|
|
val = click.prompt(msg, default=default, type=int)
|
2022-11-20 09:44:32 +00:00
|
|
|
if default and not val:
|
2020-04-11 11:08:42 +00:00
|
|
|
return default
|
|
|
|
if min <= int(val) <= max:
|
|
|
|
return int(val)
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2024-03-26 09:23:50 +00:00
|
|
|
def slugify(value: str) -> str:
|
2024-04-04 06:20:10 +00:00
|
|
|
value = unicodedata.normalize("NFKC", str(value))
|
|
|
|
value = re.sub(r"[^\w\s_-]", "", value)
|
|
|
|
value = re.sub(r"[\s_-]+", "_", value)
|
2022-01-23 08:14:40 +00:00
|
|
|
return value.strip("_").lower()
|
|
|
|
|
|
|
|
|
2024-03-26 09:23:50 +00:00
|
|
|
def titlify(value: str) -> str:
|
2024-04-04 06:20:10 +00:00
|
|
|
value = unicodedata.normalize("NFKC", str(value))
|
|
|
|
value = re.sub(r"[^\w\s\[\]().-]", "", value)
|
|
|
|
value = re.sub(r"\s+", " ", value)
|
2022-01-23 08:14:40 +00:00
|
|
|
return value.strip()
|
2021-01-14 20:38:56 +00:00
|
|
|
|
|
|
|
|
|
|
|
VIDEO_PATTERNS = [
|
|
|
|
r"^(?P<id>\d+)?$",
|
2024-04-25 00:24:20 +00:00
|
|
|
r"^https://(www\.|m\.)?twitch\.tv/videos/(?P<id>\d+)(\?.+)?$",
|
2021-01-14 20:38:56 +00:00
|
|
|
]
|
|
|
|
|
|
|
|
CLIP_PATTERNS = [
|
2021-02-13 01:44:51 +00:00
|
|
|
r"^(?P<slug>[A-Za-z0-9]+(?:-[A-Za-z0-9_-]{16})?)$",
|
2024-04-25 00:24:20 +00:00
|
|
|
r"^https://(www\.|m\.)?twitch\.tv/\w+/clip/(?P<slug>[A-Za-z0-9]+(?:-[A-Za-z0-9_-]{16})?)(\?.+)?$",
|
|
|
|
r"^https://clips\.twitch\.tv/(?P<slug>[A-Za-z0-9]+(?:-[A-Za-z0-9_-]{16})?)(\?.+)?$",
|
2021-01-14 20:38:56 +00:00
|
|
|
]
|
|
|
|
|
|
|
|
|
2024-04-23 15:14:27 +00:00
|
|
|
def parse_video_identifier(identifier: str) -> Optional[str]:
|
2021-01-14 20:38:56 +00:00
|
|
|
"""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")
|
|
|
|
|
|
|
|
|
2024-04-23 15:14:27 +00:00
|
|
|
def parse_clip_identifier(identifier: str) -> Optional[str]:
|
2021-01-14 20:38:56 +00:00
|
|
|
"""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")
|