from __future__ import annotations from typing import Any, Dict, Sequence, Optional import json import sys from helper.logger import log from pathlib import Path import mimetypes import os from helper import hydrus as hydrus_wrapper from ._shared import Cmdlet, CmdletArg, normalize_hash def _run(result: Any, _args: Sequence[str], config: Dict[str, Any]) -> int: # Help try: if any(str(a).lower() in {"-?", "/?", "--help", "-h", "help", "--cmdlet"} for a in _args): log(json.dumps(CMDLET.to_dict(), ensure_ascii=False, indent=2)) return 0 except Exception: pass # Helper to get field from both dict and object def get_field(obj: Any, field: str, default: Any = None) -> Any: if isinstance(obj, dict): return obj.get(field, default) else: return getattr(obj, field, default) # Parse -hash override override_hash: str | None = None args_list = list(_args) i = 0 while i < len(args_list): a = args_list[i] low = str(a).lower() if low in {"-hash", "--hash", "hash"} and i + 1 < len(args_list): override_hash = str(args_list[i + 1]).strip() break i += 1 # Try to determine if this is a local file or Hydrus file local_path = get_field(result, "target", None) or get_field(result, "path", None) is_local = False if local_path and isinstance(local_path, str) and not local_path.startswith(("http://", "https://")): is_local = True # LOCAL FILE PATH if is_local and local_path: try: file_path = Path(str(local_path)) if file_path.exists() and file_path.is_file(): # Get the hash from result or compute it hash_hex = normalize_hash(override_hash) if override_hash else normalize_hash(get_field(result, "hash_hex", None)) # If no hash, compute SHA256 of the file if not hash_hex: try: import hashlib with open(file_path, 'rb') as f: hash_hex = hashlib.sha256(f.read()).hexdigest() except Exception: hash_hex = None # Get MIME type mime_type, _ = mimetypes.guess_type(str(file_path)) if not mime_type: mime_type = "unknown" # Get file size try: file_size = file_path.stat().st_size except Exception: file_size = None # Try to get duration if it's a media file duration_seconds = None try: # Try to use ffprobe if available import subprocess result_proc = subprocess.run( ["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", str(file_path)], capture_output=True, text=True, timeout=5 ) if result_proc.returncode == 0 and result_proc.stdout.strip(): try: duration_seconds = float(result_proc.stdout.strip()) except ValueError: pass except Exception: pass # Get format helpers from search module try: from .search_file import _format_size as _fmt_size from .search_file import _format_duration as _fmt_dur except Exception: _fmt_size = lambda x: str(x) if x is not None else "" _fmt_dur = lambda x: str(x) if x is not None else "" size_label = _fmt_size(file_size) if file_size is not None else "" dur_label = _fmt_dur(duration_seconds) if duration_seconds is not None else "" # Get known URLs from sidecar or result urls = [] sidecar_path = Path(str(file_path) + '.tags') if sidecar_path.exists(): try: with open(sidecar_path, 'r', encoding='utf-8') as f: for line in f: line = line.strip() if line.startswith('known_url:'): url_value = line.replace('known_url:', '', 1).strip() if url_value: urls.append(url_value) except Exception: pass # Fallback to result URLs if not in sidecar if not urls: urls_from_result = get_field(result, "known_urls", None) or get_field(result, "urls", None) if isinstance(urls_from_result, list): urls.extend([str(u).strip() for u in urls_from_result if u]) # Display local file metadata log(f"PATH: {file_path}") if hash_hex: log(f"HASH: {hash_hex}") if mime_type: log(f"MIME: {mime_type}") if size_label: log(f"Size: {size_label}") if dur_label: log(f"Duration: {dur_label}") if urls: log("URLs:") for url in urls: log(f" {url}") return 0 except Exception as exc: # Fall through to Hydrus if local file handling fails pass # HYDRUS PATH hash_hex = normalize_hash(override_hash) if override_hash else normalize_hash(get_field(result, "hash_hex", None)) if not hash_hex: log("Selected result does not include a Hydrus hash or local path", file=sys.stderr) return 1 try: client = hydrus_wrapper.get_client(config) except Exception as exc: log(f"Hydrus client unavailable: {exc}", file=sys.stderr) return 1 if client is None: log("Hydrus client unavailable", file=sys.stderr) return 1 try: payload = client.fetch_file_metadata( hashes=[hash_hex], include_service_keys_to_tags=False, include_file_urls=True, include_duration=True, include_size=True, include_mime=True, ) except Exception as exc: log(f"Hydrus metadata fetch failed: {exc}", file=sys.stderr) return 1 items = payload.get("metadata") if isinstance(payload, dict) else None if not isinstance(items, list) or not items: log("No metadata found.") return 0 meta = items[0] if isinstance(items[0], dict) else None if not isinstance(meta, dict): log("No metadata found.") return 0 mime = meta.get("mime") size = meta.get("size") or meta.get("file_size") duration_value = meta.get("duration") inner = meta.get("metadata") if isinstance(meta.get("metadata"), dict) else None if duration_value is None and isinstance(inner, dict): duration_value = inner.get("duration") try: from .search_file import _format_size as _fmt_size from .search_file import _format_duration as _fmt_dur from .search_file import _hydrus_duration_seconds as _dur_secs except Exception: _fmt_size = lambda x: str(x) if x is not None else "" _dur_secs = lambda x: x _fmt_dur = lambda x: str(x) if x is not None else "" dur_seconds = _dur_secs(duration_value) dur_label = _fmt_dur(dur_seconds) if dur_seconds is not None else "" size_label = _fmt_size(size) # Display Hydrus file metadata log(f"PATH: hydrus://file/{hash_hex}") log(f"Hash: {hash_hex}") if mime: log(f"MIME: {mime}") if dur_label: log(f"Duration: {dur_label}") if size_label: log(f"Size: {size_label}") urls = meta.get("known_urls") or meta.get("urls") if isinstance(urls, list) and urls: log("URLs:") for url in urls: try: text = str(url).strip() except Exception: text = "" if text: log(f" {text}") return 0 CMDLET = Cmdlet( name="get-metadata", summary="Print metadata for local or Hydrus files (hash, mime, duration, size, URLs).", usage="get-metadata [-hash ]", aliases=["meta"], args=[ CmdletArg("hash", description="Override the Hydrus file hash (SHA256) to target instead of the selected result."), ], details=[ "- For local files: Shows path, hash (computed if needed), MIME type, size, duration, and known URLs from sidecar.", "- For Hydrus files: Shows path (hydrus://), hash, MIME, duration, size, and known URLs.", "- Automatically detects local vs Hydrus files.", "- Local file hashes are computed via SHA256 if not already available.", ], )