no logging
This commit is contained in:
@@ -1058,11 +1058,11 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
debug(f"Torrent/magnet added: {arg[:50]}...")
|
debug(f"Torrent/magnet added: {arg[:50]}...")
|
||||||
elif _is_torrent_file_or_url(arg):
|
elif _is_torrent_file_or_url(arg):
|
||||||
# Handle .torrent files and URLs
|
# Handle .torrent files and URLs
|
||||||
log(f"Processing torrent file/URL: {arg}", flush=True)
|
debug(f"Processing torrent file/URL: {arg}")
|
||||||
magnet = _process_torrent_input(arg)
|
magnet = _process_torrent_input(arg)
|
||||||
if magnet and magnet.lower().startswith('magnet:'):
|
if magnet and magnet.lower().startswith('magnet:'):
|
||||||
urls_to_download.append(magnet)
|
urls_to_download.append(magnet)
|
||||||
log(f"✓ Converted to magnet: {magnet[:70]}...", flush=True)
|
debug(f"✓ Converted to magnet: {magnet[:70]}...")
|
||||||
elif magnet:
|
elif magnet:
|
||||||
urls_to_download.append(magnet)
|
urls_to_download.append(magnet)
|
||||||
else:
|
else:
|
||||||
@@ -1081,17 +1081,17 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
line = line.strip()
|
line = line.strip()
|
||||||
if line and line.lower().startswith(('http://', 'https://')):
|
if line and line.lower().startswith(('http://', 'https://')):
|
||||||
urls_to_download.append(line)
|
urls_to_download.append(line)
|
||||||
log(f"Loaded URLs from file: {arg}", flush=True)
|
debug(f"Loaded URLs from file: {arg}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log(f"Error reading file {arg}: {e}", file=sys.stderr)
|
log(f"Error reading file {arg}: {e}", file=sys.stderr)
|
||||||
else:
|
else:
|
||||||
log(f"Ignored argument: {arg}", file=sys.stderr)
|
debug(f"Ignored argument: {arg}")
|
||||||
|
|
||||||
# Item selection (for playlists/formats)
|
# Item selection (for playlists/formats)
|
||||||
# Note: -item flag is deprecated in favor of @N pipeline selection, but kept for compatibility
|
# Note: -item flag is deprecated in favor of @N pipeline selection, but kept for compatibility
|
||||||
playlist_items = parsed.get("item")
|
playlist_items = parsed.get("item")
|
||||||
if playlist_items:
|
if playlist_items:
|
||||||
log(f"Item selection: {playlist_items}", flush=True)
|
debug(f"Item selection: {playlist_items}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1149,7 +1149,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
if isinstance(item, dict) and item.get('__playlist_url'):
|
if isinstance(item, dict) and item.get('__playlist_url'):
|
||||||
playlist_url = item.get('__playlist_url')
|
playlist_url = item.get('__playlist_url')
|
||||||
item_num = item.get('__playlist_item', 1)
|
item_num = item.get('__playlist_item', 1)
|
||||||
log(f"📍 Playlist item from add-file: #{item_num}", flush=True)
|
debug(f"📍 Playlist item from add-file: #{item_num}")
|
||||||
# Add to download list with marker
|
# Add to download list with marker
|
||||||
urls_to_download.append({
|
urls_to_download.append({
|
||||||
'__playlist_url': playlist_url,
|
'__playlist_url': playlist_url,
|
||||||
@@ -1166,7 +1166,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
|
|
||||||
if playlist_url:
|
if playlist_url:
|
||||||
# Playlist item selected - need to download this specific track
|
# Playlist item selected - need to download this specific track
|
||||||
log(f"📍 Playlist item selected: #{item_num} - {item.get('title', 'Unknown')}", flush=True)
|
debug(f"📍 Playlist item selected: #{item_num} - {item.get('title', 'Unknown')}")
|
||||||
# Add to download list - the playlist will be probed and item extracted
|
# Add to download list - the playlist will be probed and item extracted
|
||||||
# Store with special marker so we know which item to select
|
# Store with special marker so we know which item to select
|
||||||
urls_to_download.append({
|
urls_to_download.append({
|
||||||
@@ -1177,14 +1177,14 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
|
|
||||||
# ====== CHECK FOR FORMAT SELECTION RESULT ======
|
# ====== CHECK FOR FORMAT SELECTION RESULT ======
|
||||||
if isinstance(item, dict) and item.get('format_id') is not None and item.get('source_url'):
|
if isinstance(item, dict) and item.get('format_id') is not None and item.get('source_url'):
|
||||||
log(f"🎬 Format selected from pipe: {item.get('format_id')}", flush=True)
|
debug(f"🎬 Format selected from pipe: {item.get('format_id')}")
|
||||||
log(f" Source URL: {item.get('source_url')}", flush=True)
|
debug(f" Source URL: {item.get('source_url')}")
|
||||||
# Store as dict so we can extract format_id + source_url during download
|
# Store as dict so we can extract format_id + source_url during download
|
||||||
urls_to_download.append(item)
|
urls_to_download.append(item)
|
||||||
continue
|
continue
|
||||||
elif hasattr(item, 'format_id') and hasattr(item, 'source_url') and item.format_id is not None:
|
elif hasattr(item, 'format_id') and hasattr(item, 'source_url') and item.format_id is not None:
|
||||||
log(f"🎬 Format selected from pipe: {item.format_id}", flush=True)
|
debug(f"🎬 Format selected from pipe: {item.format_id}")
|
||||||
log(f" Source URL: {item.source_url}", flush=True)
|
debug(f" Source URL: {item.source_url}")
|
||||||
urls_to_download.append({
|
urls_to_download.append({
|
||||||
'format_id': item.format_id,
|
'format_id': item.format_id,
|
||||||
'source_url': item.source_url,
|
'source_url': item.source_url,
|
||||||
@@ -1204,9 +1204,9 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
isbn = metadata.get('isbn') or item.get('isbn')
|
isbn = metadata.get('isbn') or item.get('isbn')
|
||||||
olid = metadata.get('olid') or item.get('olid')
|
olid = metadata.get('olid') or item.get('olid')
|
||||||
|
|
||||||
log(f"[search-result] OpenLibrary: '{title}'", flush=True)
|
debug(f"[search-result] OpenLibrary: '{title}'")
|
||||||
if isbn:
|
if isbn:
|
||||||
log(f" ISBN: {isbn}", flush=True)
|
debug(f" ISBN: {isbn}")
|
||||||
|
|
||||||
# Check if book is borrowable from ebook_access field or status
|
# Check if book is borrowable from ebook_access field or status
|
||||||
ebook_access = metadata.get('ebook_access') or item.get('ebook_access', '')
|
ebook_access = metadata.get('ebook_access') or item.get('ebook_access', '')
|
||||||
@@ -1217,8 +1217,8 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
is_borrowable = _is_openlibrary_downloadable(ebook_access, status_text)
|
is_borrowable = _is_openlibrary_downloadable(ebook_access, status_text)
|
||||||
|
|
||||||
if is_borrowable:
|
if is_borrowable:
|
||||||
log(f" ✓ Available for borrowing on Archive.org", flush=True)
|
debug(f" ✓ Available for borrowing on Archive.org")
|
||||||
log(f" → Queued for auto-borrowing...", flush=True)
|
debug(f" → Queued for auto-borrowing...")
|
||||||
# Queue borrow request as special dict object
|
# Queue borrow request as special dict object
|
||||||
# We need OCAID (Archive.org ID), not just numeric OLID
|
# We need OCAID (Archive.org ID), not just numeric OLID
|
||||||
ocaid = archive_id
|
ocaid = archive_id
|
||||||
@@ -1233,7 +1233,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
ol_data = r.json()
|
ol_data = r.json()
|
||||||
ocaid = ol_data.get('ocaid')
|
ocaid = ol_data.get('ocaid')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log(f" ⚠ Could not fetch OCAID from OpenLibrary: {e}", file=sys.stderr)
|
debug(f" ⚠ Could not fetch OCAID from OpenLibrary: {e}")
|
||||||
|
|
||||||
if ocaid:
|
if ocaid:
|
||||||
urls_to_download.append({
|
urls_to_download.append({
|
||||||
@@ -1246,7 +1246,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
else:
|
else:
|
||||||
# OCAID not found - book claims borrowable but not on Archive.org
|
# OCAID not found - book claims borrowable but not on Archive.org
|
||||||
# Fall back to LibGen search instead
|
# Fall back to LibGen search instead
|
||||||
log(f" ⚠ Book marked borrowable but not found on Archive.org", file=sys.stderr)
|
debug(f" ⚠ Book marked borrowable but not found on Archive.org")
|
||||||
if isbn:
|
if isbn:
|
||||||
try:
|
try:
|
||||||
from helper.search_provider import get_provider
|
from helper.search_provider import get_provider
|
||||||
@@ -1258,19 +1258,19 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
url = libgen_result.get('target') if isinstance(libgen_result, dict) else getattr(libgen_result, 'target', None)
|
url = libgen_result.get('target') if isinstance(libgen_result, dict) else getattr(libgen_result, 'target', None)
|
||||||
if url:
|
if url:
|
||||||
urls_to_download.append(url)
|
urls_to_download.append(url)
|
||||||
log(f" ✓ Found on LibGen instead", flush=True)
|
debug(f" ✓ Found on LibGen instead")
|
||||||
else:
|
else:
|
||||||
log(f" ⚠ Not found on LibGen", file=sys.stderr)
|
debug(f" ⚠ Not found on LibGen")
|
||||||
else:
|
else:
|
||||||
log(f" ⚠ Not found on LibGen", file=sys.stderr)
|
debug(f" ⚠ Not found on LibGen")
|
||||||
else:
|
else:
|
||||||
log(f" ⚠ LibGen provider not available", file=sys.stderr)
|
debug(f" ⚠ LibGen provider not available")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log(f" ✗ Error searching LibGen: {e}", file=sys.stderr)
|
debug(f" ✗ Error searching LibGen: {e}")
|
||||||
else:
|
else:
|
||||||
# Book is NOT borrowable - route to LibGen
|
# Book is NOT borrowable - route to LibGen
|
||||||
if isbn:
|
if isbn:
|
||||||
log(f" ⚠ Not available on Archive.org - attempting LibGen...", flush=True)
|
debug(f" ⚠ Not available on Archive.org - attempting LibGen...")
|
||||||
try:
|
try:
|
||||||
from helper.search_provider import get_provider
|
from helper.search_provider import get_provider
|
||||||
libgen_provider = get_provider("libgen", config)
|
libgen_provider = get_provider("libgen", config)
|
||||||
@@ -1281,21 +1281,21 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
url = libgen_result.get('target') if isinstance(libgen_result, dict) else getattr(libgen_result, 'target', None)
|
url = libgen_result.get('target') if isinstance(libgen_result, dict) else getattr(libgen_result, 'target', None)
|
||||||
if url:
|
if url:
|
||||||
urls_to_download.append(url)
|
urls_to_download.append(url)
|
||||||
log(f" ✓ Found on LibGen", flush=True)
|
debug(f" ✓ Found on LibGen")
|
||||||
else:
|
else:
|
||||||
log(f" ⚠ Not found on LibGen", file=sys.stderr)
|
debug(f" ⚠ Not found on LibGen")
|
||||||
else:
|
else:
|
||||||
log(f" ⚠ Not found on LibGen", flush=True)
|
debug(f" ⚠ Not found on LibGen")
|
||||||
log(f" ▶ To search LibGen: search-file -provider libgen 'isbn:{isbn}' | @1 | download-data", flush=True)
|
debug(f" ▶ To search LibGen: search-file -provider libgen 'isbn:{isbn}' | @1 | download-data")
|
||||||
else:
|
else:
|
||||||
log(f" ▶ To search LibGen: search-file -provider libgen 'isbn:{isbn}' | @1 | download-data", flush=True)
|
debug(f" ▶ To search LibGen: search-file -provider libgen 'isbn:{isbn}' | @1 | download-data")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log(f" ⚠ Could not search LibGen: {e}", file=sys.stderr)
|
debug(f" ⚠ Could not search LibGen: {e}")
|
||||||
log(f" ▶ To search LibGen: search-file -provider libgen 'isbn:{isbn}' | @1 | download-data", flush=True)
|
debug(f" ▶ To search LibGen: search-file -provider libgen 'isbn:{isbn}' | @1 | download-data")
|
||||||
else:
|
else:
|
||||||
log(f" ⚠ ISBN not available", flush=True)
|
debug(f" ⚠ ISBN not available")
|
||||||
log(f" ▶ Visit: {item.get('target', 'https://openlibrary.org')}", flush=True)
|
debug(f" ▶ Visit: {item.get('target', 'https://openlibrary.org')}")
|
||||||
log(f" ▶ Or find ISBN and use: search-file -provider libgen 'isbn:\"<ISBN>\"'", flush=True)
|
debug(f" ▶ Or find ISBN and use: search-file -provider libgen 'isbn:\"<ISBN>\"'")
|
||||||
elif origin == 'soulseek':
|
elif origin == 'soulseek':
|
||||||
# Handle Soulseek downloads using the provider
|
# Handle Soulseek downloads using the provider
|
||||||
metadata = item.get('full_metadata', {}) if isinstance(item.get('full_metadata'), dict) else {}
|
metadata = item.get('full_metadata', {}) if isinstance(item.get('full_metadata'), dict) else {}
|
||||||
@@ -1350,18 +1350,18 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
)
|
)
|
||||||
pipeline_context.emit(result_dict)
|
pipeline_context.emit(result_dict)
|
||||||
else:
|
else:
|
||||||
log(f" ✗ Download failed (peer may be offline)", file=sys.stderr)
|
debug(f" ✗ Download failed (peer may be offline)")
|
||||||
if db:
|
if db:
|
||||||
db.append_worker_stdout(worker_id, f"✗ Download failed for {title}")
|
db.append_worker_stdout(worker_id, f"✗ Download failed for {title}")
|
||||||
log(f" ▶ Try another result: search-file -provider soulseek \"...\" | @2 | download-data", flush=True)
|
debug(f" ▶ Try another result: search-file -provider soulseek \"...\" | @2 | download-data")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log(f" ✗ Download error: {e}", file=sys.stderr)
|
debug(f" ✗ Download error: {e}")
|
||||||
if db:
|
if db:
|
||||||
db.append_worker_stdout(worker_id, f"✗ Error: {e}")
|
db.append_worker_stdout(worker_id, f"✗ Error: {e}")
|
||||||
log(f" ▶ Alternative: search-soulseek -download \"{title}\" -storage <location>", flush=True)
|
debug(f" ▶ Alternative: search-soulseek -download \"{title}\" -storage <location>")
|
||||||
else:
|
else:
|
||||||
log(f"[search-result] Soulseek: '{title}'", flush=True)
|
debug(f"[search-result] Soulseek: '{title}'")
|
||||||
log(f" ⚠ Missing download info (username/filename)", flush=True)
|
debug(f" ⚠ Missing download info (username/filename)")
|
||||||
if db:
|
if db:
|
||||||
db.append_worker_stdout(worker_id, f"⚠ Missing download info for {title}")
|
db.append_worker_stdout(worker_id, f"⚠ Missing download info for {title}")
|
||||||
elif origin == 'libgen':
|
elif origin == 'libgen':
|
||||||
@@ -1380,17 +1380,17 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
'book_id': book_id,
|
'book_id': book_id,
|
||||||
}
|
}
|
||||||
urls_to_download.append(url_entry)
|
urls_to_download.append(url_entry)
|
||||||
log(f"[search-result] LibGen: '{title}'", flush=True)
|
debug(f"[search-result] LibGen: '{title}'")
|
||||||
log(f" ✓ Queued for download", flush=True)
|
debug(f" ✓ Queued for download")
|
||||||
if mirrors:
|
if mirrors:
|
||||||
log(f" Mirrors available: {len(mirrors)}", flush=True)
|
debug(f" Mirrors available: {len(mirrors)}")
|
||||||
elif origin == 'debrid':
|
elif origin == 'debrid':
|
||||||
# Debrid results can use download-data
|
# Debrid results can use download-data
|
||||||
url = item.get('target')
|
url = item.get('target')
|
||||||
if url:
|
if url:
|
||||||
urls_to_download.append(str(url))
|
urls_to_download.append(str(url))
|
||||||
log(f"[search-result] Debrid: '{title}'", flush=True)
|
debug(f"[search-result] Debrid: '{title}'")
|
||||||
log(f" ✓ Queued for download", flush=True)
|
debug(f" ✓ Queued for download")
|
||||||
else:
|
else:
|
||||||
# Regular fields for non-search results
|
# Regular fields for non-search results
|
||||||
url = item.get('url') or item.get('link') or item.get('href') or item.get('target')
|
url = item.get('url') or item.get('link') or item.get('href') or item.get('target')
|
||||||
@@ -1407,9 +1407,9 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
isbn = metadata.get('isbn') or getattr(item, 'isbn', None)
|
isbn = metadata.get('isbn') or getattr(item, 'isbn', None)
|
||||||
olid = metadata.get('olid') or getattr(item, 'olid', None)
|
olid = metadata.get('olid') or getattr(item, 'olid', None)
|
||||||
|
|
||||||
log(f"[search-result] OpenLibrary: '{title}'", flush=True)
|
debug(f"[search-result] OpenLibrary: '{title}'")
|
||||||
if isbn:
|
if isbn:
|
||||||
log(f" ISBN: {isbn}", flush=True)
|
debug(f" ISBN: {isbn}")
|
||||||
|
|
||||||
# Check if book is borrowable from ebook_access field or status
|
# Check if book is borrowable from ebook_access field or status
|
||||||
ebook_access = metadata.get('ebook_access') or getattr(item, 'ebook_access', '')
|
ebook_access = metadata.get('ebook_access') or getattr(item, 'ebook_access', '')
|
||||||
@@ -1421,8 +1421,8 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
|
|
||||||
if is_borrowable:
|
if is_borrowable:
|
||||||
# Book IS borrowable on Archive.org
|
# Book IS borrowable on Archive.org
|
||||||
log(f" ✓ Available for borrowing on Archive.org", flush=True)
|
debug(f" ✓ Available for borrowing on Archive.org")
|
||||||
log(f" → Queued for auto-borrowing...", flush=True)
|
debug(f" → Queued for auto-borrowing...")
|
||||||
# Queue borrow request as special dict object
|
# Queue borrow request as special dict object
|
||||||
ocaid = archive_id
|
ocaid = archive_id
|
||||||
if not ocaid and isbn:
|
if not ocaid and isbn:
|
||||||
@@ -1434,7 +1434,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
ol_data = r.json()
|
ol_data = r.json()
|
||||||
ocaid = ol_data.get('ocaid')
|
ocaid = ol_data.get('ocaid')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log(f" ⚠ Could not fetch OCAID from OpenLibrary: {e}", file=sys.stderr)
|
debug(f" ⚠ Could not fetch OCAID from OpenLibrary: {e}")
|
||||||
if ocaid:
|
if ocaid:
|
||||||
urls_to_download.append({
|
urls_to_download.append({
|
||||||
'__borrow_request__': True,
|
'__borrow_request__': True,
|
||||||
@@ -1446,7 +1446,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
else:
|
else:
|
||||||
# OCAID not found - book claims borrowable but not on Archive.org
|
# OCAID not found - book claims borrowable but not on Archive.org
|
||||||
# Fall back to LibGen search instead
|
# Fall back to LibGen search instead
|
||||||
log(f" ⚠ No Archive.org ID found - attempting LibGen instead...", file=sys.stderr)
|
debug(f" ⚠ No Archive.org ID found - attempting LibGen instead...")
|
||||||
if isbn:
|
if isbn:
|
||||||
try:
|
try:
|
||||||
from helper.search_provider import get_provider
|
from helper.search_provider import get_provider
|
||||||
@@ -1458,21 +1458,21 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
url = libgen_result.get('target') if isinstance(libgen_result, dict) else getattr(libgen_result, 'target', None)
|
url = libgen_result.get('target') if isinstance(libgen_result, dict) else getattr(libgen_result, 'target', None)
|
||||||
if url:
|
if url:
|
||||||
urls_to_download.append(url)
|
urls_to_download.append(url)
|
||||||
log(f" ✓ Found on LibGen instead", flush=True)
|
debug(f" ✓ Found on LibGen instead")
|
||||||
else:
|
else:
|
||||||
log(f" ⚠ Not found on LibGen", file=sys.stderr)
|
debug(f" ⚠ Not found on LibGen")
|
||||||
else:
|
else:
|
||||||
log(f" ⚠ Not found on LibGen", file=sys.stderr)
|
debug(f" ⚠ Not found on LibGen")
|
||||||
else:
|
else:
|
||||||
log(f" ⚠ LibGen provider not available", file=sys.stderr)
|
debug(f" ⚠ LibGen provider not available")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log(f" ✗ Error searching LibGen: {e}", file=sys.stderr)
|
debug(f" ✗ Error searching LibGen: {e}")
|
||||||
else:
|
else:
|
||||||
log(f" ⚠ ISBN not available for LibGen fallback", file=sys.stderr)
|
debug(f" ⚠ ISBN not available for LibGen fallback")
|
||||||
else:
|
else:
|
||||||
# Book is NOT borrowable - route to LibGen
|
# Book is NOT borrowable - route to LibGen
|
||||||
if isbn:
|
if isbn:
|
||||||
log(f" ⚠ Not available on Archive.org - attempting LibGen...", flush=True)
|
debug(f" ⚠ Not available on Archive.org - attempting LibGen...")
|
||||||
try:
|
try:
|
||||||
from helper.search_provider import get_provider
|
from helper.search_provider import get_provider
|
||||||
libgen_provider = get_provider("libgen", config)
|
libgen_provider = get_provider("libgen", config)
|
||||||
@@ -1483,21 +1483,21 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
url = libgen_result.get('target') if isinstance(libgen_result, dict) else getattr(libgen_result, 'target', None)
|
url = libgen_result.get('target') if isinstance(libgen_result, dict) else getattr(libgen_result, 'target', None)
|
||||||
if url:
|
if url:
|
||||||
urls_to_download.append(url)
|
urls_to_download.append(url)
|
||||||
log(f" ✓ Found on LibGen", flush=True)
|
debug(f" ✓ Found on LibGen")
|
||||||
else:
|
else:
|
||||||
log(f" ⚠ Not found on LibGen", file=sys.stderr)
|
debug(f" ⚠ Not found on LibGen")
|
||||||
else:
|
else:
|
||||||
log(f" ⚠ Not found on LibGen", flush=True)
|
debug(f" ⚠ Not found on LibGen")
|
||||||
log(f" ▶ To search LibGen: search-file -provider libgen 'isbn:{isbn}' | @1 | download-data", flush=True)
|
debug(f" ▶ To search LibGen: search-file -provider libgen 'isbn:{isbn}' | @1 | download-data")
|
||||||
else:
|
else:
|
||||||
log(f" ▶ To search LibGen: search-file -provider libgen 'isbn:{isbn}' | @1 | download-data", flush=True)
|
debug(f" ▶ To search LibGen: search-file -provider libgen 'isbn:{isbn}' | @1 | download-data")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log(f" ⚠ Could not search LibGen: {e}", file=sys.stderr)
|
debug(f" ⚠ Could not search LibGen: {e}")
|
||||||
log(f" ▶ To search LibGen: search-file -provider libgen 'isbn:{isbn}' | @1 | download-data", flush=True)
|
debug(f" ▶ To search LibGen: search-file -provider libgen 'isbn:{isbn}' | @1 | download-data")
|
||||||
else:
|
else:
|
||||||
log(f" ⚠ ISBN not available", flush=True)
|
debug(f" ⚠ ISBN not available")
|
||||||
log(f" ▶ Visit: {getattr(item, 'target', 'https://openlibrary.org')}", flush=True)
|
debug(f" ▶ Visit: {getattr(item, 'target', 'https://openlibrary.org')}")
|
||||||
log(f" ▶ Or find ISBN and use: search-file -provider libgen 'isbn:\"<ISBN>\"'", flush=True)
|
debug(f" ▶ Or find ISBN and use: search-file -provider libgen 'isbn:\"<ISBN>\"'")
|
||||||
elif origin == 'soulseek':
|
elif origin == 'soulseek':
|
||||||
# Handle Soulseek downloads using the provider
|
# Handle Soulseek downloads using the provider
|
||||||
metadata = getattr(item, 'full_metadata', {}) if isinstance(getattr(item, 'full_metadata', None), dict) else {}
|
metadata = getattr(item, 'full_metadata', {}) if isinstance(getattr(item, 'full_metadata', None), dict) else {}
|
||||||
@@ -1510,8 +1510,8 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
import asyncio
|
import asyncio
|
||||||
from helper.search_provider import SoulSeekProvider
|
from helper.search_provider import SoulSeekProvider
|
||||||
provider = SoulSeekProvider(config)
|
provider = SoulSeekProvider(config)
|
||||||
log(f"[search-result] Soulseek: '{title}'", flush=True)
|
debug(f"[search-result] Soulseek: '{title}'")
|
||||||
log(f" ▶ Downloading from {username}...", flush=True)
|
debug(f" ▶ Downloading from {username}...")
|
||||||
|
|
||||||
if db:
|
if db:
|
||||||
db.append_worker_stdout(worker_id, f"Downloading from Soulseek: {title} (from {username})")
|
db.append_worker_stdout(worker_id, f"Downloading from Soulseek: {title} (from {username})")
|
||||||
@@ -1532,7 +1532,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
if success:
|
if success:
|
||||||
downloaded_file = Path(provider.DOWNLOAD_DIR) / Path(filename).name
|
downloaded_file = Path(provider.DOWNLOAD_DIR) / Path(filename).name
|
||||||
if downloaded_file.exists():
|
if downloaded_file.exists():
|
||||||
log(f" ✓ Downloaded: {downloaded_file.name}", flush=True)
|
debug(f" ✓ Downloaded: {downloaded_file.name}")
|
||||||
files_downloaded_directly += 1
|
files_downloaded_directly += 1
|
||||||
if db:
|
if db:
|
||||||
db.append_worker_stdout(worker_id, f"✓ Downloaded: {downloaded_file.name}")
|
db.append_worker_stdout(worker_id, f"✓ Downloaded: {downloaded_file.name}")
|
||||||
@@ -1552,18 +1552,18 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
)
|
)
|
||||||
pipeline_context.emit(result_dict)
|
pipeline_context.emit(result_dict)
|
||||||
else:
|
else:
|
||||||
log(f" ✗ Download failed (peer may be offline)", file=sys.stderr)
|
debug(f" ✗ Download failed (peer may be offline)")
|
||||||
if db:
|
if db:
|
||||||
db.append_worker_stdout(worker_id, f"✗ Download failed for {title}")
|
db.append_worker_stdout(worker_id, f"✗ Download failed for {title}")
|
||||||
log(f" ▶ Try another result: search-file -provider soulseek \"...\" | @2 | download-data", flush=True)
|
debug(f" ▶ Try another result: search-file -provider soulseek \"...\" | @2 | download-data")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log(f" ✗ Download error: {e}", file=sys.stderr)
|
debug(f" ✗ Download error: {e}")
|
||||||
if db:
|
if db:
|
||||||
db.append_worker_stdout(worker_id, f"✗ Error: {e}")
|
db.append_worker_stdout(worker_id, f"✗ Error: {e}")
|
||||||
log(f" ▶ Alternative: search-soulseek -download \"{title}\" -storage <location>", flush=True)
|
debug(f" ▶ Alternative: search-soulseek -download \"{title}\" -storage <location>")
|
||||||
else:
|
else:
|
||||||
log(f"[search-result] Soulseek: '{title}'", flush=True)
|
debug(f"[search-result] Soulseek: '{title}'")
|
||||||
log(f" ⚠ Missing download info (username/filename)", flush=True)
|
debug(f" ⚠ Missing download info (username/filename)")
|
||||||
if db:
|
if db:
|
||||||
db.append_worker_stdout(worker_id, f"⚠ Missing download info for {title}")
|
db.append_worker_stdout(worker_id, f"⚠ Missing download info for {title}")
|
||||||
elif origin == 'libgen':
|
elif origin == 'libgen':
|
||||||
@@ -1592,15 +1592,15 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
urls_to_download.append(str(url))
|
urls_to_download.append(str(url))
|
||||||
|
|
||||||
if not urls_to_download and files_downloaded_directly == 0:
|
if not urls_to_download and files_downloaded_directly == 0:
|
||||||
log(f"No downloadable URLs found", file=sys.stderr)
|
debug(f"No downloadable URLs found")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
log(f"Processing {len(urls_to_download)} URL(s)", flush=True)
|
debug(f"Processing {len(urls_to_download)} URL(s)")
|
||||||
for i, u in enumerate(urls_to_download, 1):
|
for i, u in enumerate(urls_to_download, 1):
|
||||||
if isinstance(u, dict):
|
if isinstance(u, dict):
|
||||||
log(f" [{i}] Format: {u.get('format_id', '?')} from {u.get('source_url', '?')[:60]}...", flush=True)
|
debug(f" [{i}] Format: {u.get('format_id', '?')} from {u.get('source_url', '?')[:60]}...")
|
||||||
else:
|
else:
|
||||||
log(f" [{i}] URL: {str(u)[:60]}...", flush=True)
|
debug(f" [{i}] URL: {str(u)[:60]}...")
|
||||||
|
|
||||||
# ========================================================================
|
# ========================================================================
|
||||||
# RESOLVE OUTPUT DIRECTORY
|
# RESOLVE OUTPUT DIRECTORY
|
||||||
@@ -1612,7 +1612,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
if storage_location:
|
if storage_location:
|
||||||
try:
|
try:
|
||||||
final_output_dir = SharedArgs.resolve_storage(storage_location)
|
final_output_dir = SharedArgs.resolve_storage(storage_location)
|
||||||
log(f"Using storage location: {storage_location} → {final_output_dir}", flush=True)
|
debug(f"Using storage location: {storage_location} → {final_output_dir}")
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
log(str(e), file=sys.stderr)
|
log(str(e), file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
@@ -1621,7 +1621,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
if final_output_dir is None and resolve_output_dir is not None:
|
if final_output_dir is None and resolve_output_dir is not None:
|
||||||
try:
|
try:
|
||||||
final_output_dir = resolve_output_dir(config)
|
final_output_dir = resolve_output_dir(config)
|
||||||
log(f"Using config resolver: {final_output_dir}", flush=True)
|
debug(f"Using config resolver: {final_output_dir}")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -1629,14 +1629,14 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
if final_output_dir is None and config and config.get("outfile"):
|
if final_output_dir is None and config and config.get("outfile"):
|
||||||
try:
|
try:
|
||||||
final_output_dir = Path(config["outfile"]).expanduser()
|
final_output_dir = Path(config["outfile"]).expanduser()
|
||||||
log(f"Using config outfile: {final_output_dir}", flush=True)
|
debug(f"Using config outfile: {final_output_dir}")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Priority 5: Default (home/Videos)
|
# Priority 5: Default (home/Videos)
|
||||||
if final_output_dir is None:
|
if final_output_dir is None:
|
||||||
final_output_dir = Path.home() / "Videos"
|
final_output_dir = Path.home() / "Videos"
|
||||||
log(f"Using default directory: {final_output_dir}", flush=True)
|
debug(f"Using default directory: {final_output_dir}")
|
||||||
|
|
||||||
# Ensure directory exists
|
# Ensure directory exists
|
||||||
try:
|
try:
|
||||||
@@ -1664,7 +1664,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
current_format_selector = format_selector
|
current_format_selector = format_selector
|
||||||
actual_url = url
|
actual_url = url
|
||||||
if isinstance(url, dict) and url.get('format_id') and url.get('source_url'):
|
if isinstance(url, dict) and url.get('format_id') and url.get('source_url'):
|
||||||
log(f"🎬 Format selected: {url.get('format_id')}", flush=True)
|
debug(f"🎬 Format selected: {url.get('format_id')}")
|
||||||
format_id = url.get('format_id')
|
format_id = url.get('format_id')
|
||||||
current_format_selector = format_id
|
current_format_selector = format_id
|
||||||
|
|
||||||
@@ -1674,7 +1674,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
if vcodec and vcodec != "none" and (not acodec or acodec == "none"):
|
if vcodec and vcodec != "none" and (not acodec or acodec == "none"):
|
||||||
# Video-only format, add bestaudio automatically
|
# Video-only format, add bestaudio automatically
|
||||||
current_format_selector = f"{format_id}+bestaudio"
|
current_format_selector = f"{format_id}+bestaudio"
|
||||||
log(f" ℹ️ Video-only format detected, automatically adding bestaudio", flush=True)
|
debug(f" ℹ️ Video-only format detected, automatically adding bestaudio")
|
||||||
|
|
||||||
actual_url = url.get('source_url')
|
actual_url = url.get('source_url')
|
||||||
url = actual_url # Use the actual URL for further processing
|
url = actual_url # Use the actual URL for further processing
|
||||||
@@ -1688,15 +1688,15 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
|
|
||||||
book_id = url.get('book_id')
|
book_id = url.get('book_id')
|
||||||
if not book_id:
|
if not book_id:
|
||||||
log(f" ✗ Missing book ID for borrowing", file=sys.stderr)
|
debug(f" ✗ Missing book ID for borrowing")
|
||||||
exit_code = 1
|
exit_code = 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
title_val = url.get('title', 'Unknown Book')
|
title_val = url.get('title', 'Unknown Book')
|
||||||
book_id_str = str(book_id)
|
book_id_str = str(book_id)
|
||||||
|
|
||||||
log(f"[auto-borrow] Starting borrow for: {title_val}", flush=True)
|
debug(f"[auto-borrow] Starting borrow for: {title_val}")
|
||||||
log(f" Book ID: {book_id_str}", flush=True)
|
debug(f" Book ID: {book_id_str}")
|
||||||
|
|
||||||
# Get Archive.org credentials
|
# Get Archive.org credentials
|
||||||
email, password = credential_openlibrary(config)
|
email, password = credential_openlibrary(config)
|
||||||
@@ -1708,33 +1708,33 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
|
|
||||||
# Attempt to borrow and download
|
# Attempt to borrow and download
|
||||||
try:
|
try:
|
||||||
log(f" → Logging into Archive.org...", flush=True)
|
debug(f" → Logging into Archive.org...")
|
||||||
from helper.archive_client import login
|
from helper.archive_client import login
|
||||||
import requests
|
import requests
|
||||||
try:
|
try:
|
||||||
session = login(email, password)
|
session = login(email, password)
|
||||||
except requests.exceptions.Timeout:
|
except requests.exceptions.Timeout:
|
||||||
log(f" ✗ Timeout logging into Archive.org (server not responding)", file=sys.stderr)
|
debug(f" ✗ Timeout logging into Archive.org (server not responding)")
|
||||||
exit_code = 1
|
exit_code = 1
|
||||||
continue
|
continue
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
log(f" ✗ Error connecting to Archive.org: {e}", file=sys.stderr)
|
debug(f" ✗ Error connecting to Archive.org: {e}")
|
||||||
exit_code = 1
|
exit_code = 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
log(f" → Borrowing book...", flush=True)
|
debug(f" → Borrowing book...")
|
||||||
try:
|
try:
|
||||||
session = loan(session, book_id_str, verbose=True)
|
session = loan(session, book_id_str, verbose=True)
|
||||||
except requests.exceptions.Timeout:
|
except requests.exceptions.Timeout:
|
||||||
log(f" ✗ Timeout while borrowing (server not responding)", file=sys.stderr)
|
debug(f" ✗ Timeout while borrowing (server not responding)")
|
||||||
exit_code = 1
|
exit_code = 1
|
||||||
continue
|
continue
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
log(f" ✗ Error while borrowing: {e}", file=sys.stderr)
|
debug(f" ✗ Error while borrowing: {e}")
|
||||||
exit_code = 1
|
exit_code = 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
log(f" → Extracting page information...", flush=True)
|
debug(f" → Extracting page information...")
|
||||||
# Try both URL formats
|
# Try both URL formats
|
||||||
book_urls = [
|
book_urls = [
|
||||||
f"https://archive.org/borrow/{book_id_str}",
|
f"https://archive.org/borrow/{book_id_str}",
|
||||||
@@ -1749,24 +1749,24 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
try:
|
try:
|
||||||
title, links, metadata = get_book_infos(session, book_url)
|
title, links, metadata = get_book_infos(session, book_url)
|
||||||
if title and links:
|
if title and links:
|
||||||
log(f" → Found {len(links)} pages", flush=True)
|
debug(f" → Found {len(links)} pages")
|
||||||
break
|
break
|
||||||
except requests.exceptions.Timeout:
|
except requests.exceptions.Timeout:
|
||||||
last_error = "Timeout while extracting pages"
|
last_error = "Timeout while extracting pages"
|
||||||
log(f" ⚠ Timeout while extracting from {book_url}", flush=True)
|
debug(f" ⚠ Timeout while extracting from {book_url}")
|
||||||
continue
|
continue
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
last_error = str(e)
|
last_error = str(e)
|
||||||
log(f" ⚠ Failed to extract from {book_url}: {e}", flush=True)
|
debug(f" ⚠ Failed to extract from {book_url}: {e}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not links:
|
if not links:
|
||||||
log(f" ✗ Could not extract book pages (Last error: {last_error})", file=sys.stderr)
|
debug(f" ✗ Could not extract book pages (Last error: {last_error})")
|
||||||
exit_code = 1
|
exit_code = 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Download pages
|
# Download pages
|
||||||
log(f" → Downloading {len(links)} pages...", flush=True)
|
debug(f" → Downloading {len(links)} pages...")
|
||||||
with tempfile.TemporaryDirectory() as temp_dir:
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
# download(session, n_threads, directory, links, scale, book_id)
|
# download(session, n_threads, directory, links, scale, book_id)
|
||||||
images = download(
|
images = download(
|
||||||
@@ -1779,16 +1779,16 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not images:
|
if not images:
|
||||||
log(f" ✗ No pages downloaded", file=sys.stderr)
|
debug(f" ✗ No pages downloaded")
|
||||||
exit_code = 1
|
exit_code = 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
log(f" ✓ Downloaded {len(images)} pages", flush=True)
|
debug(f" ✓ Downloaded {len(images)} pages")
|
||||||
|
|
||||||
# Try to merge into PDF
|
# Try to merge into PDF
|
||||||
try:
|
try:
|
||||||
import img2pdf
|
import img2pdf
|
||||||
log(f" → Merging pages into PDF...", flush=True)
|
debug(f" → Merging pages into PDF...")
|
||||||
|
|
||||||
filename = title if title else f"book_{book_id_str}"
|
filename = title if title else f"book_{book_id_str}"
|
||||||
filename = "".join(c for c in filename if c.isalnum() or c in (' ', '.', '-'))[:100]
|
filename = "".join(c for c in filename if c.isalnum() or c in (' ', '.', '-'))[:100]
|
||||||
@@ -1805,7 +1805,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
with open(output_path, 'wb') as f:
|
with open(output_path, 'wb') as f:
|
||||||
f.write(pdf_content)
|
f.write(pdf_content)
|
||||||
|
|
||||||
log(f" ✓ Successfully borrowed and saved to: {output_path}", flush=True)
|
debug(f" ✓ Successfully borrowed and saved to: {output_path}")
|
||||||
downloaded_files.append(str(output_path))
|
downloaded_files.append(str(output_path))
|
||||||
|
|
||||||
# Emit result for downstream cmdlets
|
# Emit result for downstream cmdlets
|
||||||
@@ -1836,7 +1836,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
pipeline_context.emit(pipe_obj)
|
pipeline_context.emit(pipe_obj)
|
||||||
exit_code = 0
|
exit_code = 0
|
||||||
except ImportError:
|
except ImportError:
|
||||||
log(f" ⚠ img2pdf not available - saving pages as collection", file=sys.stderr)
|
debug(f" ⚠ img2pdf not available - saving pages as collection")
|
||||||
# Just copy images to output dir
|
# Just copy images to output dir
|
||||||
filename = title if title else f"book_{book_id_str}"
|
filename = title if title else f"book_{book_id_str}"
|
||||||
filename = "".join(c for c in filename if c.isalnum() or c in (' ', '.', '-'))[:100]
|
filename = "".join(c for c in filename if c.isalnum() or c in (' ', '.', '-'))[:100]
|
||||||
@@ -1847,7 +1847,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
shutil.copytree(temp_dir, str(output_dir))
|
shutil.copytree(temp_dir, str(output_dir))
|
||||||
log(f" ✓ Successfully borrowed and saved to: {output_dir}", flush=True)
|
debug(f" ✓ Successfully borrowed and saved to: {output_dir}")
|
||||||
downloaded_files.append(str(output_dir))
|
downloaded_files.append(str(output_dir))
|
||||||
|
|
||||||
# Emit result for downstream cmdlets
|
# Emit result for downstream cmdlets
|
||||||
@@ -1877,7 +1877,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
exit_code = 0
|
exit_code = 0
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log(f" ✗ Borrow/download failed: {e}", file=sys.stderr)
|
debug(f" ✗ Borrow/download failed: {e}")
|
||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
exit_code = 1
|
exit_code = 1
|
||||||
@@ -1885,11 +1885,11 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
continue # Skip normal URL handling
|
continue # Skip normal URL handling
|
||||||
|
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
log(f" ✗ Archive.org tools not available: {e}", file=sys.stderr)
|
debug(f" ✗ Archive.org tools not available: {e}")
|
||||||
exit_code = 1
|
exit_code = 1
|
||||||
continue
|
continue
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log(f" ✗ Auto-borrow error: {e}", file=sys.stderr)
|
debug(f" ✗ Auto-borrow error: {e}")
|
||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
exit_code = 1
|
exit_code = 1
|
||||||
@@ -1905,7 +1905,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
book_id = url.get('book_id', '')
|
book_id = url.get('book_id', '')
|
||||||
|
|
||||||
if not primary_url:
|
if not primary_url:
|
||||||
log(f"Skipping libgen entry: no primary URL", file=sys.stderr)
|
debug(f"Skipping libgen entry: no primary URL")
|
||||||
exit_code = 1
|
exit_code = 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -1916,11 +1916,11 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
# Remove duplicates while preserving order
|
# Remove duplicates while preserving order
|
||||||
mirrors_to_try = list(dict.fromkeys(mirrors_to_try))
|
mirrors_to_try = list(dict.fromkeys(mirrors_to_try))
|
||||||
|
|
||||||
log(f"🔄 LibGen download with mirror fallback (book_id: {book_id})", flush=True)
|
debug(f"🔄 LibGen download with mirror fallback (book_id: {book_id})")
|
||||||
log(f" Primary: {primary_url[:80]}...", flush=True)
|
debug(f" Primary: {primary_url[:80]}...")
|
||||||
|
|
||||||
if len(mirrors_to_try) > 1:
|
if len(mirrors_to_try) > 1:
|
||||||
log(f" {len(mirrors_to_try) - 1} alternative mirror(s) available", flush=True)
|
debug(f" {len(mirrors_to_try) - 1} alternative mirror(s) available")
|
||||||
|
|
||||||
# Resolve cookies path
|
# Resolve cookies path
|
||||||
final_cookies_path_libgen = None
|
final_cookies_path_libgen = None
|
||||||
@@ -1941,7 +1941,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
for mirror_idx, mirror_url in enumerate(mirrors_to_try, 1):
|
for mirror_idx, mirror_url in enumerate(mirrors_to_try, 1):
|
||||||
try:
|
try:
|
||||||
if mirror_idx > 1:
|
if mirror_idx > 1:
|
||||||
log(f" → Trying mirror #{mirror_idx}: {mirror_url[:80]}...", flush=True)
|
debug(f" → Trying mirror #{mirror_idx}: {mirror_url[:80]}...")
|
||||||
|
|
||||||
# Use libgen_service's download_from_mirror for proper libgen handling
|
# Use libgen_service's download_from_mirror for proper libgen handling
|
||||||
from helper.libgen_service import download_from_mirror
|
from helper.libgen_service import download_from_mirror
|
||||||
@@ -1954,12 +1954,12 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
success = download_from_mirror(
|
success = download_from_mirror(
|
||||||
mirror_url=mirror_url,
|
mirror_url=mirror_url,
|
||||||
output_path=file_path,
|
output_path=file_path,
|
||||||
log_info=lambda msg: log(f" {msg}", flush=True),
|
log_info=lambda msg: debug(f" {msg}"),
|
||||||
log_error=lambda msg: log(f" ⚠ {msg}", file=sys.stderr)
|
log_error=lambda msg: debug(f" ⚠ {msg}")
|
||||||
)
|
)
|
||||||
|
|
||||||
if success and file_path.exists():
|
if success and file_path.exists():
|
||||||
log(f" ✓ Downloaded successfully from mirror #{mirror_idx}", flush=True)
|
debug(f" ✓ Downloaded successfully from mirror #{mirror_idx}")
|
||||||
successful_mirror = mirror_url
|
successful_mirror = mirror_url
|
||||||
download_succeeded = True
|
download_succeeded = True
|
||||||
|
|
||||||
@@ -1984,9 +1984,9 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
last_error = str(e)
|
last_error = str(e)
|
||||||
if mirror_idx == 1:
|
if mirror_idx == 1:
|
||||||
log(f" ⚠ Primary mirror failed: {e}", flush=True)
|
debug(f" ⚠ Primary mirror failed: {e}")
|
||||||
else:
|
else:
|
||||||
log(f" ⚠ Mirror #{mirror_idx} failed: {e}", flush=True)
|
debug(f" ⚠ Mirror #{mirror_idx} failed: {e}")
|
||||||
|
|
||||||
if not download_succeeded:
|
if not download_succeeded:
|
||||||
log(f" ✗ All mirrors failed. Last error: {last_error}", file=sys.stderr)
|
log(f" ✗ All mirrors failed. Last error: {last_error}", file=sys.stderr)
|
||||||
@@ -1998,7 +1998,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
continue # Skip to next URL
|
continue # Skip to next URL
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log(f" ✗ LibGen mirror fallback error: {e}", file=sys.stderr)
|
debug(f" ✗ LibGen mirror fallback error: {e}")
|
||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc(file=sys.stderr)
|
traceback.print_exc(file=sys.stderr)
|
||||||
exit_code = 1
|
exit_code = 1
|
||||||
@@ -2010,20 +2010,20 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
if isinstance(url, dict) and url.get('__playlist_url'):
|
if isinstance(url, dict) and url.get('__playlist_url'):
|
||||||
playlist_url = url.get('__playlist_url')
|
playlist_url = url.get('__playlist_url')
|
||||||
item_num = url.get('__playlist_item', 1)
|
item_num = url.get('__playlist_item', 1)
|
||||||
log(f"📍 Handling selected playlist item #{item_num}", flush=True)
|
debug(f"📍 Handling selected playlist item #{item_num}")
|
||||||
# Convert to actual URL and set playlist_items to download only this item
|
# Convert to actual URL and set playlist_items to download only this item
|
||||||
url = playlist_url
|
url = playlist_url
|
||||||
playlist_items = str(item_num)
|
playlist_items = str(item_num)
|
||||||
# Fall through to normal handling below
|
# Fall through to normal handling below
|
||||||
else:
|
else:
|
||||||
log(f"Skipping invalid URL entry: {url}", file=sys.stderr)
|
debug(f"Skipping invalid URL entry: {url}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
log(f"Probing URL: {url}", flush=True)
|
debug(f"Probing URL: {url}")
|
||||||
|
|
||||||
# ====== TORRENT MODE - INTERCEPT BEFORE NORMAL DOWNLOAD ======
|
# ====== TORRENT MODE - INTERCEPT BEFORE NORMAL DOWNLOAD ======
|
||||||
if torrent_mode or url.lower().startswith('magnet:'):
|
if torrent_mode or url.lower().startswith('magnet:'):
|
||||||
log(f"🧲 Torrent/magnet mode - spawning background worker...", flush=True)
|
debug(f"🧲 Torrent/magnet mode - spawning background worker...")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Get API key from config
|
# Get API key from config
|
||||||
@@ -2051,9 +2051,9 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
description=f"Torrent/magnet download via AllDebrid",
|
description=f"Torrent/magnet download via AllDebrid",
|
||||||
pipe=pipeline_context.get_current_command_text()
|
pipe=pipeline_context.get_current_command_text()
|
||||||
)
|
)
|
||||||
log(f"✓ Worker created (ID: {worker_id})", flush=True)
|
debug(f"✓ Worker created (ID: {worker_id})")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log(f"⚠ Failed to create worker: {e}", file=sys.stderr)
|
debug(f"⚠ Failed to create worker: {e}")
|
||||||
worker_manager = None
|
worker_manager = None
|
||||||
|
|
||||||
# Spawn background thread to handle the download
|
# Spawn background thread to handle the download
|
||||||
@@ -2075,7 +2075,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
)
|
)
|
||||||
|
|
||||||
worker_thread.start()
|
worker_thread.start()
|
||||||
log(f"✓ Background worker started (ID: {worker_id})", flush=True)
|
debug(f"✓ Background worker started (ID: {worker_id})")
|
||||||
|
|
||||||
# Emit worker info so user can track it
|
# Emit worker info so user can track it
|
||||||
worker_info = {
|
worker_info = {
|
||||||
@@ -2110,7 +2110,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
is_actual_playlist = False # Track if we have a real multi-item playlist
|
is_actual_playlist = False # Track if we have a real multi-item playlist
|
||||||
|
|
||||||
if probe_info:
|
if probe_info:
|
||||||
log(f"✓ Probed: {probe_info.get('title', url)} ({probe_info.get('extractor', 'unknown')})")
|
debug(f"✓ Probed: {probe_info.get('title', url)} ({probe_info.get('extractor', 'unknown')})")
|
||||||
|
|
||||||
# If it's a playlist, show the result table and skip download for now
|
# If it's a playlist, show the result table and skip download for now
|
||||||
entries = probe_info.get("entries", [])
|
entries = probe_info.get("entries", [])
|
||||||
@@ -2118,9 +2118,9 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
is_actual_playlist = True # We have a real playlist with multiple items
|
is_actual_playlist = True # We have a real playlist with multiple items
|
||||||
# Playlist detected but NO selection provided
|
# Playlist detected but NO selection provided
|
||||||
# Always show table for user to select items
|
# Always show table for user to select items
|
||||||
log(f"📋 Found playlist with {len(entries)} items")
|
debug(f"📋 Found playlist with {len(entries)} items")
|
||||||
_show_playlist_table(url, probe_info)
|
_show_playlist_table(url, probe_info)
|
||||||
log(f"ℹ️ Playlist displayed. To select items, use @* or @1,3,5-8 syntax after piping results")
|
debug(f"ℹ️ Playlist displayed. To select items, use @* or @1,3,5-8 syntax after piping results")
|
||||||
playlists_displayed += 1
|
playlists_displayed += 1
|
||||||
continue # Skip to next URL - don't download playlist without selection
|
continue # Skip to next URL - don't download playlist without selection
|
||||||
elif entries and playlist_items:
|
elif entries and playlist_items:
|
||||||
@@ -2130,13 +2130,13 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
expanded_items = _expand_playlist_selection(playlist_items, len(entries))
|
expanded_items = _expand_playlist_selection(playlist_items, len(entries))
|
||||||
playlist_items = expanded_items
|
playlist_items = expanded_items
|
||||||
selected_playlist_entries = _select_playlist_entries(entries, playlist_items)
|
selected_playlist_entries = _select_playlist_entries(entries, playlist_items)
|
||||||
log(f"📋 Found playlist with {len(entries)} items - downloading selected: {playlist_items}")
|
debug(f"📋 Found playlist with {len(entries)} items - downloading selected: {playlist_items}")
|
||||||
else:
|
else:
|
||||||
log(f"Single item: {probe_info.get('title', 'Unknown')}")
|
debug(f"Single item: {probe_info.get('title', 'Unknown')}")
|
||||||
|
|
||||||
# ====== FORMAT LISTING MODE ======
|
# ====== FORMAT LISTING MODE ======
|
||||||
if list_formats_mode and isinstance(url, str) and url.startswith(('http://', 'https://')):
|
if list_formats_mode and isinstance(url, str) and url.startswith(('http://', 'https://')):
|
||||||
log(f"Fetching formats for: {url}", flush=True)
|
debug(f"Fetching formats for: {url}")
|
||||||
from helper.download import list_formats
|
from helper.download import list_formats
|
||||||
from result_table import ResultTable
|
from result_table import ResultTable
|
||||||
|
|
||||||
@@ -2209,7 +2209,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
"source_url": url,
|
"source_url": url,
|
||||||
"index": i,
|
"index": i,
|
||||||
})
|
})
|
||||||
log(f"Use @N syntax to select a format and download", flush=True)
|
debug(f"Use @N syntax to select a format and download")
|
||||||
else:
|
else:
|
||||||
log(f"✗ No formats available for this URL", file=sys.stderr)
|
log(f"✗ No formats available for this URL", file=sys.stderr)
|
||||||
|
|
||||||
@@ -2224,7 +2224,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
from result_table import ResultTable
|
from result_table import ResultTable
|
||||||
|
|
||||||
if is_url_supported_by_ytdlp(url):
|
if is_url_supported_by_ytdlp(url):
|
||||||
log(f"Checking available formats for: {url}", flush=True)
|
debug(f"Checking available formats for: {url}")
|
||||||
all_formats = list_formats(url, no_playlist=is_youtube_url, playlist_items=playlist_items)
|
all_formats = list_formats(url, no_playlist=is_youtube_url, playlist_items=playlist_items)
|
||||||
|
|
||||||
if all_formats:
|
if all_formats:
|
||||||
@@ -2237,14 +2237,14 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
if 0 < idx <= len(formats):
|
if 0 < idx <= len(formats):
|
||||||
fmt = formats[idx-1]
|
fmt = formats[idx-1]
|
||||||
current_format_selector = fmt.get("format_id")
|
current_format_selector = fmt.get("format_id")
|
||||||
log(f"Selected format #{idx}: {current_format_selector}")
|
debug(f"Selected format #{idx}: {current_format_selector}")
|
||||||
playlist_items = None # Clear so it doesn't affect download options
|
playlist_items = None # Clear so it doesn't affect download options
|
||||||
else:
|
else:
|
||||||
log(f"Invalid format index: {idx}", file=sys.stderr)
|
log(f"Invalid format index: {idx}", file=sys.stderr)
|
||||||
|
|
||||||
elif len(formats) > 1:
|
elif len(formats) > 1:
|
||||||
# Multiple formats available
|
# Multiple formats available
|
||||||
log(f"📊 Found {len(formats)} available formats for: {probe_info.get('title', 'Unknown')}", flush=True)
|
debug(f"📊 Found {len(formats)} available formats for: {probe_info.get('title', 'Unknown')}")
|
||||||
|
|
||||||
# Always show table for format selection via @N syntax
|
# Always show table for format selection via @N syntax
|
||||||
# Show table and wait for @N selection
|
# Show table and wait for @N selection
|
||||||
@@ -2294,8 +2294,8 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
table.set_row_selection_args(i, ["-item", str(i + 1)])
|
table.set_row_selection_args(i, ["-item", str(i + 1)])
|
||||||
|
|
||||||
# Display table and emit formats so they can be selected with @N
|
# Display table and emit formats so they can be selected with @N
|
||||||
log(str(table), flush=True)
|
debug(str(table))
|
||||||
log(f"💡 Use @N syntax to select a format and download (e.g., @1)", flush=True)
|
debug(f"💡 Use @N syntax to select a format and download (e.g., @1)")
|
||||||
|
|
||||||
# Store table for @N expansion so CLI can reconstruct commands
|
# Store table for @N expansion so CLI can reconstruct commands
|
||||||
pipeline_context.set_current_stage_table(table)
|
pipeline_context.set_current_stage_table(table)
|
||||||
@@ -2317,7 +2317,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
formats_displayed = True # Mark that we displayed formats
|
formats_displayed = True # Mark that we displayed formats
|
||||||
continue # Skip download, user must select format via @N
|
continue # Skip download, user must select format via @N
|
||||||
|
|
||||||
log(f"Downloading: {url}", flush=True)
|
debug(f"Downloading: {url}")
|
||||||
|
|
||||||
# Resolve cookies path if specified
|
# Resolve cookies path if specified
|
||||||
final_cookies_path = None
|
final_cookies_path = None
|
||||||
@@ -2362,19 +2362,13 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
# Check if this was a playlist download (is_actual_playlist tracks if we have a multi-item playlist)
|
# Check if this was a playlist download (is_actual_playlist tracks if we have a multi-item playlist)
|
||||||
if is_actual_playlist:
|
if is_actual_playlist:
|
||||||
if not selected_playlist_entries:
|
if not selected_playlist_entries:
|
||||||
log(
|
debug("⚠ Playlist metadata unavailable; cannot emit selected items for this stage.")
|
||||||
"⚠ Playlist metadata unavailable; cannot emit selected items for this stage.",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
exit_code = 1
|
exit_code = 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
matched_after, _ = _snapshot_playlist_paths(selected_playlist_entries, final_output_dir)
|
matched_after, _ = _snapshot_playlist_paths(selected_playlist_entries, final_output_dir)
|
||||||
if not matched_after:
|
if not matched_after:
|
||||||
log(
|
debug("⚠ No playlist files found for the selected items after download.")
|
||||||
"⚠ No playlist files found for the selected items after download.",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
exit_code = 1
|
exit_code = 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -2389,9 +2383,9 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
|
|
||||||
emit_targets = new_playlist_files if new_playlist_files else matched_after
|
emit_targets = new_playlist_files if new_playlist_files else matched_after
|
||||||
if new_playlist_files:
|
if new_playlist_files:
|
||||||
log(f"📋 Playlist download completed: {len(new_playlist_files)} new file(s)")
|
debug(f"📋 Playlist download completed: {len(new_playlist_files)} new file(s)")
|
||||||
else:
|
else:
|
||||||
log(f"📁 Reusing {len(emit_targets)} cached playlist file(s)", flush=True)
|
debug(f"📁 Reusing {len(emit_targets)} cached playlist file(s)")
|
||||||
|
|
||||||
for playlist_file in emit_targets:
|
for playlist_file in emit_targets:
|
||||||
file_hash = _compute_file_hash(playlist_file)
|
file_hash = _compute_file_hash(playlist_file)
|
||||||
@@ -2444,7 +2438,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
downloaded_files.append(file_path)
|
downloaded_files.append(file_path)
|
||||||
pipeline_context.emit(pipe_obj)
|
pipeline_context.emit(pipe_obj)
|
||||||
|
|
||||||
log(f"✓ Downloaded: {file_path}", flush=True)
|
debug(f"✓ Downloaded: {file_path}")
|
||||||
else:
|
else:
|
||||||
log(f"Download returned no result for {url}", file=sys.stderr)
|
log(f"Download returned no result for {url}", file=sys.stderr)
|
||||||
exit_code = 1
|
exit_code = 1
|
||||||
@@ -2458,7 +2452,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
# Success if we downloaded files or displayed playlists/formats
|
# Success if we downloaded files or displayed playlists/formats
|
||||||
if downloaded_files or files_downloaded_directly > 0:
|
if downloaded_files or files_downloaded_directly > 0:
|
||||||
total_files = len(downloaded_files) + files_downloaded_directly
|
total_files = len(downloaded_files) + files_downloaded_directly
|
||||||
log(f"✓ Successfully downloaded {total_files} file(s)", flush=True)
|
debug(f"✓ Successfully downloaded {total_files} file(s)")
|
||||||
|
|
||||||
# Create a result table for the downloaded files
|
# Create a result table for the downloaded files
|
||||||
# This ensures that subsequent @N commands select from these files
|
# This ensures that subsequent @N commands select from these files
|
||||||
@@ -2496,14 +2490,14 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
if playlists_displayed:
|
if playlists_displayed:
|
||||||
log(f"✓ Displayed {playlists_displayed} playlist(s) for selection", flush=True)
|
debug(f"✓ Displayed {playlists_displayed} playlist(s) for selection")
|
||||||
if db:
|
if db:
|
||||||
db.update_worker_status(worker_id, 'completed')
|
db.update_worker_status(worker_id, 'completed')
|
||||||
db.close()
|
db.close()
|
||||||
return 0 # Success - playlists shown
|
return 0 # Success - playlists shown
|
||||||
|
|
||||||
if formats_displayed:
|
if formats_displayed:
|
||||||
log(f"✓ Format selection table displayed - use @N to select and download", flush=True)
|
debug(f"✓ Format selection table displayed - use @N to select and download")
|
||||||
if db:
|
if db:
|
||||||
db.update_worker_status(worker_id, 'completed')
|
db.update_worker_status(worker_id, 'completed')
|
||||||
db.close()
|
db.close()
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import subprocess as _subprocess
|
|||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from helper.logger import log
|
from helper.logger import log, debug
|
||||||
import uuid as _uuid
|
import uuid as _uuid
|
||||||
import time as _time
|
import time as _time
|
||||||
|
|
||||||
@@ -312,10 +312,10 @@ def _send_to_mpv_pipe(file_url: str, ipc_pipe: str, title: str, headers: Optiona
|
|||||||
if response_line:
|
if response_line:
|
||||||
resp = json.loads(response_line.decode('utf-8'))
|
resp = json.loads(response_line.decode('utf-8'))
|
||||||
if resp.get('error') != 'success':
|
if resp.get('error') != 'success':
|
||||||
log(f"[get-file] MPV error: {resp.get('error')}", file=sys.stderr)
|
debug(f"[get-file] MPV error: {resp.get('error')}", file=sys.stderr)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
log(f"[get-file] Sent to existing MPV: {title}", file=sys.stderr)
|
debug(f"[get-file] Sent to existing MPV: {title}", file=sys.stderr)
|
||||||
return True
|
return True
|
||||||
except (OSError, IOError):
|
except (OSError, IOError):
|
||||||
# Pipe not available
|
# Pipe not available
|
||||||
@@ -341,20 +341,20 @@ def _send_to_mpv_pipe(file_url: str, ipc_pipe: str, title: str, headers: Optiona
|
|||||||
if response_data:
|
if response_data:
|
||||||
resp = json.loads(response_data.decode('utf-8'))
|
resp = json.loads(response_data.decode('utf-8'))
|
||||||
if resp.get('error') != 'success':
|
if resp.get('error') != 'success':
|
||||||
log(f"[get-file] MPV error: {resp.get('error')}", file=sys.stderr)
|
debug(f"[get-file] MPV error: {resp.get('error')}", file=sys.stderr)
|
||||||
sock.close()
|
sock.close()
|
||||||
return False
|
return False
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
sock.close()
|
sock.close()
|
||||||
|
|
||||||
log(f"[get-file] Sent to existing MPV: {title}", file=sys.stderr)
|
debug(f"[get-file] Sent to existing MPV: {title}", file=sys.stderr)
|
||||||
return True
|
return True
|
||||||
except (OSError, socket.error, ConnectionRefusedError):
|
except (OSError, socket.error, ConnectionRefusedError):
|
||||||
# Pipe doesn't exist or MPV not listening - will need to start new instance
|
# Pipe doesn't exist or MPV not listening - will need to start new instance
|
||||||
return False
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log(f"[get-file] IPC error: {e}", file=sys.stderr)
|
debug(f"[get-file] IPC error: {e}", file=sys.stderr)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@@ -371,11 +371,11 @@ def _play_in_mpv(file_url: str, file_title: str, is_stream: bool = False, header
|
|||||||
try:
|
try:
|
||||||
# First try to send to existing MPV instance
|
# First try to send to existing MPV instance
|
||||||
if _send_to_mpv_pipe(file_url, ipc_pipe, file_title, headers):
|
if _send_to_mpv_pipe(file_url, ipc_pipe, file_title, headers):
|
||||||
print(f"Added to MPV: {file_title}")
|
debug(f"Added to MPV: {file_title}")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# No existing MPV or pipe unavailable - start new instance
|
# No existing MPV or pipe unavailable - start new instance
|
||||||
log(f"[get-file] Starting new MPV instance (pipe: {ipc_pipe})", file=sys.stderr)
|
debug(f"[get-file] Starting new MPV instance (pipe: {ipc_pipe})", file=sys.stderr)
|
||||||
cmd = ['mpv', file_url, f'--input-ipc-server={ipc_pipe}']
|
cmd = ['mpv', file_url, f'--input-ipc-server={ipc_pipe}']
|
||||||
|
|
||||||
# Set title for new instance
|
# Set title for new instance
|
||||||
@@ -397,8 +397,8 @@ def _play_in_mpv(file_url: str, file_title: str, is_stream: bool = False, header
|
|||||||
|
|
||||||
_subprocess.Popen(cmd, stdin=_subprocess.DEVNULL, stdout=_subprocess.DEVNULL, stderr=_subprocess.DEVNULL, **kwargs)
|
_subprocess.Popen(cmd, stdin=_subprocess.DEVNULL, stdout=_subprocess.DEVNULL, stderr=_subprocess.DEVNULL, **kwargs)
|
||||||
|
|
||||||
print(f"{'Streaming' if is_stream else 'Playing'} in MPV: {file_title}")
|
debug(f"{'Streaming' if is_stream else 'Playing'} in MPV: {file_title}")
|
||||||
log(f"[get-file] Started MPV with {file_title} (IPC: {ipc_pipe})", file=sys.stderr)
|
debug(f"[get-file] Started MPV with {file_title} (IPC: {ipc_pipe})", file=sys.stderr)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
@@ -447,7 +447,7 @@ def _handle_search_result(result: Any, args: Sequence[str], config: Dict[str, An
|
|||||||
log("Error: No storage backend specified in result", file=sys.stderr)
|
log("Error: No storage backend specified in result", file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
log(f"[get-file] Retrieving file from storage: {storage_name}", file=sys.stderr)
|
debug(f"[get-file] Retrieving file from storage: {storage_name}", file=sys.stderr)
|
||||||
|
|
||||||
# Handle different storage backends
|
# Handle different storage backends
|
||||||
if storage_name.lower() == 'hydrus':
|
if storage_name.lower() == 'hydrus':
|
||||||
@@ -536,38 +536,24 @@ def _handle_hydrus_file(file_hash: Optional[str], file_title: str, config: Dict[
|
|||||||
try:
|
try:
|
||||||
import webbrowser
|
import webbrowser
|
||||||
webbrowser.open(web_url)
|
webbrowser.open(web_url)
|
||||||
log(f"[get-file] Opened in browser: {file_title}", file=sys.stderr)
|
debug(f"[get-file] Opened in browser: {file_title}", file=sys.stderr)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return 0
|
return 0
|
||||||
elif force_mpv or (is_media and mpv_available):
|
elif force_mpv or (is_media and mpv_available):
|
||||||
# Auto-play in MPV for media files (if available), or user requested it
|
# Auto-play in MPV for media files (if available), or user requested it
|
||||||
if _play_in_mpv(stream_url, file_title, is_stream=True, headers=headers):
|
if _play_in_mpv(stream_url, file_title, is_stream=True, headers=headers):
|
||||||
# Emit result as PipeObject-compatible dict for pipelining
|
# Show pipe menu instead of emitting result for display
|
||||||
ipc_pipe = _get_fixed_ipc_pipe()
|
# This allows immediate @N selection from the playlist
|
||||||
result_dict = create_pipe_object_result(
|
from . import pipe
|
||||||
source='hydrus',
|
pipe._run(None, [], config)
|
||||||
identifier=file_hash,
|
|
||||||
file_path=stream_url,
|
|
||||||
cmdlet_name='get-file',
|
|
||||||
title=file_title,
|
|
||||||
file_hash=file_hash,
|
|
||||||
extra={
|
|
||||||
'ipc': ipc_pipe,
|
|
||||||
'action_type': 'streaming',
|
|
||||||
'web_url': web_url,
|
|
||||||
'hydrus_url': hydrus_url,
|
|
||||||
'access_key': access_key
|
|
||||||
}
|
|
||||||
)
|
|
||||||
ctx.emit(result_dict)
|
|
||||||
return 0
|
return 0
|
||||||
else:
|
else:
|
||||||
# Fall back to browser
|
# Fall back to browser
|
||||||
try:
|
try:
|
||||||
import webbrowser
|
import webbrowser
|
||||||
webbrowser.open(web_url)
|
webbrowser.open(web_url)
|
||||||
log(f"[get-file] Opened in browser instead", file=sys.stderr)
|
debug(f"[get-file] Opened in browser instead", file=sys.stderr)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return 0
|
return 0
|
||||||
@@ -593,7 +579,7 @@ def _handle_hydrus_file(file_hash: Optional[str], file_title: str, config: Dict[
|
|||||||
try:
|
try:
|
||||||
import webbrowser
|
import webbrowser
|
||||||
webbrowser.open(web_url)
|
webbrowser.open(web_url)
|
||||||
log(f"[get-file] Opened in browser: {file_title}", file=sys.stderr)
|
debug(f"[get-file] Opened in browser: {file_title}", file=sys.stderr)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return 0
|
return 0
|
||||||
@@ -641,7 +627,7 @@ def _handle_local_file(file_path: Optional[str], file_title: str, args: Sequence
|
|||||||
else: # Linux
|
else: # Linux
|
||||||
sp.run(['xdg-open', file_path])
|
sp.run(['xdg-open', file_path])
|
||||||
ctx.emit(f"Opened: {file_title}")
|
ctx.emit(f"Opened: {file_title}")
|
||||||
log(f"[get-file] Opened {file_title} with default app", file=sys.stderr)
|
debug(f"[get-file] Opened {file_title} with default app", file=sys.stderr)
|
||||||
return 0
|
return 0
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log(f"Error opening file: {e}", file=sys.stderr)
|
log(f"Error opening file: {e}", file=sys.stderr)
|
||||||
@@ -649,21 +635,10 @@ def _handle_local_file(file_path: Optional[str], file_title: str, args: Sequence
|
|||||||
elif force_mpv or (is_media and mpv_available):
|
elif force_mpv or (is_media and mpv_available):
|
||||||
# Auto-play in MPV for media files (if available), or user requested it
|
# Auto-play in MPV for media files (if available), or user requested it
|
||||||
if _play_in_mpv(file_path, file_title, is_stream=False):
|
if _play_in_mpv(file_path, file_title, is_stream=False):
|
||||||
# Emit result as PipeObject-compatible dict for pipelining
|
# Show pipe menu instead of emitting result for display
|
||||||
ipc_pipe = _get_fixed_ipc_pipe()
|
# This allows immediate @N selection from the playlist
|
||||||
result_dict = create_pipe_object_result(
|
from . import pipe
|
||||||
source='local',
|
pipe._run(None, [], config)
|
||||||
identifier=str(Path(file_path).stem) if file_path else 'unknown',
|
|
||||||
file_path=file_path,
|
|
||||||
cmdlet_name='get-file',
|
|
||||||
title=file_title,
|
|
||||||
file_hash=file_hash, # Include hash from search result if available
|
|
||||||
extra={
|
|
||||||
'ipc': ipc_pipe, # MPV IPC pipe for Lua script control
|
|
||||||
'action_type': 'playing' # Distinguish from other get-file actions
|
|
||||||
}
|
|
||||||
)
|
|
||||||
ctx.emit(result_dict)
|
|
||||||
return 0
|
return 0
|
||||||
else:
|
else:
|
||||||
# Fall back to default application
|
# Fall back to default application
|
||||||
@@ -676,7 +651,7 @@ def _handle_local_file(file_path: Optional[str], file_title: str, args: Sequence
|
|||||||
os.startfile(file_path)
|
os.startfile(file_path)
|
||||||
else: # Linux
|
else: # Linux
|
||||||
_subprocess.run(['xdg-open', file_path])
|
_subprocess.run(['xdg-open', file_path])
|
||||||
log(f"[get-file] Opened with default app instead", file=sys.stderr)
|
debug(f"[get-file] Opened with default app instead", file=sys.stderr)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return 0
|
return 0
|
||||||
@@ -694,7 +669,7 @@ def _handle_local_file(file_path: Optional[str], file_title: str, args: Sequence
|
|||||||
else: # Linux
|
else: # Linux
|
||||||
sp.run(['xdg-open', file_path])
|
sp.run(['xdg-open', file_path])
|
||||||
print(f"Opened: {file_title}")
|
print(f"Opened: {file_title}")
|
||||||
log(f"[get-file] Opened {file_title} with default app", file=sys.stderr)
|
debug(f"[get-file] Opened {file_title} with default app", file=sys.stderr)
|
||||||
|
|
||||||
# Emit result for downstream processing
|
# Emit result for downstream processing
|
||||||
result_dict = create_pipe_object_result(
|
result_dict = create_pipe_object_result(
|
||||||
@@ -751,7 +726,7 @@ def _handle_debrid_file(magnet_id: int, magnet_title: str, config: Dict[str, Any
|
|||||||
try:
|
try:
|
||||||
client = AllDebridClient(api_key)
|
client = AllDebridClient(api_key)
|
||||||
|
|
||||||
log(f"[get-file] Downloading magnet {magnet_id}: {magnet_title}", file=sys.stderr)
|
debug(f"[get-file] Downloading magnet {magnet_id}: {magnet_title}", file=sys.stderr)
|
||||||
|
|
||||||
# Fetch magnet files
|
# Fetch magnet files
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import socket
|
|||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
from ._shared import Cmdlet, CmdletArg, parse_cmdlet_args
|
from ._shared import Cmdlet, CmdletArg, parse_cmdlet_args
|
||||||
from helper.logger import log
|
from helper.logger import log, debug
|
||||||
from result_table import ResultTable
|
from result_table import ResultTable
|
||||||
from .get_file import _get_fixed_ipc_pipe
|
from .get_file import _get_fixed_ipc_pipe
|
||||||
import pipeline as ctx
|
import pipeline as ctx
|
||||||
@@ -33,13 +33,13 @@ def _send_ipc_command(command: Dict[str, Any]) -> Optional[Any]:
|
|||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
return None # MPV not running
|
return None # MPV not running
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log(f"Windows IPC Error: {e}", file=sys.stderr)
|
debug(f"Windows IPC Error: {e}", file=sys.stderr)
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
# Unix socket
|
# Unix socket
|
||||||
af_unix = getattr(socket, 'AF_UNIX', None)
|
af_unix = getattr(socket, 'AF_UNIX', None)
|
||||||
if af_unix is None:
|
if af_unix is None:
|
||||||
log("Unix sockets not supported on this platform", file=sys.stderr)
|
debug("Unix sockets not supported on this platform", file=sys.stderr)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -77,11 +77,11 @@ def _send_ipc_command(command: Dict[str, Any]) -> Optional[Any]:
|
|||||||
except (FileNotFoundError, ConnectionRefusedError):
|
except (FileNotFoundError, ConnectionRefusedError):
|
||||||
return None # MPV not running
|
return None # MPV not running
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log(f"Unix IPC Error: {e}", file=sys.stderr)
|
debug(f"Unix IPC Error: {e}", file=sys.stderr)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log(f"IPC Error: {e}", file=sys.stderr)
|
debug(f"IPC Error: {e}", file=sys.stderr)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return None
|
return None
|
||||||
@@ -112,20 +112,20 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
|||||||
cmd = {"command": ["set_property", "pause", False], "request_id": 103}
|
cmd = {"command": ["set_property", "pause", False], "request_id": 103}
|
||||||
resp = _send_ipc_command(cmd)
|
resp = _send_ipc_command(cmd)
|
||||||
if resp and resp.get("error") == "success":
|
if resp and resp.get("error") == "success":
|
||||||
log("Resumed playback")
|
debug("Resumed playback")
|
||||||
return 0
|
return 0
|
||||||
else:
|
else:
|
||||||
log("Failed to resume playback (MPV not running?)", file=sys.stderr)
|
debug("Failed to resume playback (MPV not running?)", file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
if pause_mode:
|
if pause_mode:
|
||||||
cmd = {"command": ["set_property", "pause", True], "request_id": 104}
|
cmd = {"command": ["set_property", "pause", True], "request_id": 104}
|
||||||
resp = _send_ipc_command(cmd)
|
resp = _send_ipc_command(cmd)
|
||||||
if resp and resp.get("error") == "success":
|
if resp and resp.get("error") == "success":
|
||||||
log("Paused playback")
|
debug("Paused playback")
|
||||||
return 0
|
return 0
|
||||||
else:
|
else:
|
||||||
log("Failed to pause playback (MPV not running?)", file=sys.stderr)
|
debug("Failed to pause playback (MPV not running?)", file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
# Handle piped input (add to playlist)
|
# Handle piped input (add to playlist)
|
||||||
@@ -178,12 +178,12 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
|||||||
elif resp.get("error") == "success":
|
elif resp.get("error") == "success":
|
||||||
added_count += 1
|
added_count += 1
|
||||||
if title:
|
if title:
|
||||||
log(f"Queued: {title}")
|
debug(f"Queued: {title}")
|
||||||
else:
|
else:
|
||||||
log(f"Queued: {target}")
|
debug(f"Queued: {target}")
|
||||||
else:
|
else:
|
||||||
error_msg = str(resp.get('error'))
|
error_msg = str(resp.get('error'))
|
||||||
log(f"Failed to queue item: {error_msg}", file=sys.stderr)
|
debug(f"Failed to queue item: {error_msg}", file=sys.stderr)
|
||||||
|
|
||||||
# If error indicates parameter issues, try without options
|
# If error indicates parameter issues, try without options
|
||||||
# (Though memory:// should avoid this, we keep fallback just in case)
|
# (Though memory:// should avoid this, we keep fallback just in case)
|
||||||
@@ -192,7 +192,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
|||||||
resp = _send_ipc_command(cmd)
|
resp = _send_ipc_command(cmd)
|
||||||
if resp and resp.get("error") == "success":
|
if resp and resp.get("error") == "success":
|
||||||
added_count += 1
|
added_count += 1
|
||||||
log(f"Queued (fallback): {title or target}")
|
debug(f"Queued (fallback): {title or target}")
|
||||||
|
|
||||||
if added_count > 0:
|
if added_count > 0:
|
||||||
# If we added items, we might want to play the first one if nothing is playing?
|
# If we added items, we might want to play the first one if nothing is playing?
|
||||||
@@ -203,7 +203,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
|||||||
items = _get_playlist()
|
items = _get_playlist()
|
||||||
|
|
||||||
if not items:
|
if not items:
|
||||||
log("MPV playlist is empty or MPV is not running.")
|
debug("MPV playlist is empty or MPV is not running.")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
# If index is provided, perform action (Play or Clear)
|
# If index is provided, perform action (Play or Clear)
|
||||||
@@ -213,7 +213,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
|||||||
idx = int(index_arg) - 1
|
idx = int(index_arg) - 1
|
||||||
|
|
||||||
if idx < 0 or idx >= len(items):
|
if idx < 0 or idx >= len(items):
|
||||||
log(f"Index {index_arg} out of range (1-{len(items)}).")
|
debug(f"Index {index_arg} out of range (1-{len(items)}).")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
item = items[idx]
|
item = items[idx]
|
||||||
@@ -224,13 +224,13 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
|||||||
cmd = {"command": ["playlist-remove", idx], "request_id": 101}
|
cmd = {"command": ["playlist-remove", idx], "request_id": 101}
|
||||||
resp = _send_ipc_command(cmd)
|
resp = _send_ipc_command(cmd)
|
||||||
if resp and resp.get("error") == "success":
|
if resp and resp.get("error") == "success":
|
||||||
log(f"Removed: {title}")
|
debug(f"Removed: {title}")
|
||||||
# Refresh items for listing
|
# Refresh items for listing
|
||||||
items = _get_playlist()
|
items = _get_playlist()
|
||||||
list_mode = True
|
list_mode = True
|
||||||
index_arg = None
|
index_arg = None
|
||||||
else:
|
else:
|
||||||
log(f"Failed to remove item: {resp.get('error') if resp else 'No response'}")
|
debug(f"Failed to remove item: {resp.get('error') if resp else 'No response'}")
|
||||||
return 1
|
return 1
|
||||||
else:
|
else:
|
||||||
# Play item
|
# Play item
|
||||||
@@ -241,20 +241,20 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
|||||||
unpause_cmd = {"command": ["set_property", "pause", False], "request_id": 103}
|
unpause_cmd = {"command": ["set_property", "pause", False], "request_id": 103}
|
||||||
_send_ipc_command(unpause_cmd)
|
_send_ipc_command(unpause_cmd)
|
||||||
|
|
||||||
log(f"Playing: {title}")
|
debug(f"Playing: {title}")
|
||||||
return 0
|
return 0
|
||||||
else:
|
else:
|
||||||
log(f"Failed to play item: {resp.get('error') if resp else 'No response'}")
|
debug(f"Failed to play item: {resp.get('error') if resp else 'No response'}")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
log(f"Invalid index: {index_arg}")
|
debug(f"Invalid index: {index_arg}")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
# List items (Default action or after clear)
|
# List items (Default action or after clear)
|
||||||
if list_mode or index_arg is None:
|
if list_mode or index_arg is None:
|
||||||
if not items:
|
if not items:
|
||||||
log("MPV playlist is empty.")
|
debug("MPV playlist is empty.")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
table = ResultTable("MPV Playlist")
|
table = ResultTable("MPV Playlist")
|
||||||
@@ -347,9 +347,9 @@ def _start_mpv(items: List[Any]) -> None:
|
|||||||
kwargs['creationflags'] = 0x00000008 # DETACHED_PROCESS
|
kwargs['creationflags'] = 0x00000008 # DETACHED_PROCESS
|
||||||
|
|
||||||
subprocess.Popen(cmd, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, **kwargs)
|
subprocess.Popen(cmd, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, **kwargs)
|
||||||
log(f"Started MPV with {len(cmd)-3} items")
|
debug(f"Started MPV with {len(cmd)-3} items")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log(f"Error starting MPV: {e}", file=sys.stderr)
|
debug(f"Error starting MPV: {e}", file=sys.stderr)
|
||||||
|
|
||||||
CMDLET = Cmdlet(
|
CMDLET = Cmdlet(
|
||||||
name=".pipe",
|
name=".pipe",
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ CMDLET = Cmdlet(
|
|||||||
CmdletArg("size", description="Filter by size: >100MB, <50MB, =10MB"),
|
CmdletArg("size", description="Filter by size: >100MB, <50MB, =10MB"),
|
||||||
CmdletArg("type", description="Filter by type: audio, video, image, document"),
|
CmdletArg("type", description="Filter by type: audio, video, image, document"),
|
||||||
CmdletArg("duration", description="Filter by duration: >10:00, <1:30:00"),
|
CmdletArg("duration", description="Filter by duration: >10:00, <1:30:00"),
|
||||||
CmdletArg("limit", type="integer", description="Limit results (default: 100)"),
|
CmdletArg("limit", type="integer", description="Limit results (default: 45)"),
|
||||||
CmdletArg("storage", description="Search storage backend: hydrus, local, debrid (default: all searchable)"),
|
CmdletArg("storage", description="Search storage backend: hydrus, local, debrid (default: all searchable)"),
|
||||||
CmdletArg("provider", description="Search provider: libgen, openlibrary, soulseek, debrid, local (overrides -storage)"),
|
CmdletArg("provider", description="Search provider: libgen, openlibrary, soulseek, debrid, local (overrides -storage)"),
|
||||||
],
|
],
|
||||||
@@ -190,7 +190,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
|||||||
type_filter: Optional[str] = None
|
type_filter: Optional[str] = None
|
||||||
storage_backend: Optional[str] = None
|
storage_backend: Optional[str] = None
|
||||||
provider_name: Optional[str] = None
|
provider_name: Optional[str] = None
|
||||||
limit = 100
|
limit = 45
|
||||||
|
|
||||||
# Simple argument parsing
|
# Simple argument parsing
|
||||||
i = 0
|
i = 0
|
||||||
|
|||||||
@@ -288,7 +288,7 @@ class LocalStorageBackend(StorageBackend):
|
|||||||
debug(f"Performing filename/tag search: {query_pattern}")
|
debug(f"Performing filename/tag search: {query_pattern}")
|
||||||
|
|
||||||
# Fetch more results than requested to allow for filtering
|
# Fetch more results than requested to allow for filtering
|
||||||
fetch_limit = (limit or 100) * 50
|
fetch_limit = (limit or 45) * 50
|
||||||
|
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
SELECT DISTINCT f.id, f.file_path, f.file_size
|
SELECT DISTINCT f.id, f.file_path, f.file_size
|
||||||
|
|||||||
Reference in New Issue
Block a user