1111from sqlalchemy .ext .asyncio import AsyncSession
1212from PIL import Image
1313import magic
14+ from pillow_heif import register_heif_opener
1415
1516from app .config import settings
1617from app .models .file import File
@@ -22,6 +23,7 @@ def __init__(self, db: AsyncSession):
2223 self .db = db
2324 self .upload_dir = Path (settings .UPLOAD_DIR )
2425 self .upload_dir .mkdir (parents = True , exist_ok = True )
26+ register_heif_opener ()
2527
2628 def _generate_share_token (self ) -> str :
2729 """生成唯一的分享 Token"""
@@ -109,6 +111,30 @@ async def file_iterator():
109111 while chunk := await f .read (8192 ):
110112 yield chunk
111113
114+ mime = (file_record .mime_type or "application/octet-stream" ).lower ()
115+ # 浏览器对 HEIC/HEIF 支持很差:inline 时转码为 JPEG
116+ if inline and mime in ("image/heic" , "image/heif" ):
117+ async with aiofiles .open (file_path , "rb" ) as f :
118+ raw = await f .read ()
119+ try :
120+ img = Image .open (BytesIO (raw ))
121+ if img .mode in ("RGBA" , "P" ):
122+ img = img .convert ("RGB" )
123+ buffer = BytesIO ()
124+ img .save (buffer , format = "JPEG" , quality = 92 , optimize = True )
125+ out = buffer .getvalue ()
126+ return Response (
127+ content = out ,
128+ media_type = "image/jpeg" ,
129+ headers = {
130+ "Content-Disposition" : f'inline; filename="{ Path (file_record .filename ).stem } .jpg"' ,
131+ "Content-Length" : str (len (out )),
132+ },
133+ )
134+ except Exception :
135+ # 转码失败则按原文件返回
136+ pass
137+
112138 disposition = "inline" if inline else "attachment"
113139 return StreamingResponse (
114140 file_iterator (),
0 commit comments