mirror of
https://github.com/DrizzleTime/Foxel.git
synced 2026-09-06 16:17:06 +08:00
fix: handle native video thumbnail availability in get_or_create_thumb function
This commit is contained in:
@@ -1,5 +1,4 @@
|
|||||||
from typing import List, Dict, Tuple, AsyncIterator
|
from typing import List, Dict, Tuple, AsyncIterator, Optional
|
||||||
import asyncio
|
|
||||||
import base64
|
import base64
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
@@ -11,16 +10,6 @@ from telethon.sessions import StringSession
|
|||||||
from telethon.tl import types
|
from telethon.tl import types
|
||||||
import socks
|
import socks
|
||||||
|
|
||||||
_SESSION_LOCKS: Dict[str, asyncio.Lock] = {}
|
|
||||||
|
|
||||||
|
|
||||||
def _get_session_lock(session_string: str) -> asyncio.Lock:
|
|
||||||
lock = _SESSION_LOCKS.get(session_string)
|
|
||||||
if lock is None:
|
|
||||||
lock = asyncio.Lock()
|
|
||||||
_SESSION_LOCKS[session_string] = lock
|
|
||||||
return lock
|
|
||||||
|
|
||||||
|
|
||||||
class _NamedFile:
|
class _NamedFile:
|
||||||
def __init__(self, file_obj, name: str):
|
def __init__(self, file_obj, name: str):
|
||||||
@@ -61,6 +50,7 @@ CONFIG_SCHEMA = [
|
|||||||
|
|
||||||
class TelegramAdapter:
|
class TelegramAdapter:
|
||||||
"""Telegram 存储适配器 (使用用户 Session)"""
|
"""Telegram 存储适配器 (使用用户 Session)"""
|
||||||
|
native_video_thumbnail_only = True
|
||||||
|
|
||||||
def __init__(self, record: StorageAdapter):
|
def __init__(self, record: StorageAdapter):
|
||||||
self.record = record
|
self.record = record
|
||||||
@@ -194,6 +184,14 @@ class TelegramAdapter:
|
|||||||
"""创建一个新的 TelegramClient 实例"""
|
"""创建一个新的 TelegramClient 实例"""
|
||||||
return TelegramClient(self._build_session(), self.api_id, self.api_hash, proxy=self.proxy)
|
return TelegramClient(self._build_session(), self.api_id, self.api_hash, proxy=self.proxy)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _parse_message_id(rel: str) -> int:
|
||||||
|
try:
|
||||||
|
message_id_str, _ = rel.split('_', 1)
|
||||||
|
return int(message_id_str)
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
raise FileNotFoundError(f"无效的文件路径格式: {rel}")
|
||||||
|
|
||||||
def get_effective_root(self, sub_path: str | None) -> str:
|
def get_effective_root(self, sub_path: str | None) -> str:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@@ -274,11 +272,7 @@ class TelegramAdapter:
|
|||||||
return page_entries, total_count
|
return page_entries, total_count
|
||||||
|
|
||||||
async def read_file(self, root: str, rel: str) -> bytes:
|
async def read_file(self, root: str, rel: str) -> bytes:
|
||||||
try:
|
message_id = self._parse_message_id(rel)
|
||||||
message_id_str, _ = rel.split('_', 1)
|
|
||||||
message_id = int(message_id_str)
|
|
||||||
except (ValueError, IndexError):
|
|
||||||
raise FileNotFoundError(f"无效的文件路径格式: {rel}")
|
|
||||||
|
|
||||||
client = self._get_client()
|
client = self._get_client()
|
||||||
try:
|
try:
|
||||||
@@ -293,6 +287,50 @@ class TelegramAdapter:
|
|||||||
if client.is_connected():
|
if client.is_connected():
|
||||||
await client.disconnect()
|
await client.disconnect()
|
||||||
|
|
||||||
|
async def read_file_range(self, root: str, rel: str, start: int, end: Optional[int] = None) -> bytes:
|
||||||
|
from fastapi import HTTPException
|
||||||
|
|
||||||
|
message_id = self._parse_message_id(rel)
|
||||||
|
client = self._get_client()
|
||||||
|
try:
|
||||||
|
await client.connect()
|
||||||
|
message = await client.get_messages(self.chat_id, ids=message_id)
|
||||||
|
if not message:
|
||||||
|
raise FileNotFoundError(f"在频道 {self.chat_id} 中未找到消息ID为 {message_id} 的文件")
|
||||||
|
|
||||||
|
media = message.document or message.video or message.photo
|
||||||
|
if not media:
|
||||||
|
raise FileNotFoundError(f"在频道 {self.chat_id} 中未找到消息ID为 {message_id} 的文件")
|
||||||
|
|
||||||
|
file_meta = message.file
|
||||||
|
file_size = file_meta.size if file_meta and file_meta.size is not None else getattr(media, "size", 0) or 0
|
||||||
|
if file_size > 0:
|
||||||
|
if start >= file_size:
|
||||||
|
raise HTTPException(status_code=416, detail="Requested Range Not Satisfiable")
|
||||||
|
if end is None or end >= file_size:
|
||||||
|
end = file_size - 1
|
||||||
|
elif end is None:
|
||||||
|
end = start
|
||||||
|
|
||||||
|
if end < start:
|
||||||
|
raise HTTPException(status_code=416, detail="Requested Range Not Satisfiable")
|
||||||
|
|
||||||
|
limit = end - start + 1
|
||||||
|
data = bytearray()
|
||||||
|
async for chunk in client.iter_download(media, offset=start):
|
||||||
|
if not chunk:
|
||||||
|
continue
|
||||||
|
need = limit - len(data)
|
||||||
|
if need <= 0:
|
||||||
|
break
|
||||||
|
data.extend(chunk[:need])
|
||||||
|
if len(data) >= limit:
|
||||||
|
break
|
||||||
|
return bytes(data)
|
||||||
|
finally:
|
||||||
|
if client.is_connected():
|
||||||
|
await client.disconnect()
|
||||||
|
|
||||||
async def write_file(self, root: str, rel: str, data: bytes):
|
async def write_file(self, root: str, rel: str, data: bytes):
|
||||||
"""将字节数据作为文件上传"""
|
"""将字节数据作为文件上传"""
|
||||||
client = self._get_client()
|
client = self._get_client()
|
||||||
@@ -452,20 +490,19 @@ class TelegramAdapter:
|
|||||||
from fastapi import HTTPException
|
from fastapi import HTTPException
|
||||||
|
|
||||||
try:
|
try:
|
||||||
message_id_str, _ = rel.split('_', 1)
|
message_id = self._parse_message_id(rel)
|
||||||
message_id = int(message_id_str)
|
except FileNotFoundError:
|
||||||
except (ValueError, IndexError):
|
|
||||||
raise HTTPException(status_code=400, detail=f"无效的文件路径格式: {rel}")
|
raise HTTPException(status_code=400, detail=f"无效的文件路径格式: {rel}")
|
||||||
|
|
||||||
client = self._get_client()
|
client = self._get_client()
|
||||||
lock = _get_session_lock(self.session_string)
|
|
||||||
await lock.acquire()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await client.connect()
|
await client.connect()
|
||||||
message = await client.get_messages(self.chat_id, ids=message_id)
|
message = await client.get_messages(self.chat_id, ids=message_id)
|
||||||
|
if not message:
|
||||||
|
raise FileNotFoundError(f"在频道 {self.chat_id} 中未找到消息ID为 {message_id} 的文件")
|
||||||
media = message.document or message.video or message.photo
|
media = message.document or message.video or message.photo
|
||||||
if not message or not media:
|
if not media:
|
||||||
raise FileNotFoundError(f"在频道 {self.chat_id} 中未找到消息ID为 {message_id} 的文件")
|
raise FileNotFoundError(f"在频道 {self.chat_id} 中未找到消息ID为 {message_id} 的文件")
|
||||||
|
|
||||||
file_meta = message.file
|
file_meta = message.file
|
||||||
@@ -499,6 +536,12 @@ class TelegramAdapter:
|
|||||||
"Content-Type": mime_type,
|
"Content-Type": mime_type,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if file_size <= 0:
|
||||||
|
headers["Content-Length"] = "0"
|
||||||
|
if client.is_connected():
|
||||||
|
await client.disconnect()
|
||||||
|
return StreamingResponse(iter(()), status_code=status, headers=headers)
|
||||||
|
|
||||||
if range_header:
|
if range_header:
|
||||||
try:
|
try:
|
||||||
range_val = range_header.strip().partition("=")[2]
|
range_val = range_header.strip().partition("=")[2]
|
||||||
@@ -512,6 +555,8 @@ class TelegramAdapter:
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
raise HTTPException(status_code=400, detail="Invalid Range header")
|
raise HTTPException(status_code=400, detail="Invalid Range header")
|
||||||
|
|
||||||
|
headers["Content-Length"] = str(end - start + 1)
|
||||||
|
|
||||||
async def iterator():
|
async def iterator():
|
||||||
try:
|
try:
|
||||||
limit = end - start + 1
|
limit = end - start + 1
|
||||||
@@ -526,28 +571,22 @@ class TelegramAdapter:
|
|||||||
if downloaded >= limit:
|
if downloaded >= limit:
|
||||||
break
|
break
|
||||||
finally:
|
finally:
|
||||||
try:
|
if client.is_connected():
|
||||||
if client.is_connected():
|
await client.disconnect()
|
||||||
await client.disconnect()
|
|
||||||
finally:
|
|
||||||
lock.release()
|
|
||||||
|
|
||||||
return StreamingResponse(iterator(), status_code=status, headers=headers)
|
return StreamingResponse(iterator(), status_code=status, headers=headers)
|
||||||
|
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
if client.is_connected():
|
if client.is_connected():
|
||||||
await client.disconnect()
|
await client.disconnect()
|
||||||
lock.release()
|
|
||||||
raise
|
raise
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
if client.is_connected():
|
if client.is_connected():
|
||||||
await client.disconnect()
|
await client.disconnect()
|
||||||
lock.release()
|
|
||||||
raise HTTPException(status_code=404, detail=str(e))
|
raise HTTPException(status_code=404, detail=str(e))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if client.is_connected():
|
if client.is_connected():
|
||||||
await client.disconnect()
|
await client.disconnect()
|
||||||
lock.release()
|
|
||||||
raise HTTPException(status_code=500, detail=f"Streaming failed: {str(e)}")
|
raise HTTPException(status_code=500, detail=f"Streaming failed: {str(e)}")
|
||||||
|
|
||||||
async def stat_file(self, root: str, rel: str):
|
async def stat_file(self, root: str, rel: str):
|
||||||
|
|||||||
@@ -415,6 +415,9 @@ async def get_or_create_thumb(adapter, adapter_id: int, root: str, rel: str, w:
|
|||||||
f"Failed to convert native thumbnail to WebP: {e}, falling back.")
|
f"Failed to convert native thumbnail to WebP: {e}, falling back.")
|
||||||
thumb_bytes, mime = None, None
|
thumb_bytes, mime = None, None
|
||||||
|
|
||||||
|
if is_video and getattr(adapter, "native_video_thumbnail_only", False) and not thumb_bytes:
|
||||||
|
raise HTTPException(404, detail="Native video thumbnail unavailable")
|
||||||
|
|
||||||
if not thumb_bytes:
|
if not thumb_bytes:
|
||||||
if is_video:
|
if is_video:
|
||||||
async def _maybe_transcoding_thumb() -> Tuple[bytes, str] | None:
|
async def _maybe_transcoding_thumb() -> Tuple[bytes, str] | None:
|
||||||
|
|||||||
Reference in New Issue
Block a user