55import traceback
66import asyncio
77import json
8+ import base64 # 追加: Base64エンコード用
9+ import mimetypes # 追加: MIMEタイプ判定用
810from contextlib import asynccontextmanager
911from fastapi import FastAPI , HTTPException , BackgroundTasks , UploadFile , File , Form , Request # Request を追加
10- from fastapi .responses import FileResponse , StreamingResponse
12+ from fastapi .responses import FileResponse , StreamingResponse # JSONResponse を削除
1113from fastapi .middleware .cors import CORSMiddleware
1214from pydantic import BaseModel , Field
1315from PIL import Image
@@ -142,6 +144,11 @@ class LoraListResponse(BaseModel):
142144 loras : List [str ]
143145
144146
147+ class ResultResponse (BaseModel ):
148+ video_url : str
149+ thumbnail_base64 : Optional [str ] = None
150+
151+
145152# --- Background Worker ---
146153def background_worker_task ():
147154 global worker_running , currently_processing_job_id
@@ -353,7 +360,7 @@ async def event_generator():
353360 # Send final status event if it hasn't been sent already
354361 if current_data_json != last_data_sent :
355362 yield f"event: progress\n data: { current_data_json } \n \n "
356- last_data_sent = current_data_json # Ensure last_data_sent is updated even for the final message
363+ last_data_sent = current_data_json # Ensure last_data_sent is updated even for the final message
357364 print (f"Sent final progress update for job { job_id } : Status { job .status } " )
358365
359366 # Send a dedicated 'status' event to signal completion/failure/cancellation
@@ -377,22 +384,65 @@ async def event_generator():
377384 return StreamingResponse (event_generator (), media_type = "text/event-stream" )
378385
379386
380- @app .get ("/result/{job_id}" )
381- async def get_job_result (job_id : str ):
382- # Implementation needed: Check job status, return video file if completed
387+ @app .get ("/result/{job_id}" , response_model = ResultResponse )
388+ async def get_job_result (job_id : str , request : Request ): # requestを追加してURLを構築
389+ """
390+ Returns the download URL for the completed video and the Base64 encoded thumbnail.
391+ """
383392 job = queue_manager .get_job_by_id (job_id )
384393 output_file = os .path .join (settings .OUTPUTS_DIR , f"{ job_id } .mp4" )
394+ is_completed = (job and job .status == "completed" ) or (not job and os .path .exists (output_file ))
385395
386- if job and job .status == "completed" and os .path .exists (output_file ):
387- return FileResponse (output_file , media_type = "video/mp4" , filename = f"{ job_id } .mp4" )
388- elif not job and os .path .exists (output_file ):
389- # If job not in queue but file exists, assume completed
390- print (f"Job { job_id } not in queue, but result file found. Serving file." )
396+ if not is_completed :
397+ if job :
398+ raise HTTPException (status_code = 404 , detail = f"Job '{ job_id } ' is not completed yet (status: { job .status } )." )
399+ else :
400+ raise HTTPException (status_code = 404 , detail = f"Job '{ job_id } ' not found or result file does not exist." )
401+
402+ # --- サムネイル処理 ---
403+ thumbnail_base64 = None
404+ if job and job .thumbnail and os .path .exists (job .thumbnail ):
405+ try :
406+ with open (job .thumbnail , "rb" ) as f :
407+ thumbnail_data = f .read ()
408+ thumbnail_base64_data = base64 .b64encode (thumbnail_data ).decode ("utf-8" )
409+ # MIMEタイプを推測 (例: image/jpeg)
410+ mime_type , _ = mimetypes .guess_type (job .thumbnail )
411+ if mime_type :
412+ thumbnail_base64 = f"data:{ mime_type } ;base64,{ thumbnail_base64_data } "
413+ else :
414+ # MIMEタイプが不明な場合はデフォルトを使用(またはエラー処理)
415+ thumbnail_base64 = f"data:image/jpeg;base64,{ thumbnail_base64_data } " # デフォルトをJPEGに
416+ print (f"Job { job_id } : Encoded thumbnail from { job .thumbnail } " )
417+ except Exception as e :
418+ print (f"Job { job_id } : Error reading or encoding thumbnail { job .thumbnail } : { e } " )
419+ # サムネイルの読み込み/エンコードに失敗してもエラーにはしない
420+
421+ # --- 動画URL構築 ---
422+ # request.url を使用して絶対URLまたは相対URLを構築
423+ video_url = str (request .url_for ('download_video' , job_id = job_id ))
424+
425+ return ResultResponse (
426+ video_url = video_url ,
427+ thumbnail_base64 = thumbnail_base64
428+ )
429+
430+
431+ # --- Download Endpoints ---
432+
433+ @app .get ("/download/video/{job_id}" )
434+ async def download_video (job_id : str ):
435+ """Downloads the generated video file."""
436+ output_file = os .path .join (settings .OUTPUTS_DIR , f"{ job_id } .mp4" )
437+ if os .path .exists (output_file ):
391438 return FileResponse (output_file , media_type = "video/mp4" , filename = f"{ job_id } .mp4" )
392- elif job :
393- raise HTTPException (status_code = 404 , detail = f"Job '{ job_id } ' is not completed yet (status: { job .status } )." )
394439 else :
395- raise HTTPException (status_code = 404 , detail = f"Job '{ job_id } ' not found or result file does not exist." )
440+ # Optionally check job status again here if needed
441+ job = queue_manager .get_job_by_id (job_id )
442+ if job :
443+ raise HTTPException (status_code = 404 , detail = f"Video file for job '{ job_id } ' not found, status is '{ job .status } '." )
444+ else :
445+ raise HTTPException (status_code = 404 , detail = f"Video file for job '{ job_id } ' not found." )
396446
397447
398448@app .get ("/input_image/{job_id}" )
0 commit comments