"""General‑purpose helper functions.""" import re def parse_timestamp(timestamp: str) -> int: """ Convert a timestamp in 'mm:ss' or 'HH:MM:SS' format to total seconds. Args: timestamp: String in the format 'mm:ss' or 'HH:MM:SS'. Returns: Total number of seconds. Raises: ValueError: If the format is unrecognized. """ parts = timestamp.strip().split(':') if len(parts) == 2: minutes, seconds = map(int, parts) return minutes * 60 + seconds elif len(parts) == 3: hours, minutes, seconds = map(int, parts) return hours * 3600 + minutes * 60 + seconds else: raise ValueError(f"Invalid timestamp format: {timestamp}") def format_time(seconds: int) -> str: """ Convert seconds to 'HH:MM:SS' format for FFmpeg. Args: seconds: Total number of seconds. Returns: Time string in 'HH:MM:SS' format. """ hours = seconds // 3600 minutes = (seconds % 3600) // 60 secs = seconds % 60 return f"{hours:02d}:{minutes:02d}:{secs:02d}" def sanitize_filename(name: str) -> str: """ Remove characters that are problematic on common filesystems. This is a fallback sanitizer; the main replacement is handled by apply_replacement() when --replace-bad-chars is used. Args: name: Original filename candidate. Returns: Sanitized string with dangerous characters replaced by '_'. """ # Replace characters that are forbidden on Windows/Linux/macOS. return re.sub(r'[<>:"/\\|?*]', '_', name).strip() def apply_replacement(name: str, bad_chars: str, replacement_char: str) -> str: """ Replace every occurrence of any character in bad_chars with replacement_char. Args: name: The original string. bad_chars: String containing all characters to replace. replacement_char: The character to insert. Returns: The transformed string. """ if not bad_chars: return name # Build a translation table mapping each bad character to the replacement. translation_table = str.maketrans({c: replacement_char for c in set(bad_chars)}) return name.translate(translation_table) def cleanup_good_chars(name: str, good_char: str) -> str: """ Clean up the filename after replacement to avoid awkward sequences. Steps performed: 1. Strip leading/trailing good_char characters. 2. Collapse consecutive good_char characters into a single one. 3. Remove good_char characters that are adjacent to a single non‑good_char (i.e., pattern good_char + X + good_char becomes just X), and repeat this step until no more such patterns exist. 4. After each iteration, re‑apply stripping and collapsing. This makes filenames more human‑readable, e.g.: "author_-_song" -> "author-song" "hello__world" -> "hello_world" "_hello_" -> "hello" Args: name: The string to clean (after replacement). good_char: The character used as replacement. Returns: The cleaned string. """ # Escape the good_char for regex usage. escaped = re.escape(good_char) # Helper to collapse consecutive good chars and strip edges. def collapse_and_strip(s: str) -> str: # Collapse multiple consecutive good chars into one. s = re.sub(rf'{escaped}+', good_char, s) # Remove leading/trailing good chars. s = s.strip(good_char) return s # Apply stripping and collapsing initially. name = collapse_and_strip(name) # Remove patterns: good_char + X + good_char (where X is any char != good_char). # Repeat until no further changes. while True: # Replace pattern: good_char (non-good-char) good_char -> just the non-good-char. # The negative lookahead ensures X is not the good_char. pattern = rf'{escaped}([^{escaped}]){escaped}' new_name = re.sub(pattern, r'\1', name) if new_name == name: break name = new_name # Re‑apply stripping and collapsing after removal. name = collapse_and_strip(name) return name def apply_template(template: str, placeholders: dict) -> str: """ Replace placeholders in a template string with values from a dict. Placeholders are of the form %key (e.g., %tn, %an). Use %% to escape a literal percent sign. Args: template: The template string. placeholders: Dict mapping placeholder names to values. Returns: The rendered string. """ import re result = [] i = 0 while i < len(template): ch = template[i] if ch == '%': if i + 1 < len(template) and template[i + 1] == '%': result.append('%') i += 2 continue match = re.match(r'%([a-zA-Z]+)', template[i:]) if match: key = match.group(1) value = placeholders.get(key, '') result.append(value) i += len(match.group(0)) else: result.append(ch) i += 1 else: result.append(ch) i += 1 return ''.join(result)