update
This commit is contained in:
+66
-8
@@ -13,7 +13,7 @@ from urllib.parse import quote, parse_qsl, urlencode, urlsplit, urlunsplit
|
|||||||
import httpx
|
import httpx
|
||||||
from API.httpx_shared import get_shared_httpx_client
|
from API.httpx_shared import get_shared_httpx_client
|
||||||
|
|
||||||
from SYS.logger import debug, log
|
from SYS.logger import debug, debug_panel, log
|
||||||
from SYS.utils_constant import mime_maps
|
from SYS.utils_constant import mime_maps
|
||||||
|
|
||||||
_KNOWN_EXTS = {
|
_KNOWN_EXTS = {
|
||||||
@@ -1533,7 +1533,17 @@ class HydrusNetwork(Store):
|
|||||||
Only explicit user actions (e.g. the get-file cmdlet) should open files.
|
Only explicit user actions (e.g. the get-file cmdlet) should open files.
|
||||||
"""
|
"""
|
||||||
file_hash = str(file_hash or "").strip().lower()
|
file_hash = str(file_hash or "").strip().lower()
|
||||||
debug(f"{self._log_prefix()} get_file(hash={file_hash[:12]}..., url={kwargs.get('url')})")
|
try:
|
||||||
|
debug_panel(
|
||||||
|
"Hydrus get_file",
|
||||||
|
[
|
||||||
|
("hash", file_hash),
|
||||||
|
("prefer_url", bool(kwargs.get("url"))),
|
||||||
|
],
|
||||||
|
border_style="blue",
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
# If 'url=True' is passed, we preference the browser URL even if a local path is available.
|
# If 'url=True' is passed, we preference the browser URL even if a local path is available.
|
||||||
# This is typically used by the 'get-file' cmdlet for interactive viewing.
|
# This is typically used by the 'get-file' cmdlet for interactive viewing.
|
||||||
@@ -1543,7 +1553,17 @@ class HydrusNetwork(Store):
|
|||||||
browser_url = (
|
browser_url = (
|
||||||
f"{base_url}/get_files/file?hash={file_hash}&Hydrus-Client-API-Access-Key={access_key}"
|
f"{base_url}/get_files/file?hash={file_hash}&Hydrus-Client-API-Access-Key={access_key}"
|
||||||
)
|
)
|
||||||
debug(f"{self._log_prefix()} get_file: returning browser URL per request: {browser_url}")
|
try:
|
||||||
|
debug_panel(
|
||||||
|
"Hydrus get_file",
|
||||||
|
[
|
||||||
|
("mode", "browser-url"),
|
||||||
|
("url", browser_url),
|
||||||
|
],
|
||||||
|
border_style="blue",
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
return browser_url
|
return browser_url
|
||||||
|
|
||||||
# Try to get the local disk path if possible (works if Hydrus is on same machine)
|
# Try to get the local disk path if possible (works if Hydrus is on same machine)
|
||||||
@@ -1555,18 +1575,46 @@ class HydrusNetwork(Store):
|
|||||||
if server_path:
|
if server_path:
|
||||||
local_path = Path(server_path)
|
local_path = Path(server_path)
|
||||||
if local_path.exists():
|
if local_path.exists():
|
||||||
debug(f"{self._log_prefix()} get_file: found local path: {local_path}")
|
try:
|
||||||
|
debug_panel(
|
||||||
|
"Hydrus get_file",
|
||||||
|
[
|
||||||
|
("mode", "local-path"),
|
||||||
|
("path", local_path),
|
||||||
|
],
|
||||||
|
border_style="green",
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
return local_path
|
return local_path
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
debug(f"{self._log_prefix()} get_file: could not resolve path from API: {e}")
|
try:
|
||||||
|
debug_panel(
|
||||||
|
"Hydrus get_file",
|
||||||
|
[
|
||||||
|
("mode", "path-lookup-error"),
|
||||||
|
("error", e),
|
||||||
|
],
|
||||||
|
border_style="yellow",
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
# If we found a path on the server but it's not locally accessible,
|
# If we found a path on the server but it's not locally accessible,
|
||||||
# keep it for logging but continue to the browser URL fallback so the UI
|
# keep it for logging but continue to the browser URL fallback so the UI
|
||||||
# can still open the file via the Hydrus web UI.
|
# can still open the file via the Hydrus web UI.
|
||||||
if server_path:
|
if server_path:
|
||||||
debug(
|
try:
|
||||||
f"{self._log_prefix()} get_file: server path not locally accessible, falling back to HTTP: {server_path}"
|
debug_panel(
|
||||||
|
"Hydrus get_file fallback",
|
||||||
|
[
|
||||||
|
("mode", "remote-http"),
|
||||||
|
("server_path", server_path),
|
||||||
|
],
|
||||||
|
border_style="yellow",
|
||||||
)
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
# Fallback to browser URL with access key
|
# Fallback to browser URL with access key
|
||||||
base_url = str(self.URL).rstrip("/")
|
base_url = str(self.URL).rstrip("/")
|
||||||
@@ -1574,7 +1622,17 @@ class HydrusNetwork(Store):
|
|||||||
browser_url = (
|
browser_url = (
|
||||||
f"{base_url}/get_files/file?hash={file_hash}&Hydrus-Client-API-Access-Key={access_key}"
|
f"{base_url}/get_files/file?hash={file_hash}&Hydrus-Client-API-Access-Key={access_key}"
|
||||||
)
|
)
|
||||||
debug(f"{self._log_prefix()} get_file: falling back to url={browser_url}")
|
try:
|
||||||
|
debug_panel(
|
||||||
|
"Hydrus get_file fallback",
|
||||||
|
[
|
||||||
|
("mode", "remote-http"),
|
||||||
|
("url", browser_url),
|
||||||
|
],
|
||||||
|
border_style="yellow",
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
return browser_url
|
return browser_url
|
||||||
|
|
||||||
def download_to_temp(
|
def download_to_temp(
|
||||||
|
|||||||
+3
-2
@@ -3160,6 +3160,7 @@ def check_url_exists_in_storage(
|
|||||||
final_output_dir: Optional[Path] = None,
|
final_output_dir: Optional[Path] = None,
|
||||||
*,
|
*,
|
||||||
auto_continue_duplicates: bool = True,
|
auto_continue_duplicates: bool = True,
|
||||||
|
force_prompt_in_pipeline: bool = False,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Pre-flight check to see if URLs already exist in storage.
|
"""Pre-flight check to see if URLs already exist in storage.
|
||||||
|
|
||||||
@@ -3252,7 +3253,7 @@ def check_url_exists_in_storage(
|
|||||||
cached_cmd = ""
|
cached_cmd = ""
|
||||||
cached_decision = None
|
cached_decision = None
|
||||||
|
|
||||||
if cached_decision is not None and str(cached_cmd or "") == str(current_cmd_text or ""):
|
if (not force_prompt_in_pipeline) and cached_decision is not None and str(cached_cmd or "") == str(current_cmd_text or ""):
|
||||||
_mark_preflight_checked()
|
_mark_preflight_checked()
|
||||||
if bool(cached_decision):
|
if bool(cached_decision):
|
||||||
return True
|
return True
|
||||||
@@ -3959,7 +3960,7 @@ def check_url_exists_in_storage(
|
|||||||
is_last_stage = bool(getattr(stage_ctx, "is_last_stage", False))
|
is_last_stage = bool(getattr(stage_ctx, "is_last_stage", False))
|
||||||
except Exception:
|
except Exception:
|
||||||
is_last_stage = False
|
is_last_stage = False
|
||||||
if total_stages > 1 and not is_last_stage:
|
if total_stages > 1 and not is_last_stage and not force_prompt_in_pipeline:
|
||||||
auto_confirm_reason = "pipeline stage (pre-last)"
|
auto_confirm_reason = "pipeline stage (pre-last)"
|
||||||
if auto_confirm_reason is None:
|
if auto_confirm_reason is None:
|
||||||
try:
|
try:
|
||||||
|
|||||||
+138
-8
@@ -234,8 +234,13 @@ class Add_File(Cmdlet):
|
|||||||
try:
|
try:
|
||||||
candidate_dir = Path(str(path_arg))
|
candidate_dir = Path(str(path_arg))
|
||||||
if candidate_dir.exists() and candidate_dir.is_dir():
|
if candidate_dir.exists() and candidate_dir.is_dir():
|
||||||
debug(
|
debug_panel(
|
||||||
f"[add-file] Treating -path directory as destination: {candidate_dir}"
|
"add-file destination",
|
||||||
|
[
|
||||||
|
("mode", "local export"),
|
||||||
|
("path", candidate_dir),
|
||||||
|
],
|
||||||
|
border_style="cyan",
|
||||||
)
|
)
|
||||||
location = str(candidate_dir)
|
location = str(candidate_dir)
|
||||||
path_arg = None
|
path_arg = None
|
||||||
@@ -350,6 +355,13 @@ class Add_File(Cmdlet):
|
|||||||
else:
|
else:
|
||||||
items_to_process = [result]
|
items_to_process = [result]
|
||||||
|
|
||||||
|
if result is None and not path_arg and not explicit_path_list_results and not dir_scan_results:
|
||||||
|
try:
|
||||||
|
if ctx.get_stage_context() is not None:
|
||||||
|
return 0
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
total_items = len(items_to_process) if isinstance(items_to_process, list) else 0
|
total_items = len(items_to_process) if isinstance(items_to_process, list) else 0
|
||||||
processed_items = 0
|
processed_items = 0
|
||||||
try:
|
try:
|
||||||
@@ -580,7 +592,12 @@ class Add_File(Cmdlet):
|
|||||||
progress.step("resolving source")
|
progress.step("resolving source")
|
||||||
|
|
||||||
media_path, file_hash, temp_dir_to_cleanup = self._resolve_source(
|
media_path, file_hash, temp_dir_to_cleanup = self._resolve_source(
|
||||||
item, path_arg, pipe_obj, config, store_instance=storage_registry
|
item,
|
||||||
|
path_arg,
|
||||||
|
pipe_obj,
|
||||||
|
config,
|
||||||
|
export_destination=(Path(location) if location and not is_storage_backend_location else None),
|
||||||
|
store_instance=storage_registry,
|
||||||
)
|
)
|
||||||
if not media_path and provider_name:
|
if not media_path and provider_name:
|
||||||
media_path, file_hash, temp_dir_to_cleanup = Add_File._download_provider_source(
|
media_path, file_hash, temp_dir_to_cleanup = Add_File._download_provider_source(
|
||||||
@@ -1103,6 +1120,70 @@ class Add_File(Cmdlet):
|
|||||||
pass
|
pass
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _download_remote_backend_url(
|
||||||
|
remote_url: str,
|
||||||
|
pipe_obj: models.PipeObject,
|
||||||
|
*,
|
||||||
|
file_hash: Optional[str] = None,
|
||||||
|
output_dir: Optional[Path] = None,
|
||||||
|
) -> Tuple[Optional[Path], Optional[Path]]:
|
||||||
|
"""Best-effort fetch of a remote backend URL.
|
||||||
|
|
||||||
|
Returns (downloaded_path, temp_dir_to_cleanup).
|
||||||
|
When ``output_dir`` is provided, the file is downloaded directly there and no
|
||||||
|
temp cleanup path is returned.
|
||||||
|
"""
|
||||||
|
|
||||||
|
url_text = str(remote_url or "").strip()
|
||||||
|
if not url_text:
|
||||||
|
return None, None
|
||||||
|
if not url_text.lower().startswith(_REMOTE_URL_PREFIXES):
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
tmp_dir: Optional[Path] = None
|
||||||
|
try:
|
||||||
|
download_root = output_dir
|
||||||
|
if download_root is None:
|
||||||
|
tmp_dir = Path(tempfile.mkdtemp(prefix="add-file-src-"))
|
||||||
|
download_root = tmp_dir
|
||||||
|
|
||||||
|
suggested_name = Add_File._build_provider_filename(
|
||||||
|
pipe_obj,
|
||||||
|
fallback_hash=file_hash,
|
||||||
|
source_url=url_text,
|
||||||
|
)
|
||||||
|
pipeline_progress = PipelineProgress(ctx)
|
||||||
|
|
||||||
|
downloaded = _download_direct_file(
|
||||||
|
url_text,
|
||||||
|
download_root,
|
||||||
|
quiet=False,
|
||||||
|
suggested_filename=suggested_name,
|
||||||
|
pipeline_progress=pipeline_progress,
|
||||||
|
)
|
||||||
|
downloaded_path = getattr(downloaded, "path", None)
|
||||||
|
if isinstance(downloaded_path, Path) and downloaded_path.exists():
|
||||||
|
if output_dir is not None:
|
||||||
|
pipe_obj.is_temp = False
|
||||||
|
if isinstance(pipe_obj.extra, dict):
|
||||||
|
pipe_obj.extra["_direct_export_download"] = True
|
||||||
|
else:
|
||||||
|
pipe_obj.extra = {"_direct_export_download": True}
|
||||||
|
return downloaded_path, None
|
||||||
|
|
||||||
|
pipe_obj.is_temp = True
|
||||||
|
return downloaded_path, tmp_dir
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if tmp_dir is not None:
|
||||||
|
try:
|
||||||
|
shutil.rmtree(tmp_dir, ignore_errors=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None, None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _build_provider_filename(
|
def _build_provider_filename(
|
||||||
pipe_obj: models.PipeObject,
|
pipe_obj: models.PipeObject,
|
||||||
@@ -1188,6 +1269,7 @@ class Add_File(Cmdlet):
|
|||||||
pipe_obj: models.PipeObject,
|
pipe_obj: models.PipeObject,
|
||||||
config: Dict[str,
|
config: Dict[str,
|
||||||
Any],
|
Any],
|
||||||
|
export_destination: Optional[Path] = None,
|
||||||
store_instance: Optional[Any] = None,
|
store_instance: Optional[Any] = None,
|
||||||
) -> Tuple[Optional[Path],
|
) -> Tuple[Optional[Path],
|
||||||
Optional[str],
|
Optional[str],
|
||||||
@@ -1228,12 +1310,30 @@ class Add_File(Cmdlet):
|
|||||||
pipe_obj.path = str(mp)
|
pipe_obj.path = str(mp)
|
||||||
return mp, str(r_hash), None
|
return mp, str(r_hash), None
|
||||||
if isinstance(mp, str) and mp.strip():
|
if isinstance(mp, str) and mp.strip():
|
||||||
|
try:
|
||||||
|
mp_path = Path(str(mp))
|
||||||
|
except Exception:
|
||||||
|
mp_path = None
|
||||||
|
if mp_path is not None and mp_path.exists() and mp_path.is_file():
|
||||||
|
pipe_obj.path = str(mp_path)
|
||||||
|
return mp_path, str(r_hash), None
|
||||||
|
|
||||||
dl_path, tmp_dir = Add_File._maybe_download_backend_file(
|
dl_path, tmp_dir = Add_File._maybe_download_backend_file(
|
||||||
backend, str(r_hash), pipe_obj
|
backend, str(r_hash), pipe_obj
|
||||||
)
|
)
|
||||||
if dl_path and dl_path.exists():
|
if dl_path and dl_path.exists():
|
||||||
pipe_obj.path = str(dl_path)
|
pipe_obj.path = str(dl_path)
|
||||||
return dl_path, str(r_hash), tmp_dir
|
return dl_path, str(r_hash), tmp_dir
|
||||||
|
|
||||||
|
dl_path, tmp_dir = Add_File._download_remote_backend_url(
|
||||||
|
str(mp),
|
||||||
|
pipe_obj,
|
||||||
|
file_hash=str(r_hash),
|
||||||
|
output_dir=export_destination,
|
||||||
|
)
|
||||||
|
if dl_path and dl_path.exists():
|
||||||
|
pipe_obj.path = str(dl_path)
|
||||||
|
return dl_path, str(r_hash), tmp_dir
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
debug(f"[add-file] _resolve_source backend fetch failed for {r_store}/{r_hash}: {exc}")
|
debug(f"[add-file] _resolve_source backend fetch failed for {r_store}/{r_hash}: {exc}")
|
||||||
|
|
||||||
@@ -1657,12 +1757,22 @@ class Add_File(Cmdlet):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _emit_pipe_object(pipe_obj: models.PipeObject) -> None:
|
def _emit_pipe_object(pipe_obj: models.PipeObject) -> None:
|
||||||
from SYS.result_table import format_result
|
payload = pipe_obj.to_dict()
|
||||||
|
ctx.emit(payload)
|
||||||
log(format_result(pipe_obj, title="Result"), file=sys.stderr)
|
|
||||||
ctx.emit(pipe_obj.to_dict())
|
|
||||||
ctx.set_current_stage_table(None)
|
ctx.set_current_stage_table(None)
|
||||||
|
|
||||||
|
stage_ctx = ctx.get_stage_context()
|
||||||
|
is_last = (stage_ctx is None) or bool(getattr(stage_ctx, "is_last_stage", False))
|
||||||
|
if not is_last:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
from ._shared import display_and_persist_items
|
||||||
|
|
||||||
|
display_and_persist_items([payload], title="Result", subject=payload)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _emit_storage_result(
|
def _emit_storage_result(
|
||||||
payload: Dict[str,
|
payload: Dict[str,
|
||||||
@@ -1925,7 +2035,24 @@ class Add_File(Cmdlet):
|
|||||||
log(f"❌ Invalid destination path '{location}': {exc}", file=sys.stderr)
|
log(f"❌ Invalid destination path '{location}': {exc}", file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
log(f"Exporting to local path: {destination_root}", file=sys.stderr)
|
direct_export_download = False
|
||||||
|
try:
|
||||||
|
if isinstance(pipe_obj.extra, dict):
|
||||||
|
direct_export_download = bool(pipe_obj.extra.pop("_direct_export_download", False))
|
||||||
|
except Exception:
|
||||||
|
direct_export_download = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
debug_panel(
|
||||||
|
"add-file export",
|
||||||
|
[
|
||||||
|
("destination", destination_root),
|
||||||
|
("source", media_path),
|
||||||
|
],
|
||||||
|
border_style="green",
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
result = None
|
result = None
|
||||||
tags, url, title, f_hash = Add_File._prepare_metadata(result, media_path, pipe_obj, config)
|
tags, url, title, f_hash = Add_File._prepare_metadata(result, media_path, pipe_obj, config)
|
||||||
@@ -1961,6 +2088,9 @@ class Add_File(Cmdlet):
|
|||||||
destination_root.mkdir(parents=True, exist_ok=True)
|
destination_root.mkdir(parents=True, exist_ok=True)
|
||||||
target_path = destination_root / new_name
|
target_path = destination_root / new_name
|
||||||
|
|
||||||
|
if direct_export_download:
|
||||||
|
target_path = media_path
|
||||||
|
else:
|
||||||
if target_path.exists():
|
if target_path.exists():
|
||||||
target_path = unique_path(target_path)
|
target_path = unique_path(target_path)
|
||||||
|
|
||||||
|
|||||||
@@ -1187,6 +1187,7 @@ class Download_File(Cmdlet):
|
|||||||
hydrus_available=hydrus_available,
|
hydrus_available=hydrus_available,
|
||||||
final_output_dir=final_output_dir,
|
final_output_dir=final_output_dir,
|
||||||
auto_continue_duplicates=False,
|
auto_continue_duplicates=False,
|
||||||
|
force_prompt_in_pipeline=bool(kwargs.get("force_prompt_in_pipeline")),
|
||||||
)
|
)
|
||||||
|
|
||||||
def _preflight_url_duplicates_bulk(
|
def _preflight_url_duplicates_bulk(
|
||||||
@@ -1554,6 +1555,7 @@ class Download_File(Cmdlet):
|
|||||||
final_output_dir=final_output_dir,
|
final_output_dir=final_output_dir,
|
||||||
candidate_url=canonical_url,
|
candidate_url=canonical_url,
|
||||||
extra_urls=[url],
|
extra_urls=[url],
|
||||||
|
force_prompt_in_pipeline=bool(clip_ranges),
|
||||||
):
|
):
|
||||||
duplicate_skipped_count += 1
|
duplicate_skipped_count += 1
|
||||||
log(f"Skipping download (duplicate found): {url}", file=sys.stderr)
|
log(f"Skipping download (duplicate found): {url}", file=sys.stderr)
|
||||||
@@ -2823,6 +2825,101 @@ class Download_File(Cmdlet):
|
|||||||
return f"{hours:02d}:{minutes:02d}:{secs:02d}"
|
return f"{hours:02d}:{minutes:02d}:{secs:02d}"
|
||||||
return f"{minutes:02d}:{secs:02d}"
|
return f"{minutes:02d}:{secs:02d}"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _rebase_subtitle_timestamp_text(text: str, offset_seconds: int) -> str:
|
||||||
|
if not text:
|
||||||
|
return text
|
||||||
|
|
||||||
|
try:
|
||||||
|
offset_value = float(offset_seconds)
|
||||||
|
except Exception:
|
||||||
|
return text
|
||||||
|
|
||||||
|
if offset_value <= 0:
|
||||||
|
return text
|
||||||
|
|
||||||
|
timestamp_re = re.compile(r"(?<!\d)(?P<ts>(?:\d{2}:)?\d{2}:\d{2}(?:[\.,]\d{1,3})?)(?!\d)")
|
||||||
|
|
||||||
|
def _shift(match: re.Match[str]) -> str:
|
||||||
|
original = str(match.group("ts") or "")
|
||||||
|
if not original:
|
||||||
|
return original
|
||||||
|
|
||||||
|
frac_sep = "."
|
||||||
|
frac_digits = 0
|
||||||
|
base = original
|
||||||
|
frac_seconds = 0.0
|
||||||
|
if "." in original:
|
||||||
|
base, frac = original.split(".", 1)
|
||||||
|
frac_sep = "."
|
||||||
|
frac_digits = len(frac)
|
||||||
|
try:
|
||||||
|
frac_seconds = float(f"0.{frac}") if frac else 0.0
|
||||||
|
except Exception:
|
||||||
|
frac_seconds = 0.0
|
||||||
|
elif "," in original:
|
||||||
|
base, frac = original.split(",", 1)
|
||||||
|
frac_sep = ","
|
||||||
|
frac_digits = len(frac)
|
||||||
|
try:
|
||||||
|
frac_seconds = float(f"0.{frac}") if frac else 0.0
|
||||||
|
except Exception:
|
||||||
|
frac_seconds = 0.0
|
||||||
|
|
||||||
|
parts = base.split(":")
|
||||||
|
if len(parts) == 3:
|
||||||
|
hours_s, minutes_s, seconds_s = parts
|
||||||
|
include_hours = True
|
||||||
|
elif len(parts) == 2:
|
||||||
|
hours_s = "0"
|
||||||
|
minutes_s, seconds_s = parts
|
||||||
|
include_hours = False
|
||||||
|
else:
|
||||||
|
return original
|
||||||
|
|
||||||
|
try:
|
||||||
|
total = (
|
||||||
|
(int(hours_s) * 3600)
|
||||||
|
+ (int(minutes_s) * 60)
|
||||||
|
+ int(seconds_s)
|
||||||
|
+ frac_seconds
|
||||||
|
+ offset_value
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
return original
|
||||||
|
|
||||||
|
total = max(0.0, total)
|
||||||
|
whole_seconds = int(total)
|
||||||
|
fraction = total - whole_seconds
|
||||||
|
hours, remainder = divmod(whole_seconds, 3600)
|
||||||
|
minutes, seconds = divmod(remainder, 60)
|
||||||
|
|
||||||
|
if frac_digits > 0:
|
||||||
|
scale = 10 ** frac_digits
|
||||||
|
frac_value = int(round(fraction * scale))
|
||||||
|
if frac_value >= scale:
|
||||||
|
frac_value = 0
|
||||||
|
seconds += 1
|
||||||
|
if seconds >= 60:
|
||||||
|
seconds = 0
|
||||||
|
minutes += 1
|
||||||
|
if minutes >= 60:
|
||||||
|
minutes = 0
|
||||||
|
hours += 1
|
||||||
|
frac_text = f"{frac_value:0{frac_digits}d}"
|
||||||
|
if include_hours or hours > 0:
|
||||||
|
return f"{hours:02d}:{minutes:02d}:{seconds:02d}{frac_sep}{frac_text}"
|
||||||
|
return f"{minutes:02d}:{seconds:02d}{frac_sep}{frac_text}"
|
||||||
|
|
||||||
|
if include_hours or hours > 0:
|
||||||
|
return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
|
||||||
|
return f"{minutes:02d}:{seconds:02d}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
return timestamp_re.sub(_shift, str(text))
|
||||||
|
except Exception:
|
||||||
|
return text
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _format_clip_range(cls, start_s: int, end_s: int) -> str:
|
def _format_clip_range(cls, start_s: int, end_s: int) -> str:
|
||||||
force_hours = bool(start_s >= 3600 or end_s >= 3600)
|
force_hours = bool(start_s >= 3600 or end_s >= 3600)
|
||||||
@@ -2854,6 +2951,13 @@ class Download_File(Cmdlet):
|
|||||||
|
|
||||||
po["tag"] = tags
|
po["tag"] = tags
|
||||||
|
|
||||||
|
notes = po.get("notes")
|
||||||
|
if isinstance(notes, dict):
|
||||||
|
sub_text = notes.get("sub")
|
||||||
|
if isinstance(sub_text, str) and sub_text.strip():
|
||||||
|
notes["sub"] = cls._rebase_subtitle_timestamp_text(sub_text, start_s)
|
||||||
|
po["notes"] = notes
|
||||||
|
|
||||||
if len(pipe_objects) < 2:
|
if len(pipe_objects) < 2:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user