Added video studio router and endpoints. Added research router and endpoints. Added youtube router and endpoints. Added onboarding utils router and endpoints. Added onboarding utils service. Added onboarding utils models. Added onboarding utils routes. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils.

This commit is contained in:
ajaysi
2026-01-01 17:56:25 +05:30
parent 7512933c65
commit b134e9dc7e
252 changed files with 40333 additions and 2712 deletions

View File

@@ -26,6 +26,76 @@ YOUTUBE_AUDIO_DIR.mkdir(parents=True, exist_ok=True)
# Initialize audio service
audio_service = StoryAudioGenerationService(output_dir=str(YOUTUBE_AUDIO_DIR))
# WaveSpeed Minimax Speech voice ids include language-specific voices
# Ref: https://wavespeed.ai/docs/docs-api/minimax/minimax_speech_voice_id
LANGUAGE_CODE_TO_LANGUAGE_BOOST = {
"en": "English",
"es": "Spanish",
"fr": "French",
"de": "German",
"pt": "Portuguese",
"it": "Italian",
"hi": "Hindi",
"ar": "Arabic",
"ru": "Russian",
"ja": "Japanese",
"ko": "Korean",
"zh": "Chinese",
"vi": "Vietnamese",
"id": "Indonesian",
"tr": "Turkish",
"nl": "Dutch",
"pl": "Polish",
"th": "Thai",
"uk": "Ukrainian",
"el": "Greek",
"cs": "Czech",
"fi": "Finnish",
"ro": "Romanian",
}
# Default language-specific Minimax voices (first-choice). We keep English on the existing "persona" voices.
LANGUAGE_BOOST_TO_DEFAULT_VOICE_ID = {
"Spanish": "Spanish_male_1_v1",
"French": "French_male_1_v1",
"German": "German_male_1_v1",
"Portuguese": "Portuguese_male_1_v1",
"Italian": "Italian_male_1_v1",
"Hindi": "Hindi_male_1_v1",
"Arabic": "Arabic_male_1_v1",
"Russian": "Russian_male_1_v1",
"Japanese": "Japanese_male_1_v1",
"Korean": "Korean_male_1_v1",
"Chinese": "Chinese_male_1_v1",
"Vietnamese": "Vietnamese_male_1_v1",
"Indonesian": "Indonesian_male_1_v1",
"Turkish": "Turkish_male_1_v1",
"Dutch": "Dutch_male_1_v1",
"Polish": "Polish_male_1_v1",
"Thai": "Thai_male_1_v1",
"Ukrainian": "Ukrainian_male_1_v1",
"Greek": "Greek_male_1_v1",
"Czech": "Czech_male_1_v1",
"Finnish": "Finnish_male_1_v1",
"Romanian": "Romanian_male_1_v1",
}
def _resolve_language_boost(language: Optional[str], explicit_language_boost: Optional[str]) -> str:
"""
Determine the effective WaveSpeed `language_boost`.
- If user explicitly provided language_boost, use it (including "auto").
- Else if language code provided, map to the WaveSpeed boost label.
- Else default to English (backwards compatible).
"""
if explicit_language_boost is not None and str(explicit_language_boost).strip() != "":
return str(explicit_language_boost).strip()
if language is not None and str(language).strip() != "":
lang_code = str(language).strip().lower()
return LANGUAGE_CODE_TO_LANGUAGE_BOOST.get(lang_code, "auto")
return "English"
def select_optimal_emotion(scene_title: str, narration: str, video_plan_context: Optional[Dict[str, Any]] = None) -> str:
"""
@@ -153,6 +223,7 @@ class YouTubeAudioRequest(BaseModel):
scene_title: str
text: str
voice_id: Optional[str] = None # Will auto-select based on content if not provided
language: Optional[str] = None # Language code for multilingual audio (e.g., "en", "es", "fr")
speed: float = 1.0
volume: float = 1.0
pitch: float = 0.0
@@ -164,7 +235,7 @@ class YouTubeAudioRequest(BaseModel):
bitrate: int = 256000 # Highest quality: 256kbps (valid values: 32000, 64000, 128000, 256000)
channel: Optional[str] = "2" # Stereo for richer audio (valid values: "1" or "2")
format: Optional[str] = "mp3" # Universal format for web
language_boost: Optional[str] = "English" # Optimize for English content
language_boost: Optional[str] = None # If not provided, inferred from `language` (or defaults to English)
enable_sync_mode: bool = True
# Context for intelligent voice/emotion selection
video_plan_context: Optional[Dict[str, Any]] = None # Optional video plan for context-aware voice selection
@@ -224,13 +295,24 @@ async def generate_youtube_scene_audio(
logger.info(f"[YouTubeAudio] Text preprocessing: {len(request.text)} -> {len(processed_text)} characters")
effective_language_boost = _resolve_language_boost(request.language, request.language_boost)
# Intelligent voice and emotion selection based on content analysis
if not request.voice_id:
selected_voice = select_optimal_voice(
request.scene_title,
processed_text,
request.video_plan_context
)
# If non-English language is selected, default to the language-specific Minimax voice_id.
# Otherwise keep the existing English persona voice selection logic.
if effective_language_boost in LANGUAGE_BOOST_TO_DEFAULT_VOICE_ID and effective_language_boost not in ["English", "auto"]:
selected_voice = LANGUAGE_BOOST_TO_DEFAULT_VOICE_ID[effective_language_boost]
logger.info(
f"[VoiceSelection] Using language-specific default voice '{selected_voice}' "
f"(language_boost={effective_language_boost}, language={request.language})"
)
else:
selected_voice = select_optimal_voice(
request.scene_title,
processed_text,
request.video_plan_context
)
else:
selected_voice = request.voice_id
@@ -244,7 +326,10 @@ async def generate_youtube_scene_audio(
else:
selected_emotion = request.emotion
logger.info(f"[YouTubeAudio] Voice selection: {selected_voice}, Emotion: {selected_emotion}")
logger.info(
f"[YouTubeAudio] Voice selection: {selected_voice}, Emotion: {selected_emotion}, "
f"language={request.language}, language_boost={effective_language_boost}"
)
# Build kwargs for optional parameters - use defaults if None
# WaveSpeed API requires specific values, so we provide sensible defaults
@@ -252,7 +337,11 @@ async def generate_youtube_scene_audio(
optional_kwargs = {}
# DEBUG: Log what values we received
logger.info(f"[YouTubeAudio] Request parameters: sample_rate={request.sample_rate}, bitrate={request.bitrate}, channel={request.channel}, format={request.format}, language_boost={request.language_boost}")
logger.info(
f"[YouTubeAudio] Request parameters: sample_rate={request.sample_rate}, bitrate={request.bitrate}, "
f"channel={request.channel}, format={request.format}, language_boost={request.language_boost}, "
f"effective_language_boost={effective_language_boost}, language={request.language}"
)
# sample_rate: Use provided value or omit (WaveSpeed will use default)
if request.sample_rate is not None:
@@ -276,9 +365,9 @@ async def generate_youtube_scene_audio(
if request.format is not None:
optional_kwargs["format"] = request.format
# language_boost: Use provided value or omit (WaveSpeed will use default)
if request.language_boost is not None:
optional_kwargs["language_boost"] = request.language_boost
# language_boost: always send resolved value (improves pronunciation and helps multilingual voices)
if effective_language_boost is not None and str(effective_language_boost).strip() != "":
optional_kwargs["language_boost"] = effective_language_boost
logger.info(f"[YouTubeAudio] Final optional_kwargs: {optional_kwargs}")

View File

@@ -287,7 +287,7 @@ async def create_video_plan(
# Check for existing YouTube creator avatar in asset library
asset_service = ContentAssetService(db)
existing_avatars = asset_service.get_assets(
existing_avatars, _ = asset_service.get_user_assets(
user_id=user_id,
asset_type=AssetType.IMAGE,
source_module=AssetSource.YOUTUBE_CREATOR,
@@ -685,11 +685,12 @@ async def render_single_scene_video(
async def get_render_status(
task_id: str,
current_user: Dict[str, Any] = Depends(get_current_user),
) -> Dict[str, Any]:
) -> Optional[Dict[str, Any]]:
"""
Get the status of a video rendering task.
Returns current progress, status, and result when complete.
Returns None if task not found (matches podcast pattern for graceful handling).
"""
try:
require_authenticated_user(current_user)
@@ -697,24 +698,17 @@ async def get_render_status(
logger.debug(f"[YouTubeAPI] Getting render status for task: {task_id}")
task_status = task_manager.get_task_status(task_id)
if not task_status:
logger.warning(
f"[YouTubeAPI] Task {task_id} not found. "
f"Available tasks: {list(task_manager.task_storage.keys())[:5]}..."
)
raise HTTPException(
status_code=404,
detail={
"error": "Task not found",
"message": "The render task was not found. It may have expired, been cleaned up, or the server may have restarted.",
"task_id": task_id,
"user_action": "Please try rendering again."
}
# Log at DEBUG level - null is expected when tasks expire or server restarts
# This prevents log spam from frontend polling for expired/completed tasks
# Return None instead of raising 404 to match podcast pattern for graceful frontend handling
logger.debug(
f"[YouTubeAPI] Task {task_id} not found (may have expired or been cleaned up). "
f"Available tasks: {len(task_manager.task_storage)}"
)
return None
return task_status
except HTTPException:
raise
except Exception as e:
logger.error(f"[YouTubeAPI] Error getting render status: {e}", exc_info=True)
raise HTTPException(
@@ -1201,6 +1195,12 @@ def _execute_scene_video_render_task(
result=result,
)
# Verify the task status was updated correctly (matches podcast pattern)
updated_status = task_manager.get_task_status(task_id)
logger.info(
f"[YouTubeRenderer] Task status after update: task_id={task_id}, status={updated_status.get('status') if updated_status else 'None'}, has_result={bool(updated_status.get('result') if updated_status else False)}, video_url={updated_status.get('result', {}).get('video_url') if updated_status else 'N/A'}"
)
logger.info(
f"[YouTubeRenderer] ✅ Single-scene render {task_id} completed (scene {scene_num}), cost=${total_cost:.2f}"
)
@@ -1348,27 +1348,37 @@ async def list_videos(
List videos for the current user from the asset library (source: youtube_creator).
Used to rescue/persist scene videos after reloads.
"""
user_id = require_authenticated_user(current_user)
asset_service = ContentAssetService(db)
try:
user_id = require_authenticated_user(current_user)
asset_service = ContentAssetService(db)
assets = asset_service.get_assets(
user_id=user_id,
asset_type=AssetType.VIDEO,
source_module=AssetSource.YOUTUBE_CREATOR,
limit=100,
)
assets, _ = asset_service.get_user_assets(
user_id=user_id,
asset_type=AssetType.VIDEO,
source_module=AssetSource.YOUTUBE_CREATOR,
limit=100,
)
videos = []
for asset in assets:
videos.append({
"scene_number": asset.asset_metadata.get("scene_number") if asset.asset_metadata else None,
"video_url": asset.file_url,
"filename": asset.filename,
"created_at": asset.created_at,
"resolution": asset.asset_metadata.get("resolution") if asset.asset_metadata else None,
})
videos = []
for asset in assets:
try:
videos.append({
"scene_number": asset.asset_metadata.get("scene_number") if asset.asset_metadata else None,
"video_url": asset.file_url,
"filename": asset.filename,
"created_at": asset.created_at.isoformat() if asset.created_at else None,
"resolution": asset.asset_metadata.get("resolution") if asset.asset_metadata else None,
})
except Exception as asset_error:
logger.warning(f"[YouTubeAPI] Error processing asset {asset.id if hasattr(asset, 'id') else 'unknown'}: {asset_error}")
continue # Skip this asset and continue with others
return VideoListResponse(videos=videos)
logger.info(f"[YouTubeAPI] Listed {len(videos)} videos for user {user_id}")
return VideoListResponse(videos=videos)
except Exception as e:
logger.error(f"[YouTubeAPI] Error listing videos: {e}", exc_info=True)
# Return empty list on error rather than failing completely
return VideoListResponse(videos=[], success=False, message=f"Failed to list videos: {str(e)}")
def _execute_combine_video_task(