This commit is contained in:
nose
2025-12-07 00:21:30 -08:00
parent f29709d951
commit 6b05dc5552
23 changed files with 2196 additions and 1133 deletions

View File

@@ -12,8 +12,8 @@ from __future__ import annotations
import sys
from helper.logger import log
from helper.metadata_search import get_metadata_provider
from helper.logger import log, debug
from helper.metadata_search import get_metadata_provider, list_metadata_providers
import subprocess
from pathlib import Path
from typing import Any, Dict, List, Optional, Sequence, Tuple
@@ -475,6 +475,21 @@ def _extract_scrapable_identifiers(tags_list: List[str]) -> Dict[str, str]:
return identifiers
def _extract_tag_value(tags_list: List[str], namespace: str) -> Optional[str]:
"""Get first tag value for a namespace (e.g., artist:, title:)."""
ns = namespace.lower()
for tag in tags_list:
if not isinstance(tag, str) or ':' not in tag:
continue
prefix, _, value = tag.partition(':')
if prefix.strip().lower() != ns:
continue
candidate = value.strip()
if candidate:
return candidate
return None
def _scrape_url_metadata(url: str) -> Tuple[Optional[str], List[str], List[Tuple[str, str]], List[Dict[str, Any]]]:
"""Scrape metadata from a URL using yt-dlp.
@@ -1012,6 +1027,25 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
--emit: Emit result without interactive prompt (quiet mode)
-scrape <url|provider>: Scrape metadata from URL or provider name (itunes, openlibrary, googlebooks)
"""
args_list = [str(arg) for arg in (args or [])]
raw_args = list(args_list)
# Support numeric selection tokens (e.g., "@1" leading to argument "1") without treating
# them as hash overrides. This lets users pick from the most recent table overlay/results.
if len(args_list) == 1:
token = args_list[0]
if not token.startswith("-") and token.isdigit():
try:
idx = int(token) - 1
items_pool = ctx.get_last_result_items()
if 0 <= idx < len(items_pool):
result = items_pool[idx]
args_list = []
debug(f"[get_tag] Resolved numeric selection arg {token} -> last_result_items[{idx}]")
else:
debug(f"[get_tag] Numeric selection arg {token} out of range (items={len(items_pool)})")
except Exception as exc:
debug(f"[get_tag] Failed to resolve numeric selection arg {token}: {exc}")
# Helper to get field from both dict and object
def get_field(obj: Any, field: str, default: Any = None) -> Any:
if isinstance(obj, dict):
@@ -1020,10 +1054,10 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
return getattr(obj, field, default)
# Parse arguments using shared parser
parsed_args = parse_cmdlet_args(args, CMDLET)
parsed_args = parse_cmdlet_args(args_list, CMDLET)
# Detect if -scrape flag was provided without a value (parse_cmdlet_args skips missing values)
scrape_flag_present = any(str(arg).lower() in {"-scrape", "--scrape"} for arg in args)
scrape_flag_present = any(str(arg).lower() in {"-scrape", "--scrape"} for arg in args_list)
# Extract values
hash_override_raw = parsed_args.get("hash")
@@ -1033,10 +1067,14 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
scrape_url = parsed_args.get("scrape")
scrape_requested = scrape_flag_present or scrape_url is not None
explicit_hash_flag = any(str(arg).lower() in {"-hash", "--hash"} for arg in raw_args)
if hash_override_raw is not None:
if not hash_override or not looks_like_hash(hash_override):
log("Invalid hash format: expected 64 hex characters", file=sys.stderr)
return 1
debug(f"[get_tag] Ignoring invalid hash override '{hash_override_raw}' (explicit_flag={explicit_hash_flag})")
if explicit_hash_flag:
log("Invalid hash format: expected 64 hex characters", file=sys.stderr)
return 1
hash_override = None
if scrape_requested and (not scrape_url or str(scrape_url).strip() == ""):
log("-scrape requires a URL or provider name", file=sys.stderr)
@@ -1085,6 +1123,9 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
identifier_tags = [str(t) for t in tags_from_sidecar if isinstance(t, (str, bytes))]
except Exception:
pass
title_from_tags = _extract_tag_value(identifier_tags, "title")
artist_from_tags = _extract_tag_value(identifier_tags, "artist")
identifiers = _extract_scrapable_identifiers(identifier_tags)
identifier_query: Optional[str] = None
@@ -1095,19 +1136,35 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
identifier_query = identifiers.get("musicbrainz") or identifiers.get("musicbrainzalbum")
# Determine query from identifier first, else title on the result or filename
title_hint = get_field(result, "title", None) or get_field(result, "name", None)
title_hint = title_from_tags or get_field(result, "title", None) or get_field(result, "name", None)
if not title_hint:
file_path = get_field(result, "path", None) or get_field(result, "filename", None)
if file_path:
title_hint = Path(str(file_path)).stem
artist_hint = artist_from_tags or get_field(result, "artist", None) or get_field(result, "uploader", None)
if not artist_hint:
meta_field = get_field(result, "metadata", None)
if isinstance(meta_field, dict):
meta_artist = meta_field.get("artist") or meta_field.get("uploader")
if meta_artist:
artist_hint = str(meta_artist)
combined_query: Optional[str] = None
if not identifier_query and title_hint and artist_hint and provider.name in {"itunes", "musicbrainz"}:
if provider.name == "musicbrainz":
combined_query = f'recording:"{title_hint}" AND artist:"{artist_hint}"'
else:
combined_query = f"{title_hint} {artist_hint}"
query_hint = identifier_query or title_hint
query_hint = identifier_query or combined_query or title_hint
if not query_hint:
log("No title or identifier available to search for metadata", file=sys.stderr)
return 1
if identifier_query:
log(f"Using identifier for metadata search: {identifier_query}")
elif combined_query:
log(f"Using title+artist for metadata search: {title_hint} - {artist_hint}")
else:
log(f"Using title for metadata search: {query_hint}")
@@ -1319,6 +1376,13 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
return 0
_SCRAPE_CHOICES = []
try:
_SCRAPE_CHOICES = sorted(list_metadata_providers().keys())
except Exception:
_SCRAPE_CHOICES = ["itunes", "openlibrary", "googlebooks", "google", "musicbrainz"]
CMDLET = Cmdlet(
name="get-tag",
summary="Get tags from Hydrus or local sidecar metadata",
@@ -1341,8 +1405,9 @@ CMDLET = Cmdlet(
CmdletArg(
name="-scrape",
type="string",
description="Scrape metadata from URL or provider name (returns tags as JSON or table)",
required=False
description="Scrape metadata from URL or provider name (returns tags as JSON or table)",
required=False,
choices=_SCRAPE_CHOICES,
)
]
)