updated bandcamp and list parsing

This commit is contained in:
2026-04-13 14:28:38 -07:00
parent 01f5e35b61
commit 97e310be70
5 changed files with 182 additions and 14 deletions
@@ -0,0 +1 @@
{"store":"rpi"}
+20 -2
View File
@@ -16,6 +16,15 @@ class Bandcamp(Provider):
TABLE_AUTO_STAGES = {
"bandcamp": ["download-file"],
}
AUTO_STAGE_USE_SELECTION_ARGS = True
@staticmethod
def _download_selection_args(target_url: str, media_type: str) -> Optional[List[str]]:
target = str(target_url or "").strip()
kind = str(media_type or "").strip().lower()
if not target or kind == "artist":
return None
return ["-url", target]
@staticmethod
def _base_url(raw_url: str) -> str:
@@ -89,6 +98,8 @@ class Bandcamp(Provider):
"album" if "/album/" in target else
("track" if "/track/" in target else "item")
)
selection_args = self._download_selection_args(target, kind)
selection_action = (["download-file"] + selection_args) if selection_args else None
results.append(
SearchResult(
@@ -111,6 +122,8 @@ class Bandcamp(Provider):
"url": target,
"artist_url": base,
},
selection_args=selection_args,
selection_action=selection_action,
)
)
except Exception as exc:
@@ -318,6 +331,8 @@ class Bandcamp(Provider):
itemtype = item.query_selector(".itemtype")
media_type = itemtype.inner_text().strip() if itemtype else "album"
selection_args = self._download_selection_args(str(target_url or ""), media_type)
selection_action = (["download-file"] + selection_args) if selection_args else None
results.append(
SearchResult(
@@ -335,13 +350,16 @@ class Bandcamp(Provider):
("Type",
media_type),
("Url",
base_url or str(target_url or "")),
str(target_url or "")),
],
full_metadata={
"artist": artist,
"type": media_type,
"url": base_url or str(target_url or ""),
"url": str(target_url or ""),
"artist_url": base_url,
},
selection_args=selection_args,
selection_action=selection_action,
)
)
+7 -2
View File
@@ -630,6 +630,8 @@ class Table:
"""Base arguments for the source command"""
self.header_lines: List[str] = []
"""Optional metadata lines rendered under the title"""
self.preserve_order: bool = bool(preserve_order)
"""If True, skip automatic sorting so display order matches input order."""
self.perseverance: bool = preserve_order
"""If True, skip automatic sorting so display order matches input order."""
self.interactive: bool = False
@@ -687,7 +689,9 @@ class Table:
def _perseverance(self, perseverance: bool = True) -> "Table":
"""Configure whether this table should skip automatic sorting."""
self.perseverance = bool(perseverance)
keep_order = bool(perseverance)
self.perseverance = keep_order
self.preserve_order = keep_order
return self
def add_row(self) -> Row:
@@ -741,7 +745,8 @@ class Table:
self.title = title
self.source_command = command
self.source_args = args or []
self.perseverance = preserve_order
self.perseverance = bool(preserve_order)
self.preserve_order = bool(preserve_order)
return self
def copy_with_title(self, new_title: str) -> "Table":
+3 -1
View File
@@ -1920,11 +1920,13 @@ def create_pipe_object_result(
Returns:
Dict with all PipeObject fields for emission
"""
store_override = extra.pop("store", None)
result = build_file_result_payload(
title=title,
path=file_path,
hash_value=hash_value,
store=source,
store=store_override if store_override is not None else source,
tag=tag,
source=source,
id=identifier,
+151 -9
View File
@@ -124,6 +124,48 @@ class Download_File(Cmdlet):
raise DownloadError("Could not determine downloaded file path")
return resolved
@staticmethod
def _selection_run_label(run_args: Sequence[str]) -> str:
try:
urls = extract_urls_from_selection_args(
run_args,
extra_url_prefixes=_ALLDEBRID_PREFIXES,
)
if urls:
return str(urls[0])
except Exception:
pass
for arg in run_args:
text = str(arg or "").strip()
if text and not text.startswith("-"):
return text
return "item"
@staticmethod
def _batch_progress_state(config: Optional[Dict[str, Any]]) -> tuple[bool, int, int, str]:
if not isinstance(config, dict):
return False, 0, 0, ""
suppress_nested = bool(config.get("_download_file_suppress_nested_pipe_progress"))
if not suppress_nested:
return False, 0, 0, ""
try:
total = max(0, int(config.get("_download_file_batch_total") or 0))
except Exception:
total = 0
try:
index = max(0, int(config.get("_download_file_batch_index") or 0))
except Exception:
index = 0
try:
label = str(config.get("_download_file_batch_label") or "").strip()
except Exception:
label = ""
return True, total, index, label
def _process_explicit_urls(
self,
*,
@@ -139,14 +181,32 @@ class Download_File(Cmdlet):
) -> tuple[int,
Optional[int]]:
downloaded_count = 0
suppress_nested, batch_total, batch_index, batch_label = self._batch_progress_state(config)
total_urls = len(raw_urls or [])
try:
if total_urls > 1 and not suppress_nested:
progress.begin_pipe(total_items=total_urls, items_preview=list(raw_urls[:5]))
except Exception:
pass
SearchResult = registry.get("SearchResult")
get_provider = registry.get("get_provider")
match_provider_name_for_url = registry.get("match_provider_name_for_url")
for url in raw_urls:
for idx, url in enumerate(raw_urls, 1):
try:
debug(f"Processing URL: {url}")
try:
display_total = batch_total if batch_total > 0 else total_urls
display_index = batch_index if batch_total > 0 else idx
display_label = batch_label or str(url)
if display_total > 0:
progress.set_status(
f"downloading {display_index}/{display_total}: {display_label}"
)
except Exception:
pass
# Check providers first
provider_name = None
@@ -1455,6 +1515,9 @@ class Download_File(Cmdlet):
downloaded_pipe_objects: List[Dict[str, Any]] = []
pipe_seq = 0
clip_sections_spec = self._build_clip_sections_spec(clip_ranges)
suppress_nested, batch_total, batch_index, batch_label = self._batch_progress_state(config)
total_urls = len(supported_url or [])
aggregate_status_mode = bool(suppress_nested or total_urls > 1)
if clip_sections_spec:
try:
@@ -1462,11 +1525,15 @@ class Download_File(Cmdlet):
except Exception:
pass
for url in supported_url:
for url_index, url in enumerate(supported_url, 1):
try:
debug(f"[download-file] Processing URL in loop: {url}")
debug(f"[download-file] ytdl_format parameter passed in: {ytdl_format}")
display_total = batch_total if batch_total > 0 else total_urls
display_index = batch_index if batch_total > 0 else url_index
display_label = batch_label or str(url)
canonical_url = url
if not skip_per_url_preflight or clip_ranges:
canonical_url = self._canonicalize_url_for_storage(
@@ -1487,7 +1554,16 @@ class Download_File(Cmdlet):
log(f"Skipping download (duplicate found): {url}", file=sys.stderr)
continue
PipelineProgress(pipeline_context).begin_steps(2)
if aggregate_status_mode:
try:
if display_total > 0:
PipelineProgress(pipeline_context).set_status(
f"downloading {display_index}/{display_total}: {display_label}"
)
except Exception:
pass
else:
PipelineProgress(pipeline_context).begin_steps(2)
actual_format = ytdl_format
actual_playlist_items = playlist_items
@@ -1631,7 +1707,8 @@ class Download_File(Cmdlet):
write_sub=write_sub,
)
PipelineProgress(pipeline_context).step("downloading")
if not aggregate_status_mode:
PipelineProgress(pipeline_context).step("downloading")
debug(f"Starting download for {url} (format: {actual_format or 'default'}) with {download_timeout_seconds}s activity timeout...")
result_obj = _download_with_timeout(opts, timeout_seconds=download_timeout_seconds, config=config)
debug(f"Download completed for {url}, building pipe object...")
@@ -1953,7 +2030,8 @@ class Download_File(Cmdlet):
debug(f"Emitting {len(pipe_objects)} result(s) to pipeline...")
PipelineProgress(pipeline_context).step("finalized")
if not aggregate_status_mode:
PipelineProgress(pipeline_context).step("finalized")
stage_ctx = pipeline_context.get_stage_context()
emit_enabled = bool(stage_ctx is not None)
@@ -1995,6 +2073,7 @@ class Download_File(Cmdlet):
) -> int:
try:
debug("Starting streaming download handler")
suppress_nested, _batch_total, _batch_index, _batch_label = self._batch_progress_state(config)
ytdlp_tool = YtDlpTool(config)
@@ -2028,10 +2107,11 @@ class Download_File(Cmdlet):
else:
debug("[download-file] Skipping local UI: running inside pipeline stage")
try:
progress.begin_pipe(
total_items=len(supported_url),
items_preview=supported_url,
)
if not suppress_nested:
progress.begin_pipe(
total_items=len(supported_url),
items_preview=supported_url,
)
except Exception as err:
debug(f"[download-file] PipelineProgress begin_pipe error: {err}")
except Exception as e:
@@ -2852,14 +2932,26 @@ class Download_File(Cmdlet):
original_skip_preflight = None
original_timeout = None
original_skip_direct = None
original_batch_total = None
original_batch_index = None
original_batch_label = None
original_suppress_nested = None
try:
if isinstance(config, dict):
original_skip_preflight = config.get("_skip_url_preflight")
original_timeout = config.get("_pipeobject_timeout_seconds")
original_skip_direct = config.get("_skip_direct_on_streaming_failure")
original_batch_total = config.get("_download_file_batch_total")
original_batch_index = config.get("_download_file_batch_index")
original_batch_label = config.get("_download_file_batch_label")
original_suppress_nested = config.get("_download_file_suppress_nested_pipe_progress")
except Exception:
original_skip_preflight = None
original_timeout = None
original_batch_total = None
original_batch_index = None
original_batch_label = None
original_suppress_nested = None
try:
if selection_urls:
@@ -2878,10 +2970,44 @@ class Download_File(Cmdlet):
failures = 0
last_code = 0
total_selection = len(selection_runs)
preview_items = list(selection_urls[:5]) or [
self._selection_run_label(run_args)
for run_args in selection_runs[:5]
]
try:
progress.ensure_local_ui(
label="download-file",
total_items=total_selection,
items_preview=preview_items,
)
except Exception:
pass
try:
progress.begin_pipe(
total_items=total_selection,
items_preview=preview_items,
)
except Exception:
pass
debug(f"[download-file] Processing {total_selection} selected item(s) from table...")
for idx, run_args in enumerate(selection_runs, 1):
run_label = self._selection_run_label(run_args)
debug(f"[download-file] Item {idx}/{total_selection}: {run_args}")
debug("[download-file] Re-invoking download-file for selected item...")
try:
progress.set_status(
f"downloading {idx}/{total_selection}: {run_label}"
)
except Exception:
pass
try:
if isinstance(config, dict):
config["_download_file_batch_total"] = total_selection
config["_download_file_batch_index"] = idx
config["_download_file_batch_label"] = run_label
config["_download_file_suppress_nested_pipe_progress"] = True
except Exception:
pass
exit_code = self._run_impl(None, run_args, config)
if exit_code == 0:
successes += 1
@@ -2909,6 +3035,22 @@ class Download_File(Cmdlet):
config.pop("_skip_direct_on_streaming_failure", None)
else:
config["_skip_direct_on_streaming_failure"] = original_skip_direct
if original_batch_total is None:
config.pop("_download_file_batch_total", None)
else:
config["_download_file_batch_total"] = original_batch_total
if original_batch_index is None:
config.pop("_download_file_batch_index", None)
else:
config["_download_file_batch_index"] = original_batch_index
if original_batch_label is None:
config.pop("_download_file_batch_label", None)
else:
config["_download_file_batch_label"] = original_batch_label
if original_suppress_nested is None:
config.pop("_download_file_suppress_nested_pipe_progress", None)
else:
config["_download_file_suppress_nested_pipe_progress"] = original_suppress_nested
except Exception:
pass