From e96525347b4caba9a72fea4b6ee4dc66818e9f67 Mon Sep 17 00:00:00 2001 From: ajaysi Date: Wed, 19 Nov 2025 09:55:32 +0530 Subject: [PATCH] AI story writer enhancements, text to video and voice generation, subscription management, and more. --- backend/api/images.py | 14 + backend/api/story_writer/router.py | 534 ++++++++++- backend/api/story_writer/routes/__init__.py | 21 + .../api/story_writer/routes/cache_routes.py | 42 + .../story_writer/routes/media_generation.py | 289 ++++++ .../api/story_writer/routes/story_content.py | 195 ++++ .../api/story_writer/routes/story_setup.py | 141 +++ .../api/story_writer/routes/story_tasks.py | 130 +++ .../story_writer/routes/video_generation.py | 511 +++++++++++ backend/api/story_writer/utils/hd_video.py | 22 +- backend/api/story_writer/utils/media_utils.py | 79 ++ backend/api/subscription_api.py | 244 ++++- backend/logging_config.py | 9 +- backend/middleware/auth_middleware.py | 62 +- backend/models/story_models.py | 92 ++ backend/models/subscription_models.py | 4 + .../llm_providers/main_audio_generation.py | 301 +++++++ .../llm_providers/main_text_generation.py | 15 + .../llm_providers/main_video_generation.py | 377 ++++---- .../services/onboarding/api_key_manager.py | 3 +- .../story_writer/audio_generation_service.py | 86 ++ .../story_writer/image_generation_service.py | 78 ++ .../story_writer/video_generation_service.py | 71 +- .../subscription/monitoring_middleware.py | 30 +- .../subscription/preflight_validator.py | 51 ++ .../services/subscription/pricing_service.py | 40 + backend/services/subscription/schema_utils.py | 3 + backend/services/wavespeed/__init__.py | 1 + backend/services/wavespeed/client.py | 471 ++++++++++ backend/services/wavespeed/infinitetalk.py | 122 +++ backend/services/wavespeed/kling_animation.py | 360 ++++++++ ...elcome_to_the_Cloud_Kitchen___9ddc13cd.mp3 | Bin 0 -> 107904 bytes ...cene_2_The_Star_Recipe_Begins_68356250.mp3 | Bin 0 -> 173007 bytes ...cene_2_The_Star_Recipe_Begins_ed9941a3.mp3 | Bin 0 -> 99840 bytes ...athering_Sparkling_Space_Dust_d8174f84.mp3 | Bin 0 -> 102720 bytes .../scene_4_Collecting_Wishes_c38d9001.mp3 | Bin 0 -> 93696 bytes .../scene_5_The_Gravity_Mixer_e6255f00.mp3 | Bin 0 -> 99072 bytes .../scene_6_The_Glowing_Mixture_c0163e9c.mp3 | Bin 0 -> 100224 bytes .../scene_7_A_New_Star_Is_Born_c3f3f2c4.mp3 | Bin 0 -> 122496 bytes ...elcome_to_the_Cloud_Kitchen___ae6436d9.png | Bin 0 -> 1235968 bytes docs/LINKEDIN_WRITER_MULTIMEDIA_REVAMP.md | 658 ++++++++++++++ ...RSONA_VOICE_AVATAR_HYPERPERSONALIZATION.md | 615 +++++++++++++ docs/STORY_WRITER_VIDEO_ENHANCEMENT.md | 834 ++++++++++++++++++ docs/WAVESPEED_AI_FEATURE_PROPOSAL.md | 516 +++++++++++ docs/WAVESPEED_AI_FEATURE_SUMMARY.md | 165 ++++ docs/WAVESPEED_IMPLEMENTATION_ROADMAP.md | 335 +++++++ .../StoryWriter/Phases/StoryExport.tsx | 38 +- .../StoryWriter/Phases/StoryOutline.tsx | 541 +++++++++++- .../StoryOutlineParts/AudioScriptModal.tsx | 561 +++++++++++- .../Phases/StoryOutlineParts/BookPages.tsx | 238 ++++- .../StoryOutlineParts/ImageEditModal.tsx | 128 ++- .../StoryOutlineParts/OutlineHoverActions.tsx | 5 +- .../StoryWriter/Phases/StoryWriting.tsx | 94 +- .../components/StoryWriter/StoryWriter.tsx | 27 +- .../components/AudioPlayerList.tsx | 42 +- .../StoryWriter/components/HdVideoSection.tsx | 72 +- .../StoryWriter/components/VideoSection.tsx | 55 +- .../src/components/shared/OperationButton.tsx | 273 ++++++ frontend/src/hooks/usePreflightCheck.ts | 257 ++++++ frontend/src/hooks/useStoryWriterState.ts | 29 + frontend/src/services/billingService.ts | 121 +++ frontend/src/services/storyWriterApi.ts | 257 +++++- frontend/src/utils/fetchMediaBlobUrl.ts | 18 +- preflight-check-cost-estimation.plan.md | 490 ++++++++++ 64 files changed, 10367 insertions(+), 400 deletions(-) create mode 100644 backend/api/story_writer/routes/__init__.py create mode 100644 backend/api/story_writer/routes/cache_routes.py create mode 100644 backend/api/story_writer/routes/media_generation.py create mode 100644 backend/api/story_writer/routes/story_content.py create mode 100644 backend/api/story_writer/routes/story_setup.py create mode 100644 backend/api/story_writer/routes/story_tasks.py create mode 100644 backend/api/story_writer/routes/video_generation.py create mode 100644 backend/services/llm_providers/main_audio_generation.py create mode 100644 backend/services/wavespeed/__init__.py create mode 100644 backend/services/wavespeed/client.py create mode 100644 backend/services/wavespeed/infinitetalk.py create mode 100644 backend/services/wavespeed/kling_animation.py create mode 100644 backend/story_audio/scene_1_Welcome_to_the_Cloud_Kitchen___9ddc13cd.mp3 create mode 100644 backend/story_audio/scene_2_The_Star_Recipe_Begins_68356250.mp3 create mode 100644 backend/story_audio/scene_2_The_Star_Recipe_Begins_ed9941a3.mp3 create mode 100644 backend/story_audio/scene_3_Gathering_Sparkling_Space_Dust_d8174f84.mp3 create mode 100644 backend/story_audio/scene_4_Collecting_Wishes_c38d9001.mp3 create mode 100644 backend/story_audio/scene_5_The_Gravity_Mixer_e6255f00.mp3 create mode 100644 backend/story_audio/scene_6_The_Glowing_Mixture_c0163e9c.mp3 create mode 100644 backend/story_audio/scene_7_A_New_Star_Is_Born_c3f3f2c4.mp3 create mode 100644 backend/story_images/scene_1_Welcome_to_the_Cloud_Kitchen___ae6436d9.png create mode 100644 docs/LINKEDIN_WRITER_MULTIMEDIA_REVAMP.md create mode 100644 docs/PERSONA_VOICE_AVATAR_HYPERPERSONALIZATION.md create mode 100644 docs/STORY_WRITER_VIDEO_ENHANCEMENT.md create mode 100644 docs/WAVESPEED_AI_FEATURE_PROPOSAL.md create mode 100644 docs/WAVESPEED_AI_FEATURE_SUMMARY.md create mode 100644 docs/WAVESPEED_IMPLEMENTATION_ROADMAP.md create mode 100644 frontend/src/components/shared/OperationButton.tsx create mode 100644 frontend/src/hooks/usePreflightCheck.ts create mode 100644 preflight-check-cost-estimation.plan.md 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