Save local changes (GSC/Bing integrations) before merging PR #354

This commit is contained in:
ajaysi
2026-02-13 13:11:27 +05:30
parent 43e66835ac
commit 08a1f4a1d8
144 changed files with 8310 additions and 2748 deletions

View File

@@ -4,10 +4,10 @@ Podcast Audio Handlers
Audio generation, combining, and serving endpoints.
"""
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Form
from fastapi.responses import FileResponse
from sqlalchemy.orm import Session
from typing import Dict, Any
from typing import Dict, Any, Optional
from pathlib import Path
from urllib.parse import urlparse
import tempfile
@@ -31,6 +31,83 @@ from ..models import (
router = APIRouter()
@router.post("/audio/upload")
async def upload_podcast_audio(
file: UploadFile = File(...),
project_id: Optional[str] = Form(None),
current_user: Dict[str, Any] = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Upload an audio file (voice sample) for a podcast project.
Returns the audio URL for use in video generation.
"""
user_id = require_authenticated_user(current_user)
# Validate file type
if not file.content_type or not file.content_type.startswith('audio/'):
# Allow octet-stream if extension is audio
allowed_exts = ['.mp3', '.wav', '.m4a', '.aac']
file_ext = Path(file.filename).suffix.lower()
if file_ext not in allowed_exts and file.content_type != 'application/octet-stream':
raise HTTPException(status_code=400, detail="File must be an audio file")
# Validate file size (max 20MB)
file_content = await file.read()
if len(file_content) > 20 * 1024 * 1024:
raise HTTPException(status_code=400, detail="Audio file size must be less than 20MB")
try:
# Generate filename
file_ext = Path(file.filename).suffix or '.mp3'
unique_id = str(uuid.uuid4())[:8]
audio_filename = f"audio_{project_id or 'temp'}_{unique_id}{file_ext}"
audio_path = PODCAST_AUDIO_DIR / audio_filename
# Save file
with open(audio_path, "wb") as f:
f.write(file_content)
logger.info(f"[Podcast] Audio uploaded: {audio_path}")
# Create audio URL
audio_url = f"/api/podcast/audio/{audio_filename}"
# Save to asset library if project_id provided
if project_id:
try:
save_asset_to_library(
db=db,
user_id=user_id,
asset_type="audio",
source_module="podcast_maker",
filename=audio_filename,
file_url=audio_url,
file_path=str(audio_path),
file_size=len(file_content),
mime_type=file.content_type,
title=f"Uploaded Audio - {project_id}",
description="Uploaded podcast audio/voice sample",
tags=["podcast", "audio", "upload", project_id],
asset_metadata={
"project_id": project_id,
"type": "uploaded_audio",
"status": "completed",
},
)
except Exception as e:
logger.warning(f"[Podcast] Failed to save audio asset: {e}")
return {
"audio_url": audio_url,
"audio_filename": audio_filename,
"message": "Audio uploaded successfully"
}
except Exception as exc:
logger.error(f"[Podcast] Audio upload failed: {exc}", exc_info=True)
raise HTTPException(status_code=500, detail=f"Audio upload failed: {str(exc)}")
@router.post("/audio", response_model=PodcastAudioResponse)
async def generate_podcast_audio(
request: PodcastAudioRequest,

View File

@@ -10,6 +10,7 @@ from fastapi import HTTPException
from loguru import logger
from .constants import PODCAST_AUDIO_DIR, PODCAST_IMAGES_DIR
from utils.media_utils import load_media_bytes
def load_podcast_audio_bytes(audio_url: str) -> bytes:
@@ -54,49 +55,23 @@ def load_podcast_audio_bytes(audio_url: str) -> bytes:
def load_podcast_image_bytes(image_url: str) -> bytes:
"""Load podcast image bytes from URL. Only handles /api/podcast/images/ URLs."""
"""Load podcast image bytes from URL. Uses centralized media loader."""
if not image_url:
raise HTTPException(status_code=400, detail="Image URL is required")
logger.info(f"[Podcast] Loading image from URL: {image_url}")
try:
parsed = urlparse(image_url)
path = parsed.path if parsed.scheme else image_url
# REUSE: Use centralized media loader which handles cross-module lookups
image_bytes = load_media_bytes(image_url)
# Only handle /api/podcast/images/ URLs
prefix = "/api/podcast/images/"
if prefix not in path:
logger.error(f"[Podcast] Unsupported image URL format: {image_url}")
raise HTTPException(
status_code=400,
detail=f"Unsupported image URL format: {image_url}. Only /api/podcast/images/ URLs are supported."
)
filename = path.split(prefix, 1)[1].split("?", 1)[0].strip()
if not filename:
logger.error(f"[Podcast] Could not extract filename from URL: {image_url}")
raise HTTPException(status_code=400, detail=f"Could not extract filename from URL: {image_url}")
logger.info(f"[Podcast] Extracted filename: {filename}")
logger.info(f"[Podcast] PODCAST_IMAGES_DIR: {PODCAST_IMAGES_DIR}")
# Podcast images are stored in podcast_images directory
image_path = (PODCAST_IMAGES_DIR / filename).resolve()
logger.info(f"[Podcast] Resolved image path: {image_path}")
# Security check: ensure path is within PODCAST_IMAGES_DIR
if not str(image_path).startswith(str(PODCAST_IMAGES_DIR)):
logger.error(f"[Podcast] Attempted path traversal when resolving image: {image_url} -> {image_path}")
raise HTTPException(status_code=403, detail="Invalid image path")
if not image_path.exists():
logger.error(f"[Podcast] Image file not found: {image_path}")
raise HTTPException(status_code=404, detail=f"Image file not found: {filename}")
image_bytes = image_path.read_bytes()
logger.info(f"[Podcast] ✅ Successfully loaded image: {len(image_bytes)} bytes from {image_path}")
if not image_bytes:
logger.error(f"[Podcast] Image file not found for URL: {image_url}")
raise HTTPException(status_code=404, detail=f"Image file not found: {image_url}")
logger.info(f"[Podcast] ✅ Successfully loaded image: {len(image_bytes)} bytes")
return image_bytes
except HTTPException:
raise
except Exception as exc: