251 lines
10 KiB
Python
251 lines
10 KiB
Python
import os
|
|
import json
|
|
from functools import wraps
|
|
from django.shortcuts import render, get_object_or_404, redirect
|
|
from django.utils import timezone
|
|
from django.contrib import messages
|
|
from django.http import JsonResponse
|
|
from .models import Project, PipelineStep, CgiAsset, StudioConfig, Scene
|
|
from ai.local_ai_api import LocalAIApi
|
|
from .pexels import fetch_first, fetch_video
|
|
|
|
def studio_admin_required(view_func):
|
|
"""Decorator to restrict access to studio admin only."""
|
|
@wraps(view_func)
|
|
def _wrapped_view(request, *args, **kwargs):
|
|
if not request.session.get('is_studio_admin', False):
|
|
messages.warning(request, "Acesso restrito. Por favor, insira sua chave de administrador.")
|
|
return redirect('admin_login')
|
|
return view_func(request, *args, **kwargs)
|
|
return _wrapped_view
|
|
|
|
def home(request):
|
|
"""Render the CGI Studio Command Center."""
|
|
config, created = StudioConfig.objects.get_or_create(id=1)
|
|
if created or not config.admin_access_key:
|
|
config.save()
|
|
|
|
projects = Project.objects.prefetch_related('steps').all().order_by('-created_at')
|
|
|
|
total_projects = projects.count()
|
|
active_productions = projects.filter(status='PROD').count()
|
|
completed_projects = projects.filter(status='DONE').count()
|
|
|
|
context = {
|
|
"projects": projects,
|
|
"total_projects": total_projects,
|
|
"active_productions": active_productions,
|
|
"completed_projects": completed_projects,
|
|
"current_time": timezone.now(),
|
|
"is_admin": request.session.get('is_studio_admin', False),
|
|
}
|
|
return render(request, "core/index.html", context)
|
|
|
|
@studio_admin_required
|
|
def studio_ai(request):
|
|
"""Page to configure and launch AI-automated productions."""
|
|
context = {
|
|
"project_types": Project.TYPES,
|
|
"voice_presets": Project.VOICE_CHOICES,
|
|
}
|
|
return render(request, "core/studio_ai.html", context)
|
|
|
|
@studio_admin_required
|
|
def generate_production(request):
|
|
"""AI logic to create a full SUPER PRODUCTION with Video, Audio, and CRUD."""
|
|
if request.method == "POST":
|
|
category = request.POST.get("category", "Sci-Fi")
|
|
proj_type = request.POST.get("project_type", "MOVIE")
|
|
theme = request.POST.get("theme", "Future of humanity")
|
|
voice_preset = request.POST.get("voice_preset", "male_1")
|
|
|
|
prompt = f"""
|
|
Create a detailed SUPER PRODUCTION plan for a {proj_type} in the {category} category.
|
|
Theme: {theme}
|
|
|
|
Requirements:
|
|
1. Unique Title and a compelling description.
|
|
2. A full cinematic screenplay (script).
|
|
3. A visual query for a Banner/Capa (2-4 words).
|
|
4. 3 Characters with names, physical descriptions, and a 'voice_preset' (choice of: v_male_1, v_male_2, v_female_1, v_female_2).
|
|
5. 6 Key Scenes with titles, descriptions, and a unique 'video_query' (2-4 words) for each scene.
|
|
6. Metadata: Budget, Rating, Duration.
|
|
|
|
Return JSON:
|
|
{{
|
|
"title": "...",
|
|
"description": "...",
|
|
"full_script": "...",
|
|
"banner_query": "...",
|
|
"budget": "...",
|
|
"rating": "...",
|
|
"duration": "...",
|
|
"characters": [
|
|
{{"name": "...", "description": "...", "voice_preset": "..."}}
|
|
],
|
|
"scenes": [
|
|
{{"title": "...", "description": "...", "video_query": "..."}}
|
|
]
|
|
}}
|
|
"""
|
|
|
|
response = LocalAIApi.create_response({
|
|
"input": [
|
|
{"role": "system", "content": "You are a Hollywood AI Director creating cinematic video-based productions."},
|
|
{"role": "user", "content": prompt},
|
|
],
|
|
"text": {"format": {"type": "json_object"}},
|
|
})
|
|
|
|
if response.get("success"):
|
|
try:
|
|
data = LocalAIApi.decode_json_from_response(response)
|
|
|
|
# Fetch Banner
|
|
banner_data = fetch_first(data.get('banner_query', data['title']))
|
|
banner_url = banner_data['local_path'] if banner_data else ""
|
|
|
|
# Create the Project
|
|
project = Project.objects.create(
|
|
title=data['title'],
|
|
project_type=proj_type,
|
|
category=category,
|
|
description=data['description'],
|
|
full_script=data.get('full_script', ''),
|
|
thumbnail_url=banner_url,
|
|
banner_url=banner_url,
|
|
is_ai_generated=True,
|
|
status='DONE',
|
|
voice_preset=voice_preset,
|
|
estimated_budget=data.get('budget', '$200M'),
|
|
rating=data.get('rating', 'PG-13'),
|
|
duration=data.get('duration', '130 min')
|
|
)
|
|
|
|
# Create default Pipeline Steps
|
|
for stage in [s[0] for s in PipelineStep.STAGES]:
|
|
PipelineStep.objects.create(project=project, name=stage, progress=100, is_completed=True)
|
|
|
|
# Characters
|
|
for char in data['characters']:
|
|
CgiAsset.objects.create(
|
|
project=project,
|
|
name=char['name'],
|
|
asset_type='CHAR',
|
|
physical_description=char['description'],
|
|
voice_preset=char.get('voice_preset', 'v_male_1')
|
|
)
|
|
|
|
# Scenes + Videos
|
|
for i, scene_data in enumerate(data['scenes']):
|
|
v_query = scene_data.get('video_query', scene_data['title'])
|
|
|
|
# Fetch Video for scene
|
|
video_res = fetch_video(f"{data['title']} {v_query}")
|
|
video_path = video_res['local_path'] if video_res else ""
|
|
|
|
# Fetch Image as fallback/thumbnail for scene
|
|
image_res = fetch_first(f"{data['title']} {v_query}", orientation="landscape")
|
|
image_path = image_res['local_path'] if image_res else ""
|
|
|
|
Scene.objects.create(
|
|
project=project,
|
|
number=i+1,
|
|
title=scene_data['title'],
|
|
description=scene_data['description'],
|
|
visual_prompt=v_query,
|
|
image_url=image_path,
|
|
video_url=video_path
|
|
)
|
|
|
|
# Overall production video (using the first scene's video as main)
|
|
if project.scenes.exists():
|
|
project.video_url = project.scenes.first().video_url
|
|
project.save()
|
|
|
|
messages.success(request, f"Produção '{project.title}' gerada com Sucesso! Banner e Vídeos salvos.")
|
|
return redirect('production_library')
|
|
|
|
except Exception as e:
|
|
messages.error(request, f"Erro ao processar: {str(e)}")
|
|
else:
|
|
messages.error(request, f"Erro AI: {response.get('error')}")
|
|
|
|
return redirect('studio_ai')
|
|
|
|
@studio_admin_required
|
|
def edit_production(request, slug):
|
|
"""View to edit an existing production's metadata."""
|
|
project = get_object_or_404(Project, slug=slug)
|
|
if request.method == "POST":
|
|
project.title = request.POST.get("title", project.title)
|
|
project.description = request.POST.get("description", project.description)
|
|
project.category = request.POST.get("category", project.category)
|
|
project.estimated_budget = request.POST.get("budget", project.estimated_budget)
|
|
project.duration = request.POST.get("duration", project.duration)
|
|
project.rating = request.POST.get("rating", project.rating)
|
|
project.save()
|
|
messages.success(request, f"Produção '{project.title}' atualizada com sucesso.")
|
|
return redirect('production_library')
|
|
|
|
return render(request, "core/edit_production.html", {"project": project})
|
|
|
|
@studio_admin_required
|
|
def delete_production(request, slug):
|
|
"""View to delete a production."""
|
|
project = get_object_or_404(Project, slug=slug)
|
|
title = project.title
|
|
project.delete()
|
|
messages.success(request, f"Produção '{title}' excluída.")
|
|
return redirect('production_library')
|
|
|
|
@studio_admin_required
|
|
def production_library(request):
|
|
"""View to see all completed AI productions."""
|
|
productions = Project.objects.filter(is_ai_generated=True).order_by('-created_at')
|
|
return render(request, "core/production_library.html", {"productions": productions})
|
|
|
|
@studio_admin_required
|
|
def watch_production(request, slug):
|
|
"""View to 'watch' (read and experience) a complete production."""
|
|
project = get_object_or_404(Project.objects.prefetch_related('assets', 'scenes'), slug=slug)
|
|
return render(request, "core/watch_production.html", {"project": project})
|
|
|
|
def admin_login(request):
|
|
"""View to enter the unique admin access key."""
|
|
if request.method == "POST":
|
|
key = request.POST.get("access_key")
|
|
try:
|
|
config = StudioConfig.objects.get(id=1)
|
|
if key == config.admin_access_key:
|
|
request.session['is_studio_admin'] = True
|
|
messages.success(request, "Bem-vindo, Comandante do Estúdio!")
|
|
return redirect('home')
|
|
else:
|
|
messages.error(request, "Chave de acesso inválida.")
|
|
except StudioConfig.DoesNotExist:
|
|
messages.error(request, "Configuração do estúdio não encontrada.")
|
|
|
|
return render(request, "core/admin_login.html")
|
|
|
|
def admin_logout(request):
|
|
"""Logout the studio admin."""
|
|
request.session['is_studio_admin'] = False
|
|
return redirect('home')
|
|
|
|
@studio_admin_required
|
|
def asset_library(request):
|
|
"""View all digital assets (Characters, Props, Environments)."""
|
|
assets = CgiAsset.objects.select_related('project').all()
|
|
asset_types = {
|
|
'CHAR': assets.filter(asset_type='CHAR'),
|
|
'PROP': assets.filter(asset_type='PROP'),
|
|
'ENV': assets.filter(asset_type='ENV'),
|
|
}
|
|
return render(request, "core/asset_library.html", {"assets": assets, "asset_types": asset_types})
|
|
|
|
def project_detail(request, slug):
|
|
"""Render the detailed pipeline for a specific production."""
|
|
project = get_object_or_404(Project.objects.prefetch_related('steps', 'assets', 'scenes'), slug=slug)
|
|
return render(request, "core/project_detail.html", {"project": project})
|