f
This commit is contained in:
@@ -188,13 +188,6 @@ class SharedArgs:
|
||||
query_key="store",
|
||||
)
|
||||
|
||||
PATH = CmdletArg(
|
||||
name="path",
|
||||
type="string",
|
||||
choices=[], # Dynamically populated via get_store_choices()
|
||||
description="selects store",
|
||||
)
|
||||
|
||||
URL = CmdletArg(
|
||||
name="url",
|
||||
type="string",
|
||||
@@ -206,7 +199,6 @@ class SharedArgs:
|
||||
description="selects provider",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@staticmethod
|
||||
def get_store_choices(config: Optional[Dict[str, Any]] = None, force: bool = False) -> List[str]:
|
||||
"""Get list of available store backend names.
|
||||
@@ -765,6 +757,166 @@ def parse_cmdlet_args(args: Sequence[str],
|
||||
return result
|
||||
|
||||
|
||||
def resolve_target_dir(
|
||||
parsed: Dict[str, Any],
|
||||
config: Dict[str, Any],
|
||||
*,
|
||||
handle_creations: bool = True
|
||||
) -> Optional[Path]:
|
||||
"""Resolve a target directory from -path, -output, -storage, or config fallback.
|
||||
|
||||
Args:
|
||||
parsed: Parsed cmdlet arguments dict.
|
||||
config: System configuration dict.
|
||||
handle_creations: Whether to create the directory if it doesn't exist.
|
||||
|
||||
Returns:
|
||||
Path to the resolved directory, or None if invalid.
|
||||
"""
|
||||
# Priority 1: Explicit -path or -output
|
||||
target = parsed.get("path") or parsed.get("output")
|
||||
if target:
|
||||
try:
|
||||
p = Path(str(target)).expanduser().resolve()
|
||||
if handle_creations:
|
||||
p.mkdir(parents=True, exist_ok=True)
|
||||
return p
|
||||
except Exception as e:
|
||||
log(f"Cannot use target path {target}: {e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
# Priority 2: --storage flag
|
||||
storage_val = parsed.get("storage")
|
||||
if storage_val:
|
||||
try:
|
||||
return SharedArgs.resolve_storage(storage_val)
|
||||
except Exception as e:
|
||||
log(f"Invalid storage location: {e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
# Priority 3: Config fallback via single source of truth
|
||||
try:
|
||||
from SYS.config import resolve_output_dir
|
||||
out_dir = resolve_output_dir(config)
|
||||
if handle_creations:
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
return out_dir
|
||||
except Exception:
|
||||
import tempfile
|
||||
p = Path(tempfile.gettempdir()) / "Medios-Macina"
|
||||
if handle_creations:
|
||||
p.mkdir(parents=True, exist_ok=True)
|
||||
return p
|
||||
|
||||
|
||||
def coerce_to_path(value: Any) -> Path:
|
||||
"""Extract a Path from common provider result shapes (Path, str, dict, object)."""
|
||||
if isinstance(value, Path):
|
||||
return value
|
||||
if isinstance(value, str):
|
||||
return Path(value)
|
||||
|
||||
# Try attribute
|
||||
p = getattr(value, "path", None)
|
||||
if p:
|
||||
return Path(str(p))
|
||||
|
||||
# Try dict
|
||||
if isinstance(value, dict):
|
||||
p = value.get("path")
|
||||
if p:
|
||||
return Path(str(p))
|
||||
|
||||
raise ValueError(f"Cannot coerce {type(value).__name__} to Path (missing 'path' field)")
|
||||
|
||||
|
||||
|
||||
def resolve_media_kind_by_extension(path: Path) -> str:
|
||||
"""Resolve media kind (audio, video, image, document, other) from file extension."""
|
||||
if not isinstance(path, Path):
|
||||
try:
|
||||
path = Path(str(path))
|
||||
except Exception:
|
||||
return "other"
|
||||
|
||||
suffix = path.suffix.lower()
|
||||
if suffix in {".mp3",
|
||||
".flac",
|
||||
".wav",
|
||||
".m4a",
|
||||
".aac",
|
||||
".ogg",
|
||||
".opus",
|
||||
".wma",
|
||||
".mka"}:
|
||||
return "audio"
|
||||
if suffix in {
|
||||
".mp4",
|
||||
".mkv",
|
||||
".webm",
|
||||
".mov",
|
||||
".avi",
|
||||
".flv",
|
||||
".mpg",
|
||||
".mpeg",
|
||||
".ts",
|
||||
".m4v",
|
||||
".wmv",
|
||||
}:
|
||||
return "video"
|
||||
if suffix in {".jpg",
|
||||
".jpeg",
|
||||
".png",
|
||||
".gif",
|
||||
".webp",
|
||||
".bmp",
|
||||
".tiff"}:
|
||||
return "image"
|
||||
if suffix in {".pdf",
|
||||
".epub",
|
||||
".txt",
|
||||
".mobi",
|
||||
".azw3",
|
||||
".cbz",
|
||||
".cbr",
|
||||
".doc",
|
||||
".docx"}:
|
||||
return "document"
|
||||
return "other"
|
||||
|
||||
|
||||
def build_pipeline_preview(raw_urls: Sequence[str], piped_items: Sequence[Any]) -> List[str]:
|
||||
"""Construct a short preview list for pipeline/cmdlet progress UI."""
|
||||
preview: List[str] = []
|
||||
|
||||
# 1. Add raw URLs
|
||||
try:
|
||||
for u in (raw_urls or [])[:3]:
|
||||
if u:
|
||||
preview.append(str(u))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 2. Add titles from piped items
|
||||
if len(preview) < 5:
|
||||
try:
|
||||
for item in (piped_items or [])[:5]:
|
||||
if len(preview) >= 5:
|
||||
break
|
||||
title = get_field(item, "title") or get_field(item, "target") or "Piped item"
|
||||
preview.append(str(title))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 3. Handle empty case
|
||||
if not preview:
|
||||
total = len(raw_urls or []) + len(piped_items or [])
|
||||
if total:
|
||||
preview.append(f"Processing {total} item(s)...")
|
||||
|
||||
return preview
|
||||
|
||||
|
||||
def normalize_hash(hash_hex: Optional[str]) -> Optional[str]:
|
||||
"""Normalize a hash string to lowercase, or return None if invalid.
|
||||
|
||||
@@ -2034,36 +2186,45 @@ def collapse_namespace_tag(
|
||||
|
||||
|
||||
def extract_tag_from_result(result: Any) -> list[str]:
|
||||
"""Extract all tags from a result dict or PipeObject.
|
||||
|
||||
Handles mixed types (lists, sets, strings) and various field names.
|
||||
"""
|
||||
tag: list[str] = []
|
||||
|
||||
def _extend(candidate: Any) -> None:
|
||||
if not candidate:
|
||||
return
|
||||
if isinstance(candidate, (list, set, tuple)):
|
||||
tag.extend(str(t) for t in candidate if t is not None)
|
||||
elif isinstance(candidate, str):
|
||||
tag.append(candidate)
|
||||
|
||||
if isinstance(result, models.PipeObject):
|
||||
tag.extend(result.tag or [])
|
||||
if isinstance(result.extra, dict):
|
||||
extra_tag = result.extra.get("tag")
|
||||
if isinstance(extra_tag, list):
|
||||
tag.extend(extra_tag)
|
||||
elif isinstance(extra_tag, str):
|
||||
tag.append(extra_tag)
|
||||
_extend(result.extra.get("tag"))
|
||||
if isinstance(result.metadata, dict):
|
||||
_extend(result.metadata.get("tag"))
|
||||
_extend(result.metadata.get("tags"))
|
||||
elif hasattr(result, "tag"):
|
||||
# Handle objects with tag attribute (e.g. SearchResult)
|
||||
val = getattr(result, "tag")
|
||||
if isinstance(val, (list, set, tuple)):
|
||||
tag.extend(val)
|
||||
elif isinstance(val, str):
|
||||
tag.append(val)
|
||||
_extend(getattr(result, "tag"))
|
||||
|
||||
if isinstance(result, dict):
|
||||
raw_tag = result.get("tag")
|
||||
if isinstance(raw_tag, list):
|
||||
tag.extend(raw_tag)
|
||||
elif isinstance(raw_tag, str):
|
||||
tag.append(raw_tag)
|
||||
_extend(result.get("tag"))
|
||||
_extend(result.get("tags"))
|
||||
|
||||
extra = result.get("extra")
|
||||
if isinstance(extra, dict):
|
||||
extra_tag = extra.get("tag")
|
||||
if isinstance(extra_tag, list):
|
||||
tag.extend(extra_tag)
|
||||
elif isinstance(extra_tag, str):
|
||||
tag.append(extra_tag)
|
||||
_extend(extra.get("tag"))
|
||||
_extend(extra.get("tags"))
|
||||
|
||||
fm = result.get("full_metadata") or result.get("metadata")
|
||||
if isinstance(fm, dict):
|
||||
_extend(fm.get("tag"))
|
||||
_extend(fm.get("tags"))
|
||||
|
||||
return merge_sequences(tag, case_sensitive=True)
|
||||
|
||||
|
||||
@@ -2079,6 +2240,11 @@ def extract_title_from_result(result: Any) -> Optional[str]:
|
||||
|
||||
|
||||
def extract_url_from_result(result: Any) -> list[str]:
|
||||
"""Extract all unique URLs from a result dict or PipeObject.
|
||||
|
||||
Handles mixed types (lists, strings) and various field names (url, source_url, webpage_url).
|
||||
Centralizes extraction logic for cmdlets like download-file, add-file, get-url.
|
||||
"""
|
||||
url: list[str] = []
|
||||
|
||||
def _extend(candidate: Any) -> None:
|
||||
@@ -2089,40 +2255,48 @@ def extract_url_from_result(result: Any) -> list[str]:
|
||||
elif isinstance(candidate, str):
|
||||
url.append(candidate)
|
||||
|
||||
# Priority 1: PipeObject (structured data)
|
||||
if isinstance(result, models.PipeObject):
|
||||
_extend(result.extra.get("url"))
|
||||
_extend(result.extra.get("url")) # Also check singular url
|
||||
_extend(result.url)
|
||||
_extend(result.source_url)
|
||||
# Also check extra and metadata for legacy or rich captures
|
||||
if isinstance(result.extra, dict):
|
||||
_extend(result.extra.get("url"))
|
||||
_extend(result.extra.get("source_url"))
|
||||
if isinstance(result.metadata, dict):
|
||||
_extend(result.metadata.get("url"))
|
||||
_extend(result.metadata.get("url"))
|
||||
_extend(result.metadata.get("url"))
|
||||
_extend(result.metadata.get("source_url"))
|
||||
_extend(result.metadata.get("webpage_url"))
|
||||
if isinstance(getattr(result, "full_metadata", None), dict):
|
||||
fm = getattr(result, "full_metadata", None)
|
||||
if isinstance(fm, dict):
|
||||
_extend(fm.get("url"))
|
||||
_extend(fm.get("url"))
|
||||
_extend(fm.get("url"))
|
||||
elif hasattr(result, "url") or hasattr(result, "url"):
|
||||
# Handle objects with url/url attribute
|
||||
_extend(getattr(result, "url", None))
|
||||
_extend(getattr(result, "url", None))
|
||||
_extend(fm.get("source_url"))
|
||||
_extend(fm.get("webpage_url"))
|
||||
|
||||
# Priority 2: Generic objects with .url or .source_url attribute
|
||||
elif hasattr(result, "url") or hasattr(result, "source_url"):
|
||||
_extend(getattr(result, "url", None))
|
||||
_extend(getattr(result, "source_url", None))
|
||||
|
||||
# Priority 3: Dictionary
|
||||
if isinstance(result, dict):
|
||||
_extend(result.get("url"))
|
||||
_extend(result.get("url"))
|
||||
_extend(result.get("url"))
|
||||
fm = result.get("full_metadata")
|
||||
if isinstance(fm, dict):
|
||||
_extend(fm.get("url"))
|
||||
_extend(fm.get("url"))
|
||||
_extend(fm.get("url"))
|
||||
_extend(result.get("source_url"))
|
||||
_extend(result.get("webpage_url"))
|
||||
|
||||
extra = result.get("extra")
|
||||
if isinstance(extra, dict):
|
||||
_extend(extra.get("url"))
|
||||
_extend(extra.get("url"))
|
||||
_extend(extra.get("url"))
|
||||
|
||||
fm = result.get("full_metadata") or result.get("metadata")
|
||||
if isinstance(fm, dict):
|
||||
_extend(fm.get("url"))
|
||||
_extend(fm.get("source_url"))
|
||||
_extend(fm.get("webpage_url"))
|
||||
|
||||
return merge_sequences(url, case_sensitive=True)
|
||||
from SYS.metadata import normalize_urls
|
||||
return normalize_urls(url)
|
||||
|
||||
|
||||
def extract_relationships(result: Any) -> Optional[Dict[str, Any]]:
|
||||
|
||||
Reference in New Issue
Block a user