diff --git a/backend/api/images.py b/backend/api/images.py index 433d0e52..a6ec462a 100644 --- a/backend/api/images.py +++ b/backend/api/images.py @@ -134,6 +134,12 @@ def generate( current_video_calls = getattr(summary, "video_calls", 0) or 0 video_limit = limits['limits'].get("video_calls", 0) if limits else 0 + # Get audio stats for unified log + current_audio_calls = getattr(summary, "audio_calls", 0) or 0 + audio_limit = limits['limits'].get("audio_calls", 0) if limits else 0 + # Only show ∞ for Enterprise tier when limit is 0 (unlimited) + audio_limit_display = audio_limit if (audio_limit > 0 or tier != 'enterprise') else '∞' + db_track.commit() logger.info(f"[images.generate] ✅ Successfully tracked usage: user {user_id} -> stability -> {new_calls} calls") @@ -148,6 +154,7 @@ def generate( ├─ Calls: {current_calls_before} → {new_calls} / {call_limit if call_limit > 0 else '∞'} ├─ Image Editing: {current_image_edit_calls} / {image_edit_limit if image_edit_limit > 0 else '∞'} ├─ Videos: {current_video_calls} / {video_limit if video_limit > 0 else '∞'} +├─ Audio: {current_audio_calls} / {audio_limit_display} └─ Status: ✅ Allowed & Tracked """) except Exception as track_error: @@ -437,6 +444,12 @@ def edit( current_video_calls = getattr(summary, "video_calls", 0) or 0 video_limit = limits['limits'].get("video_calls", 0) if limits else 0 + # Get audio stats for unified log + current_audio_calls = getattr(summary, "audio_calls", 0) or 0 + audio_limit = limits['limits'].get("audio_calls", 0) if limits else 0 + # Only show ∞ for Enterprise tier when limit is 0 (unlimited) + audio_limit_display = audio_limit if (audio_limit > 0 or tier != 'enterprise') else '∞' + db_track.commit() logger.info(f"[images.edit] ✅ Successfully tracked usage: user {user_id} -> image_edit -> {new_calls} calls") @@ -451,6 +464,7 @@ def edit( ├─ Calls: {current_calls_before} → {new_calls} / {call_limit if call_limit > 0 else '∞'} ├─ Images: {current_image_gen_calls} / {image_gen_limit if image_gen_limit > 0 else '∞'} ├─ Videos: {current_video_calls} / {video_limit if video_limit > 0 else '∞'} +├─ Audio: {current_audio_calls} / {audio_limit_display} └─ Status: ✅ Allowed & Tracked """) except Exception as track_error: diff --git a/backend/api/story_writer/router.py b/backend/api/story_writer/router.py index 086a7b13..74da0a67 100644 --- a/backend/api/story_writer/router.py +++ b/backend/api/story_writer/router.py @@ -5,12 +5,19 @@ Main router for story generation operations including premise, outline, content generation, and full story creation. """ -from fastapi import APIRouter, HTTPException, Depends, BackgroundTasks -from typing import Any, Dict, Union, List, Optional +import mimetypes +from pathlib import Path +from typing import Any, Dict, List, Optional, Union + +from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Request from loguru import logger -from middleware.auth_middleware import get_current_user +from middleware.auth_middleware import get_current_user, get_current_user_with_query_token from models.story_models import ( + AnimateSceneRequest, + AnimateSceneVoiceoverRequest, + AnimateSceneResponse, + ResumeSceneAnimationRequest, StoryGenerationRequest, StorySetupGenerationRequest, StorySetupGenerationResponse, @@ -34,24 +41,66 @@ from models.story_models import ( StoryVideoResult, TaskStatus, ) +from pydantic import BaseModel, Field +from services.database import get_db +from services.llm_providers.main_video_generation import track_video_usage from services.story_writer.story_service import StoryWriterService -from .task_manager import task_manager -from .cache_manager import cache_manager +from services.story_writer.video_generation_service import StoryVideoGenerationService +from services.subscription import PricingService +from services.subscription.preflight_validator import validate_scene_animation_operation +from services.wavespeed.kling_animation import animate_scene_image, resume_scene_animation +from services.wavespeed.infinitetalk import animate_scene_with_voiceover from uuid import uuid4 -from pydantic import BaseModel -from pathlib import Path +from utils.logger_utils import get_service_logger +from .cache_manager import cache_manager +from .routes import cache_routes, media_generation, story_content, story_setup, story_tasks, video_generation +from .task_manager import task_manager from .utils.auth import require_authenticated_user -from .utils.media_utils import resolve_media_file -from .utils.hd_video import ( - generate_hd_video_payload, - generate_hd_video_scene_payload, -) +from .utils.hd_video import generate_hd_video_payload, generate_hd_video_scene_payload +from .utils.media_utils import load_story_image_bytes, load_story_audio_bytes, resolve_media_file +from urllib.parse import quote router = APIRouter(prefix="/api/story", tags=["Story Writer"]) +# Include modular routers (order preserved roughly by workflow) +router.include_router(story_setup.router) +router.include_router(story_content.router) +router.include_router(story_tasks.router) +router.include_router(media_generation.router) +router.include_router(video_generation.router) +router.include_router(cache_routes.router) + service = StoryWriterService() +scene_logger = get_service_logger("api.story_writer.scene_animation") +AI_VIDEO_SUBDIR = Path("AI_Videos") + + +def _build_authenticated_media_url(request: Request, path: str) -> str: + """Append the caller's auth token to a media URL so