sssssss
This commit is contained in:
@@ -372,6 +372,18 @@ def _handle_search_result(result: Any, args: Sequence[str], config: Dict[str, An
|
||||
log("Error: No magnet ID in debrid result", file=sys.stderr)
|
||||
return 1
|
||||
return _handle_debrid_file(magnet_id, file_title, config, args)
|
||||
elif storage_name.lower() in {'bandcamp', 'youtube'}:
|
||||
# Handle Bandcamp/YouTube via yt-dlp
|
||||
url = get_field(result, 'target', None)
|
||||
if not url:
|
||||
# Try to find URL in other fields
|
||||
url = get_field(result, 'url', None)
|
||||
|
||||
if not url:
|
||||
log(f"Error: No URL found for {storage_name} result", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
return _handle_ytdlp_download(url, file_title, config, args)
|
||||
else:
|
||||
log(f"Unknown storage backend: {storage_name}", file=sys.stderr)
|
||||
return 1
|
||||
@@ -507,8 +519,28 @@ def _handle_local_file(file_path: Optional[str], file_title: str, config: Dict[s
|
||||
try:
|
||||
source = Path(file_path)
|
||||
if not source.exists():
|
||||
log(f"Error: File not found: {file_path}", file=sys.stderr)
|
||||
return 1
|
||||
# Try to resolve by hash if the path looks like a hash
|
||||
resolved_local = False
|
||||
if looks_like_hash(str(file_path)):
|
||||
try:
|
||||
from config import get_local_storage_path
|
||||
from helper.local_library import LocalLibraryDB
|
||||
storage_path = get_local_storage_path(config)
|
||||
if storage_path:
|
||||
with LocalLibraryDB(storage_path) as db:
|
||||
resolved_path = db.search_by_hash(str(file_path))
|
||||
if resolved_path and resolved_path.exists():
|
||||
source = resolved_path
|
||||
file_path = str(resolved_path)
|
||||
resolved_local = True
|
||||
# Also set file_hash since we know it
|
||||
file_hash = str(file_path)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not resolved_local:
|
||||
log(f"Error: File not found: {file_path}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
# Check for explicit user flags
|
||||
force_mpv = any(str(a).lower() in {'-mpv', '--mpv', 'mpv'} for a in args)
|
||||
@@ -741,7 +773,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
# Also check for 'source' field (from add-file and other cmdlets)
|
||||
if not origin:
|
||||
origin = get_field(actual_result, 'source', None)
|
||||
if origin and origin.lower() in {'hydrus', 'local', 'debrid', 'alldebrid'}:
|
||||
if origin and origin.lower() in {'hydrus', 'local', 'debrid', 'alldebrid', 'bandcamp', 'youtube'}:
|
||||
# This is a search result with explicit origin - handle it via _handle_search_result
|
||||
return _handle_search_result(actual_result, args, config)
|
||||
|
||||
@@ -1023,8 +1055,28 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
if isinstance(local_target, str) and not is_url and not (hash_spec and file_hash):
|
||||
p = Path(local_target)
|
||||
if not p.exists():
|
||||
log(f"File missing: {p}")
|
||||
return 1
|
||||
# Check if it's a hash and try to resolve locally
|
||||
resolved_local = False
|
||||
if looks_like_hash(local_target):
|
||||
try:
|
||||
from config import get_local_storage_path
|
||||
from helper.local_library import LocalLibraryDB
|
||||
storage_path = get_local_storage_path(config)
|
||||
if storage_path:
|
||||
with LocalLibraryDB(storage_path) as db:
|
||||
resolved_path = db.search_by_hash(local_target)
|
||||
if resolved_path and resolved_path.exists():
|
||||
p = resolved_path
|
||||
resolved_local = True
|
||||
# Also set file_hash since we know it
|
||||
file_hash = local_target
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not resolved_local:
|
||||
log(f"File missing: {p}")
|
||||
return 1
|
||||
|
||||
source_path = p
|
||||
try:
|
||||
source_size = p.stat().st_size
|
||||
@@ -1046,127 +1098,158 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
except OSError:
|
||||
pass
|
||||
elif file_hash:
|
||||
# Try local resolution first if origin is local or just in case
|
||||
resolved_local = False
|
||||
try:
|
||||
client = hydrus_wrapper.get_client(config)
|
||||
except Exception as exc:
|
||||
log(f"Hydrus client unavailable: {exc}")
|
||||
return 1
|
||||
|
||||
if client is None:
|
||||
log("Hydrus client unavailable")
|
||||
return 1
|
||||
|
||||
# Fetch metadata and tags (needed for both -metadata flag and audio tagging)
|
||||
# Fetch tags
|
||||
try:
|
||||
tags_payload = client.fetch_file_metadata(hashes=[file_hash], include_service_keys_to_tags=True)
|
||||
from config import get_local_storage_path
|
||||
from helper.local_library import LocalLibraryDB
|
||||
storage_path = get_local_storage_path(config)
|
||||
if storage_path:
|
||||
with LocalLibraryDB(storage_path) as db:
|
||||
resolved_path = db.search_by_hash(file_hash)
|
||||
if resolved_path and resolved_path.exists():
|
||||
source_path = resolved_path
|
||||
resolved_local = True
|
||||
try:
|
||||
source_size = source_path.stat().st_size
|
||||
except OSError:
|
||||
source_size = None
|
||||
duration_sec = _ffprobe_duration_seconds(source_path)
|
||||
except Exception:
|
||||
tags_payload = {}
|
||||
|
||||
# Fetch URLs
|
||||
try:
|
||||
urls_payload = client.fetch_file_metadata(hashes=[file_hash], include_file_urls=True)
|
||||
except Exception:
|
||||
urls_payload = {}
|
||||
|
||||
# Extract title from metadata if base_name is still 'export'
|
||||
if base_name == 'export' and tags_payload:
|
||||
pass
|
||||
|
||||
if not resolved_local:
|
||||
try:
|
||||
file_metadata = tags_payload.get('file_metadata', [])
|
||||
if file_metadata and isinstance(file_metadata, list) and len(file_metadata) > 0:
|
||||
meta = file_metadata[0]
|
||||
if isinstance(meta, dict):
|
||||
tags_dict = meta.get('tags', {})
|
||||
if isinstance(tags_dict, dict):
|
||||
# Look for title in storage tags
|
||||
for service in tags_dict.values():
|
||||
if isinstance(service, dict):
|
||||
storage = service.get('storage_tags', {})
|
||||
if isinstance(storage, dict):
|
||||
for tag_list in storage.values():
|
||||
if isinstance(tag_list, list):
|
||||
for tag in tag_list:
|
||||
if isinstance(tag, str) and tag.lower().startswith('title:'):
|
||||
title_val = tag.split(':', 1)[1].strip()
|
||||
if title_val:
|
||||
base_name = _sanitize_name(title_val)
|
||||
break
|
||||
if base_name != 'export':
|
||||
break
|
||||
if base_name != 'export':
|
||||
break
|
||||
client = hydrus_wrapper.get_client(config)
|
||||
except Exception as exc:
|
||||
log(f"Hydrus client unavailable: {exc}")
|
||||
return 1
|
||||
|
||||
if client is None:
|
||||
log("Hydrus client unavailable")
|
||||
return 1
|
||||
|
||||
# Fetch metadata and tags (needed for both -metadata flag and audio tagging)
|
||||
# Fetch tags
|
||||
try:
|
||||
tags_payload = client.fetch_file_metadata(hashes=[file_hash], include_service_keys_to_tags=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Normal file export (happens regardless of -metadata flag)
|
||||
try:
|
||||
from helper.hydrus import hydrus_export as _hydrus_export
|
||||
except Exception:
|
||||
_hydrus_export = None # type: ignore
|
||||
if _hydrus_export is None:
|
||||
log("Hydrus export helper unavailable")
|
||||
return 1
|
||||
download_dir = out_override if (out_override and out_override.is_dir()) else default_dir
|
||||
try:
|
||||
download_dir.mkdir(parents=True, exist_ok=True)
|
||||
except Exception:
|
||||
# If mkdir fails, fall back to default_dir
|
||||
download_dir = default_dir
|
||||
|
||||
# Verify the directory is writable; if not, fall back to default
|
||||
try:
|
||||
test_file = download_dir / f".downlow_write_test_{_uuid.uuid4().hex[:8]}"
|
||||
test_file.touch()
|
||||
test_file.unlink()
|
||||
except (OSError, PermissionError):
|
||||
# Directory is not writable, use default_dir instead
|
||||
download_dir = default_dir
|
||||
tags_payload = {}
|
||||
|
||||
# Fetch URLs
|
||||
try:
|
||||
urls_payload = client.fetch_file_metadata(hashes=[file_hash], include_file_urls=True)
|
||||
except Exception:
|
||||
urls_payload = {}
|
||||
|
||||
# Extract title from metadata if base_name is still 'export'
|
||||
if base_name == 'export' and tags_payload:
|
||||
try:
|
||||
file_metadata = tags_payload.get('file_metadata', [])
|
||||
if file_metadata and isinstance(file_metadata, list) and len(file_metadata) > 0:
|
||||
meta = file_metadata[0]
|
||||
if isinstance(meta, dict):
|
||||
tags_dict = meta.get('tags', {})
|
||||
if isinstance(tags_dict, dict):
|
||||
# Look for title in storage tags
|
||||
for service in tags_dict.values():
|
||||
if isinstance(service, dict):
|
||||
storage = service.get('storage_tags', {})
|
||||
if isinstance(storage, dict):
|
||||
for tag_list in storage.values():
|
||||
if isinstance(tag_list, list):
|
||||
for tag in tag_list:
|
||||
if isinstance(tag, str) and tag.lower().startswith('title:'):
|
||||
title_val = tag.split(':', 1)[1].strip()
|
||||
if title_val:
|
||||
base_name = _sanitize_name(title_val)
|
||||
break
|
||||
if base_name != 'export':
|
||||
break
|
||||
if base_name != 'export':
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Normal file export (happens regardless of -metadata flag)
|
||||
try:
|
||||
from helper.hydrus import hydrus_export as _hydrus_export
|
||||
except Exception:
|
||||
_hydrus_export = None # type: ignore
|
||||
if _hydrus_export is None:
|
||||
log("Hydrus export helper unavailable")
|
||||
return 1
|
||||
download_dir = out_override if (out_override and out_override.is_dir()) else default_dir
|
||||
try:
|
||||
download_dir.mkdir(parents=True, exist_ok=True)
|
||||
except Exception:
|
||||
# If mkdir fails, fall back to default_dir
|
||||
download_dir = default_dir
|
||||
|
||||
# Verify the directory is writable; if not, fall back to default
|
||||
try:
|
||||
test_file = download_dir / f".downlow_write_test_{_uuid.uuid4().hex[:8]}"
|
||||
test_file.touch()
|
||||
test_file.unlink()
|
||||
except (OSError, PermissionError):
|
||||
# Directory is not writable, use default_dir instead
|
||||
download_dir = default_dir
|
||||
try:
|
||||
download_dir.mkdir(parents=True, exist_ok=True)
|
||||
except Exception:
|
||||
pass
|
||||
token = (_uuid.uuid4().hex[:8])
|
||||
provisional_stem = f"{base_name}.dlhx_{token}"
|
||||
provisional = download_dir / f"{provisional_stem}.bin"
|
||||
class _Args:
|
||||
pass
|
||||
token = (_uuid.uuid4().hex[:8])
|
||||
provisional_stem = f"{base_name}.dlhx_{token}"
|
||||
provisional = download_dir / f"{provisional_stem}.bin"
|
||||
class _Args:
|
||||
pass
|
||||
args_obj = _Args()
|
||||
setattr(args_obj, 'output', provisional)
|
||||
setattr(args_obj, 'format', 'copy')
|
||||
setattr(args_obj, 'tmp_dir', str(download_dir))
|
||||
setattr(args_obj, 'metadata_json', None)
|
||||
setattr(args_obj, 'hydrus_url', get_hydrus_url(config, "home") or "http://localhost:45869")
|
||||
setattr(args_obj, 'access_key', get_hydrus_access_key(config, "home") or "")
|
||||
setattr(args_obj, 'timeout', float(config.get('HydrusNetwork_Request_Timeout') or 60.0))
|
||||
try:
|
||||
file_url = client.file_url(file_hash)
|
||||
except Exception:
|
||||
file_url = None
|
||||
setattr(args_obj, 'file_url', file_url)
|
||||
setattr(args_obj, 'file_hash', file_hash)
|
||||
import io as _io, contextlib as _contextlib
|
||||
_buf = _io.StringIO()
|
||||
status = 1
|
||||
with _contextlib.redirect_stdout(_buf):
|
||||
status = _hydrus_export(args_obj, None)
|
||||
if status != 0:
|
||||
stderr_text = _buf.getvalue().strip()
|
||||
if stderr_text:
|
||||
log(stderr_text)
|
||||
return status
|
||||
json_text = _buf.getvalue().strip().splitlines()[-1] if _buf.getvalue() else ''
|
||||
final_from_json: Optional[Path] = None
|
||||
try:
|
||||
payload = json.loads(json_text) if json_text else None
|
||||
if isinstance(payload, dict):
|
||||
outp = payload.get('output')
|
||||
if isinstance(outp, str) and outp:
|
||||
final_from_json = Path(outp)
|
||||
except Exception:
|
||||
final_from_json = None
|
||||
if final_from_json and final_from_json.exists():
|
||||
source_path = final_from_json
|
||||
else:
|
||||
args_obj = _Args()
|
||||
setattr(args_obj, 'output', provisional)
|
||||
setattr(args_obj, 'format', 'copy')
|
||||
setattr(args_obj, 'tmp_dir', str(download_dir))
|
||||
setattr(args_obj, 'metadata_json', None)
|
||||
setattr(args_obj, 'hydrus_url', get_hydrus_url(config, "home") or "http://localhost:45869")
|
||||
setattr(args_obj, 'access_key', get_hydrus_access_key(config, "home") or "")
|
||||
setattr(args_obj, 'timeout', float(config.get('HydrusNetwork_Request_Timeout') or 60.0))
|
||||
try:
|
||||
file_url = client.file_url(file_hash)
|
||||
except Exception:
|
||||
file_url = None
|
||||
setattr(args_obj, 'file_url', file_url)
|
||||
setattr(args_obj, 'file_hash', file_hash)
|
||||
import io as _io, contextlib as _contextlib
|
||||
_buf = _io.StringIO()
|
||||
status = 1
|
||||
with _contextlib.redirect_stdout(_buf):
|
||||
status = _hydrus_export(args_obj, None)
|
||||
if status != 0:
|
||||
stderr_text = _buf.getvalue().strip()
|
||||
if stderr_text:
|
||||
log(stderr_text)
|
||||
return status
|
||||
json_text = _buf.getvalue().strip().splitlines()[-1] if _buf.getvalue() else ''
|
||||
final_from_json: Optional[Path] = None
|
||||
try:
|
||||
payload = json.loads(json_text) if json_text else None
|
||||
if isinstance(payload, dict):
|
||||
outp = payload.get('output')
|
||||
if isinstance(outp, str) and outp:
|
||||
final_from_json = Path(outp)
|
||||
except Exception:
|
||||
final_from_json = None
|
||||
if final_from_json and final_from_json.exists():
|
||||
source_path = final_from_json
|
||||
else:
|
||||
candidates = [p for p in provisional.parent.glob(provisional_stem + '*') if p.exists() and p.is_file()]
|
||||
non_provisional = [p for p in candidates if p.suffix.lower() not in {'.bin', '.hydrus'}]
|
||||
pick_from = non_provisional if non_provisional else candidates
|
||||
if pick_from:
|
||||
try:
|
||||
source_path = max(pick_from, key=lambda p: p.stat().st_mtime)
|
||||
except Exception:
|
||||
source_path = pick_from[0]
|
||||
else:
|
||||
source_path = provisional
|
||||
candidates = [p for p in provisional.parent.glob(provisional_stem + '*') if p.exists() and p.is_file()]
|
||||
non_provisional = [p for p in candidates if p.suffix.lower() not in {'.bin', '.hydrus'}]
|
||||
pick_from = non_provisional if non_provisional else candidates
|
||||
@@ -1177,16 +1260,6 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
source_path = pick_from[0]
|
||||
else:
|
||||
source_path = provisional
|
||||
candidates = [p for p in provisional.parent.glob(provisional_stem + '*') if p.exists() and p.is_file()]
|
||||
non_provisional = [p for p in candidates if p.suffix.lower() not in {'.bin', '.hydrus'}]
|
||||
pick_from = non_provisional if non_provisional else candidates
|
||||
if pick_from:
|
||||
try:
|
||||
source_path = max(pick_from, key=lambda p: p.stat().st_mtime)
|
||||
except Exception:
|
||||
source_path = pick_from[0]
|
||||
else:
|
||||
source_path = provisional
|
||||
try:
|
||||
source_size = source_size or (source_path.stat().st_size if source_path.exists() else None)
|
||||
except OSError:
|
||||
@@ -1479,6 +1552,77 @@ def _unique_path(p: Path) -> Path:
|
||||
return p
|
||||
|
||||
|
||||
def _handle_ytdlp_download(url: str, title: str, config: Dict[str, Any], args: Sequence[str]) -> int:
|
||||
"""Handle download/streaming of URL using yt-dlp."""
|
||||
if not url:
|
||||
log("Error: No URL provided", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
# Check for -storage local
|
||||
args_list = list(map(str, args))
|
||||
storage_mode = None
|
||||
if '-storage' in args_list:
|
||||
try:
|
||||
idx = args_list.index('-storage')
|
||||
if idx + 1 < len(args_list):
|
||||
storage_mode = args_list[idx + 1].lower()
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
force_local = (storage_mode == 'local')
|
||||
|
||||
if not force_local:
|
||||
# Default: Stream to MPV
|
||||
if _play_in_mpv(url, title, is_stream=True):
|
||||
from . import pipe
|
||||
pipe._run(None, [], config)
|
||||
return 0
|
||||
else:
|
||||
# Fallback to browser
|
||||
try:
|
||||
import webbrowser
|
||||
webbrowser.open(url)
|
||||
debug(f"[get-file] Opened in browser: {title}", file=sys.stderr)
|
||||
return 0
|
||||
except Exception:
|
||||
pass
|
||||
return 1
|
||||
|
||||
# Download mode
|
||||
try:
|
||||
import yt_dlp
|
||||
except ImportError:
|
||||
log("Error: yt-dlp not installed. Please install it to download.", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
log(f"Downloading {title}...", file=sys.stderr)
|
||||
|
||||
# Determine output directory
|
||||
download_dir = resolve_output_dir(config)
|
||||
try:
|
||||
download_dir.mkdir(parents=True, exist_ok=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Configure yt-dlp
|
||||
ydl_opts = {
|
||||
'outtmpl': str(download_dir / '%(title)s.%(ext)s'),
|
||||
'quiet': False,
|
||||
'no_warnings': True,
|
||||
# Use best audio/video
|
||||
'format': 'best',
|
||||
}
|
||||
|
||||
try:
|
||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||
ydl.download([url])
|
||||
log(f"Downloaded to: {download_dir}", file=sys.stderr)
|
||||
return 0
|
||||
except Exception as e:
|
||||
log(f"Error downloading: {e}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
CMDLET = Cmdlet(
|
||||
name="get-file",
|
||||
summary="Export files: from Hydrus database OR from AllDebrid magnets via pipe. Auto-detects source and handles accordingly.",
|
||||
|
||||
Reference in New Issue
Block a user