Merge_PR_415_enforce_runtime_only_workspace_creation

This commit is contained in:
ajaysi
2026-03-12 16:01:23 +05:30
12 changed files with 241 additions and 99 deletions

View File

@@ -12,17 +12,13 @@ from api.story_writer.utils.auth import require_authenticated_user
from utils.asset_tracker import save_asset_to_library
from models.story_models import StoryAudioResult
from services.story_writer.audio_generation_service import StoryAudioGenerationService
from pathlib import Path
from utils.logger_utils import get_service_logger
router = APIRouter(tags=["youtube-audio"])
logger = get_service_logger("api.youtube.audio")
# Audio output directory
# api/youtube/handlers/audio.py -> handlers -> youtube -> api -> backend -> root
base_dir = Path(__file__).resolve().parents[4]
YOUTUBE_AUDIO_DIR = base_dir / "workspace" / "media" / "youtube_audio"
YOUTUBE_AUDIO_DIR.mkdir(parents=True, exist_ok=True)
from ..paths import YOUTUBE_AUDIO_DIR, ensure_youtube_media_dirs
# Initialize audio service
audio_service = StoryAudioGenerationService(output_dir=str(YOUTUBE_AUDIO_DIR))
@@ -266,6 +262,7 @@ async def generate_youtube_scene_audio(
Similar to Podcast's audio generation endpoint.
"""
user_id = require_authenticated_user(current_user)
ensure_youtube_media_dirs(user_id)
if not request.text or not request.text.strip():
raise HTTPException(status_code=400, detail="Text is required")

View File

@@ -1,6 +1,5 @@
"""YouTube Creator avatar upload and AI optimization handlers."""
from pathlib import Path
import uuid
from typing import Dict, Any, Optional
@@ -18,12 +17,7 @@ from utils.logger_utils import get_service_logger
router = APIRouter(prefix="/avatar", tags=["youtube-avatar"])
logger = get_service_logger("api.youtube.avatar")
# Directories
# api/youtube/handlers/avatar.py -> handlers -> youtube -> api -> backend -> root
base_dir = Path(__file__).parent.parent.parent.parent.parent
DATA_MEDIA_DIR = base_dir / "data" / "media"
YOUTUBE_AVATARS_DIR = DATA_MEDIA_DIR / "youtube_avatars"
YOUTUBE_AVATARS_DIR.mkdir(parents=True, exist_ok=True)
from ..paths import YOUTUBE_AVATARS_DIR, ensure_youtube_media_dirs
def require_authenticated_user(current_user: Dict[str, Any]) -> str:
@@ -256,6 +250,7 @@ async def upload_youtube_avatar(
):
"""Upload a YouTube creator avatar image."""
user_id = require_authenticated_user(current_user)
ensure_youtube_media_dirs(user_id)
if not file:
raise HTTPException(status_code=400, detail="No file uploaded")
@@ -328,6 +323,7 @@ async def make_avatar_presentable(
Uses AI image editing with enhanced prompts to optimize the uploaded photo.
"""
user_id = require_authenticated_user(current_user)
ensure_youtube_media_dirs(user_id)
try:
avatar_bytes = _load_youtube_image_bytes(avatar_url)
@@ -488,6 +484,7 @@ async def generate_creator_avatar(
the video type, audience, tone, and brand style.
"""
user_id = require_authenticated_user(current_user)
ensure_youtube_media_dirs(user_id)
try:
return await _generate_avatar_from_context(
@@ -518,6 +515,7 @@ async def regenerate_creator_avatar(
to provide variation while maintaining the same optimization based on plan data.
"""
user_id = require_authenticated_user(current_user)
ensure_youtube_media_dirs(user_id)
try:
# Parse video plan to extract context

View File

@@ -1,6 +1,6 @@
from pathlib import Path
"""YouTube Creator scene image generation handlers."""
from pathlib import Path
from typing import Dict, Any, Optional
import uuid
from concurrent.futures import ThreadPoolExecutor
@@ -23,13 +23,7 @@ from ..task_manager import task_manager
router = APIRouter(tags=["youtube-image"])
logger = get_service_logger("api.youtube.image")
# Directories
# api/youtube/handlers/images.py -> handlers -> youtube -> api -> backend -> root
base_dir = Path(__file__).parent.parent.parent.parent.parent
DATA_MEDIA_DIR = base_dir / "data" / "media"
YOUTUBE_IMAGES_DIR = DATA_MEDIA_DIR / "youtube_images"
YOUTUBE_IMAGES_DIR.mkdir(parents=True, exist_ok=True)
YOUTUBE_AVATARS_DIR = DATA_MEDIA_DIR / "youtube_avatars"
from ..paths import YOUTUBE_IMAGES_DIR, YOUTUBE_AVATARS_DIR, ensure_youtube_media_dirs
# Thread pool for background image generation
_image_executor = ThreadPoolExecutor(max_workers=2, thread_name_prefix="youtube_image")
@@ -102,6 +96,7 @@ async def generate_youtube_scene_image(
"""Generate a YouTube scene image with background task processing."""
logger.info(f"[YouTube] Image generation request received: scene='{request.scene_title}', user={current_user.get('id')}")
user_id = require_authenticated_user(current_user)
ensure_youtube_media_dirs(user_id)
logger.info(f"[YouTube] User authenticated: {user_id}")
if not request.scene_title:
@@ -312,7 +307,6 @@ def _execute_image_generation_task(task_id: str, request_data: dict, user_id: st
image_metadata = _save_scene_image(image_bytes, request.scene_id)
# Verify file was saved correctly
from pathlib import Path
saved_path = Path(image_metadata["image_path"])
if not saved_path.exists() or saved_path.stat().st_size == 0:
raise IOError(f"Image file was not saved correctly: {saved_path}")

View File

@@ -0,0 +1,21 @@
"""Centralized YouTube media paths and runtime directory creation."""
from pathlib import Path
from typing import Iterable, Optional
from services.workspace_dirs import ensure_user_workspace_dirs
BASE_DIR = Path(__file__).resolve().parents[3]
DATA_MEDIA_DIR = BASE_DIR / "workspace" / "media"
YOUTUBE_VIDEO_DIR = DATA_MEDIA_DIR / "youtube_videos"
YOUTUBE_AVATARS_DIR = DATA_MEDIA_DIR / "youtube_avatars"
YOUTUBE_IMAGES_DIR = DATA_MEDIA_DIR / "youtube_images"
YOUTUBE_AUDIO_DIR = DATA_MEDIA_DIR / "youtube_audio"
def ensure_youtube_media_dirs(user_id: str, capabilities: Optional[Iterable[str]] = None) -> None:
"""Ensure YouTube-related media directories at request/runtime."""
ensure_user_workspace_dirs(user_id, capabilities=capabilities or {"media", "content"})
for directory in [YOUTUBE_VIDEO_DIR, YOUTUBE_AVATARS_DIR, YOUTUBE_IMAGES_DIR, YOUTUBE_AUDIO_DIR]:
directory.mkdir(parents=True, exist_ok=True)

View File

@@ -34,17 +34,12 @@ from .handlers import audio as audio_handlers
router = APIRouter(prefix="/youtube", tags=["youtube"])
logger = get_service_logger("api.youtube")
# Video output and image directories
# api/youtube/router.py -> youtube -> api -> backend -> root
base_dir = Path(__file__).resolve().parents[3]
DATA_MEDIA_DIR = base_dir / "workspace" / "media"
YOUTUBE_VIDEO_DIR = DATA_MEDIA_DIR / "youtube_videos"
YOUTUBE_AVATARS_DIR = DATA_MEDIA_DIR / "youtube_avatars"
YOUTUBE_IMAGES_DIR = DATA_MEDIA_DIR / "youtube_images"
# Ensure directories exist
for directory in [YOUTUBE_VIDEO_DIR, YOUTUBE_AVATARS_DIR, YOUTUBE_IMAGES_DIR]:
directory.mkdir(parents=True, exist_ok=True)
from .paths import (
YOUTUBE_VIDEO_DIR,
YOUTUBE_AVATARS_DIR,
YOUTUBE_IMAGES_DIR,
ensure_youtube_media_dirs,
)
# Include sub-routers for avatar, images, and audio
router.include_router(avatar_handlers.router)