Files
ALwrity/backend/api/youtube/publish_router.py

219 lines
7.6 KiB
Python

"""
YouTube Publish Router
Handles video upload/publishing to YouTube via the Data API v3.
Uses stored OAuth credentials for authentication.
"""
from typing import Optional, List
from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks, Query
from pydantic import BaseModel, Field
from loguru import logger
from middleware.auth_middleware import get_current_user
from services.youtube.youtube_oauth_service import YouTubeOAuthService
from services.youtube.youtube_publish_service import YouTubePublishService
from .oauth_router import get_oauth_service
from .task_manager import task_manager
router = APIRouter(prefix="/youtube/publish", tags=["youtube-publish"])
class PublishRequest(BaseModel):
token_id: int = Field(..., description="YouTube OAuth token row ID (which channel to publish to)")
video_source: str = Field(..., description="URL or local file path to the video")
title: str = Field(..., min_length=1, max_length=100, description="Video title (max 100 chars)")
description: str = Field("", description="Video description")
tags: List[str] = Field(default_factory=list, description="Video tags")
privacy_status: str = Field("unlisted", pattern="^(public|private|unlisted)$", description="Privacy status")
category_id: str = Field("22", description="YouTube category ID (default: People & Blogs)")
made_for_kids: bool = Field(False, description="Whether content is made for children")
class PublishResponse(BaseModel):
success: bool
task_id: Optional[str] = None
video_id: Optional[str] = None
video_url: Optional[str] = None
error: Optional[str] = None
message: str = ""
def get_publish_service(
oauth_service: YouTubeOAuthService = Depends(get_oauth_service),
) -> YouTubePublishService:
return YouTubePublishService(oauth_service)
@router.post("", response_model=PublishResponse)
def start_publish(
request: PublishRequest,
background_tasks: BackgroundTasks,
user: dict = Depends(get_current_user),
publish_service: YouTubePublishService = Depends(get_publish_service),
):
"""Start publishing a video to YouTube as a background task."""
try:
user_id = user.get("id")
if not user_id:
raise HTTPException(status_code=401, detail="Authentication required")
# Verify token belongs to user
oauth_service = publish_service.oauth_service
status = oauth_service.get_connection_status(user_id)
tokens = [c for c in status.get("channels", []) if c["token_id"] == request.token_id and c["is_active"]]
if not tokens:
raise HTTPException(status_code=400, detail="Invalid or inactive token_id")
# Create background task
task_id = task_manager.create_task("youtube_publish")
logger.info(
f"YouTube publish: created task {task_id} for user {user_id}, "
f"title='{request.title[:50]}', channel={tokens[0].get('channel_name', 'unknown')}"
)
background_tasks.add_task(
_execute_publish_task,
task_id=task_id,
user_id=user_id,
token_id=request.token_id,
video_source=request.video_source,
title=request.title,
description=request.description,
tags=request.tags,
privacy_status=request.privacy_status,
category_id=request.category_id,
made_for_kids=request.made_for_kids,
publish_service=publish_service,
)
return PublishResponse(
success=True,
task_id=task_id,
message="Publishing to YouTube started. Poll task_id for progress.",
)
except HTTPException:
raise
except Exception as e:
logger.error(f"YouTube publish: error starting task: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.get("/{task_id}", response_model=PublishResponse)
def get_publish_status(
task_id: str,
user: dict = Depends(get_current_user),
):
"""Check the status of a YouTube publish task."""
try:
user_id = user.get("id")
if not user_id:
raise HTTPException(status_code=401, detail="Authentication required")
task_status = task_manager.get_task_status(task_id)
if not task_status:
return PublishResponse(
success=False,
error="Task not found",
message="Publish task not found (may have expired).",
)
status = task_status.get("status", "unknown")
result = task_status.get("result") or {}
error = task_status.get("error")
if status == "completed":
return PublishResponse(
success=True,
task_id=task_id,
video_id=result.get("video_id"),
video_url=result.get("video_url"),
message=task_status.get("message", "Published successfully"),
)
elif status == "failed":
return PublishResponse(
success=False,
task_id=task_id,
error=error or result.get("error", "Publish failed"),
message=task_status.get("message", "Publish failed"),
)
else:
return PublishResponse(
success=False,
task_id=task_id,
message=task_status.get("message", "Publishing in progress..."),
)
except HTTPException:
raise
except Exception as e:
logger.error(f"YouTube publish: status check error: {e}")
raise HTTPException(status_code=500, detail=str(e))
def _execute_publish_task(
task_id: str,
user_id: str,
token_id: int,
video_source: str,
title: str,
description: str,
tags: List[str],
privacy_status: str,
category_id: str,
made_for_kids: bool,
publish_service: YouTubePublishService,
):
"""Background task to execute video publish."""
logger.info(f"YouTube publish: background task {task_id} starting for user {user_id}")
try:
task_manager.update_task_status(
task_id, "processing", progress=10.0, message="Preparing video for upload..."
)
result = publish_service.publish_video(
user_id=user_id,
token_id=token_id,
video_source=video_source,
title=title,
description=description,
tags=tags,
privacy_status=privacy_status,
category_id=category_id,
made_for_kids=made_for_kids,
)
if result.get("success"):
task_manager.update_task_status(
task_id,
"completed",
progress=100.0,
message=f"Published successfully: {result.get('video_url', '')}",
result=result,
)
logger.info(
f"YouTube publish: task {task_id} completed — "
f"video_id={result.get('video_id')}, url={result.get('video_url')}"
)
else:
error_msg = result.get("error", "Unknown publish error")
logger.error(f"YouTube publish: task {task_id} failed: {error_msg}")
task_manager.update_task_status(
task_id,
"failed",
error=error_msg,
message="Publish failed",
result=result,
)
except Exception as e:
logger.error(f"YouTube publish: background task {task_id} error: {e}")
task_manager.update_task_status(
task_id,
"failed",
error=str(e),
message="Publish error",
result={"error": str(e)},
)