mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-03-16 08:09:15 +00:00
78 lines
2.8 KiB
Python
78 lines
2.8 KiB
Python
from __future__ import annotations
|
|
|
|
import re
|
|
from typing import Optional, Union
|
|
from zlib import crc32
|
|
|
|
TIMESTAMP_FORMAT = re.compile(r"^(?P<hour>\d{2}):(?P<minute>\d{2}):(?P<second>\d{2})(?P<ms>.\d{3}|)$")
|
|
|
|
|
|
class Chapter:
|
|
def __init__(self, timestamp: Union[str, int, float], name: Optional[str] = None):
|
|
"""
|
|
Create a new Chapter with a Timestamp and optional name.
|
|
|
|
The timestamp may be in the following formats:
|
|
- "HH:MM:SS" string, e.g., `25:05:23`.
|
|
- "HH:MM:SS.mss" string, e.g., `25:05:23.120`.
|
|
- a timecode integer in milliseconds, e.g., `90323120` is `25:05:23.120`.
|
|
- a timecode float in seconds, e.g., `90323.12` is `25:05:23.120`.
|
|
|
|
If you have a timecode integer in seconds, just multiply it by 1000.
|
|
If you have a timecode float in milliseconds (no decimal value), just convert
|
|
it to an integer.
|
|
"""
|
|
if timestamp is None:
|
|
raise ValueError("The timestamp must be provided.")
|
|
|
|
if not isinstance(timestamp, (str, int, float)):
|
|
raise TypeError(f"Expected timestamp to be {str}, {int} or {float}, not {type(timestamp)}")
|
|
if not isinstance(name, (str, type(None))):
|
|
raise TypeError(f"Expected name to be {str}, not {type(name)}")
|
|
|
|
if not isinstance(timestamp, str):
|
|
if isinstance(timestamp, int): # ms
|
|
hours, remainder = divmod(timestamp, 1000 * 60 * 60)
|
|
minutes, remainder = divmod(remainder, 1000 * 60)
|
|
seconds, ms = divmod(remainder, 1000)
|
|
elif isinstance(timestamp, float): # seconds.ms
|
|
hours, remainder = divmod(timestamp, 60 * 60)
|
|
minutes, remainder = divmod(remainder, 60)
|
|
seconds, ms = divmod(int(remainder * 1000), 1000)
|
|
else:
|
|
raise TypeError
|
|
timestamp = f"{int(hours):02}:{int(minutes):02}:{int(seconds):02}.{str(ms).zfill(3)[:3]}"
|
|
|
|
timestamp_m = TIMESTAMP_FORMAT.match(timestamp)
|
|
if not timestamp_m:
|
|
raise ValueError(f"The timestamp format is invalid: {timestamp}")
|
|
|
|
hour, minute, second, ms = timestamp_m.groups()
|
|
if not ms:
|
|
timestamp += ".000"
|
|
|
|
self.timestamp = timestamp
|
|
self.name = name
|
|
|
|
def __repr__(self) -> str:
|
|
return "{name}({items})".format(
|
|
name=self.__class__.__name__, items=", ".join([f"{k}={repr(v)}" for k, v in self.__dict__.items()])
|
|
)
|
|
|
|
def __str__(self) -> str:
|
|
return " | ".join(filter(bool, ["CHP", self.timestamp, self.name]))
|
|
|
|
@property
|
|
def id(self) -> str:
|
|
"""Compute an ID from the Chapter data."""
|
|
checksum = crc32(str(self).encode("utf8"))
|
|
return hex(checksum)
|
|
|
|
@property
|
|
def named(self) -> bool:
|
|
"""Check if Chapter is named."""
|
|
return bool(self.name)
|
|
|
|
|
|
__all__ = ("Chapter",)
|