feat: add video transcoding URL retrieval and enhance thumbnail generation logic

This commit is contained in:
shiyu
2025-12-15 22:01:07 +08:00
parent 78de3b46be
commit aaae37e7cb
2 changed files with 74 additions and 15 deletions

View File

@@ -290,6 +290,11 @@ class QuarkAdapter:
return None
return None
async def get_video_transcoding_url(self, fid: str) -> Optional[str]:
if not self.use_transcoding_address:
return None
return await self._get_transcoding_url(fid)
def _is_video_name(self, name: str) -> bool:
mime, _ = mimetypes.guess_type(name)
return bool(mime and mime.startswith("video/"))
@@ -316,6 +321,29 @@ class QuarkAdapter:
resp.raise_for_status()
return resp.content
async def read_file_range(self, root: str, rel: str, start: int, end: Optional[int] = None) -> bytes:
if not rel or rel.endswith("/"):
raise IsADirectoryError("Path is a directory")
parent = rel.rsplit("/", 1)[0] if "/" in rel else ""
name = rel.rsplit("/", 1)[-1]
base_fid = root or self.root_fid
parent_fid = await self._resolve_dir_fid_from(base_fid, parent)
it = await self._find_child(parent_fid, name)
if not it or it["is_dir"]:
raise FileNotFoundError(rel)
url = await self._get_download_url(it["fid"])
headers = dict(self._download_headers())
headers["Range"] = f"bytes={start}-" if end is None else f"bytes={start}-{end}"
async with httpx.AsyncClient(timeout=self._timeout, follow_redirects=True) as client:
resp = await client.get(url, headers=headers)
if resp.status_code == 404:
raise FileNotFoundError(rel)
if resp.status_code == 416:
raise HTTPException(416, detail="Requested Range Not Satisfiable")
resp.raise_for_status()
return resp.content
async def stream_file(self, root: str, rel: str, range_header: str | None):
if not rel or rel.endswith("/"):
raise IsADirectoryError("Path is a directory")

View File

@@ -17,7 +17,7 @@ VIDEO_TAIL_LIMIT = 2 * 1024 * 1024 # 2MB
VIDEO_TAIL_FALLBACK_LIMIT = 4 * 1024 * 1024 # 4MB
VIDEO_HEAD_LIMIT = 2 * 1024 * 1024 # 2MB
VIDEO_HEAD_FALLBACK_LIMIT = 4 * 1024 * 1024 # 4MB
VIDEO_THUMB_SEEK_SECONDS = (20, 10, 5, 3, 1, 0)
VIDEO_THUMB_SEEK_SECONDS = (15, 10, 5, 3, 1, 0)
VIDEO_BLACK_FRAME_MEAN_THRESHOLD = 12.0
CACHE_ROOT = Path('data/.thumb_cache')
@@ -223,10 +223,14 @@ async def _run_ffmpeg_extract_frame(src_path: str, dst_path: str, *, seek_second
"-y",
"-hide_banner",
"-loglevel", "error",
"-i", src_path,
]
if seek_seconds is not None:
cmd += ["-ss", str(seek_seconds)]
is_http_input = src_path.startswith(("http://", "https://"))
if is_http_input and seek_seconds is not None:
cmd += ["-ss", str(seek_seconds), "-i", src_path]
else:
cmd += ["-i", src_path]
if seek_seconds is not None:
cmd += ["-ss", str(seek_seconds)]
cmd += [
"-frames:v", "1",
dst_path,
@@ -350,6 +354,28 @@ async def get_or_create_thumb(adapter, adapter_id: int, root: str, rel: str, w:
if not thumb_bytes:
if is_video:
async def _maybe_transcoding_thumb() -> Tuple[bytes, str] | None:
fid = (stat or {}).get("fid") if isinstance(stat, dict) else None
get_url = getattr(adapter, "get_video_transcoding_url", None)
if not fid or not callable(get_url):
return None
try:
url = await get_url(str(fid))
except Exception as e:
print(f"Video transcoding url fetch failed: {e}")
return None
if not url:
return None
try:
return await _generate_video_thumb_from_src_path(url, w, h, fit)
except Exception as e:
print(f"Video transcoding thumbnail generation failed: {e}")
return None
def _is_hevc_decoder_missing(exc: Exception) -> bool:
msg = str(exc).lower()
return ("no decoder found" in msg) and ("hevc" in msg or "h265" in msg)
async def _read_head(limit: int) -> bytes:
try:
return await _read_video_head(adapter, root, rel, size, limit=limit)
@@ -377,17 +403,22 @@ async def get_or_create_thumb(adapter, adapter_id: int, root: str, rel: str, w:
thumb_bytes, mime = await _generate_video_thumb_from_segments(
head_bytes, tail_bytes, tail_offset, rel, w, h, fit
)
except Exception:
try:
tail_bytes, tail_offset = await _read_tail(VIDEO_TAIL_FALLBACK_LIMIT)
thumb_bytes, mime = await _generate_video_thumb_from_segments(
head_bytes, tail_bytes, tail_offset, rel, w, h, fit
)
except HTTPException:
raise
except Exception as e2:
print(f"Video thumbnail generation failed: {e2}")
raise HTTPException(500, detail=f"Video thumbnail generation failed: {e2}")
except Exception as e1:
if _is_hevc_decoder_missing(e1):
got = await _maybe_transcoding_thumb()
if got is not None:
thumb_bytes, mime = got
if not thumb_bytes:
try:
tail_bytes, tail_offset = await _read_tail(VIDEO_TAIL_FALLBACK_LIMIT)
thumb_bytes, mime = await _generate_video_thumb_from_segments(
head_bytes, tail_bytes, tail_offset, rel, w, h, fit
)
except HTTPException:
raise
except Exception as e2:
print(f"Video thumbnail generation failed: {e2}")
raise HTTPException(500, detail=f"Video thumbnail generation failed: {e2}")
if thumb_bytes and _is_black_image_bytes(thumb_bytes):
try: