Compare commits
3 Commits
09d7945b27
...
1368d51d4b
| Author | SHA1 | Date | |
|---|---|---|---|
|
1368d51d4b
|
|||
|
9cdc368a5b
|
|||
|
942615348d
|
@@ -0,0 +1 @@
|
|||||||
|
claude --resume 48d54aac-a89b-48c3-8a76-23e9eb73722d
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
SPOTIFY_SP_DC="AQBzQVulTMDML5PjpVXadGZIgaPqfLPTxbv1ZSJI4ypO3UKA-ZcbDqU2omZybyH_vqlM67PM33_og7UEvrxEktk2u6ND1syOEPiXjPzr600KNOLVXr9hwuFD9RcXgOGm_aLaqUlYJcum9Zchjq-upKLmyEWCESdGoQGQT8_maQ4pZ-6wFiL5GaDn1WgpZgjW71zxCVqf8GuJoscSXWhUPo0jJ-1xQvyvVT3YooIf9i1mOG-vDnAFru6xeaH4O2mVWQN6PJPVYNG582E"
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"python-envs.defaultEnvManager": "ms-python.python:venv"
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Claude.md
|
||||||
|
|
||||||
|
The role of this file is to describe common mistakes and confusion points that agents might encounter as they work in this project. If you ever encounter something in the project that surprises you, please alert the developer working with you and indicate that this is the case in this file to help prevent future agents from having the same issue.
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# lrcfetch
|
# LRX-CLI
|
||||||
|
|
||||||
A CLI tool for fetching LRC lyrics on Linux. Automatically detects the currently playing track via MPRIS/DBus and retrieves synced (or plain with all time tags set to `[00:00.00]` if failed to find any synced) lyrics from multiple sources.
|
A CLI tool for fetching LRC lyrics on Linux. Automatically detects the currently playing track via MPRIS/DBus and retrieves synced (or plain with all time tags set to `[00:00.00]` if failed to find any synced) lyrics from multiple sources.
|
||||||
|
|
||||||
@@ -16,76 +16,76 @@ Lyrics are fetched using a fallback pipeline (first synced result wins):
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
See `lrcfetch --help` for full command reference. Common use cases:
|
See `lrx --help` for full command reference. Common use cases:
|
||||||
|
|
||||||
- Fetch lyrics for the currently playing track:
|
- Fetch lyrics for the currently playing track:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
lrcfetch fetch
|
lrx fetch
|
||||||
```
|
```
|
||||||
|
|
||||||
using a specific player or source to fetch from:
|
using a specific player or source to fetch from:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
lrcfetch --player mpd fetch --method lrclib-search
|
lrx --player mpd fetch --method lrclib-search
|
||||||
```
|
```
|
||||||
|
|
||||||
- Search by metadata (bypasses MPRIS):
|
- Search by metadata (bypasses MPRIS):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
lrcfetch search -t "My Love" -a "Westlife"
|
lrx search -t "My Love" -a "Westlife"
|
||||||
lrcfetch search --trackid "5p0ietGkLNEqx1Z7ijkw5g"
|
lrx search --trackid "5p0ietGkLNEqx1Z7ijkw5g"
|
||||||
```
|
```
|
||||||
|
|
||||||
or for a local file:
|
or for a local file:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
lrcfetch search --path "/path/to/Westlife - My Love.flac"
|
lrx search --path "/path/to/Westlife - My Love.flac"
|
||||||
```
|
```
|
||||||
|
|
||||||
- Export to sidecar `.lrc` file:
|
- Export to sidecar `.lrc` file:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
lrcfetch export
|
lrx export
|
||||||
```
|
```
|
||||||
|
|
||||||
or to a custom path:
|
or to a custom path:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
lrcfetch export --output /path/to/lyrics.lrc
|
lrx export --output /path/to/lyrics.lrc
|
||||||
```
|
```
|
||||||
|
|
||||||
- Cache management:
|
- Cache management:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
lrcfetch cache stats # show cache statistics
|
lrx cache stats # show cache statistics
|
||||||
lrcfetch cache query # query cache for current track
|
lrx cache query # query cache for current track
|
||||||
lrcfetch cache clear # clears cache of current track
|
lrx cache clear # clears cache of current track
|
||||||
lrcfetch cache clear --all # clears entire cache
|
lrx cache clear --all # clears entire cache
|
||||||
```
|
```
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Set credentials via environment variable or `.env` file:
|
Set credentials via environment variable or `.env` file:
|
||||||
|
|
||||||
- `~/.config/lrcfetch/.env` — user-level
|
- `~/.config/lrx/.env` — user-level
|
||||||
- `.env` in working directory — project-local
|
- `.env` in working directory — project-local
|
||||||
- Shell environment — highest priority
|
- Shell environment — highest priority
|
||||||
|
|
||||||
```env
|
```env
|
||||||
SPOTIFY_SP_DC=your_cookie_value
|
SPOTIFY_SP_DC=your_cookie_value
|
||||||
QQ_MUSIC_API_URL=https://api.example.com
|
QQ_MUSIC_API_URL=https://api.example.com
|
||||||
LRCFETCH_PLAYER=spotify
|
PREFERRED_PLAYER=spotify
|
||||||
```
|
```
|
||||||
|
|
||||||
- `SPOTIFY_SP_DC` — required for Spotify source. Defaults to empty (disabled Spotify source).
|
- `SPOTIFY_SP_DC` — required for Spotify source. Defaults to empty (disabled Spotify source).
|
||||||
- `QQ_MUSIC_API_URL` — required for QQ Music source. Defaults to empty (disabled QQ Music source).
|
- `QQ_MUSIC_API_URL` — required for QQ Music source. Defaults to empty (disabled QQ Music source).
|
||||||
- `LRCFETCH_PLAYER` — preferred MPRIS player when multiple are active. Defaults to `spotify`. Only used when no `--player` flag is given and more than one player (or none of them) is currently playing.
|
- `PREFERRED_PLAYER` — preferred MPRIS player when multiple are active. Defaults to `spotify`. Only used when no `--player` flag is given and more than one player (or none of them) is currently playing.
|
||||||
|
|
||||||
Shell completion (zsh/fish/bash):
|
Shell completion (zsh/fish/bash):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
lrcfetch --install-completion
|
lrx --install-completion
|
||||||
```
|
```
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from lrcfetch.cli import run
|
from lrx_cli.cli import run
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
run()
|
run()
|
||||||
@@ -22,7 +22,7 @@ from .lrc import get_sidecar_path
|
|||||||
|
|
||||||
|
|
||||||
app = cyclopts.App(
|
app = cyclopts.App(
|
||||||
help="LRCFetch — Fetch line-synced lyrics for your music player.",
|
help="LRX-CLI — Fetch line-synced lyrics for your music player.",
|
||||||
)
|
)
|
||||||
app.register_install_completion_command()
|
app.register_install_completion_command()
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ from loguru import logger
|
|||||||
from importlib.metadata import version
|
from importlib.metadata import version
|
||||||
|
|
||||||
# Application
|
# Application
|
||||||
APP_NAME = "lrcfetch"
|
APP_NAME = "lrx-cli"
|
||||||
APP_AUTHOR = "Uyanide"
|
APP_AUTHOR = "Uyanide"
|
||||||
APP_VERSION = version(APP_NAME)
|
APP_VERSION = version(APP_NAME)
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ DB_PATH = os.path.join(CACHE_DIR, "cache.db")
|
|||||||
|
|
||||||
# .env loading
|
# .env loading
|
||||||
_config_env = Path(user_config_dir(APP_NAME, APP_AUTHOR)) / ".env"
|
_config_env = Path(user_config_dir(APP_NAME, APP_AUTHOR)) / ".env"
|
||||||
load_dotenv(_config_env) # ~/.config/lrcfetch/.env
|
load_dotenv(_config_env) # ~/.config/lrx-cli/.env
|
||||||
load_dotenv() # .env in cwd (does NOT override existing vars)
|
load_dotenv() # .env in cwd (does NOT override existing vars)
|
||||||
|
|
||||||
# HTTP
|
# HTTP
|
||||||
@@ -62,11 +62,11 @@ LRCLIB_SEARCH_URL = "https://lrclib.net/api/search"
|
|||||||
QQ_MUSIC_API_URL = os.environ.get("QQ_MUSIC_API_URL", "").rstrip("/")
|
QQ_MUSIC_API_URL = os.environ.get("QQ_MUSIC_API_URL", "").rstrip("/")
|
||||||
|
|
||||||
# Player preference (used when multiple MPRIS players are active)
|
# Player preference (used when multiple MPRIS players are active)
|
||||||
PREFERRED_PLAYER = os.environ.get("LRCFETCH_PLAYER", "spotify")
|
PREFERRED_PLAYER = os.environ.get("PREFERRED_PLAYER", "spotify")
|
||||||
|
|
||||||
# User-Agents
|
# User-Agents
|
||||||
UA_BROWSER = "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0"
|
UA_BROWSER = "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0"
|
||||||
UA_LRCFETCH = f"LRCFetch {APP_VERSION} (https://github.com/Uyanide/lrcfetch)"
|
UA_LRX = f"LRX-CLI {APP_VERSION} (https://github.com/Uyanide/lrx-cli)"
|
||||||
|
|
||||||
os.makedirs(CACHE_DIR, exist_ok=True)
|
os.makedirs(CACHE_DIR, exist_ok=True)
|
||||||
|
|
||||||
@@ -35,5 +35,6 @@ def enrich_track(track: TrackMeta) -> TrackMeta:
|
|||||||
# Only apply fields that are still None
|
# Only apply fields that are still None
|
||||||
updates = {k: v for k, v in result.items() if getattr(track, k, None) is None}
|
updates = {k: v for k, v in result.items() if getattr(track, k, None) is None}
|
||||||
if updates:
|
if updates:
|
||||||
track = track.model_copy(update=updates)
|
for k, v in updates.items():
|
||||||
|
setattr(track, k, v)
|
||||||
return track
|
return track
|
||||||
@@ -22,7 +22,7 @@ from ..config import (
|
|||||||
TTL_NOT_FOUND,
|
TTL_NOT_FOUND,
|
||||||
TTL_NETWORK_ERROR,
|
TTL_NETWORK_ERROR,
|
||||||
LRCLIB_API_URL,
|
LRCLIB_API_URL,
|
||||||
UA_LRCFETCH,
|
UA_LRX,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ class LrclibFetcher(BaseFetcher):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
with httpx.Client(timeout=HTTP_TIMEOUT) as client:
|
with httpx.Client(timeout=HTTP_TIMEOUT) as client:
|
||||||
resp = client.get(url, headers={"User-Agent": UA_LRCFETCH})
|
resp = client.get(url, headers={"User-Agent": UA_LRX})
|
||||||
|
|
||||||
if resp.status_code == 404:
|
if resp.status_code == 404:
|
||||||
logger.debug(f"LRCLIB: not found for {track.display_name()}")
|
logger.debug(f"LRCLIB: not found for {track.display_name()}")
|
||||||
@@ -24,7 +24,7 @@ from ..config import (
|
|||||||
TTL_NETWORK_ERROR,
|
TTL_NETWORK_ERROR,
|
||||||
DURATION_TOLERANCE_MS,
|
DURATION_TOLERANCE_MS,
|
||||||
LRCLIB_SEARCH_URL,
|
LRCLIB_SEARCH_URL,
|
||||||
UA_LRCFETCH,
|
UA_LRX,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ class LrclibSearchFetcher(BaseFetcher):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
with httpx.Client(timeout=HTTP_TIMEOUT) as client:
|
with httpx.Client(timeout=HTTP_TIMEOUT) as client:
|
||||||
resp = client.get(url, headers={"User-Agent": UA_LRCFETCH})
|
resp = client.get(url, headers={"User-Agent": UA_LRX})
|
||||||
|
|
||||||
if resp.status_code != 200:
|
if resp.status_code != 200:
|
||||||
logger.error(f"LRCLIB-search: API returned {resp.status_code}")
|
logger.error(f"LRCLIB-search: API returned {resp.status_code}")
|
||||||
@@ -8,8 +8,8 @@ import asyncio
|
|||||||
from dbus_next.aio.message_bus import MessageBus
|
from dbus_next.aio.message_bus import MessageBus
|
||||||
from dbus_next.constants import BusType
|
from dbus_next.constants import BusType
|
||||||
from dbus_next.message import Message
|
from dbus_next.message import Message
|
||||||
from lrcfetch.models import TrackMeta
|
from lrx_cli.models import TrackMeta
|
||||||
from lrcfetch.config import PREFERRED_PLAYER
|
from lrx_cli.config import PREFERRED_PLAYER
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from typing import Optional, List, Any
|
from typing import Optional, List, Any
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ async def _select_player(
|
|||||||
|
|
||||||
When specific_player is given, filter by name match.
|
When specific_player is given, filter by name match.
|
||||||
Otherwise: prefer the currently playing player. If multiple are playing,
|
Otherwise: prefer the currently playing player. If multiple are playing,
|
||||||
prefer the one matching LRCFETCH_PLAYER env var (default: spotify).
|
prefer the one matching PREFERRED_PLAYER env var (default: spotify).
|
||||||
"""
|
"""
|
||||||
players = await _list_mpris_players(bus)
|
players = await _list_mpris_players(bus)
|
||||||
if not players:
|
if not players:
|
||||||
@@ -82,7 +82,7 @@ async def _select_player(
|
|||||||
if len(candidates) == 1:
|
if len(candidates) == 1:
|
||||||
return candidates[0]
|
return candidates[0]
|
||||||
|
|
||||||
# Multiple candidates: prefer LRCFETCH_PLAYER
|
# Multiple candidates: prefer PREFERRED_PLAYER
|
||||||
preferred = PREFERRED_PLAYER.lower()
|
preferred = PREFERRED_PLAYER.lower()
|
||||||
if preferred:
|
if preferred:
|
||||||
for p in candidates:
|
for p in candidates:
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from lrcfetch.cli import run
|
from lrx_cli.cli import run
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
run()
|
run()
|
||||||
|
|||||||
+2
-2
@@ -3,7 +3,7 @@ requires = ["hatchling"]
|
|||||||
build-backend = "hatchling.build"
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "lrcfetch"
|
name = "lrx-cli"
|
||||||
version = "0.1.7"
|
version = "0.1.7"
|
||||||
description = "Fetch line-synced lyrics for your music player."
|
description = "Fetch line-synced lyrics for your music player."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@@ -19,7 +19,7 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
lrcfetch = "lrcfetch.cli:run"
|
lrx = "lrx_cli.cli:run"
|
||||||
|
|
||||||
[tool.ruff.lint]
|
[tool.ruff.lint]
|
||||||
ignore = ["E402"]
|
ignore = ["E402"]
|
||||||
|
|||||||
Reference in New Issue
Block a user