diff --git a/pyproject.toml b/pyproject.toml index 9dc4fc1..308fdc2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "lrx-cli" -version = "0.7.8" +version = "0.7.9" description = "Fetch line-synced lyrics for your music player." readme = "README.md" requires-python = ">=3.13" diff --git a/src/lrx_cli/__init__.py b/src/lrx_cli/__init__.py index e69de29..7e57b97 100644 --- a/src/lrx_cli/__init__.py +++ b/src/lrx_cli/__init__.py @@ -0,0 +1,21 @@ +from .config import AppConfig, GeneralConfig, CredentialConfig, load_config +from .core import LrcManager +from .models import CacheStatus, TrackMeta, LyricResult +from .lrc import LRCData, LyricLine +from .fetchers import FetcherMethodType +from .utils import get_sidecar_path + +__all__ = [ + "AppConfig", + "GeneralConfig", + "CredentialConfig", + "load_config", + "LrcManager", + "CacheStatus", + "TrackMeta", + "LRCData", + "LyricLine", + "LyricResult", + "FetcherMethodType", + "get_sidecar_path", +] diff --git a/src/lrx_cli/cache.py b/src/lrx_cli/cache.py index 498cb28..256dcec 100644 --- a/src/lrx_cli/cache.py +++ b/src/lrx_cli/cache.py @@ -24,7 +24,7 @@ from .config import ( SLOT_UNSYNCED, ) from .models import TrackMeta, LyricResult, CacheStatus -from .ranking import is_positive_status, select_best_positive +from .utils import is_positive_status, select_best_positive _ALL_SLOTS = (SLOT_SYNCED, SLOT_UNSYNCED) diff --git a/src/lrx_cli/cli.py b/src/lrx_cli/cli.py index ca3c66a..c781f1f 100644 --- a/src/lrx_cli/cli.py +++ b/src/lrx_cli/cli.py @@ -23,11 +23,11 @@ from .config import ( load_config, enable_debug, ) +from .utils import get_sidecar_path from .models import TrackMeta from .mpris import get_current_track from .core import LrcManager from .fetchers import FetcherMethodType -from .lrc import get_sidecar_path from .watch import WatchCoordinator from .watch.control import ControlClient, parse_delta from .watch.view.pipe import PipeOutput diff --git a/src/lrx_cli/core.py b/src/lrx_cli/core.py index b5e8a7c..c0aa50a 100644 --- a/src/lrx_cli/core.py +++ b/src/lrx_cli/core.py @@ -28,7 +28,7 @@ from .config import ( ) from .models import TrackMeta, LyricResult, CacheStatus from .enrichers import create_enrichers, enrich_track -from .ranking import is_better_result, select_best_positive +from .utils import is_better_result, select_best_positive # Maps CacheStatus to the default TTL used when storing results diff --git a/src/lrx_cli/enrichers/audio_tag.py b/src/lrx_cli/enrichers/audio_tag.py index 9299434..acc9acb 100644 --- a/src/lrx_cli/enrichers/audio_tag.py +++ b/src/lrx_cli/enrichers/audio_tag.py @@ -12,7 +12,7 @@ from mutagen._file import File, FileType from .base import BaseEnricher from ..models import TrackMeta -from ..lrc import get_audio_path +from ..utils import get_audio_path class AudioTagEnricher(BaseEnricher): diff --git a/src/lrx_cli/enrichers/file_name.py b/src/lrx_cli/enrichers/file_name.py index a5bf37b..8604116 100644 --- a/src/lrx_cli/enrichers/file_name.py +++ b/src/lrx_cli/enrichers/file_name.py @@ -12,7 +12,7 @@ from loguru import logger from .base import BaseEnricher from ..models import TrackMeta -from ..lrc import get_audio_path +from ..utils import get_audio_path # Common track-number prefixes: "01 - ", "01. ", "1 - ", etc. diff --git a/src/lrx_cli/fetchers/local.py b/src/lrx_cli/fetchers/local.py index ccf7408..d5e7136 100644 --- a/src/lrx_cli/fetchers/local.py +++ b/src/lrx_cli/fetchers/local.py @@ -16,7 +16,8 @@ from mutagen.flac import FLAC from .base import BaseFetcher, FetchResult from ..models import CacheStatus, TrackMeta, LyricResult -from ..lrc import get_audio_path, get_sidecar_path, LRCData +from ..lrc import LRCData +from ..utils import get_audio_path, get_sidecar_path class LocalFetcher(BaseFetcher): diff --git a/src/lrx_cli/lrc.py b/src/lrx_cli/lrc.py index 1106350..e86867b 100644 --- a/src/lrx_cli/lrc.py +++ b/src/lrx_cli/lrc.py @@ -9,9 +9,7 @@ from __future__ import annotations from abc import ABC, abstractmethod from dataclasses import dataclass, field import re -from pathlib import Path from typing import Optional -from urllib.parse import unquote from .models import CacheStatus @@ -465,34 +463,3 @@ class LRCData: """ normalized = self.normalize() return self._serialize_lines(normalized._lines, include_word_sync=False) - - -def get_audio_path(audio_url: str, ensure_exists: bool = False) -> Optional[Path]: - """Convert file:// URL to Path, return None if invalid or (if ensure_exists) file doesn't exist.""" - if not audio_url.startswith("file://"): - return None - file_path = unquote(audio_url.replace("file://", "", 1)) - path = Path(file_path) - if ensure_exists and not path.exists(): - return None - return path - - -def get_sidecar_path( - audio_url: str, - ensure_audio_exists: bool = False, - ensure_exists: bool = False, - extension: str = ".lrc", -) -> Optional[Path]: - """Given a file:// URL, return the corresponding .lrc sidecar path. - - If ensure_audio_exists is True, return None if the audio file does not exist. - If ensure_exists is True, return None if the .lrc file does not exist. - """ - audio_path = get_audio_path(audio_url, ensure_exists=ensure_audio_exists) - if not audio_path: - return None - lrc_path = audio_path.with_suffix(extension) - if ensure_exists and not lrc_path.exists(): - return None - return lrc_path diff --git a/src/lrx_cli/ranking.py b/src/lrx_cli/utils.py similarity index 54% rename from src/lrx_cli/ranking.py rename to src/lrx_cli/utils.py index 00ef040..1b67018 100644 --- a/src/lrx_cli/ranking.py +++ b/src/lrx_cli/utils.py @@ -1,14 +1,56 @@ -"""Shared ranking rules for LyricResult selection. - -This module centralizes how positive lyric results are compared so cache/core -and other callers use the same precedence and edge-case handling. +""" +Author: Uyanide pywang0608@foxmail.com +Date: 2026-04-10 17:06:37 +Description: Utility functions """ from __future__ import annotations -from typing import Optional +from typing import TYPE_CHECKING, Optional +from urllib.parse import unquote +from pathlib import Path -from .models import CacheStatus, LyricResult +from .models import CacheStatus + +if TYPE_CHECKING: + from .models import LyricResult + + +# Paths + + +def get_audio_path(audio_url: str, ensure_exists: bool = False) -> Optional[Path]: + """Convert file:// URL to Path, return None if invalid or (if ensure_exists) file doesn't exist.""" + if not audio_url.startswith("file://"): + return None + file_path = unquote(audio_url.replace("file://", "", 1)) + path = Path(file_path) + if ensure_exists and not path.exists(): + return None + return path + + +def get_sidecar_path( + audio_url: str, + ensure_audio_exists: bool = False, + ensure_exists: bool = False, + extension: str = ".lrc", +) -> Optional[Path]: + """Given a file:// URL, return the corresponding .lrc sidecar path. + + If ensure_audio_exists is True, return None if the audio file does not exist. + If ensure_exists is True, return None if the .lrc file does not exist. + """ + audio_path = get_audio_path(audio_url, ensure_exists=ensure_audio_exists) + if not audio_path: + return None + lrc_path = audio_path.with_suffix(extension) + if ensure_exists and not lrc_path.exists(): + return None + return lrc_path + + +# Ranking def is_positive_status(status: CacheStatus) -> bool: diff --git a/uv.lock b/uv.lock index 9a53e49..272f10c 100644 --- a/uv.lock +++ b/uv.lock @@ -153,7 +153,7 @@ wheels = [ [[package]] name = "lrx-cli" -version = "0.7.8" +version = "0.7.9" source = { editable = "." } dependencies = [ { name = "cyclopts" },