175 lines
6.7 KiB
Python
175 lines
6.7 KiB
Python
import os
|
||
import posixpath
|
||
|
||
from yt_dlp.utils import sanitize_filename
|
||
|
||
from ytdl_sub.script.functions import Functions
|
||
from ytdl_sub.script.types.map import Map
|
||
from ytdl_sub.script.types.resolvable import AnyArgument, Integer, ReturnableArgument, String
|
||
from ytdl_sub.script.utils.exceptions import RuntimeException
|
||
from ytdl_sub.utils.file_path import FilePathTruncater
|
||
|
||
|
||
def _pad(num: int, width: int):
|
||
return str(num).zfill(width)
|
||
|
||
|
||
_days_in_month = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
||
|
||
|
||
class CustomFunctions:
|
||
@staticmethod
|
||
def legacy_bracket_safety(value: ReturnableArgument) -> ReturnableArgument:
|
||
"""
|
||
ytdl-sub used to replace brackets ('{', '}') with unicode brackets ('{', '}') to not
|
||
interfere with its legacy variable scripting system. This function replicates that
|
||
behavior.
|
||
"""
|
||
if isinstance(value, String):
|
||
value = String(value.value.replace("{", "{").replace("}", "}"))
|
||
return value
|
||
|
||
@staticmethod
|
||
def to_native_filepath(filepath: String) -> String:
|
||
"""
|
||
Convert any unix-based path separators ('/') with the OS's native
|
||
separator. In addition, expand ~ to absolute directories.
|
||
"""
|
||
return String(os.path.expanduser(filepath.value.replace(posixpath.sep, os.sep)))
|
||
|
||
@staticmethod
|
||
def truncate_filepath_if_too_long(filepath: String) -> String:
|
||
"""
|
||
If a file-path is too long for the OS, this function will truncate it while preserving
|
||
the extension.
|
||
"""
|
||
return String(FilePathTruncater.maybe_truncate_file_path(filepath.value))
|
||
|
||
@staticmethod
|
||
def sanitize(value: AnyArgument) -> String:
|
||
"""
|
||
Sanitize a string using yt-dlp's ``sanitize_filename`` method to ensure it's safe to use
|
||
for file/directory names on any OS.
|
||
"""
|
||
return String(sanitize_filename(str(value)))
|
||
|
||
@staticmethod
|
||
def sanitize_plex_episode(string: String) -> String:
|
||
"""
|
||
Sanitize a string using ``sanitize`` and replace numerics with their respective fixed-width
|
||
numbers. This is used to have Plex avoid scraping numbers like ``4x4`` as the
|
||
season and/or episode.
|
||
"""
|
||
sanitized_string = CustomFunctions.sanitize(string).value
|
||
out = ""
|
||
for char in sanitized_string:
|
||
match char:
|
||
case "0":
|
||
out += "0"
|
||
case "1":
|
||
out += "1"
|
||
case "2":
|
||
out += "2"
|
||
case "3":
|
||
out += "3"
|
||
case "4":
|
||
out += "4"
|
||
case "5":
|
||
out += "5"
|
||
case "6":
|
||
out += "6"
|
||
case "7":
|
||
out += "7"
|
||
case "8":
|
||
out += "8"
|
||
case "9":
|
||
out += "9"
|
||
case _:
|
||
out += char
|
||
return String(out)
|
||
|
||
@staticmethod
|
||
def to_date_metadata(yyyymmdd: String) -> Map:
|
||
"""
|
||
Takes a date in the form of YYYYMMDD and returns a Map containing:
|
||
|
||
- date (String, YYYYMMDD)
|
||
- date_standardized (String, YYYY-MM-DD)
|
||
- year (Integer)
|
||
- month (Integer)
|
||
- day (Integer)
|
||
- year_truncated (Integer, YY from YY[YY])
|
||
- month_padded (String)
|
||
- day_padded (String)
|
||
- year_truncated_reversed (Integer, 100 - year_truncated)
|
||
- month_reversed (Integer, 13 - month)
|
||
- month_reversed_padded (String)
|
||
- day_reversed (Integer, total_days_in_month + 1 - day)
|
||
- day_reversed_padded (String)
|
||
- day_of_year (Integer)
|
||
- day_of_year_padded (String, padded 3)
|
||
- day_of_year_reversed (Integer, total_days_in_year + 1 - day_of_year)
|
||
- day_of_year_reversed_padded (String, padded 3)
|
||
"""
|
||
date_str = yyyymmdd.value
|
||
if not (date_str.isnumeric() and len(date_str) == 8):
|
||
raise RuntimeException(
|
||
f"Expected input of date_metadata to be YYYYMMDD, but received {date_str}"
|
||
)
|
||
|
||
year: int = int(date_str[:4])
|
||
month_padded: str = date_str[4:6]
|
||
day_padded: str = date_str[6:8]
|
||
|
||
month: int = int(month_padded)
|
||
day: int = int(day_padded)
|
||
year_truncated: int = int(str(year)[-2:])
|
||
|
||
day_of_year: int = sum(_days_in_month[:month]) + day
|
||
total_days_in_month: int = _days_in_month[month]
|
||
total_days_in_year: int = 365
|
||
if year % 4 == 0:
|
||
total_days_in_year += 1
|
||
if month == 2:
|
||
total_days_in_month += 1
|
||
if month > 2:
|
||
day_of_year += 1
|
||
|
||
day_of_year_reversed: int = total_days_in_year + 1 - day_of_year
|
||
month_reversed: int = 13 - month
|
||
day_reversed: int = total_days_in_month + 1 - day
|
||
|
||
return Map(
|
||
{
|
||
String("date"): yyyymmdd,
|
||
String("date_standardized"): String(f"{year}-{month_padded}-{day_padded}"),
|
||
String("year"): Integer(year),
|
||
String("month"): Integer(month),
|
||
String("day"): Integer(day),
|
||
String("year_truncated"): Integer(year_truncated),
|
||
String("month_padded"): String(month_padded),
|
||
String("day_padded"): String(day_padded),
|
||
String("year_truncated_reversed"): Integer(100 - year_truncated),
|
||
String("month_reversed"): Integer(month_reversed),
|
||
String("month_reversed_padded"): String(_pad(month_reversed, width=2)),
|
||
String("day_reversed"): Integer(day_reversed),
|
||
String("day_reversed_padded"): String(_pad(day_reversed, width=2)),
|
||
String("day_of_year"): Integer(day_of_year),
|
||
String("day_of_year_padded"): String(_pad(day_of_year, width=3)),
|
||
String("day_of_year_reversed"): Integer(day_of_year_reversed),
|
||
String("day_of_year_reversed_padded"): String(_pad(day_of_year_reversed, width=3)),
|
||
}
|
||
)
|
||
|
||
@staticmethod
|
||
def register():
|
||
"""
|
||
Register Custom functions once and only once
|
||
"""
|
||
if not Functions.is_built_in("sanitize"):
|
||
Functions.register_function(CustomFunctions.legacy_bracket_safety)
|
||
Functions.register_function(CustomFunctions.truncate_filepath_if_too_long)
|
||
Functions.register_function(CustomFunctions.to_native_filepath)
|
||
Functions.register_function(CustomFunctions.sanitize)
|
||
Functions.register_function(CustomFunctions.sanitize_plex_episode)
|
||
Functions.register_function(CustomFunctions.to_date_metadata)
|