STUDIO AI AUTOMÁTICO , iniciar uma Super Produção
This commit is contained in:
parent
e17e54022a
commit
570bafbe47
Binary file not shown.
BIN
core/__pycache__/pexels.cpython-311.pyc
Normal file
BIN
core/__pycache__/pexels.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,33 @@
|
||||
# Generated by Django 5.2.7 on 2026-02-16 02:12
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0004_cgiasset_physical_description_project_category_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='project',
|
||||
name='duration',
|
||||
field=models.CharField(blank=True, default='120 min', max_length=50),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='project',
|
||||
name='estimated_budget',
|
||||
field=models.CharField(blank=True, default='$150M', max_length=100),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='project',
|
||||
name='rating',
|
||||
field=models.CharField(default='PG-13', max_length=10),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='scene',
|
||||
name='image_url',
|
||||
field=models.CharField(blank=True, max_length=500),
|
||||
),
|
||||
]
|
||||
Binary file not shown.
@ -42,6 +42,11 @@ class Project(models.Model):
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
is_ai_generated = models.BooleanField(default=False)
|
||||
|
||||
# New fields for "Super Production"
|
||||
estimated_budget = models.CharField(max_length=100, blank=True, default="$150M")
|
||||
rating = models.CharField(max_length=10, default="PG-13")
|
||||
duration = models.CharField(max_length=50, blank=True, default="120 min")
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.slug:
|
||||
@ -66,6 +71,7 @@ class Scene(models.Model):
|
||||
title = models.CharField(max_length=255)
|
||||
description = models.TextField()
|
||||
visual_prompt = models.TextField(blank=True)
|
||||
image_url = models.CharField(max_length=500, blank=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['number']
|
||||
@ -120,4 +126,4 @@ class CgiAsset(models.Model):
|
||||
assigned_artist = models.CharField(max_length=100, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name} ({self.get_asset_type_display()})"
|
||||
return f"{self.name} ({self.get_asset_type_display()})"
|
||||
|
||||
42
core/pexels.py
Normal file
42
core/pexels.py
Normal file
@ -0,0 +1,42 @@
|
||||
import os
|
||||
import requests
|
||||
from pathlib import Path
|
||||
|
||||
API_KEY = os.getenv("PEXELS_KEY", "Vc99rnmOhHhJAbgGQoKLZtsaIVfkeownoQNbTj78VemUjKh08ZYRbf18")
|
||||
CACHE_DIR = Path("static/images/pexels")
|
||||
|
||||
def fetch_first(query: str, orientation: str = "landscape") -> dict | None:
|
||||
if not API_KEY:
|
||||
return None
|
||||
|
||||
headers = {"Authorization": API_KEY}
|
||||
url = "https://api.pexels.com/v1/search"
|
||||
params = {"query": query, "orientation": orientation, "per_page": 1, "page": 1}
|
||||
|
||||
try:
|
||||
resp = requests.get(url, headers=headers, params=params, timeout=15)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
|
||||
photo = (data.get("photos") or [None])[0]
|
||||
if not photo:
|
||||
return None
|
||||
|
||||
src = photo["src"].get("large2x") or photo["src"].get("large") or photo["src"].get("original")
|
||||
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
target = CACHE_DIR / f"{photo['id']}.jpg"
|
||||
|
||||
if src:
|
||||
img_resp = requests.get(src, timeout=15)
|
||||
img_resp.raise_for_status()
|
||||
target.write_bytes(img_resp.content)
|
||||
|
||||
return {
|
||||
"id": photo["id"],
|
||||
"local_path": f"images/pexels/{photo['id']}.jpg",
|
||||
"photographer": photo.get("photographer"),
|
||||
"photographer_url": photo.get("photographer_url"),
|
||||
}
|
||||
except Exception as e:
|
||||
print(f"Error fetching from Pexels: {e}")
|
||||
return None
|
||||
@ -49,6 +49,41 @@
|
||||
.shadow-neon {
|
||||
box-shadow: 0 0 15px rgba(0, 229, 255, 0.2);
|
||||
}
|
||||
.text-gradient-neon {
|
||||
background: linear-gradient(45deg, var(--electric-cyan), var(--neon-purple));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
.btn-neon-purple {
|
||||
background: var(--neon-purple);
|
||||
color: white;
|
||||
border: none;
|
||||
box-shadow: 0 0 15px rgba(112, 0, 255, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.btn-neon-purple:hover {
|
||||
background: #8221ff;
|
||||
color: white;
|
||||
box-shadow: 0 0 25px rgba(112, 0, 255, 0.5);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
.btn-outline-cyan {
|
||||
border: 2px solid var(--electric-cyan);
|
||||
color: var(--electric-cyan);
|
||||
background: transparent;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.btn-outline-cyan:hover {
|
||||
background: var(--electric-cyan);
|
||||
color: var(--bg-deep);
|
||||
box-shadow: 0 0 20px rgba(0, 229, 255, 0.4);
|
||||
}
|
||||
.glass-card {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 1.5rem;
|
||||
}
|
||||
</style>
|
||||
{% block extra_head %}{% endblock %}
|
||||
</head>
|
||||
@ -67,10 +102,13 @@
|
||||
<a class="nav-link" href="{% url 'home' %}">Command Center</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'asset_library' %}">Asset Library</a>
|
||||
<a class="nav-link" href="{% url 'asset_library' %}">Assets</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'studio_ai' %}">Studio AI</a>
|
||||
<a class="nav-link" href="{% url 'production_library' %}">Biblioteca AI</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link fw-bold text-cyan" href="{% url 'studio_ai' %}"><i class="bi bi-cpu-fill"></i> Studio AI</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@ -81,7 +119,7 @@
|
||||
{% if messages %}
|
||||
<div class="container mt-3">
|
||||
{% for message in messages %}
|
||||
<div class="alert {% if message.tags %}alert-{{ message.tags }}{% endif %} bg-dark border-{{ message.tags }} text-white">
|
||||
<div class="alert {% if message.tags %}alert-{{ message.tags }}{% endif %} bg-dark border-{{ message.tags }} text-white shadow-neon">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
@ -92,7 +130,7 @@
|
||||
|
||||
<footer class="py-5 mt-5 border-top border-secondary border-opacity-10">
|
||||
<div class="container text-center">
|
||||
<p class="text-muted small">© 2026 CGI Virtual Studio. Secure Command Center.</p>
|
||||
<p class="text-muted small">© 2026 CGI Virtual Studio. Powered by AI Technology.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@ -100,4 +138,4 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
{% block extra_js %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
90
core/templates/core/production_library.html
Normal file
90
core/templates/core/production_library.html
Normal file
@ -0,0 +1,90 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container py-5">
|
||||
<div class="d-flex justify-content-between align-items-center mb-5">
|
||||
<div>
|
||||
<h1 class="display-4 fw-bold text-gradient-neon">BIBLIOTECA STUDIO AI</h1>
|
||||
<p class="lead text-muted">Sua coleção de Super Produções geradas por Inteligência Artificial.</p>
|
||||
</div>
|
||||
<a href="{% url 'studio_ai' %}" class="btn btn-neon-purple fw-bold px-4 py-2 rounded-pill">
|
||||
<i class="bi bi-plus-lg me-2"></i> NOVA PRODUÇÃO
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
{% for production in productions %}
|
||||
<div class="col-md-4">
|
||||
<div class="card glass-card border-0 h-100 shadow-sm hover-up overflow-hidden">
|
||||
<div class="position-relative">
|
||||
{% if production.thumbnail_url %}
|
||||
<img src="{% static production.thumbnail_url %}" class="card-img-top" alt="{{ production.title }}" style="height: 250px; object-fit: cover;">
|
||||
{% else %}
|
||||
<div class="bg-dark d-flex align-items-center justify-content-center" style="height: 250px;">
|
||||
<i class="bi bi-film text-secondary display-1"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="position-absolute top-0 start-0 m-3">
|
||||
<span class="badge bg-cyan text-dark fw-bold px-2 py-1">{{ production.get_project_type_display }}</span>
|
||||
</div>
|
||||
<div class="position-absolute bottom-0 end-0 m-3">
|
||||
<span class="badge bg-black bg-opacity-75 text-white fw-bold"><i class="bi bi-clock me-1"></i>{{ production.duration }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-4 text-white">
|
||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||
<h3 class="h4 fw-bold mb-0 text-cyan">{{ production.title }}</h3>
|
||||
<span class="badge border border-secondary text-secondary">{{ production.rating }}</span>
|
||||
</div>
|
||||
<p class="text-muted small mb-3">{{ production.category }} • {{ production.created_at|date:"d/m/Y" }}</p>
|
||||
<p class="card-text text-light-muted mb-4 line-clamp-3">{{ production.description }}</p>
|
||||
<div class="d-grid gap-2">
|
||||
<a href="{% url 'watch_production' production.slug %}" class="btn btn-neon-purple fw-bold rounded-pill">
|
||||
<i class="bi bi-play-fill me-1"></i> ASSISTIR EXPERIÊNCIA
|
||||
</a>
|
||||
<div class="d-flex justify-content-between mt-2">
|
||||
<a href="{% url 'project_detail' production.slug %}" class="btn btn-link btn-sm text-muted text-decoration-none">
|
||||
<i class="bi bi-gear-fill me-1"></i> Pipeline
|
||||
</a>
|
||||
<span class="text-muted small">AI Generation v2.0</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="col-12 text-center py-5">
|
||||
<div class="py-5">
|
||||
<div class="mb-4">
|
||||
<i class="bi bi-collection-play text-muted display-1 opacity-25"></i>
|
||||
</div>
|
||||
<h2 class="text-white fw-bold">Ainda não há produções na sua biblioteca.</h2>
|
||||
<p class="text-muted lead">O Studio AI está pronto para transformar suas ideias em obras-primas.</p>
|
||||
<a href="{% url 'studio_ai' %}" class="btn btn-lg btn-neon-purple mt-3 px-5 py-3 fw-bold rounded-pill">COMEÇAR AGORA</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.hover-up {
|
||||
transition: all 0.4s cubic-bezier(0.165, 0.84, 0.44, 1);
|
||||
}
|
||||
.hover-up:hover {
|
||||
transform: translateY(-12px);
|
||||
box-shadow: 0 20px 40px rgba(0,255,255,0.1) !important;
|
||||
border: 1px solid rgba(0, 255, 255, 0.2) !important;
|
||||
}
|
||||
.text-light-muted {
|
||||
color: #adb5bd;
|
||||
}
|
||||
.line-clamp-3 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
223
core/templates/core/watch_production.html
Normal file
223
core/templates/core/watch_production.html
Normal file
@ -0,0 +1,223 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Header Cinematográfico -->
|
||||
<div class="production-header position-relative overflow-hidden" style="height: 70vh;">
|
||||
{% if project.thumbnail_url %}
|
||||
<img src="{% static project.thumbnail_url %}" class="w-100 h-100 object-fit-cover position-absolute top-0 start-0" alt="{{ project.title }}" style="filter: brightness(0.4) saturate(1.2);">
|
||||
{% else %}
|
||||
<div class="w-100 h-100 bg-dark position-absolute top-0 start-0"></div>
|
||||
{% endif %}
|
||||
|
||||
<div class="container h-100 position-relative d-flex align-items-end pb-5">
|
||||
<div class="text-white">
|
||||
<div class="d-flex align-items-center gap-3 mb-3">
|
||||
<span class="badge bg-cyan text-dark fw-bold px-3 py-2">{{ project.get_project_type_display }}</span>
|
||||
<span class="badge bg-outline-white text-white border border-white px-3 py-2">{{ project.category }}</span>
|
||||
<span class="badge bg-danger fw-bold px-3 py-2">{{ project.rating }}</span>
|
||||
</div>
|
||||
<h1 class="display-1 fw-bold mb-3 text-gradient-neon">{{ project.title }}</h1>
|
||||
<div class="d-flex gap-4 mb-4 text-muted fw-bold">
|
||||
<span><i class="bi bi-clock me-2"></i>{{ project.duration }}</span>
|
||||
<span><i class="bi bi-cash-stack me-2"></i>ORÇAMENTO: {{ project.estimated_budget }}</span>
|
||||
</div>
|
||||
<p class="lead col-lg-8 mb-4 shadow-sm">{{ project.description }}</p>
|
||||
<div class="d-flex gap-3">
|
||||
<button class="btn btn-lg btn-neon-purple px-5 py-3 fw-bold rounded-pill shadow-lg" onclick="startCinemaMode()">
|
||||
<i class="bi bi-play-circle-fill me-2"></i> ASSISTIR AGORA (AI CINEMA)
|
||||
</button>
|
||||
<button class="btn btn-lg btn-outline-light px-5 py-3 fw-bold rounded-pill" onclick="document.getElementById('script-section').scrollIntoView({behavior: 'smooth'})">
|
||||
<i class="bi bi-file-earmark-text me-2"></i> LER ROTEIRO
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container py-5">
|
||||
<!-- Elenco (Personagens) -->
|
||||
<div class="mb-5">
|
||||
<h2 class="text-white border-start border-cyan border-4 ps-3 mb-4 fw-bold">ELENCO PRINCIPAL</h2>
|
||||
<div class="row g-4">
|
||||
{% for asset in project.assets.all %}
|
||||
{% if asset.asset_type == 'CHAR' %}
|
||||
<div class="col-md-4">
|
||||
<div class="card glass-card border-0 h-100 hover-up">
|
||||
<div class="card-body p-4 text-center">
|
||||
<div class="rounded-circle bg-dark d-inline-flex align-items-center justify-content-center mb-3" style="width: 80px; height: 80px;">
|
||||
<i class="bi bi-person-bounding-box text-cyan fs-2"></i>
|
||||
</div>
|
||||
<h4 class="text-cyan fw-bold mb-2">{{ asset.name }}</h4>
|
||||
<p class="text-white-50 small m-0">{{ asset.physical_description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cenas Cinematográficas -->
|
||||
<div class="mb-5" id="scenes-section">
|
||||
<h2 class="text-white border-start border-cyan border-4 ps-3 mb-4 fw-bold">GALERIA DE CENAS</h2>
|
||||
<div class="row g-4">
|
||||
{% for scene in project.scenes.all %}
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="scene-card position-relative overflow-hidden rounded-4 shadow-lg h-100" style="min-height: 300px;">
|
||||
{% if scene.image_url %}
|
||||
<img src="{% static scene.image_url %}" class="w-100 h-100 object-fit-cover position-absolute top-0 start-0 transition-transform" alt="{{ scene.title }}">
|
||||
{% else %}
|
||||
<div class="w-100 h-100 bg-dark position-absolute top-0 start-0"></div>
|
||||
{% endif %}
|
||||
|
||||
<div class="scene-overlay position-absolute bottom-0 start-0 w-100 p-4 bg-gradient-dark">
|
||||
<span class="badge bg-cyan text-dark mb-2">CENA {{ scene.number }}</span>
|
||||
<h5 class="text-white fw-bold mb-2">{{ scene.title }}</h5>
|
||||
<p class="text-light-muted small m-0 line-clamp-3">{{ scene.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Roteiro Completo -->
|
||||
<div id="script-section" class="py-5">
|
||||
<div class="glass-card p-5 rounded-5 shadow-lg border-secondary border-opacity-25 bg-black">
|
||||
<div class="text-center mb-5">
|
||||
<h2 class="text-gradient-neon display-4 fw-bold">ROTEIRO ORIGINAL</h2>
|
||||
<hr class="w-25 mx-auto border-cyan border-2">
|
||||
</div>
|
||||
<div class="screenplay text-white-50" style="font-family: 'Courier New', Courier, monospace; line-height: 1.8; max-width: 850px; margin: 0 auto; white-space: pre-line; font-size: 1.2rem;">
|
||||
{{ project.full_script }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cinema Mode Overlay -->
|
||||
<div id="cinemaOverlay" class="position-fixed top-0 start-0 w-100 h-100 bg-black z-3 d-none flex-column align-items-center justify-content-center text-center p-4">
|
||||
<button class="btn btn-outline-light position-absolute top-0 end-0 m-4 rounded-circle" onclick="stopCinemaMode()">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</button>
|
||||
|
||||
<div id="cinemaContent" class="container">
|
||||
<div id="cinemaImageContainer" class="mb-4 ratio ratio-16x9 mx-auto shadow-2xl rounded-4 overflow-hidden" style="max-width: 900px;">
|
||||
<img src="" id="cinemaImage" class="w-100 h-100 object-fit-cover">
|
||||
</div>
|
||||
<div class="cinema-text text-white">
|
||||
<h2 id="cinemaTitle" class="display-4 fw-bold text-cyan mb-3"></h2>
|
||||
<p id="cinemaDesc" class="lead fs-3"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cinema-controls position-absolute bottom-0 w-100 p-5 d-flex justify-content-center gap-4">
|
||||
<button class="btn btn-dark rounded-circle btn-lg" id="prevScene"><i class="bi bi-chevron-left"></i></button>
|
||||
<div class="d-flex align-items-center text-white-50">
|
||||
CENA <span id="currentSceneNum" class="mx-2 text-white fw-bold">1</span> / {{ project.scenes.count }}
|
||||
</div>
|
||||
<button class="btn btn-dark rounded-circle btn-lg" id="nextScene"><i class="bi bi-chevron-right"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.bg-gradient-dark {
|
||||
background: linear-gradient(transparent, rgba(0,0,0,0.9));
|
||||
}
|
||||
.text-light-muted {
|
||||
color: #ced4da;
|
||||
}
|
||||
.line-clamp-3 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
.scene-card:hover img {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
.transition-transform {
|
||||
transition: transform 0.8s ease;
|
||||
}
|
||||
.hover-up:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
.z-3 { z-index: 9999; }
|
||||
|
||||
/* Animation for cinema mode */
|
||||
#cinemaContent {
|
||||
animation: fadeIn 1s ease-in-out;
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(20px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const scenes = [
|
||||
{% for scene in project.scenes.all %}
|
||||
{
|
||||
title: "{{ scene.title|escapejs }}",
|
||||
description: "{{ scene.description|escapejs }}",
|
||||
image: "{% if scene.image_url %}{% static scene.image_url %}{% endif %}",
|
||||
number: {{ scene.number }}
|
||||
},
|
||||
{% endfor %}
|
||||
];
|
||||
|
||||
let currentSceneIdx = 0;
|
||||
|
||||
function startCinemaMode() {
|
||||
document.getElementById('cinemaOverlay').classList.remove('d-none');
|
||||
document.getElementById('cinemaOverlay').classList.add('d-flex');
|
||||
updateCinemaScene();
|
||||
}
|
||||
|
||||
function stopCinemaMode() {
|
||||
document.getElementById('cinemaOverlay').classList.add('d-none');
|
||||
document.getElementById('cinemaOverlay').classList.remove('d-flex');
|
||||
}
|
||||
|
||||
function updateCinemaScene() {
|
||||
const scene = scenes[currentSceneIdx];
|
||||
document.getElementById('cinemaTitle').innerText = scene.title;
|
||||
document.getElementById('cinemaDesc').innerText = scene.description;
|
||||
document.getElementById('cinemaImage').src = scene.image || '';
|
||||
document.getElementById('currentSceneNum').innerText = scene.number;
|
||||
|
||||
// Animation reset
|
||||
const content = document.getElementById('cinemaContent');
|
||||
content.style.animation = 'none';
|
||||
content.offsetHeight; /* trigger reflow */
|
||||
content.style.animation = null;
|
||||
}
|
||||
|
||||
document.getElementById('nextScene').onclick = () => {
|
||||
if (currentSceneIdx < scenes.length - 1) {
|
||||
currentSceneIdx++;
|
||||
updateCinemaScene();
|
||||
} else {
|
||||
stopCinemaMode();
|
||||
messages.info("Fim da Experiência Cinematográfica.");
|
||||
}
|
||||
};
|
||||
|
||||
document.getElementById('prevScene').onclick = () => {
|
||||
if (currentSceneIdx > 0) {
|
||||
currentSceneIdx--;
|
||||
updateCinemaScene();
|
||||
}
|
||||
};
|
||||
|
||||
// Keyboard controls
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (document.getElementById('cinemaOverlay').classList.contains('d-flex')) {
|
||||
if (e.key === 'ArrowRight') document.getElementById('nextScene').click();
|
||||
if (e.key === 'ArrowLeft') document.getElementById('prevScene').click();
|
||||
if (e.key === 'Escape') stopCinemaMode();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -8,5 +8,7 @@ urlpatterns = [
|
||||
path('assets/', views.asset_library, name='asset_library'),
|
||||
path('studio-ai/', views.studio_ai, name='studio_ai'),
|
||||
path('generate-production/', views.generate_production, name='generate_production'),
|
||||
path('library/', views.production_library, name='production_library'),
|
||||
path('watch/<slug:slug>/', views.watch_production, name='watch_production'),
|
||||
path('project/<slug:slug>/', views.project_detail, name='project_detail'),
|
||||
]
|
||||
]
|
||||
|
||||
@ -7,6 +7,7 @@ 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
|
||||
|
||||
def studio_admin_required(view_func):
|
||||
"""Decorator to restrict access to studio admin only."""
|
||||
@ -47,37 +48,45 @@ def studio_ai(request):
|
||||
|
||||
@studio_admin_required
|
||||
def generate_production(request):
|
||||
"""AI logic to create a full production automatically."""
|
||||
"""AI logic to create a full SUPER PRODUCTION automatically."""
|
||||
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")
|
||||
|
||||
prompt = f"""
|
||||
Create a detailed production plan for a {proj_type} in the {category} category.
|
||||
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. 3 Main Characters with names and detailed physical descriptions (based on REAL humans/actors style).
|
||||
3. 5 Key Scenes with titles and narrative descriptions.
|
||||
2. A full cinematic screenplay (script) for the entire production, ready to be read.
|
||||
3. A short query (2-4 words) for a cinematic visual image representing this production.
|
||||
4. 3 Main Characters with names and detailed physical descriptions (based on REAL humans/actors style).
|
||||
5. 8 Key Scenes with titles, narrative descriptions, and a unique 'visual_query' (2-4 words) for each scene.
|
||||
6. Metadata: Estimated budget (e.g., $200M), Age Rating (e.g., PG-13, R), and Duration (e.g., 140 min).
|
||||
|
||||
Return the result ONLY in JSON format with the following structure:
|
||||
{{
|
||||
"title": "...",
|
||||
"description": "...",
|
||||
"full_script": "...",
|
||||
"thumbnail_query": "...",
|
||||
"budget": "...",
|
||||
"rating": "...",
|
||||
"duration": "...",
|
||||
"characters": [
|
||||
{{"name": "...", "description": "...", "type": "CHAR"}}
|
||||
],
|
||||
"scenes": [
|
||||
{{"title": "...", "description": "..."}}
|
||||
{{"title": "...", "description": "...", "visual_query": "..."}}
|
||||
]
|
||||
}}
|
||||
"""
|
||||
|
||||
response = LocalAIApi.create_response({
|
||||
"input": [
|
||||
{"role": "system", "content": "You are an expert Hollywood Producer and AI Cinema Director."},
|
||||
{"role": "system", "content": "You are an expert Hollywood Producer and AI Cinema Director specialized in Super Productions."},
|
||||
{"role": "user", "content": prompt},
|
||||
],
|
||||
"text": {"format": {"type": "json_object"}},
|
||||
@ -87,20 +96,31 @@ def generate_production(request):
|
||||
try:
|
||||
data = LocalAIApi.decode_json_from_response(response)
|
||||
|
||||
# Fetch thumbnail from Pexels
|
||||
thumbnail_data = fetch_first(data.get('thumbnail_query', data['title']))
|
||||
thumbnail_url = ""
|
||||
if thumbnail_data:
|
||||
thumbnail_url = thumbnail_data['local_path']
|
||||
|
||||
# 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=thumbnail_url,
|
||||
is_ai_generated=True,
|
||||
status='PRE'
|
||||
status='DONE',
|
||||
estimated_budget=data.get('budget', '$150M'),
|
||||
rating=data.get('rating', 'PG-13'),
|
||||
duration=data.get('duration', '120 min')
|
||||
)
|
||||
|
||||
# Create default Pipeline Steps
|
||||
stages = [s[0] for s in PipelineStep.STAGES]
|
||||
for stage in stages:
|
||||
PipelineStep.objects.create(project=project, name=stage, progress=0)
|
||||
PipelineStep.objects.create(project=project, name=stage, progress=100, is_completed=True)
|
||||
|
||||
# Create Characters (Assets)
|
||||
for char in data['characters']:
|
||||
@ -111,17 +131,26 @@ def generate_production(request):
|
||||
physical_description=char['description']
|
||||
)
|
||||
|
||||
# Create Scenes
|
||||
for i, scene in enumerate(data['scenes']):
|
||||
# Create Scenes and fetch images
|
||||
for i, scene_data in enumerate(data['scenes']):
|
||||
scene_image_url = ""
|
||||
# Fetch image for EACH scene
|
||||
v_query = scene_data.get('visual_query', scene_data['title'])
|
||||
scene_img_data = fetch_first(f"{data['title']} {v_query}", orientation="landscape")
|
||||
if scene_img_data:
|
||||
scene_image_url = scene_img_data['local_path']
|
||||
|
||||
Scene.objects.create(
|
||||
project=project,
|
||||
number=i+1,
|
||||
title=scene['title'],
|
||||
description=scene['description']
|
||||
title=scene_data['title'],
|
||||
description=scene_data['description'],
|
||||
visual_prompt=scene_data.get('visual_query', ''),
|
||||
image_url=scene_image_url
|
||||
)
|
||||
|
||||
messages.success(request, f"Super Produção '{project.title}' criada com sucesso pelo Studio AI!")
|
||||
return redirect('project_detail', slug=project.slug)
|
||||
messages.success(request, f"Super Produção '{project.title}' criada e salva na Biblioteca!")
|
||||
return redirect('production_library')
|
||||
|
||||
except Exception as e:
|
||||
messages.error(request, f"Erro ao processar resposta da AI: {str(e)}")
|
||||
@ -130,6 +159,18 @@ def generate_production(request):
|
||||
|
||||
return redirect('studio_ai')
|
||||
|
||||
@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":
|
||||
@ -178,4 +219,4 @@ def project_detail(request, slug):
|
||||
"assets": project.assets.all(),
|
||||
"scenes": project.scenes.all(),
|
||||
}
|
||||
return render(request, "core/project_detail.html", context)
|
||||
return render(request, "core/project_detail.html", context)
|
||||
@ -1,3 +1,4 @@
|
||||
Django==5.2.7
|
||||
mysqlclient==2.2.7
|
||||
python-dotenv==1.1.1
|
||||
httpx
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user