updated bandcamp and list parsing
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
{"store":"rpi"}
|
||||||
+20
-2
@@ -16,6 +16,15 @@ class Bandcamp(Provider):
|
|||||||
TABLE_AUTO_STAGES = {
|
TABLE_AUTO_STAGES = {
|
||||||
"bandcamp": ["download-file"],
|
"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
|
@staticmethod
|
||||||
def _base_url(raw_url: str) -> str:
|
def _base_url(raw_url: str) -> str:
|
||||||
@@ -89,6 +98,8 @@ class Bandcamp(Provider):
|
|||||||
"album" if "/album/" in target else
|
"album" if "/album/" in target else
|
||||||
("track" if "/track/" in target else "item")
|
("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(
|
results.append(
|
||||||
SearchResult(
|
SearchResult(
|
||||||
@@ -111,6 +122,8 @@ class Bandcamp(Provider):
|
|||||||
"url": target,
|
"url": target,
|
||||||
"artist_url": base,
|
"artist_url": base,
|
||||||
},
|
},
|
||||||
|
selection_args=selection_args,
|
||||||
|
selection_action=selection_action,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
@@ -318,6 +331,8 @@ class Bandcamp(Provider):
|
|||||||
|
|
||||||
itemtype = item.query_selector(".itemtype")
|
itemtype = item.query_selector(".itemtype")
|
||||||
media_type = itemtype.inner_text().strip() if itemtype else "album"
|
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(
|
results.append(
|
||||||
SearchResult(
|
SearchResult(
|
||||||
@@ -335,13 +350,16 @@ class Bandcamp(Provider):
|
|||||||
("Type",
|
("Type",
|
||||||
media_type),
|
media_type),
|
||||||
("Url",
|
("Url",
|
||||||
base_url or str(target_url or "")),
|
str(target_url or "")),
|
||||||
],
|
],
|
||||||
full_metadata={
|
full_metadata={
|
||||||
"artist": artist,
|
"artist": artist,
|
||||||
"type": media_type,
|
"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
@@ -630,6 +630,8 @@ class Table:
|
|||||||
"""Base arguments for the source command"""
|
"""Base arguments for the source command"""
|
||||||
self.header_lines: List[str] = []
|
self.header_lines: List[str] = []
|
||||||
"""Optional metadata lines rendered under the title"""
|
"""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
|
self.perseverance: bool = preserve_order
|
||||||
"""If True, skip automatic sorting so display order matches input order."""
|
"""If True, skip automatic sorting so display order matches input order."""
|
||||||
self.interactive: bool = False
|
self.interactive: bool = False
|
||||||
@@ -687,7 +689,9 @@ class Table:
|
|||||||
|
|
||||||
def _perseverance(self, perseverance: bool = True) -> "Table":
|
def _perseverance(self, perseverance: bool = True) -> "Table":
|
||||||
"""Configure whether this table should skip automatic sorting."""
|
"""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
|
return self
|
||||||
|
|
||||||
def add_row(self) -> Row:
|
def add_row(self) -> Row:
|
||||||
@@ -741,7 +745,8 @@ class Table:
|
|||||||
self.title = title
|
self.title = title
|
||||||
self.source_command = command
|
self.source_command = command
|
||||||
self.source_args = args or []
|
self.source_args = args or []
|
||||||
self.perseverance = preserve_order
|
self.perseverance = bool(preserve_order)
|
||||||
|
self.preserve_order = bool(preserve_order)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def copy_with_title(self, new_title: str) -> "Table":
|
def copy_with_title(self, new_title: str) -> "Table":
|
||||||
|
|||||||
+3
-1
@@ -1920,11 +1920,13 @@ def create_pipe_object_result(
|
|||||||
Returns:
|
Returns:
|
||||||
Dict with all PipeObject fields for emission
|
Dict with all PipeObject fields for emission
|
||||||
"""
|
"""
|
||||||
|
store_override = extra.pop("store", None)
|
||||||
|
|
||||||
result = build_file_result_payload(
|
result = build_file_result_payload(
|
||||||
title=title,
|
title=title,
|
||||||
path=file_path,
|
path=file_path,
|
||||||
hash_value=hash_value,
|
hash_value=hash_value,
|
||||||
store=source,
|
store=store_override if store_override is not None else source,
|
||||||
tag=tag,
|
tag=tag,
|
||||||
source=source,
|
source=source,
|
||||||
id=identifier,
|
id=identifier,
|
||||||
|
|||||||
+151
-9
@@ -124,6 +124,48 @@ class Download_File(Cmdlet):
|
|||||||
raise DownloadError("Could not determine downloaded file path")
|
raise DownloadError("Could not determine downloaded file path")
|
||||||
return resolved
|
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(
|
def _process_explicit_urls(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -139,14 +181,32 @@ class Download_File(Cmdlet):
|
|||||||
) -> tuple[int,
|
) -> tuple[int,
|
||||||
Optional[int]]:
|
Optional[int]]:
|
||||||
downloaded_count = 0
|
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")
|
SearchResult = registry.get("SearchResult")
|
||||||
get_provider = registry.get("get_provider")
|
get_provider = registry.get("get_provider")
|
||||||
match_provider_name_for_url = registry.get("match_provider_name_for_url")
|
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:
|
try:
|
||||||
debug(f"Processing URL: {url}")
|
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
|
# Check providers first
|
||||||
provider_name = None
|
provider_name = None
|
||||||
@@ -1455,6 +1515,9 @@ class Download_File(Cmdlet):
|
|||||||
downloaded_pipe_objects: List[Dict[str, Any]] = []
|
downloaded_pipe_objects: List[Dict[str, Any]] = []
|
||||||
pipe_seq = 0
|
pipe_seq = 0
|
||||||
clip_sections_spec = self._build_clip_sections_spec(clip_ranges)
|
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:
|
if clip_sections_spec:
|
||||||
try:
|
try:
|
||||||
@@ -1462,11 +1525,15 @@ class Download_File(Cmdlet):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
for url in supported_url:
|
for url_index, url in enumerate(supported_url, 1):
|
||||||
try:
|
try:
|
||||||
debug(f"[download-file] Processing URL in loop: {url}")
|
debug(f"[download-file] Processing URL in loop: {url}")
|
||||||
debug(f"[download-file] ytdl_format parameter passed in: {ytdl_format}")
|
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
|
canonical_url = url
|
||||||
if not skip_per_url_preflight or clip_ranges:
|
if not skip_per_url_preflight or clip_ranges:
|
||||||
canonical_url = self._canonicalize_url_for_storage(
|
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)
|
log(f"Skipping download (duplicate found): {url}", file=sys.stderr)
|
||||||
continue
|
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_format = ytdl_format
|
||||||
actual_playlist_items = playlist_items
|
actual_playlist_items = playlist_items
|
||||||
@@ -1631,7 +1707,8 @@ class Download_File(Cmdlet):
|
|||||||
write_sub=write_sub,
|
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...")
|
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)
|
result_obj = _download_with_timeout(opts, timeout_seconds=download_timeout_seconds, config=config)
|
||||||
debug(f"Download completed for {url}, building pipe object...")
|
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...")
|
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()
|
stage_ctx = pipeline_context.get_stage_context()
|
||||||
emit_enabled = bool(stage_ctx is not None)
|
emit_enabled = bool(stage_ctx is not None)
|
||||||
@@ -1995,6 +2073,7 @@ class Download_File(Cmdlet):
|
|||||||
) -> int:
|
) -> int:
|
||||||
try:
|
try:
|
||||||
debug("Starting streaming download handler")
|
debug("Starting streaming download handler")
|
||||||
|
suppress_nested, _batch_total, _batch_index, _batch_label = self._batch_progress_state(config)
|
||||||
|
|
||||||
ytdlp_tool = YtDlpTool(config)
|
ytdlp_tool = YtDlpTool(config)
|
||||||
|
|
||||||
@@ -2028,10 +2107,11 @@ class Download_File(Cmdlet):
|
|||||||
else:
|
else:
|
||||||
debug("[download-file] Skipping local UI: running inside pipeline stage")
|
debug("[download-file] Skipping local UI: running inside pipeline stage")
|
||||||
try:
|
try:
|
||||||
progress.begin_pipe(
|
if not suppress_nested:
|
||||||
total_items=len(supported_url),
|
progress.begin_pipe(
|
||||||
items_preview=supported_url,
|
total_items=len(supported_url),
|
||||||
)
|
items_preview=supported_url,
|
||||||
|
)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
debug(f"[download-file] PipelineProgress begin_pipe error: {err}")
|
debug(f"[download-file] PipelineProgress begin_pipe error: {err}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -2852,14 +2932,26 @@ class Download_File(Cmdlet):
|
|||||||
original_skip_preflight = None
|
original_skip_preflight = None
|
||||||
original_timeout = None
|
original_timeout = None
|
||||||
original_skip_direct = None
|
original_skip_direct = None
|
||||||
|
original_batch_total = None
|
||||||
|
original_batch_index = None
|
||||||
|
original_batch_label = None
|
||||||
|
original_suppress_nested = None
|
||||||
try:
|
try:
|
||||||
if isinstance(config, dict):
|
if isinstance(config, dict):
|
||||||
original_skip_preflight = config.get("_skip_url_preflight")
|
original_skip_preflight = config.get("_skip_url_preflight")
|
||||||
original_timeout = config.get("_pipeobject_timeout_seconds")
|
original_timeout = config.get("_pipeobject_timeout_seconds")
|
||||||
original_skip_direct = config.get("_skip_direct_on_streaming_failure")
|
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:
|
except Exception:
|
||||||
original_skip_preflight = None
|
original_skip_preflight = None
|
||||||
original_timeout = None
|
original_timeout = None
|
||||||
|
original_batch_total = None
|
||||||
|
original_batch_index = None
|
||||||
|
original_batch_label = None
|
||||||
|
original_suppress_nested = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if selection_urls:
|
if selection_urls:
|
||||||
@@ -2878,10 +2970,44 @@ class Download_File(Cmdlet):
|
|||||||
failures = 0
|
failures = 0
|
||||||
last_code = 0
|
last_code = 0
|
||||||
total_selection = len(selection_runs)
|
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...")
|
debug(f"[download-file] Processing {total_selection} selected item(s) from table...")
|
||||||
for idx, run_args in enumerate(selection_runs, 1):
|
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(f"[download-file] Item {idx}/{total_selection}: {run_args}")
|
||||||
debug("[download-file] Re-invoking download-file for selected item...")
|
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)
|
exit_code = self._run_impl(None, run_args, config)
|
||||||
if exit_code == 0:
|
if exit_code == 0:
|
||||||
successes += 1
|
successes += 1
|
||||||
@@ -2909,6 +3035,22 @@ class Download_File(Cmdlet):
|
|||||||
config.pop("_skip_direct_on_streaming_failure", None)
|
config.pop("_skip_direct_on_streaming_failure", None)
|
||||||
else:
|
else:
|
||||||
config["_skip_direct_on_streaming_failure"] = original_skip_direct
|
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:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user