Add YAPF style + ignore, and format tracked Python files
This commit is contained in:
@@ -39,8 +39,7 @@ CmdletArg = sh.CmdletArg
|
||||
SharedArgs = sh.SharedArgs
|
||||
parse_cmdlet_args = sh.parse_cmdlet_args
|
||||
get_field = sh.get_field
|
||||
from config import get_local_storage_path
|
||||
|
||||
from SYS.config import get_local_storage_path
|
||||
|
||||
try:
|
||||
from metadata import extract_title
|
||||
@@ -148,7 +147,8 @@ def _resolve_candidate_urls_for_item(
|
||||
result: Any,
|
||||
backend: Any,
|
||||
file_hash: str,
|
||||
config: Dict[str, Any],
|
||||
config: Dict[str,
|
||||
Any],
|
||||
) -> List[str]:
|
||||
"""Get candidate URLs from backend and/or piped result."""
|
||||
try:
|
||||
@@ -165,7 +165,10 @@ def _resolve_candidate_urls_for_item(
|
||||
urls.extend(normalize_urls(backend_urls))
|
||||
else:
|
||||
urls.extend(
|
||||
[str(u).strip() for u in backend_urls if isinstance(u, str) and str(u).strip()]
|
||||
[
|
||||
str(u).strip() for u in backend_urls
|
||||
if isinstance(u, str) and str(u).strip()
|
||||
]
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
@@ -180,7 +183,10 @@ def _resolve_candidate_urls_for_item(
|
||||
raw = meta.get("url")
|
||||
if isinstance(raw, list):
|
||||
urls.extend(
|
||||
[str(u).strip() for u in raw if isinstance(u, str) and str(u).strip()]
|
||||
[
|
||||
str(u).strip() for u in raw
|
||||
if isinstance(u, str) and str(u).strip()
|
||||
]
|
||||
)
|
||||
elif isinstance(raw, str) and raw.strip():
|
||||
urls.append(raw.strip())
|
||||
@@ -203,7 +209,9 @@ def _resolve_candidate_urls_for_item(
|
||||
if isinstance(val, str) and val.strip():
|
||||
urls.append(val.strip())
|
||||
elif isinstance(val, list):
|
||||
urls.extend([str(u).strip() for u in val if isinstance(u, str) and str(u).strip()])
|
||||
urls.extend(
|
||||
[str(u).strip() for u in val if isinstance(u, str) and str(u).strip()]
|
||||
)
|
||||
|
||||
meta_field = _get(result, "metadata", None)
|
||||
if isinstance(meta_field, dict) and meta_field.get("url"):
|
||||
@@ -211,7 +219,9 @@ def _resolve_candidate_urls_for_item(
|
||||
if normalize_urls:
|
||||
urls.extend(normalize_urls(val))
|
||||
elif isinstance(val, list):
|
||||
urls.extend([str(u).strip() for u in val if isinstance(u, str) and str(u).strip()])
|
||||
urls.extend(
|
||||
[str(u).strip() for u in val if isinstance(u, str) and str(u).strip()]
|
||||
)
|
||||
elif isinstance(val, str) and val.strip():
|
||||
urls.append(val.strip())
|
||||
|
||||
@@ -263,7 +273,6 @@ def _pick_supported_ytdlp_url(urls: List[str]) -> Optional[str]:
|
||||
_scrape_isbn_metadata = _ol_scrape_isbn_metadata # type: ignore[assignment]
|
||||
_scrape_openlibrary_metadata = _ol_scrape_openlibrary_metadata # type: ignore[assignment]
|
||||
|
||||
|
||||
# Tag item for ResultTable display and piping
|
||||
from dataclasses import dataclass
|
||||
|
||||
@@ -308,7 +317,8 @@ def _emit_tags_as_table(
|
||||
file_hash: Optional[str],
|
||||
store: str = "hydrus",
|
||||
service_name: Optional[str] = None,
|
||||
config: Optional[Dict[str, Any]] = None,
|
||||
config: Optional[Dict[str,
|
||||
Any]] = None,
|
||||
item_title: Optional[str] = None,
|
||||
path: Optional[str] = None,
|
||||
subject: Optional[Any] = None,
|
||||
@@ -357,7 +367,9 @@ def _emit_tags_as_table(
|
||||
|
||||
def _filter_scraped_tags(tags: List[str]) -> List[str]:
|
||||
"""Filter out tags we don't want to import from scraping."""
|
||||
blocked = {"title", "artist", "source"}
|
||||
blocked = {"title",
|
||||
"artist",
|
||||
"source"}
|
||||
out: List[str] = []
|
||||
seen: set[str] = set()
|
||||
for t in tags:
|
||||
@@ -529,12 +541,14 @@ def _handle_title_rename(old_path: Path, tags_list: List[str]) -> Optional[Path]
|
||||
new_tags_path = old_path.parent / (new_name + ".tag")
|
||||
if new_tags_path.exists():
|
||||
log(
|
||||
f"Warning: Target sidecar already exists: {new_tags_path.name}", file=sys.stderr
|
||||
f"Warning: Target sidecar already exists: {new_tags_path.name}",
|
||||
file=sys.stderr
|
||||
)
|
||||
else:
|
||||
old_tags_path.rename(new_tags_path)
|
||||
log(
|
||||
f"Renamed sidecar: {old_tags_path.name} → {new_tags_path.name}", file=sys.stderr
|
||||
f"Renamed sidecar: {old_tags_path.name} → {new_tags_path.name}",
|
||||
file=sys.stderr
|
||||
)
|
||||
|
||||
return new_path
|
||||
@@ -564,7 +578,10 @@ def _read_sidecar_fallback(p: Path) -> tuple[Optional[str], List[str], List[str]
|
||||
h: Optional[str] = None
|
||||
|
||||
# Namespaces to exclude from tags
|
||||
excluded_namespaces = {"hash", "url", "url", "relationship"}
|
||||
excluded_namespaces = {"hash",
|
||||
"url",
|
||||
"url",
|
||||
"relationship"}
|
||||
|
||||
for line in raw.splitlines():
|
||||
s = line.strip()
|
||||
@@ -594,7 +611,11 @@ def _read_sidecar_fallback(p: Path) -> tuple[Optional[str], List[str], List[str]
|
||||
|
||||
|
||||
def _write_sidecar(
|
||||
p: Path, media: Path, tag_list: List[str], url: List[str], hash_in_sidecar: Optional[str]
|
||||
p: Path,
|
||||
media: Path,
|
||||
tag_list: List[str],
|
||||
url: List[str],
|
||||
hash_in_sidecar: Optional[str]
|
||||
) -> Path:
|
||||
"""Write tags to sidecar file and handle title-based renaming.
|
||||
|
||||
@@ -634,15 +655,17 @@ def _emit_tag_payload(
|
||||
tags_list: List[str],
|
||||
*,
|
||||
hash_value: Optional[str],
|
||||
extra: Optional[Dict[str, Any]] = None,
|
||||
extra: Optional[Dict[str,
|
||||
Any]] = None,
|
||||
store_label: Optional[str] = None,
|
||||
) -> int:
|
||||
"""Emit tag values as structured payload to pipeline."""
|
||||
payload: Dict[str, Any] = {
|
||||
"source": source,
|
||||
"tag": list(tags_list),
|
||||
"count": len(tags_list),
|
||||
}
|
||||
payload: Dict[str,
|
||||
Any] = {
|
||||
"source": source,
|
||||
"tag": list(tags_list),
|
||||
"count": len(tags_list),
|
||||
}
|
||||
if hash_value:
|
||||
payload["hash"] = hash_value
|
||||
if extra:
|
||||
@@ -662,7 +685,11 @@ def _emit_tag_payload(
|
||||
if ctx.get_stage_context() is not None:
|
||||
for idx, tag_name in enumerate(tags_list, start=1):
|
||||
tag_item = TagItem(
|
||||
tag_name=tag_name, tag_index=idx, hash=hash_value, store=source, service_name=None
|
||||
tag_name=tag_name,
|
||||
tag_index=idx,
|
||||
hash=hash_value,
|
||||
store=source,
|
||||
service_name=None
|
||||
)
|
||||
ctx.emit(tag_item)
|
||||
else:
|
||||
@@ -730,7 +757,12 @@ def _extract_tag_value(tags_list: List[str], namespace: str) -> Optional[str]:
|
||||
|
||||
def _scrape_url_metadata(
|
||||
url: str,
|
||||
) -> Tuple[Optional[str], List[str], List[Tuple[str, str]], List[Dict[str, Any]]]:
|
||||
) -> Tuple[Optional[str],
|
||||
List[str],
|
||||
List[Tuple[str,
|
||||
str]],
|
||||
List[Dict[str,
|
||||
Any]]]:
|
||||
"""Scrape metadata from a URL using yt-dlp.
|
||||
|
||||
Returns:
|
||||
@@ -810,10 +842,12 @@ def _scrape_url_metadata(
|
||||
playlist_items.append(
|
||||
{
|
||||
"index": idx,
|
||||
"id": entry.get("id", f"track_{idx}"),
|
||||
"id": entry.get("id",
|
||||
f"track_{idx}"),
|
||||
"title": item_title,
|
||||
"duration": item_duration,
|
||||
"url": entry.get("url") or entry.get("webpage_url", ""),
|
||||
"url": entry.get("url") or entry.get("webpage_url",
|
||||
""),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -837,14 +871,16 @@ def _scrape_url_metadata(
|
||||
|
||||
for tag in entry_tags:
|
||||
# Extract the namespace (part before the colon)
|
||||
tag_namespace = tag.split(":", 1)[0].lower() if ":" in tag else None
|
||||
tag_namespace = tag.split(":",
|
||||
1)[0].lower(
|
||||
) if ":" in tag else None
|
||||
|
||||
# Skip if this namespace already exists in tags (from album level)
|
||||
if tag_namespace and tag_namespace in single_value_namespaces:
|
||||
# Check if any tag with this namespace already exists in tags
|
||||
already_has_namespace = any(
|
||||
t.split(":", 1)[0].lower() == tag_namespace
|
||||
for t in tags
|
||||
t.split(":",
|
||||
1)[0].lower() == tag_namespace for t in tags
|
||||
if ":" in t
|
||||
)
|
||||
if already_has_namespace:
|
||||
@@ -858,8 +894,21 @@ def _scrape_url_metadata(
|
||||
elif (data.get("playlist_count") or 0) > 0 and "entries" not in data:
|
||||
try:
|
||||
# Make a second call with --flat-playlist to get the actual tracks
|
||||
flat_cmd = ["yt-dlp", "-j", "--no-warnings", "--flat-playlist", "-f", "best", url]
|
||||
flat_result = subprocess.run(flat_cmd, capture_output=True, text=True, timeout=30)
|
||||
flat_cmd = [
|
||||
"yt-dlp",
|
||||
"-j",
|
||||
"--no-warnings",
|
||||
"--flat-playlist",
|
||||
"-f",
|
||||
"best",
|
||||
url
|
||||
]
|
||||
flat_result = subprocess.run(
|
||||
flat_cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30
|
||||
)
|
||||
if flat_result.returncode == 0:
|
||||
flat_lines = flat_result.stdout.strip().split("\n")
|
||||
# With --flat-playlist, each line is a separate track JSON object
|
||||
@@ -868,15 +917,27 @@ def _scrape_url_metadata(
|
||||
if line.strip().startswith("{"):
|
||||
try:
|
||||
entry = json_module.loads(line)
|
||||
item_title = entry.get("title", entry.get("id", f"Track {idx}"))
|
||||
item_title = entry.get(
|
||||
"title",
|
||||
entry.get("id",
|
||||
f"Track {idx}")
|
||||
)
|
||||
item_duration = entry.get("duration", 0)
|
||||
playlist_items.append(
|
||||
{
|
||||
"index": idx,
|
||||
"id": entry.get("id", f"track_{idx}"),
|
||||
"title": item_title,
|
||||
"duration": item_duration,
|
||||
"url": entry.get("url") or entry.get("webpage_url", ""),
|
||||
"index":
|
||||
idx,
|
||||
"id":
|
||||
entry.get("id",
|
||||
f"track_{idx}"),
|
||||
"title":
|
||||
item_title,
|
||||
"duration":
|
||||
item_duration,
|
||||
"url":
|
||||
entry.get("url")
|
||||
or entry.get("webpage_url",
|
||||
""),
|
||||
}
|
||||
)
|
||||
except json_module.JSONDecodeError:
|
||||
@@ -935,7 +996,9 @@ def _extract_url_formats(formats: list) -> List[Tuple[str, str]]:
|
||||
if height < 480:
|
||||
continue
|
||||
res_key = f"{height}p"
|
||||
if res_key not in video_formats or tbr > video_formats[res_key].get("tbr", 0):
|
||||
if res_key not in video_formats or tbr > video_formats[res_key].get(
|
||||
"tbr",
|
||||
0):
|
||||
video_formats[res_key] = {
|
||||
"label": f"{height}p ({ext})",
|
||||
"format_id": format_id,
|
||||
@@ -945,7 +1008,9 @@ def _extract_url_formats(formats: list) -> List[Tuple[str, str]]:
|
||||
# Audio-only format
|
||||
elif acodec and acodec != "none" and (not vcodec or vcodec == "none"):
|
||||
audio_key = f"audio_{abr}"
|
||||
if audio_key not in audio_formats or abr > audio_formats[audio_key].get("abr", 0):
|
||||
if audio_key not in audio_formats or abr > audio_formats[audio_key].get(
|
||||
"abr",
|
||||
0):
|
||||
audio_formats[audio_key] = {
|
||||
"label": f"audio ({ext})",
|
||||
"format_id": format_id,
|
||||
@@ -955,9 +1020,9 @@ def _extract_url_formats(formats: list) -> List[Tuple[str, str]]:
|
||||
result = []
|
||||
|
||||
# Add video formats in descending resolution order
|
||||
for res in sorted(
|
||||
video_formats.keys(), key=lambda x: int(x.replace("p", "")), reverse=True
|
||||
):
|
||||
for res in sorted(video_formats.keys(),
|
||||
key=lambda x: int(x.replace("p", "")),
|
||||
reverse=True):
|
||||
fmt = video_formats[res]
|
||||
result.append((fmt["label"], fmt["format_id"]))
|
||||
|
||||
@@ -1019,12 +1084,15 @@ def _perform_scraping(tags_list: List[str]) -> List[str]:
|
||||
log(f"Scraping OpenLibrary: {olid}")
|
||||
new_tags.extend(_scrape_openlibrary_metadata(olid))
|
||||
elif "isbn_13" in identifiers or "isbn_10" in identifiers or "isbn" in identifiers:
|
||||
isbn = identifiers.get("isbn_13") or identifiers.get("isbn_10") or identifiers.get("isbn")
|
||||
isbn = identifiers.get("isbn_13") or identifiers.get(
|
||||
"isbn_10"
|
||||
) or identifiers.get("isbn")
|
||||
if isbn:
|
||||
log(f"Scraping ISBN: {isbn}")
|
||||
new_tags.extend(_scrape_isbn_metadata(isbn))
|
||||
|
||||
existing_tags_lower = {tag.lower() for tag in tags_list}
|
||||
existing_tags_lower = {tag.lower()
|
||||
for tag in tags_list}
|
||||
scraped_unique = []
|
||||
seen = set()
|
||||
for tag in new_tags:
|
||||
@@ -1074,7 +1142,9 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
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}")
|
||||
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:
|
||||
@@ -1087,7 +1157,10 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
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_list)
|
||||
scrape_flag_present = any(
|
||||
str(arg).lower() in {"-scrape",
|
||||
"--scrape"} for arg in args_list
|
||||
)
|
||||
|
||||
# Extract values
|
||||
query_raw = parsed_args.get("query")
|
||||
@@ -1122,39 +1195,54 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
# NOTE: We intentionally do not reuse _scrape_url_metadata() here because it
|
||||
# performs namespace deduplication that would collapse multi-valued tags.
|
||||
file_hash = normalize_hash(hash_override) or normalize_hash(
|
||||
get_field(result, "hash", None)
|
||||
get_field(result,
|
||||
"hash",
|
||||
None)
|
||||
)
|
||||
store_name = get_field(result, "store", None)
|
||||
subject_path = (
|
||||
get_field(result, "path", None)
|
||||
or get_field(result, "target", None)
|
||||
or get_field(result, "filename", None)
|
||||
get_field(result,
|
||||
"path",
|
||||
None) or get_field(result,
|
||||
"target",
|
||||
None)
|
||||
or get_field(result,
|
||||
"filename",
|
||||
None)
|
||||
)
|
||||
item_title = (
|
||||
get_field(result, "title", None)
|
||||
or get_field(result, "name", None)
|
||||
or get_field(result, "filename", None)
|
||||
get_field(result,
|
||||
"title",
|
||||
None) or get_field(result,
|
||||
"name",
|
||||
None)
|
||||
or get_field(result,
|
||||
"filename",
|
||||
None)
|
||||
)
|
||||
|
||||
# Only run overwrite-apply when the item is store-backed.
|
||||
# If this is a URL-only PipeObject, fall through to provider mode below.
|
||||
if (
|
||||
file_hash
|
||||
and store_name
|
||||
and str(file_hash).strip().lower() != "unknown"
|
||||
and str(store_name).strip().upper() not in {"PATH", "URL"}
|
||||
):
|
||||
if (file_hash and store_name and str(file_hash).strip().lower() != "unknown"
|
||||
and str(store_name).strip().upper() not in {"PATH",
|
||||
"URL"}):
|
||||
try:
|
||||
from Store import Store
|
||||
|
||||
storage = Store(config)
|
||||
backend = storage[str(store_name)]
|
||||
except Exception as exc:
|
||||
log(f"Failed to resolve store backend '{store_name}': {exc}", file=sys.stderr)
|
||||
log(
|
||||
f"Failed to resolve store backend '{store_name}': {exc}",
|
||||
file=sys.stderr
|
||||
)
|
||||
return 1
|
||||
|
||||
candidate_urls = _resolve_candidate_urls_for_item(
|
||||
result, backend, file_hash, config
|
||||
result,
|
||||
backend,
|
||||
file_hash,
|
||||
config
|
||||
)
|
||||
scrape_target = _pick_supported_ytdlp_url(candidate_urls)
|
||||
if not scrape_target:
|
||||
@@ -1201,7 +1289,8 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
try:
|
||||
tags.extend(
|
||||
_extract_subtitle_tags(
|
||||
info_for_subs if isinstance(info_for_subs, dict) else {}
|
||||
info_for_subs if isinstance(info_for_subs,
|
||||
dict) else {}
|
||||
)
|
||||
)
|
||||
except Exception:
|
||||
@@ -1220,7 +1309,11 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
existing_tags = []
|
||||
try:
|
||||
if existing_tags:
|
||||
backend.delete_tag(file_hash, list(existing_tags), config=config)
|
||||
backend.delete_tag(
|
||||
file_hash,
|
||||
list(existing_tags),
|
||||
config=config
|
||||
)
|
||||
except Exception as exc:
|
||||
debug(f"[get_tag] ytdlp overwrite: delete_tag failed: {exc}")
|
||||
try:
|
||||
@@ -1250,7 +1343,10 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
"store": str(store_name),
|
||||
"path": str(subject_path) if subject_path else None,
|
||||
"title": item_title,
|
||||
"extra": {"applied_provider": "ytdlp", "scrape_url": scrape_target},
|
||||
"extra": {
|
||||
"applied_provider": "ytdlp",
|
||||
"scrape_url": scrape_target
|
||||
},
|
||||
},
|
||||
)
|
||||
return 0
|
||||
@@ -1264,7 +1360,8 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
output = {
|
||||
"title": title,
|
||||
"tag": tags,
|
||||
"formats": [(label, fmt_id) for label, fmt_id in formats],
|
||||
"formats": [(label,
|
||||
fmt_id) for label, fmt_id in formats],
|
||||
"playlist_items": playlist_items,
|
||||
}
|
||||
print(json_module.dumps(output, ensure_ascii=False))
|
||||
@@ -1281,7 +1378,9 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
# the piped PipeObject). Always prefer the current store-backed tags when possible.
|
||||
identifier_tags: List[str] = []
|
||||
file_hash_for_scrape = normalize_hash(hash_override) or normalize_hash(
|
||||
get_field(result, "hash", None)
|
||||
get_field(result,
|
||||
"hash",
|
||||
None)
|
||||
)
|
||||
store_for_scrape = get_field(result, "store", None)
|
||||
if file_hash_for_scrape and store_for_scrape:
|
||||
@@ -1292,7 +1391,9 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
backend = storage[str(store_for_scrape)]
|
||||
current_tags, _src = backend.get_tag(file_hash_for_scrape, config=config)
|
||||
if isinstance(current_tags, (list, tuple, set)) and current_tags:
|
||||
identifier_tags = [str(t) for t in current_tags if isinstance(t, (str, bytes))]
|
||||
identifier_tags = [
|
||||
str(t) for t in current_tags if isinstance(t, (str, bytes))
|
||||
]
|
||||
except Exception:
|
||||
# Fall back to whatever is present on the piped result if store lookup fails.
|
||||
pass
|
||||
@@ -1301,27 +1402,34 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
if not identifier_tags:
|
||||
result_tags = get_field(result, "tag", None)
|
||||
if isinstance(result_tags, list):
|
||||
identifier_tags = [str(t) for t in result_tags if isinstance(t, (str, bytes))]
|
||||
identifier_tags = [
|
||||
str(t) for t in result_tags if isinstance(t, (str, bytes))
|
||||
]
|
||||
|
||||
# As a last resort, try local sidecar only when the item is not store-backed.
|
||||
if not identifier_tags and (not file_hash_for_scrape or not store_for_scrape):
|
||||
file_path = (
|
||||
get_field(result, "target", None)
|
||||
or get_field(result, "path", None)
|
||||
or get_field(result, "filename", None)
|
||||
get_field(result,
|
||||
"target",
|
||||
None) or get_field(result,
|
||||
"path",
|
||||
None)
|
||||
or get_field(result,
|
||||
"filename",
|
||||
None)
|
||||
)
|
||||
if (
|
||||
isinstance(file_path, str)
|
||||
and file_path
|
||||
and not file_path.lower().startswith(("http://", "https://"))
|
||||
):
|
||||
if (isinstance(file_path,
|
||||
str) and file_path and not file_path.lower().startswith(
|
||||
("http://",
|
||||
"https://"))):
|
||||
try:
|
||||
media_path = Path(str(file_path))
|
||||
if media_path.exists():
|
||||
tags_from_sidecar = read_sidecar(media_path)
|
||||
if isinstance(tags_from_sidecar, list):
|
||||
identifier_tags = [
|
||||
str(t) for t in tags_from_sidecar if isinstance(t, (str, bytes))
|
||||
str(t) for t in tags_from_sidecar
|
||||
if isinstance(t, (str, bytes))
|
||||
]
|
||||
except Exception:
|
||||
pass
|
||||
@@ -1332,12 +1440,12 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
identifiers = _extract_scrapable_identifiers(identifier_tags)
|
||||
identifier_query: Optional[str] = None
|
||||
if identifiers:
|
||||
if provider.name in {"openlibrary", "googlebooks", "google"}:
|
||||
if provider.name in {"openlibrary",
|
||||
"googlebooks",
|
||||
"google"}:
|
||||
identifier_query = (
|
||||
identifiers.get("isbn_13")
|
||||
or identifiers.get("isbn_10")
|
||||
or identifiers.get("isbn")
|
||||
or identifiers.get("openlibrary")
|
||||
identifiers.get("isbn_13") or identifiers.get("isbn_10")
|
||||
or identifiers.get("isbn") or identifiers.get("openlibrary")
|
||||
)
|
||||
elif provider.name == "itunes":
|
||||
identifier_query = identifiers.get("musicbrainz") or identifiers.get(
|
||||
@@ -1346,16 +1454,26 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
|
||||
# Determine query from identifier first, else title on the result or filename
|
||||
title_hint = (
|
||||
title_from_tags or get_field(result, "title", None) or get_field(result, "name", None)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
@@ -1365,12 +1483,9 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
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 (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:
|
||||
@@ -1380,18 +1495,27 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
url_hint: Optional[str] = None
|
||||
if provider.name == "ytdlp":
|
||||
raw_url = (
|
||||
get_field(result, "url", None)
|
||||
or get_field(result, "source_url", None)
|
||||
or get_field(result, "target", None)
|
||||
get_field(result,
|
||||
"url",
|
||||
None) or get_field(result,
|
||||
"source_url",
|
||||
None) or get_field(result,
|
||||
"target",
|
||||
None)
|
||||
)
|
||||
if isinstance(raw_url, list) and raw_url:
|
||||
raw_url = raw_url[0]
|
||||
if isinstance(raw_url, str) and raw_url.strip().startswith(("http://", "https://")):
|
||||
if isinstance(raw_url,
|
||||
str) and raw_url.strip().startswith(("http://",
|
||||
"https://")):
|
||||
url_hint = raw_url.strip()
|
||||
|
||||
query_hint = url_hint or 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)
|
||||
log(
|
||||
"No title or identifier available to search for metadata",
|
||||
file=sys.stderr
|
||||
)
|
||||
return 1
|
||||
|
||||
if identifier_query:
|
||||
@@ -1423,7 +1547,10 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
config=config,
|
||||
item_title=str(items[0].get("title") or "ytdlp"),
|
||||
path=None,
|
||||
subject={"provider": "ytdlp", "url": str(query_hint)},
|
||||
subject={
|
||||
"provider": "ytdlp",
|
||||
"url": str(query_hint)
|
||||
},
|
||||
)
|
||||
return 0
|
||||
|
||||
@@ -1433,15 +1560,21 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
table.set_source_command("get-tag", [])
|
||||
selection_payload = []
|
||||
hash_for_payload = normalize_hash(hash_override) or normalize_hash(
|
||||
get_field(result, "hash", None)
|
||||
get_field(result,
|
||||
"hash",
|
||||
None)
|
||||
)
|
||||
store_for_payload = get_field(result, "store", None)
|
||||
# Preserve a consistent path field when present so selecting a metadata row
|
||||
# keeps referring to the original file.
|
||||
path_for_payload = (
|
||||
get_field(result, "path", None)
|
||||
or get_field(result, "target", None)
|
||||
or get_field(result, "filename", None)
|
||||
get_field(result,
|
||||
"path",
|
||||
None) or get_field(result,
|
||||
"target",
|
||||
None) or get_field(result,
|
||||
"filename",
|
||||
None)
|
||||
)
|
||||
for idx, item in enumerate(items):
|
||||
tags = _filter_scraped_tags(provider.to_tags(item))
|
||||
@@ -1488,22 +1621,35 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
result_provider = get_field(result, "provider", None)
|
||||
result_tags = get_field(result, "tag", None)
|
||||
if result_provider and isinstance(result_tags, list) and result_tags:
|
||||
file_hash = normalize_hash(hash_override) or normalize_hash(get_field(result, "hash", None))
|
||||
file_hash = normalize_hash(hash_override) or normalize_hash(
|
||||
get_field(result,
|
||||
"hash",
|
||||
None)
|
||||
)
|
||||
store_name = get_field(result, "store", None)
|
||||
subject_path = (
|
||||
get_field(result, "path", None)
|
||||
or get_field(result, "target", None)
|
||||
or get_field(result, "filename", None)
|
||||
get_field(result,
|
||||
"path",
|
||||
None) or get_field(result,
|
||||
"target",
|
||||
None) or get_field(result,
|
||||
"filename",
|
||||
None)
|
||||
)
|
||||
if not file_hash or not store_name:
|
||||
log("Selected metadata row is missing hash/store; cannot apply tags", file=sys.stderr)
|
||||
log(
|
||||
"Selected metadata row is missing hash/store; cannot apply tags",
|
||||
file=sys.stderr
|
||||
)
|
||||
_emit_tags_as_table(
|
||||
tags_list=[str(t) for t in result_tags if t is not None],
|
||||
file_hash=file_hash,
|
||||
store=str(store_name or "local"),
|
||||
service_name=None,
|
||||
config=config,
|
||||
item_title=str(get_field(result, "title", None) or result_provider),
|
||||
item_title=str(get_field(result,
|
||||
"title",
|
||||
None) or result_provider),
|
||||
path=str(subject_path) if subject_path else None,
|
||||
subject=result,
|
||||
)
|
||||
@@ -1513,7 +1659,9 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
if str(result_provider).strip().lower() == "ytdlp":
|
||||
apply_tags = [str(t) for t in result_tags if t is not None]
|
||||
else:
|
||||
apply_tags = _filter_scraped_tags([str(t) for t in result_tags if t is not None])
|
||||
apply_tags = _filter_scraped_tags(
|
||||
[str(t) for t in result_tags if t is not None]
|
||||
)
|
||||
if not apply_tags:
|
||||
log(
|
||||
"No applicable scraped tags to apply (title:/artist:/source: are skipped)",
|
||||
@@ -1547,17 +1695,25 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
service_name=None,
|
||||
config=config,
|
||||
item_title=str(
|
||||
get_field(result, "title", None)
|
||||
or get_field(result, "name", None)
|
||||
or str(result_provider)
|
||||
get_field(result,
|
||||
"title",
|
||||
None) or get_field(result,
|
||||
"name",
|
||||
None) or str(result_provider)
|
||||
),
|
||||
path=str(subject_path) if subject_path else None,
|
||||
subject={
|
||||
"hash": file_hash,
|
||||
"store": str(store_name),
|
||||
"path": str(subject_path) if subject_path else None,
|
||||
"title": get_field(result, "title", None) or get_field(result, "name", None),
|
||||
"extra": {"applied_provider": str(result_provider)},
|
||||
"title": get_field(result,
|
||||
"title",
|
||||
None) or get_field(result,
|
||||
"name",
|
||||
None),
|
||||
"extra": {
|
||||
"applied_provider": str(result_provider)
|
||||
},
|
||||
},
|
||||
)
|
||||
return 0
|
||||
@@ -1603,28 +1759,37 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
# Always output to ResultTable (pipeline mode only)
|
||||
# Extract title for table header
|
||||
item_title = (
|
||||
get_field(result, "title", None)
|
||||
or get_field(result, "name", None)
|
||||
or get_field(result, "filename", None)
|
||||
get_field(result,
|
||||
"title",
|
||||
None) or get_field(result,
|
||||
"name",
|
||||
None) or get_field(result,
|
||||
"filename",
|
||||
None)
|
||||
)
|
||||
|
||||
# Build a subject payload representing the file whose tags are being shown
|
||||
subject_store = get_field(result, "store", None) or store_name
|
||||
subject_path = (
|
||||
get_field(result, "path", None)
|
||||
or get_field(result, "target", None)
|
||||
or get_field(result, "filename", None)
|
||||
get_field(result,
|
||||
"path",
|
||||
None) or get_field(result,
|
||||
"target",
|
||||
None) or get_field(result,
|
||||
"filename",
|
||||
None)
|
||||
)
|
||||
subject_payload: Dict[str, Any] = {
|
||||
"tag": list(current),
|
||||
"title": item_title,
|
||||
"name": item_title,
|
||||
"store": subject_store,
|
||||
"service_name": service_name,
|
||||
"extra": {
|
||||
"tag": list(current),
|
||||
},
|
||||
}
|
||||
subject_payload: Dict[str,
|
||||
Any] = {
|
||||
"tag": list(current),
|
||||
"title": item_title,
|
||||
"name": item_title,
|
||||
"store": subject_store,
|
||||
"service_name": service_name,
|
||||
"extra": {
|
||||
"tag": list(current),
|
||||
},
|
||||
}
|
||||
if file_hash:
|
||||
subject_payload["hash"] = file_hash
|
||||
if subject_path:
|
||||
@@ -1646,7 +1811,12 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
|
||||
# If emit requested or store key provided, emit payload
|
||||
if emit_mode:
|
||||
_emit_tag_payload(source, current, hash_value=file_hash, store_label=store_label)
|
||||
_emit_tag_payload(
|
||||
source,
|
||||
current,
|
||||
hash_value=file_hash,
|
||||
store_label=store_label
|
||||
)
|
||||
|
||||
return 0
|
||||
|
||||
@@ -1671,7 +1841,8 @@ class Get_Tag(Cmdlet):
|
||||
super().__init__(
|
||||
name="get-tag",
|
||||
summary="Get tag values from Hydrus or local sidecar metadata",
|
||||
usage='get-tag [-query "hash:<sha256>"] [--store <key>] [--emit] [-scrape <url|provider>]',
|
||||
usage=
|
||||
'get-tag [-query "hash:<sha256>"] [--store <key>] [--emit] [-scrape <url|provider>]',
|
||||
alias=[],
|
||||
arg=[
|
||||
SharedArgs.QUERY,
|
||||
@@ -1690,7 +1861,8 @@ class Get_Tag(Cmdlet):
|
||||
CmdletArg(
|
||||
name="-scrape",
|
||||
type="string",
|
||||
description="Scrape metadata from URL/provider, or use 'ytdlp' to scrape from the item's URL and overwrite tags",
|
||||
description=
|
||||
"Scrape metadata from URL/provider, or use 'ytdlp' to scrape from the item's URL and overwrite tags",
|
||||
required=False,
|
||||
choices=_SCRAPE_CHOICES,
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user