kl
This commit is contained in:
@@ -375,7 +375,7 @@
|
|||||||
"(filespace\\.com/[a-zA-Z0-9]{12})"
|
"(filespace\\.com/[a-zA-Z0-9]{12})"
|
||||||
],
|
],
|
||||||
"regexp": "(filespace\\.com/fd/([a-zA-Z0-9]{12}))|((filespace\\.com/[a-zA-Z0-9]{12}))",
|
"regexp": "(filespace\\.com/fd/([a-zA-Z0-9]{12}))|((filespace\\.com/[a-zA-Z0-9]{12}))",
|
||||||
"status": false
|
"status": true
|
||||||
},
|
},
|
||||||
"filezip": {
|
"filezip": {
|
||||||
"name": "filezip",
|
"name": "filezip",
|
||||||
@@ -595,7 +595,7 @@
|
|||||||
"(simfileshare\\.net/download/[0-9]+/)"
|
"(simfileshare\\.net/download/[0-9]+/)"
|
||||||
],
|
],
|
||||||
"regexp": "(simfileshare\\.net/download/[0-9]+/)",
|
"regexp": "(simfileshare\\.net/download/[0-9]+/)",
|
||||||
"status": false
|
"status": true
|
||||||
},
|
},
|
||||||
"streamtape": {
|
"streamtape": {
|
||||||
"name": "streamtape",
|
"name": "streamtape",
|
||||||
|
|||||||
246
MPV/LUA/main.lua
246
MPV/LUA/main.lua
@@ -1876,6 +1876,212 @@ local function _download_url_for_current_item(url)
|
|||||||
return base, true
|
return base, true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function M._extract_youtube_video_id(url)
|
||||||
|
url = trim(tostring(url or ''))
|
||||||
|
if url == '' then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local lower = url:lower()
|
||||||
|
local video_id = nil
|
||||||
|
if lower:match('youtu%.be/') then
|
||||||
|
video_id = url:match('youtu%.be/([^%?&#/]+)')
|
||||||
|
elseif lower:match('youtube%.com/watch') or lower:match('youtube%-nocookie%.com/watch') then
|
||||||
|
video_id = _extract_query_param(url, 'v')
|
||||||
|
elseif lower:match('youtube%.com/shorts/') or lower:match('youtube%-nocookie%.com/shorts/') then
|
||||||
|
video_id = url:match('/shorts/([^%?&#/]+)')
|
||||||
|
elseif lower:match('youtube%.com/live/') or lower:match('youtube%-nocookie%.com/live/') then
|
||||||
|
video_id = url:match('/live/([^%?&#/]+)')
|
||||||
|
elseif lower:match('youtube%.com/embed/') or lower:match('youtube%-nocookie%.com/embed/') then
|
||||||
|
video_id = url:match('/embed/([^%?&#/]+)')
|
||||||
|
end
|
||||||
|
|
||||||
|
video_id = trim(tostring(video_id or ''))
|
||||||
|
if video_id == '' or not video_id:match('^[%w_-]+$') then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return video_id
|
||||||
|
end
|
||||||
|
|
||||||
|
function M._suspicious_ytdl_format_reason(fmt, url, raw)
|
||||||
|
fmt = trim(tostring(fmt or ''))
|
||||||
|
url = trim(tostring(url or ''))
|
||||||
|
if fmt == '' then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local lower_fmt = fmt:lower()
|
||||||
|
if lower_fmt:match('^https?://') or lower_fmt:match('^rtmp') or (url ~= '' and fmt == url) then
|
||||||
|
return 'format string is a url'
|
||||||
|
end
|
||||||
|
|
||||||
|
local youtube_id = M._extract_youtube_video_id(url)
|
||||||
|
if youtube_id and fmt == youtube_id then
|
||||||
|
return 'format matches current youtube video id'
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(raw) == 'table' and youtube_id then
|
||||||
|
local raw_id = trim(tostring(raw.id or ''))
|
||||||
|
if raw_id ~= '' and raw_id == youtube_id and fmt == raw_id then
|
||||||
|
return 'format matches raw youtube video id'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function M._clear_suspicious_ytdl_format_for_url(url, reason)
|
||||||
|
url = trim(tostring(url or ''))
|
||||||
|
if url == '' then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local raw = mp.get_property_native('ytdl-raw-info')
|
||||||
|
local bad_props = {}
|
||||||
|
local bad_value = nil
|
||||||
|
local bad_reason = nil
|
||||||
|
local checks = {
|
||||||
|
{
|
||||||
|
prop = 'ytdl-format',
|
||||||
|
value = mp.get_property_native('ytdl-format'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop = 'file-local-options/ytdl-format',
|
||||||
|
value = mp.get_property('file-local-options/ytdl-format'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop = 'options/ytdl-format',
|
||||||
|
value = mp.get_property('options/ytdl-format'),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item in ipairs(checks) do
|
||||||
|
local candidate = trim(tostring(item.value or ''))
|
||||||
|
local why = M._suspicious_ytdl_format_reason(candidate, url, raw)
|
||||||
|
if why then
|
||||||
|
if not bad_value then
|
||||||
|
bad_value = candidate
|
||||||
|
end
|
||||||
|
if not bad_reason then
|
||||||
|
bad_reason = why
|
||||||
|
end
|
||||||
|
bad_props[#bad_props + 1] = tostring(item.prop)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if #bad_props == 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
pcall(mp.set_property, 'options/ytdl-format', '')
|
||||||
|
pcall(mp.set_property, 'file-local-options/ytdl-format', '')
|
||||||
|
pcall(mp.set_property, 'ytdl-format', '')
|
||||||
|
_lua_log(
|
||||||
|
'ytdl-format: cleared suspicious selector=' .. tostring(bad_value or '')
|
||||||
|
.. ' props=' .. table.concat(bad_props, ',')
|
||||||
|
.. ' reason=' .. tostring(bad_reason or reason or 'invalid')
|
||||||
|
.. ' url=' .. tostring(url)
|
||||||
|
)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function M._prepare_ytdl_format_for_web_load(url, reason)
|
||||||
|
url = trim(tostring(url or ''))
|
||||||
|
if url == '' then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if M._clear_suspicious_ytdl_format_for_url(url, reason) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local normalized_url = url:gsub('#.*$', '')
|
||||||
|
local base, query = normalized_url:match('^([^?]+)%?(.*)$')
|
||||||
|
if base then
|
||||||
|
local kept = {}
|
||||||
|
for pair in query:gmatch('[^&]+') do
|
||||||
|
local raw_key = pair:match('^([^=]+)') or pair
|
||||||
|
local key = tostring(_percent_decode(raw_key) or raw_key or ''):lower()
|
||||||
|
local keep = true
|
||||||
|
if key == 't' or key == 'start' or key == 'time_continue' or key == 'timestamp' or key == 'time' or key == 'begin' then
|
||||||
|
keep = false
|
||||||
|
elseif key:match('^utm_') then
|
||||||
|
keep = false
|
||||||
|
end
|
||||||
|
if keep then
|
||||||
|
kept[#kept + 1] = pair
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if #kept > 0 then
|
||||||
|
normalized_url = base .. '?' .. table.concat(kept, '&')
|
||||||
|
else
|
||||||
|
normalized_url = base
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local explicit_reload_url = normalized_url
|
||||||
|
explicit_reload_url = explicit_reload_url:gsub('^[%a][%w+%.%-]*://', '')
|
||||||
|
explicit_reload_url = explicit_reload_url:gsub('^www%.', '')
|
||||||
|
explicit_reload_url = explicit_reload_url:gsub('/+$', '')
|
||||||
|
explicit_reload_url = explicit_reload_url:lower()
|
||||||
|
|
||||||
|
local is_explicit_reload = (
|
||||||
|
explicit_reload_url ~= ''
|
||||||
|
and _skip_next_store_check_url ~= ''
|
||||||
|
and explicit_reload_url == _skip_next_store_check_url
|
||||||
|
)
|
||||||
|
|
||||||
|
local active_props = {}
|
||||||
|
local first_value = nil
|
||||||
|
local checks = {
|
||||||
|
{
|
||||||
|
prop = 'ytdl-format',
|
||||||
|
value = mp.get_property_native('ytdl-format'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop = 'file-local-options/ytdl-format',
|
||||||
|
value = mp.get_property('file-local-options/ytdl-format'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop = 'options/ytdl-format',
|
||||||
|
value = mp.get_property('options/ytdl-format'),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item in ipairs(checks) do
|
||||||
|
local candidate = trim(tostring(item.value or ''))
|
||||||
|
if candidate ~= '' then
|
||||||
|
if not first_value then
|
||||||
|
first_value = candidate
|
||||||
|
end
|
||||||
|
active_props[#active_props + 1] = tostring(item.prop) .. '=' .. candidate
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if #active_props == 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if is_explicit_reload then
|
||||||
|
_lua_log(
|
||||||
|
'ytdl-format: preserving explicit reload selector reason=' .. tostring(reason or 'on-load')
|
||||||
|
.. ' url=' .. tostring(url)
|
||||||
|
.. ' values=' .. table.concat(active_props, '; ')
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
pcall(mp.set_property, 'options/ytdl-format', '')
|
||||||
|
pcall(mp.set_property, 'file-local-options/ytdl-format', '')
|
||||||
|
pcall(mp.set_property, 'ytdl-format', '')
|
||||||
|
_lua_log(
|
||||||
|
'ytdl-format: cleared stale selector=' .. tostring(first_value or '')
|
||||||
|
.. ' reason=' .. tostring(reason or 'on-load')
|
||||||
|
.. ' url=' .. tostring(url)
|
||||||
|
.. ' values=' .. table.concat(active_props, '; ')
|
||||||
|
)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
local function _normalize_url_for_store_lookup(url)
|
local function _normalize_url_for_store_lookup(url)
|
||||||
url = trim(tostring(url or ''))
|
url = trim(tostring(url or ''))
|
||||||
if url == '' then
|
if url == '' then
|
||||||
@@ -3742,6 +3948,8 @@ function M._apply_web_subtitle_load_defaults(reason)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
M._prepare_ytdl_format_for_web_load(target, reason or 'on-load')
|
||||||
|
|
||||||
local raw = M._build_web_ytdl_raw_options()
|
local raw = M._build_web_ytdl_raw_options()
|
||||||
if raw and raw ~= '' then
|
if raw and raw ~= '' then
|
||||||
pcall(mp.set_property, 'file-local-options/ytdl-raw-options', raw)
|
pcall(mp.set_property, 'file-local-options/ytdl-raw-options', raw)
|
||||||
@@ -4664,22 +4872,35 @@ _show_format_list_osd = function(items, max_items)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function _current_ytdl_format_string()
|
local function _current_ytdl_format_string()
|
||||||
|
local url = _current_url_for_web_actions() or _current_target() or ''
|
||||||
|
local raw = mp.get_property_native('ytdl-raw-info')
|
||||||
|
|
||||||
-- Preferred: mpv exposes the active ytdl format string.
|
-- Preferred: mpv exposes the active ytdl format string.
|
||||||
local fmt = trim(tostring(mp.get_property_native('ytdl-format') or ''))
|
local fmt = trim(tostring(mp.get_property_native('ytdl-format') or ''))
|
||||||
if fmt ~= '' then
|
local suspicious_reason = M._suspicious_ytdl_format_reason(fmt, url, raw)
|
||||||
|
if fmt ~= '' and not suspicious_reason then
|
||||||
return fmt
|
return fmt
|
||||||
|
elseif fmt ~= '' then
|
||||||
|
_lua_log('ytdl-format: ignoring suspicious current format source=ytdl-format value=' .. tostring(fmt) .. ' reason=' .. tostring(suspicious_reason))
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Fallbacks: option value, or raw info if available.
|
-- Fallbacks: option value, or raw info if available.
|
||||||
local opt = trim(tostring(mp.get_property('options/ytdl-format') or ''))
|
local opt = trim(tostring(mp.get_property('options/ytdl-format') or ''))
|
||||||
if opt ~= '' then
|
suspicious_reason = M._suspicious_ytdl_format_reason(opt, url, raw)
|
||||||
|
if opt ~= '' and not suspicious_reason then
|
||||||
return opt
|
return opt
|
||||||
|
elseif opt ~= '' then
|
||||||
|
_lua_log('ytdl-format: ignoring suspicious current format source=options/ytdl-format value=' .. tostring(opt) .. ' reason=' .. tostring(suspicious_reason))
|
||||||
end
|
end
|
||||||
|
|
||||||
local raw = mp.get_property_native('ytdl-raw-info')
|
|
||||||
if type(raw) == 'table' then
|
if type(raw) == 'table' then
|
||||||
if raw.format_id and tostring(raw.format_id) ~= '' then
|
if raw.format_id and tostring(raw.format_id) ~= '' then
|
||||||
return tostring(raw.format_id)
|
local raw_format_id = tostring(raw.format_id)
|
||||||
|
suspicious_reason = M._suspicious_ytdl_format_reason(raw_format_id, url, raw)
|
||||||
|
if not suspicious_reason then
|
||||||
|
return raw_format_id
|
||||||
|
end
|
||||||
|
_lua_log('ytdl-format: ignoring suspicious current format source=ytdl-raw-info.format_id value=' .. tostring(raw_format_id) .. ' reason=' .. tostring(suspicious_reason))
|
||||||
end
|
end
|
||||||
local rf = raw.requested_formats
|
local rf = raw.requested_formats
|
||||||
if type(rf) == 'table' then
|
if type(rf) == 'table' then
|
||||||
@@ -4690,7 +4911,12 @@ local function _current_ytdl_format_string()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
if #parts >= 1 then
|
if #parts >= 1 then
|
||||||
return table.concat(parts, '+')
|
local joined = table.concat(parts, '+')
|
||||||
|
suspicious_reason = M._suspicious_ytdl_format_reason(joined, url, raw)
|
||||||
|
if not suspicious_reason then
|
||||||
|
return joined
|
||||||
|
end
|
||||||
|
_lua_log('ytdl-format: ignoring suspicious current format source=ytdl-raw-info.requested_formats value=' .. tostring(joined) .. ' reason=' .. tostring(suspicious_reason))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -4957,6 +5183,16 @@ local function _apply_ytdl_format_and_reload(url, fmt)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local suspicious_reason = M._suspicious_ytdl_format_reason(fmt, url, mp.get_property_native('ytdl-raw-info'))
|
||||||
|
if suspicious_reason then
|
||||||
|
pcall(mp.set_property, 'options/ytdl-format', '')
|
||||||
|
pcall(mp.set_property, 'file-local-options/ytdl-format', '')
|
||||||
|
pcall(mp.set_property, 'ytdl-format', '')
|
||||||
|
_lua_log('change-format: rejected suspicious format=' .. tostring(fmt) .. ' reason=' .. tostring(suspicious_reason) .. ' url=' .. tostring(url))
|
||||||
|
mp.osd_message('Invalid format selection', 3)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
local pos = mp.get_property_number('time-pos')
|
local pos = mp.get_property_number('time-pos')
|
||||||
local paused = mp.get_property_native('pause') and true or false
|
local paused = mp.get_property_native('pause') and true or false
|
||||||
local media_title = trim(tostring(mp.get_property('media-title') or ''))
|
local media_title = trim(tostring(mp.get_property('media-title') or ''))
|
||||||
|
|||||||
@@ -1831,8 +1831,7 @@ def main(argv: Optional[list[str]] = None) -> int:
|
|||||||
try:
|
try:
|
||||||
if target_client.sock is None:
|
if target_client.sock is None:
|
||||||
if use_shared_ipc_client:
|
if use_shared_ipc_client:
|
||||||
if note_ipc_unavailable is not None:
|
_note_ipc_unavailable(f"helper-command-connect:{label or '?'}")
|
||||||
note_ipc_unavailable(f"helper-command-connect:{label or '?'}")
|
|
||||||
return False
|
return False
|
||||||
if not target_client.connect():
|
if not target_client.connect():
|
||||||
_append_helper_log(
|
_append_helper_log(
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import re
|
|||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import shutil
|
import shutil
|
||||||
|
from collections import deque
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List, Literal, Optional, Sequence, Tuple
|
from typing import Any, Dict, List, Literal, Optional, Sequence, Tuple
|
||||||
|
|
||||||
@@ -1094,78 +1095,13 @@ class HydrusNetwork(Store):
|
|||||||
return token.replace("*", "").replace("?", "")
|
return token.replace("*", "").replace("?", "")
|
||||||
|
|
||||||
# Fast-path: exact URL via /add_urls/get_url_files when a full URL is provided.
|
# Fast-path: exact URL via /add_urls/get_url_files when a full URL is provided.
|
||||||
|
exact_url_attempted = False
|
||||||
try:
|
try:
|
||||||
if pattern.startswith("http://") or pattern.startswith(
|
if pattern.startswith("http://") or pattern.startswith("https://"):
|
||||||
"https://"):
|
exact_url_attempted = True
|
||||||
from API.HydrusNetwork import HydrusRequestSpec
|
metadata_list = self.lookup_url_metadata(pattern, minimal=minimal)
|
||||||
|
|
||||||
spec = HydrusRequestSpec(
|
|
||||||
method="GET",
|
|
||||||
endpoint="/add_urls/get_url_files",
|
|
||||||
query={
|
|
||||||
"url": pattern
|
|
||||||
},
|
|
||||||
)
|
|
||||||
response = client._perform_request(
|
|
||||||
spec
|
|
||||||
) # type: ignore[attr-defined]
|
|
||||||
hashes = []
|
|
||||||
file_ids = []
|
|
||||||
if isinstance(response, dict):
|
|
||||||
raw_hashes = response.get("hashes") or response.get(
|
|
||||||
"file_hashes"
|
|
||||||
)
|
|
||||||
if isinstance(raw_hashes, list):
|
|
||||||
hashes = [
|
|
||||||
str(h).strip() for h in raw_hashes
|
|
||||||
if isinstance(h, str) and str(h).strip()
|
|
||||||
]
|
|
||||||
raw_ids = response.get("file_ids")
|
|
||||||
if isinstance(raw_ids, list):
|
|
||||||
for item in raw_ids:
|
|
||||||
try:
|
|
||||||
file_ids.append(int(item))
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if file_ids:
|
|
||||||
payload = client.fetch_file_metadata(
|
|
||||||
file_ids=file_ids,
|
|
||||||
include_file_url=True,
|
|
||||||
include_service_keys_to_tags=not minimal,
|
|
||||||
include_duration=not minimal,
|
|
||||||
include_size=not minimal,
|
|
||||||
include_mime=not minimal,
|
|
||||||
)
|
|
||||||
metas = (
|
|
||||||
payload.get("metadata",
|
|
||||||
[]) if isinstance(payload,
|
|
||||||
dict) else []
|
|
||||||
)
|
|
||||||
if isinstance(metas, list):
|
|
||||||
metadata_list = [
|
|
||||||
m for m in metas if isinstance(m, dict)
|
|
||||||
]
|
|
||||||
elif hashes:
|
|
||||||
payload = client.fetch_file_metadata(
|
|
||||||
hashes=hashes,
|
|
||||||
include_file_url=True,
|
|
||||||
include_service_keys_to_tags=not minimal,
|
|
||||||
include_duration=not minimal,
|
|
||||||
include_size=not minimal,
|
|
||||||
include_mime=not minimal,
|
|
||||||
)
|
|
||||||
metas = (
|
|
||||||
payload.get("metadata",
|
|
||||||
[]) if isinstance(payload,
|
|
||||||
dict) else []
|
|
||||||
)
|
|
||||||
if isinstance(metas, list):
|
|
||||||
metadata_list = [
|
|
||||||
m for m in metas if isinstance(m, dict)
|
|
||||||
]
|
|
||||||
except Exception:
|
except Exception:
|
||||||
metadata_list = None
|
metadata_list = [] if exact_url_attempted else None
|
||||||
|
|
||||||
# Fallback: substring scan
|
# Fallback: substring scan
|
||||||
if metadata_list is None:
|
if metadata_list is None:
|
||||||
@@ -2108,6 +2044,115 @@ class HydrusNetwork(Store):
|
|||||||
debug(f"{self._log_prefix()} get_url failed: {exc}")
|
debug(f"{self._log_prefix()} get_url failed: {exc}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
def lookup_url_metadata(self, url_value: str, *, minimal: bool = False) -> List[Dict[str, Any]]:
|
||||||
|
"""Resolve an exact URL to Hydrus metadata using /add_urls/get_url_files variants."""
|
||||||
|
candidate_url = str(url_value or "").strip()
|
||||||
|
if not candidate_url:
|
||||||
|
return []
|
||||||
|
|
||||||
|
client = self._client
|
||||||
|
if client is None:
|
||||||
|
return []
|
||||||
|
|
||||||
|
try:
|
||||||
|
from API.HydrusNetwork import HydrusRequestSpec, _generate_hydrus_url_variants
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
|
||||||
|
pending: deque[str] = deque(_generate_hydrus_url_variants(candidate_url) or [candidate_url])
|
||||||
|
seen_urls: set[str] = set()
|
||||||
|
file_ids: List[int] = []
|
||||||
|
hashes: List[str] = []
|
||||||
|
seen_ids: set[int] = set()
|
||||||
|
seen_hashes: set[str] = set()
|
||||||
|
|
||||||
|
while pending:
|
||||||
|
current = str(pending.popleft() or "").strip()
|
||||||
|
if not current or current in seen_urls:
|
||||||
|
continue
|
||||||
|
seen_urls.add(current)
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = client._perform_request(
|
||||||
|
HydrusRequestSpec(
|
||||||
|
method="GET",
|
||||||
|
endpoint="/add_urls/get_url_files",
|
||||||
|
query={"url": current},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not isinstance(response, dict):
|
||||||
|
continue
|
||||||
|
|
||||||
|
raw_hashes = response.get("hashes") or response.get("file_hashes")
|
||||||
|
if isinstance(raw_hashes, list):
|
||||||
|
for item in raw_hashes:
|
||||||
|
try:
|
||||||
|
file_hash = str(item or "").strip().lower()
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
if not file_hash or file_hash in seen_hashes:
|
||||||
|
continue
|
||||||
|
seen_hashes.add(file_hash)
|
||||||
|
hashes.append(file_hash)
|
||||||
|
|
||||||
|
raw_ids = response.get("file_ids") or response.get("file_id")
|
||||||
|
id_values = raw_ids if isinstance(raw_ids, list) else [raw_ids] if raw_ids is not None else []
|
||||||
|
for item in id_values:
|
||||||
|
try:
|
||||||
|
file_id = int(item)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
continue
|
||||||
|
if file_id in seen_ids:
|
||||||
|
continue
|
||||||
|
seen_ids.add(file_id)
|
||||||
|
file_ids.append(file_id)
|
||||||
|
|
||||||
|
for key in ("normalized_url", "redirect_url", "url"):
|
||||||
|
value = response.get(key)
|
||||||
|
if isinstance(value, str):
|
||||||
|
next_url = value.strip()
|
||||||
|
if next_url and next_url not in seen_urls:
|
||||||
|
pending.append(next_url)
|
||||||
|
|
||||||
|
if not file_ids and not hashes:
|
||||||
|
return []
|
||||||
|
|
||||||
|
try:
|
||||||
|
payload = client.fetch_file_metadata(
|
||||||
|
file_ids=file_ids or None,
|
||||||
|
hashes=hashes or None,
|
||||||
|
include_file_url=True,
|
||||||
|
include_service_keys_to_tags=not minimal,
|
||||||
|
include_duration=not minimal,
|
||||||
|
include_size=not minimal,
|
||||||
|
include_mime=not minimal,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
|
||||||
|
metadata = payload.get("metadata") if isinstance(payload, dict) else None
|
||||||
|
if not isinstance(metadata, list):
|
||||||
|
return []
|
||||||
|
return [entry for entry in metadata if isinstance(entry, dict)]
|
||||||
|
|
||||||
|
def find_hashes_by_url(self, url_value: str) -> List[str]:
|
||||||
|
hashes: List[str] = []
|
||||||
|
seen: set[str] = set()
|
||||||
|
for entry in self.lookup_url_metadata(url_value, minimal=True):
|
||||||
|
raw_hash = entry.get("hash") or entry.get("hash_hex") or entry.get("file_hash")
|
||||||
|
try:
|
||||||
|
file_hash = str(raw_hash or "").strip().lower()
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
if len(file_hash) != 64 or file_hash in seen:
|
||||||
|
continue
|
||||||
|
seen.add(file_hash)
|
||||||
|
hashes.append(file_hash)
|
||||||
|
return hashes
|
||||||
|
|
||||||
def get_url_info(self, url: str, **kwargs: Any) -> dict[str, Any] | None:
|
def get_url_info(self, url: str, **kwargs: Any) -> dict[str, Any] | None:
|
||||||
"""Return Hydrus URL info for a single URL (Hydrus-only helper).
|
"""Return Hydrus URL info for a single URL (Hydrus-only helper).
|
||||||
|
|
||||||
|
|||||||
@@ -326,10 +326,8 @@ class Add_File(Cmdlet):
|
|||||||
is_storage_backend_location = False
|
is_storage_backend_location = False
|
||||||
if location:
|
if location:
|
||||||
try:
|
try:
|
||||||
# Check against the cached startup list of available backends
|
store_for_lookup = storage_registry or Store(config)
|
||||||
from cmdlet._shared import SharedArgs
|
is_storage_backend_location = Add_File._resolve_backend_by_name(store_for_lookup, str(location)) is not None
|
||||||
available_backends = SharedArgs.get_store_choices(config)
|
|
||||||
is_storage_backend_location = location in available_backends
|
|
||||||
except Exception:
|
except Exception:
|
||||||
is_storage_backend_location = False
|
is_storage_backend_location = False
|
||||||
|
|
||||||
@@ -608,8 +606,8 @@ class Add_File(Cmdlet):
|
|||||||
if location:
|
if location:
|
||||||
try:
|
try:
|
||||||
store = storage_registry or Store(config)
|
store = storage_registry or Store(config)
|
||||||
backends = store.list_backends()
|
resolved_backend = Add_File._resolve_backend_by_name(store, str(location))
|
||||||
if location in backends:
|
if resolved_backend is not None:
|
||||||
code = self._handle_storage_backend(
|
code = self._handle_storage_backend(
|
||||||
item,
|
item,
|
||||||
media_path,
|
media_path,
|
||||||
|
|||||||
@@ -2685,6 +2685,14 @@ class Download_File(Cmdlet):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if HydrusNetwork is not None and isinstance(backend, HydrusNetwork):
|
||||||
|
hashes = backend.find_hashes_by_url(canonical_url) or []
|
||||||
|
for existing_hash in hashes:
|
||||||
|
normalized = sh.normalize_hash(existing_hash)
|
||||||
|
if normalized:
|
||||||
|
return normalized
|
||||||
|
continue
|
||||||
|
|
||||||
hits = backend.search(f"url:{canonical_url}", limit=5) or []
|
hits = backend.search(f"url:{canonical_url}", limit=5) or []
|
||||||
except Exception:
|
except Exception:
|
||||||
hits = []
|
hits = []
|
||||||
|
|||||||
@@ -1000,22 +1000,15 @@ def _build_hydrus_header(config: Dict[str, Any]) -> Optional[str]:
|
|||||||
def _build_ytdl_options(config: Optional[Dict[str,
|
def _build_ytdl_options(config: Optional[Dict[str,
|
||||||
Any]],
|
Any]],
|
||||||
hydrus_header: Optional[str]) -> Optional[str]:
|
hydrus_header: Optional[str]) -> Optional[str]:
|
||||||
"""Compose ytdl-raw-options string including cookies and optional Hydrus header."""
|
"""Compose mpv ytdl-raw-options for playback.
|
||||||
|
|
||||||
|
Keep this limited to options that are truly required for MPV playback.
|
||||||
|
In particular, do not pass the repo cookiefile via `cookies=...` here:
|
||||||
|
mpv's ytdl-hook can fail to resolve some YouTube/Shorts URLs when a global
|
||||||
|
cookiefile is forced, even though the same URLs work with MPV defaults.
|
||||||
|
"""
|
||||||
opts: List[str] = []
|
opts: List[str] = []
|
||||||
cookies_path = None
|
|
||||||
try:
|
|
||||||
from tool.ytdlp import YtDlpTool
|
|
||||||
|
|
||||||
cookiefile = YtDlpTool(config or {}).resolve_cookiefile()
|
|
||||||
if cookiefile is not None:
|
|
||||||
cookies_path = str(cookiefile)
|
|
||||||
except Exception:
|
|
||||||
cookies_path = None
|
|
||||||
|
|
||||||
if cookies_path:
|
|
||||||
opts.append(f"cookies={cookies_path.replace('\\', '/')}")
|
|
||||||
# Do not force chrome cookies if none are found; let yt-dlp use its defaults or fail gracefully.
|
|
||||||
|
|
||||||
if hydrus_header:
|
if hydrus_header:
|
||||||
opts.append(f"add-header={hydrus_header}")
|
opts.append(f"add-header={hydrus_header}")
|
||||||
return ",".join(opts) if opts else None
|
return ",".join(opts) if opts else None
|
||||||
@@ -2584,19 +2577,10 @@ def _start_mpv(
|
|||||||
|
|
||||||
_schedule_notes_prefetch(items[:1], config)
|
_schedule_notes_prefetch(items[:1], config)
|
||||||
|
|
||||||
cookies_path = None
|
if ytdl_opts:
|
||||||
try:
|
debug(f"Starting MPV with ytdl raw options: {ytdl_opts}")
|
||||||
from tool.ytdlp import YtDlpTool
|
|
||||||
|
|
||||||
cookiefile = YtDlpTool(config or {}).resolve_cookiefile()
|
|
||||||
if cookiefile is not None:
|
|
||||||
cookies_path = str(cookiefile)
|
|
||||||
except Exception:
|
|
||||||
cookies_path = None
|
|
||||||
if cookies_path:
|
|
||||||
debug(f"Starting MPV with cookies file: {cookies_path.replace('\\', '/')}")
|
|
||||||
else:
|
else:
|
||||||
debug("Starting MPV with browser cookies: chrome")
|
debug("Starting MPV with default yt-dlp raw options")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
extra_args: List[str] = [
|
extra_args: List[str] = [
|
||||||
|
|||||||
Reference in New Issue
Block a user