|
|
@@ -1,14 +1,15 @@
|
|
|
-from fastapi import FastAPI, File, UploadFile, HTTPException
|
|
|
-from fastapi.responses import RedirectResponse, FileResponse
|
|
|
-from pydantic import BaseModel
|
|
|
-from typing import Optional
|
|
|
-import subprocess
|
|
|
+import io
|
|
|
import os
|
|
|
+import subprocess
|
|
|
import time
|
|
|
-from PIL import Image # Pillowライブラリを追加
|
|
|
-import io
|
|
|
+from typing import Optional
|
|
|
+
|
|
|
+from fastapi import FastAPI, File, HTTPException, UploadFile
|
|
|
+from fastapi.responses import FileResponse, RedirectResponse
|
|
|
+from PIL import Image # Pillow library for image processing
|
|
|
+from pydantic import BaseModel
|
|
|
|
|
|
-# レスポンスモデル
|
|
|
+# Response models
|
|
|
class ConvertResponse(BaseModel):
|
|
|
pdf_path: str
|
|
|
converted: bool
|
|
|
@@ -22,10 +23,10 @@ class HealthResponse(BaseModel):
|
|
|
version: str
|
|
|
uptime: float
|
|
|
|
|
|
-# FastAPI アプリケーション
|
|
|
+# FastAPI Application
|
|
|
app = FastAPI(
|
|
|
- title="LibreOffice ドキュメント変換サービス",
|
|
|
- description="Word/PPT/Excel/PDF を PDF に変換し、混合内容のドキュメント処理をサポートします",
|
|
|
+ title="LibreOffice Document Conversion Service",
|
|
|
+ description="Convert Word/PPT/Excel/PDF to PDF and support mixed content document processing",
|
|
|
version="1.0.0",
|
|
|
docs_url="/docs",
|
|
|
redoc_url="/redoc"
|
|
|
@@ -35,12 +36,12 @@ start_time = time.time()
|
|
|
|
|
|
@app.get("/", include_in_schema=False)
|
|
|
async def root():
|
|
|
- """ドキュメントページへリダイレクト"""
|
|
|
+ """Redirect to documentation page"""
|
|
|
return RedirectResponse(url="/docs")
|
|
|
|
|
|
@app.get("/health", response_model=HealthResponse)
|
|
|
async def health():
|
|
|
- """ヘルスチェックインターフェース"""
|
|
|
+ """Health check interface"""
|
|
|
return HealthResponse(
|
|
|
status="healthy",
|
|
|
service="libreoffice-converter",
|
|
|
@@ -51,11 +52,11 @@ async def health():
|
|
|
@app.post("/convert")
|
|
|
async def convert(file: UploadFile = File(...)):
|
|
|
"""
|
|
|
- ドキュメント変換インターフェース
|
|
|
- 戻り値: PDF ファイルストリーム
|
|
|
+ Document conversion interface
|
|
|
+ Returns: PDF file stream
|
|
|
"""
|
|
|
try:
|
|
|
- # ファイル形式の検証
|
|
|
+ # File format validation
|
|
|
allowed_extensions = [
|
|
|
'.pdf', '.doc', '.docx', '.ppt', '.pptx', '.xls', '.xlsx',
|
|
|
'.md', '.txt', '.rtf', '.odt', '.ods', '.odp',
|
|
|
@@ -66,25 +67,25 @@ async def convert(file: UploadFile = File(...)):
|
|
|
if file_ext not in allowed_extensions:
|
|
|
raise HTTPException(
|
|
|
status_code=400,
|
|
|
- detail=f"サポートされていないファイル形式です: {file_ext}。サポート対象: {', '.join(allowed_extensions)}"
|
|
|
+ detail=f"Unsupported file format: {file_ext}. Supported formats: {', '.join(allowed_extensions)}"
|
|
|
)
|
|
|
|
|
|
- # uploads ディレクトリの存在を確認
|
|
|
+ # Check uploads directory existence
|
|
|
upload_dir = "/app/uploads" if os.path.exists("/app/uploads") else "./uploads"
|
|
|
os.makedirs(upload_dir, exist_ok=True)
|
|
|
|
|
|
- # アップロードファイルの保存
|
|
|
+ # Save uploaded file
|
|
|
filepath = os.path.join(upload_dir, file.filename)
|
|
|
with open(filepath, "wb") as buffer:
|
|
|
content = await file.read()
|
|
|
buffer.write(content)
|
|
|
|
|
|
- # PDF の場合はそのまま返却
|
|
|
+ # For PDF files, return directly without conversion
|
|
|
if file_ext == '.pdf':
|
|
|
return FileResponse(filepath, filename=file.filename, media_type='application/pdf')
|
|
|
|
|
|
if file_ext == '.md':
|
|
|
- # Node.js スクリプトを使用して Markdown を PDF にレンダリング
|
|
|
+ # Use Node.js script to render Markdown to PDF
|
|
|
expected_pdf = filepath.rsplit('.', 1)[0] + '.pdf'
|
|
|
cmd = [
|
|
|
'node',
|
|
|
@@ -93,14 +94,14 @@ async def convert(file: UploadFile = File(...)):
|
|
|
expected_pdf
|
|
|
]
|
|
|
elif file_ext in ['.jpg', '.jpeg', '.png', '.bmp', '.gif', '.tiff', '.webp']:
|
|
|
- # 画像ファイルの場合は Pillow を使用して PDF に変換
|
|
|
+ # For image files, use Pillow to convert to PDF
|
|
|
expected_pdf = filepath.rsplit('.', 1)[0] + '.pdf'
|
|
|
|
|
|
- # 画像を開いてPDFとして保存
|
|
|
+ # Open image and save as PDF
|
|
|
with Image.open(filepath) as img:
|
|
|
- # RGBAモードの場合はRGBに変換(透明度がある画像対応)
|
|
|
+ # Convert RGBA mode to RGB (support for transparent images)
|
|
|
if img.mode in ('RGBA', 'LA', 'P'):
|
|
|
- # 白い背景に変換
|
|
|
+ # Convert to white background
|
|
|
background = Image.new('RGB', img.size, (255, 255, 255))
|
|
|
if img.mode == 'P':
|
|
|
img = img.convert('RGBA')
|
|
|
@@ -109,21 +110,21 @@ async def convert(file: UploadFile = File(...)):
|
|
|
elif img.mode != 'RGB':
|
|
|
img = img.convert('RGB')
|
|
|
|
|
|
- # PDFとして保存
|
|
|
+ # Save as PDF
|
|
|
img.save(expected_pdf, 'PDF', resolution=100.0, save_all=False)
|
|
|
|
|
|
- # PDF生成が完了したことを確認
|
|
|
+ # Verify PDF generation completed
|
|
|
if not os.path.exists(expected_pdf):
|
|
|
raise HTTPException(
|
|
|
status_code=500,
|
|
|
- detail="画像からPDFへの変換は成功しましたが、出力ファイルが見つかりません"
|
|
|
+ detail="Image to PDF conversion succeeded but output file not found"
|
|
|
)
|
|
|
|
|
|
- # 画像変換完了、PDFファイルを返却
|
|
|
+ # Image conversion completed, return PDF file
|
|
|
filename_base = os.path.splitext(file.filename)[0]
|
|
|
return FileResponse(expected_pdf, filename=f"{filename_base}.pdf", media_type='application/pdf')
|
|
|
else:
|
|
|
- # LibreOffice による変換
|
|
|
+ # Conversion using LibreOffice
|
|
|
cmd = [
|
|
|
'soffice',
|
|
|
'--headless',
|
|
|
@@ -136,7 +137,7 @@ async def convert(file: UploadFile = File(...)):
|
|
|
cmd,
|
|
|
capture_output=True,
|
|
|
text=True,
|
|
|
- timeout=600, # 複雑なMarkdown変換をサポートするために10分に延長
|
|
|
+ timeout=600, # Extended to 10 minutes to support complex Markdown conversion
|
|
|
)
|
|
|
|
|
|
# Combine stdout and stderr for error reporting since capture_output uses PIPE
|
|
|
@@ -144,7 +145,7 @@ async def convert(file: UploadFile = File(...)):
|
|
|
if result.stderr:
|
|
|
combined_output += "\n" + result.stderr
|
|
|
|
|
|
- # Node.jsスクリプトの実際の出力を表示して、デバッグ
|
|
|
+ # Display Node.js script output for debugging
|
|
|
print(f"Node.js script output: {combined_output}")
|
|
|
|
|
|
if result.returncode != 0:
|
|
|
@@ -158,15 +159,15 @@ async def convert(file: UploadFile = File(...)):
|
|
|
print(f"Subprocess output: {combined_output}")
|
|
|
raise HTTPException(
|
|
|
status_code=500,
|
|
|
- detail=f"変換に失敗しました: {combined_output}"
|
|
|
+ detail=f"Conversion failed: {combined_output}"
|
|
|
)
|
|
|
|
|
|
- # 出力ファイルの確認
|
|
|
+ # Verify output file
|
|
|
expected_pdf = filepath.rsplit('.', 1)[0] + '.pdf'
|
|
|
if not os.path.exists(expected_pdf):
|
|
|
raise HTTPException(
|
|
|
status_code=500,
|
|
|
- detail="変換は成功しましたが、出力ファイルが見つかりません"
|
|
|
+ detail="Conversion succeeded but output file not found"
|
|
|
)
|
|
|
|
|
|
filename_base = os.path.splitext(file.filename)[0]
|
|
|
@@ -175,13 +176,13 @@ async def convert(file: UploadFile = File(...)):
|
|
|
except HTTPException:
|
|
|
raise
|
|
|
except subprocess.TimeoutExpired:
|
|
|
- raise HTTPException(status_code=504, detail="変換タイムアウト (300秒)")
|
|
|
+ raise HTTPException(status_code=504, detail="Conversion timeout (300 seconds)")
|
|
|
except Exception as e:
|
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
@app.get("/version")
|
|
|
async def version():
|
|
|
- """バージョン情報"""
|
|
|
+ """Version information"""
|
|
|
return {
|
|
|
"service": "libreoffice-converter",
|
|
|
"version": "1.0.0",
|