jjlj
This commit is contained in:
92
metadata.py
92
metadata.py
@@ -5,7 +5,7 @@ import sys
|
||||
import shutil
|
||||
import sqlite3
|
||||
import requests
|
||||
from helper.logger import log
|
||||
from helper.logger import log, debug
|
||||
from urllib.parse import urlsplit, urlunsplit, unquote
|
||||
from collections import deque
|
||||
from pathlib import Path
|
||||
@@ -1312,7 +1312,7 @@ def _read_sidecar_metadata(sidecar_path: Path) -> tuple[Optional[str], List[str]
|
||||
|
||||
|
||||
|
||||
def rename_by_metadata(file_path: Path, tags: Iterable[str]) -> Optional[Path]:
|
||||
def rename(file_path: Path, tags: Iterable[str]) -> Optional[Path]:
|
||||
"""Rename a file based on title: tag in the tags list.
|
||||
|
||||
If a title: tag is present, renames the file and any .tags/.metadata sidecars.
|
||||
@@ -1350,13 +1350,13 @@ def rename_by_metadata(file_path: Path, tags: Iterable[str]) -> Optional[Path]:
|
||||
if new_path.exists():
|
||||
try:
|
||||
new_path.unlink()
|
||||
log(f"[rename_by_metadata] Replaced existing file: {new_name}", file=sys.stderr)
|
||||
debug(f"Replaced existing file: {new_name}", file=sys.stderr)
|
||||
except Exception as e:
|
||||
log(f"[rename_by_metadata] Warning: Could not replace target file {new_name}: {e}", file=sys.stderr)
|
||||
debug(f"Warning: Could not replace target file {new_name}: {e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
file_path.rename(new_path)
|
||||
log(f"[rename_by_metadata] Renamed file: {old_name} → {new_name}", file=sys.stderr)
|
||||
debug(f"Renamed file: {old_name} → {new_name}", file=sys.stderr)
|
||||
|
||||
# Rename the .tags sidecar if it exists
|
||||
old_tags_path = file_path.parent / (old_name + '.tags')
|
||||
@@ -1369,21 +1369,21 @@ def rename_by_metadata(file_path: Path, tags: Iterable[str]) -> Optional[Path]:
|
||||
pass
|
||||
else:
|
||||
old_tags_path.rename(new_tags_path)
|
||||
log(f"[rename_by_metadata] Renamed sidecar: {old_tags_path.name} → {new_tags_path.name}", file=sys.stderr)
|
||||
debug(f"Renamed sidecar: {old_tags_path.name} → {new_tags_path.name}", file=sys.stderr)
|
||||
|
||||
# Rename the .metadata sidecar if it exists
|
||||
old_metadata_path = file_path.parent / (old_name + '.metadata')
|
||||
if old_metadata_path.exists():
|
||||
new_metadata_path = file_path.parent / (new_name + '.metadata')
|
||||
if new_metadata_path.exists():
|
||||
log(f"[rename_by_metadata] Warning: Target metadata already exists: {new_metadata_path.name}", file=sys.stderr)
|
||||
debug(f"Warning: Target metadata already exists: {new_metadata_path.name}", file=sys.stderr)
|
||||
else:
|
||||
old_metadata_path.rename(new_metadata_path)
|
||||
log(f"[rename_by_metadata] Renamed metadata: {old_metadata_path.name} → {new_metadata_path.name}", file=sys.stderr)
|
||||
debug(f"Renamed metadata: {old_metadata_path.name} → {new_metadata_path.name}", file=sys.stderr)
|
||||
|
||||
return new_path
|
||||
except Exception as exc:
|
||||
log(f"[rename_by_metadata] Warning: Failed to rename file: {exc}", file=sys.stderr)
|
||||
debug(f"Warning: Failed to rename file: {exc}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
@@ -1419,10 +1419,10 @@ def write_tags(media_path: Path, tags: Iterable[str], known_urls: Iterable[str],
|
||||
|
||||
if db_tags:
|
||||
db.add_tags(media_path, db_tags)
|
||||
log(f"Added tags to database for {media_path.name}")
|
||||
debug(f"Added tags to database for {media_path.name}")
|
||||
return
|
||||
except Exception as e:
|
||||
log(f"Failed to add tags to database: {e}", file=sys.stderr)
|
||||
debug(f"Failed to add tags to database: {e}", file=sys.stderr)
|
||||
# Fall through to sidecar creation as fallback
|
||||
|
||||
# Create sidecar path
|
||||
@@ -1449,7 +1449,7 @@ def write_tags(media_path: Path, tags: Iterable[str], known_urls: Iterable[str],
|
||||
|
||||
if lines:
|
||||
sidecar.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
log(f"Wrote tags to {sidecar}")
|
||||
debug(f"Tags: {sidecar}")
|
||||
# Clean up legacy files
|
||||
for legacy_path in [media_path.with_name(media_path.name + '.tags'),
|
||||
media_path.with_name(media_path.name + '.tags.txt')]:
|
||||
@@ -1464,7 +1464,7 @@ def write_tags(media_path: Path, tags: Iterable[str], known_urls: Iterable[str],
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except OSError as exc:
|
||||
log(f"Failed to write tag sidecar {sidecar}: {exc}", file=sys.stderr)
|
||||
debug(f"Failed to write tag sidecar {sidecar}: {exc}", file=sys.stderr)
|
||||
|
||||
|
||||
def write_metadata(media_path: Path, hash_value: Optional[str] = None, known_urls: Optional[Iterable[str]] = None, relationships: Optional[Iterable[str]] = None, db=None) -> None:
|
||||
@@ -1503,10 +1503,10 @@ def write_metadata(media_path: Path, hash_value: Optional[str] = None, known_url
|
||||
|
||||
if db_tags:
|
||||
db.add_tags(media_path, db_tags)
|
||||
log(f"Added metadata to database for {media_path.name}")
|
||||
debug(f"Added metadata to database for {media_path.name}")
|
||||
return
|
||||
except Exception as e:
|
||||
log(f"Failed to add metadata to database: {e}", file=sys.stderr)
|
||||
debug(f"Failed to add metadata to database: {e}", file=sys.stderr)
|
||||
# Fall through to sidecar creation as fallback
|
||||
|
||||
# Create sidecar path
|
||||
@@ -1535,7 +1535,7 @@ def write_metadata(media_path: Path, hash_value: Optional[str] = None, known_url
|
||||
# Write metadata file
|
||||
if lines:
|
||||
sidecar.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
log(f"Wrote metadata to {sidecar}")
|
||||
debug(f"Wrote metadata to {sidecar}")
|
||||
else:
|
||||
# Remove if no content
|
||||
try:
|
||||
@@ -1543,7 +1543,7 @@ def write_metadata(media_path: Path, hash_value: Optional[str] = None, known_url
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except OSError as exc:
|
||||
log(f"Failed to write metadata sidecar {sidecar}: {exc}", file=sys.stderr)
|
||||
debug(f"Failed to write metadata sidecar {sidecar}: {exc}", file=sys.stderr)
|
||||
|
||||
|
||||
def extract_title(tags: Iterable[str]) -> Optional[str]:
|
||||
@@ -1892,7 +1892,7 @@ def extract_ytdlp_tags(entry: Dict[str, Any]) -> List[str]:
|
||||
Example:
|
||||
>>> entry = {'artist': 'The Beatles', 'album': 'Abbey Road', 'duration': 5247}
|
||||
>>> tags = extract_ytdlp_tags(entry)
|
||||
>>> log(tags)
|
||||
>>> debug(tags)
|
||||
['artist:The Beatles', 'album:Abbey Road']
|
||||
"""
|
||||
tags: List[str] = []
|
||||
@@ -1986,7 +1986,7 @@ def dedup_tags_by_namespace(tags: List[str], keep_first: bool = True) -> List[st
|
||||
... 'album:Abbey Road', 'artist:Beatles'
|
||||
... ]
|
||||
>>> dedup = dedup_tags_by_namespace(tags)
|
||||
>>> log(dedup)
|
||||
>>> debug(dedup)
|
||||
['artist:Beatles', 'album:Abbey Road', 'tag:rock']
|
||||
"""
|
||||
if not tags:
|
||||
@@ -2053,7 +2053,7 @@ def merge_multiple_tag_lists(
|
||||
>>> list1 = ['artist:Beatles', 'album:Abbey Road']
|
||||
>>> list2 = ['artist:Beatles', 'album:Abbey Road', 'tag:rock']
|
||||
>>> merged = merge_multiple_tag_lists([list1, list2])
|
||||
>>> log(merged)
|
||||
>>> debug(merged)
|
||||
['artist:Beatles', 'album:Abbey Road', 'tag:rock']
|
||||
"""
|
||||
if not sources:
|
||||
@@ -2137,7 +2137,7 @@ def read_tags_from_file(file_path: Path) -> List[str]:
|
||||
|
||||
Example:
|
||||
>>> tags = read_tags_from_file(Path('file.txt.tags'))
|
||||
>>> log(tags)
|
||||
>>> debug(tags)
|
||||
['artist:Beatles', 'album:Abbey Road']
|
||||
"""
|
||||
file_path = Path(file_path)
|
||||
@@ -2271,7 +2271,7 @@ def embed_metadata_in_file(
|
||||
# Check if FFmpeg is available
|
||||
ffmpeg_path = shutil.which('ffmpeg')
|
||||
if not ffmpeg_path:
|
||||
log(f"⚠️ FFmpeg not found; cannot embed metadata in {file_path.name}", file=sys.stderr)
|
||||
debug(f"⚠️ FFmpeg not found; cannot embed metadata in {file_path.name}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
# Create temporary file for output
|
||||
@@ -2294,18 +2294,18 @@ def embed_metadata_in_file(
|
||||
# Replace original with temp file
|
||||
file_path.unlink()
|
||||
temp_file.rename(file_path)
|
||||
log(f"✅ Embedded metadata in file: {file_path.name}", file=sys.stderr)
|
||||
debug(f"✅ Embedded metadata in file: {file_path.name}", file=sys.stderr)
|
||||
return True
|
||||
else:
|
||||
# Clean up temp file if it exists
|
||||
if temp_file.exists():
|
||||
temp_file.unlink()
|
||||
log(f"❌ FFmpeg metadata embedding failed for {file_path.name}", file=sys.stderr)
|
||||
debug(f"❌ FFmpeg metadata embedding failed for {file_path.name}", file=sys.stderr)
|
||||
if result.stderr:
|
||||
# Safely decode stderr, ignoring invalid UTF-8 bytes
|
||||
try:
|
||||
stderr_text = result.stderr.decode('utf-8', errors='replace')[:200]
|
||||
log(f"FFmpeg stderr: {stderr_text}", file=sys.stderr)
|
||||
debug(f"FFmpeg stderr: {stderr_text}", file=sys.stderr)
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
@@ -2315,7 +2315,7 @@ def embed_metadata_in_file(
|
||||
temp_file.unlink()
|
||||
except Exception:
|
||||
pass
|
||||
log(f"❌ Error embedding metadata: {exc}", file=sys.stderr)
|
||||
debug(f"❌ Error embedding metadata: {exc}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
|
||||
@@ -2402,7 +2402,7 @@ def normalize_tags_from_source(
|
||||
Example:
|
||||
>>> entry = {'artist': 'Beatles', 'album': 'Abbey Road'}
|
||||
>>> tags = normalize_tags_from_source(entry, 'ytdlp')
|
||||
>>> log(tags)
|
||||
>>> debug(tags)
|
||||
['artist:Beatles', 'album:Abbey Road']
|
||||
"""
|
||||
if source_type == 'auto':
|
||||
@@ -2600,10 +2600,10 @@ def imdb(imdb_id: str = typer.Argument(..., help="IMDb identifier (ttXXXXXXX)"))
|
||||
"""Lookup an IMDb title."""
|
||||
try:
|
||||
result = imdb_tag(imdb_id)
|
||||
log(json.dumps(result, ensure_ascii=False), flush=True)
|
||||
debug(json.dumps(result, ensure_ascii=False), flush=True)
|
||||
except Exception as exc:
|
||||
error_payload = {"error": str(exc)}
|
||||
log(json.dumps(error_payload, ensure_ascii=False), flush=True)
|
||||
debug(json.dumps(error_payload, ensure_ascii=False), flush=True)
|
||||
raise typer.Exit(code=1)
|
||||
|
||||
@app.command(help="Lookup a MusicBrainz entity")
|
||||
@@ -2614,10 +2614,10 @@ def musicbrainz(
|
||||
"""Lookup a MusicBrainz entity."""
|
||||
try:
|
||||
result = fetch_musicbrainz_tags(mbid, entity)
|
||||
log(json.dumps(result, ensure_ascii=False), flush=True)
|
||||
debug(json.dumps(result, ensure_ascii=False), flush=True)
|
||||
except Exception as exc:
|
||||
error_payload = {"error": str(exc)}
|
||||
log(json.dumps(error_payload, ensure_ascii=False), flush=True)
|
||||
debug(json.dumps(error_payload, ensure_ascii=False), flush=True)
|
||||
raise typer.Exit(code=1)
|
||||
|
||||
@app.command(name="remote-tags", help="Normalize a remote metadata payload")
|
||||
@@ -2633,10 +2633,10 @@ def remote_tags(payload: Optional[str] = typer.Option(None, "--payload", help="J
|
||||
if context and not isinstance(context, dict):
|
||||
raise ValueError("context must be an object")
|
||||
result = build_remote_bundle(metadata, existing, context)
|
||||
log(json.dumps(result, ensure_ascii=False), flush=True)
|
||||
debug(json.dumps(result, ensure_ascii=False), flush=True)
|
||||
except Exception as exc:
|
||||
error_payload = {"error": str(exc)}
|
||||
log(json.dumps(error_payload, ensure_ascii=False), flush=True)
|
||||
debug(json.dumps(error_payload, ensure_ascii=False), flush=True)
|
||||
raise typer.Exit(code=1)
|
||||
|
||||
@app.command(name="remote-fetch", help="Resolve remote metadata bundle")
|
||||
@@ -2645,10 +2645,10 @@ def remote_fetch(payload: Optional[str] = typer.Option(None, "--payload", help="
|
||||
try:
|
||||
payload_data = _load_payload(payload)
|
||||
result = resolve_remote_metadata(payload_data)
|
||||
log(json.dumps(result, ensure_ascii=False), flush=True)
|
||||
debug(json.dumps(result, ensure_ascii=False), flush=True)
|
||||
except Exception as exc:
|
||||
error_payload = {"error": str(exc)}
|
||||
log(json.dumps(error_payload, ensure_ascii=False), flush=True)
|
||||
debug(json.dumps(error_payload, ensure_ascii=False), flush=True)
|
||||
raise typer.Exit(code=1)
|
||||
|
||||
@app.command(name="expand-tag", help="Expand metadata references into tags")
|
||||
@@ -2657,10 +2657,10 @@ def expand_tag(payload: Optional[str] = typer.Option(None, "--payload", help="JS
|
||||
try:
|
||||
payload_data = _load_payload(payload)
|
||||
result = expand_metadata_tag(payload_data)
|
||||
log(json.dumps(result, ensure_ascii=False), flush=True)
|
||||
debug(json.dumps(result, ensure_ascii=False), flush=True)
|
||||
except Exception as exc:
|
||||
error_payload = {"error": str(exc)}
|
||||
log(json.dumps(error_payload, ensure_ascii=False), flush=True)
|
||||
debug(json.dumps(error_payload, ensure_ascii=False), flush=True)
|
||||
raise typer.Exit(code=1)
|
||||
|
||||
@app.command(name="hydrus-fetch", help="Fetch Hydrus metadata for a file")
|
||||
@@ -2669,10 +2669,10 @@ def hydrus_fetch(payload: Optional[str] = typer.Option(None, "--payload", help="
|
||||
try:
|
||||
payload_data = _load_payload(payload)
|
||||
result = fetch_hydrus_metadata(payload_data)
|
||||
log(json.dumps(result, ensure_ascii=False), flush=True)
|
||||
debug(json.dumps(result, ensure_ascii=False), flush=True)
|
||||
except Exception as exc:
|
||||
error_payload = {"error": str(exc)}
|
||||
log(json.dumps(error_payload, ensure_ascii=False), flush=True)
|
||||
debug(json.dumps(error_payload, ensure_ascii=False), flush=True)
|
||||
raise typer.Exit(code=1)
|
||||
|
||||
@app.command(name="hydrus-fetch-url", help="Fetch Hydrus metadata using a source URL")
|
||||
@@ -2681,10 +2681,10 @@ def hydrus_fetch_url(payload: Optional[str] = typer.Option(None, "--payload", he
|
||||
try:
|
||||
payload_data = _load_payload(payload)
|
||||
result = fetch_hydrus_metadata_by_url(payload_data)
|
||||
log(json.dumps(result, ensure_ascii=False), flush=True)
|
||||
debug(json.dumps(result, ensure_ascii=False), flush=True)
|
||||
except Exception as exc:
|
||||
error_payload = {"error": str(exc)}
|
||||
log(json.dumps(error_payload, ensure_ascii=False), flush=True)
|
||||
debug(json.dumps(error_payload, ensure_ascii=False), flush=True)
|
||||
raise typer.Exit(code=1)
|
||||
|
||||
@app.command(name="sync-sidecar", help="Synchronise .tags sidecar with supplied data")
|
||||
@@ -2693,10 +2693,10 @@ def sync_sidecar_cmd(payload: Optional[str] = typer.Option(None, "--payload", he
|
||||
try:
|
||||
payload_data = _load_payload(payload)
|
||||
result = sync_sidecar(payload_data)
|
||||
log(json.dumps(result, ensure_ascii=False), flush=True)
|
||||
debug(json.dumps(result, ensure_ascii=False), flush=True)
|
||||
except Exception as exc:
|
||||
error_payload = {"error": str(exc)}
|
||||
log(json.dumps(error_payload, ensure_ascii=False), flush=True)
|
||||
debug(json.dumps(error_payload, ensure_ascii=False), flush=True)
|
||||
raise typer.Exit(code=1)
|
||||
|
||||
@app.command(name="update-tag", help="Update or rename a tag")
|
||||
@@ -2705,10 +2705,10 @@ def update_tag_cmd(payload: Optional[str] = typer.Option(None, "--payload", help
|
||||
try:
|
||||
payload_data = _load_payload(payload)
|
||||
result = apply_tag_mutation(payload_data, 'update')
|
||||
log(json.dumps(result, ensure_ascii=False), flush=True)
|
||||
debug(json.dumps(result, ensure_ascii=False), flush=True)
|
||||
except Exception as exc:
|
||||
error_payload = {"error": str(exc)}
|
||||
log(json.dumps(error_payload, ensure_ascii=False), flush=True)
|
||||
debug(json.dumps(error_payload, ensure_ascii=False), flush=True)
|
||||
raise typer.Exit(code=1)
|
||||
|
||||
def main(argv: Optional[List[str]] = None) -> int:
|
||||
@@ -3102,7 +3102,7 @@ def fetch_openlibrary_metadata_tags(isbn: Optional[str] = None, olid: Optional[s
|
||||
metadata_tags.append(subject_clean)
|
||||
|
||||
except Exception as e:
|
||||
log(f"⚠ Failed to fetch OpenLibrary metadata: {e}")
|
||||
debug(f"⚠ Failed to fetch OpenLibrary metadata: {e}")
|
||||
|
||||
return metadata_tags
|
||||
|
||||
|
||||
Reference in New Issue
Block a user