|
|
"""Filename parsing helpers used by import flows."""
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
import os
|
|
|
import re
|
|
|
from typing import Any, Dict
|
|
|
|
|
|
|
|
|
_TRACK_PATTERNS = (
|
|
|
r"^(\d+)\s*[-\.]\s*(.+?)\s*[-–]\s*(.+)$",
|
|
|
r"^(.+?)\s*[-–]\s*(.+)$",
|
|
|
r"^(\d+)\s*[-\.]\s*(.+)$",
|
|
|
)
|
|
|
|
|
|
|
|
|
def extract_track_number_from_filename(filename: str, title: str = None) -> int:
|
|
|
"""Extract track number from a filename. Returns 1 if not found."""
|
|
|
basename = os.path.splitext(os.path.basename(filename))[0].strip()
|
|
|
|
|
|
match = re.match(r"^\d[\-\.](\d{1,2})\s*[\-\.]\s*", basename)
|
|
|
if match:
|
|
|
num = int(match.group(1))
|
|
|
if 1 <= num <= 99:
|
|
|
return num
|
|
|
|
|
|
match = re.match(r"^\(?(\d{1,3})\)?\s*[\-\.)\]]\s*", basename)
|
|
|
if match:
|
|
|
num = int(match.group(1))
|
|
|
if 1 <= num <= 999:
|
|
|
return num
|
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
def parse_filename_metadata(filename: str) -> Dict[str, Any]:
|
|
|
"""Extract artist/title/album hints from a loose filename."""
|
|
|
raw_path = str(filename or "")
|
|
|
normalized_path = raw_path.replace("\\", "/")
|
|
|
base_name = os.path.splitext(os.path.basename(normalized_path))[0]
|
|
|
|
|
|
result: Dict[str, Any] = {
|
|
|
"artist": "",
|
|
|
"title": "",
|
|
|
"album": "",
|
|
|
"track_number": None,
|
|
|
}
|
|
|
|
|
|
if not base_name:
|
|
|
return result
|
|
|
|
|
|
for pattern in _TRACK_PATTERNS:
|
|
|
match = re.match(pattern, base_name)
|
|
|
if not match:
|
|
|
continue
|
|
|
|
|
|
groups = match.groups()
|
|
|
if len(groups) == 3:
|
|
|
try:
|
|
|
result["track_number"] = int(groups[0])
|
|
|
result["artist"] = result["artist"] or groups[1].strip()
|
|
|
result["title"] = result["title"] or groups[2].strip()
|
|
|
except ValueError:
|
|
|
result["artist"] = result["artist"] or groups[0].strip()
|
|
|
result["title"] = result["title"] or f"{groups[1]} - {groups[2]}".strip()
|
|
|
elif len(groups) == 2:
|
|
|
if groups[0].isdigit():
|
|
|
try:
|
|
|
result["track_number"] = int(groups[0])
|
|
|
result["title"] = result["title"] or groups[1].strip()
|
|
|
except ValueError:
|
|
|
pass
|
|
|
else:
|
|
|
result["artist"] = result["artist"] or groups[0].strip()
|
|
|
result["title"] = result["title"] or groups[1].strip()
|
|
|
break
|
|
|
|
|
|
if not result["title"]:
|
|
|
result["title"] = base_name
|
|
|
|
|
|
if not result["album"] and "/" in normalized_path:
|
|
|
path_parts = normalized_path.split("/")
|
|
|
for part in reversed(path_parts[:-1]):
|
|
|
if not part or part.startswith("@"):
|
|
|
continue
|
|
|
|
|
|
cleaned = re.sub(r"^\d+\s*[-\.]\s*", "", part).strip()
|
|
|
if len(cleaned) > 3:
|
|
|
result["album"] = cleaned
|
|
|
break
|
|
|
|
|
|
return result
|