diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 8c4b496..3efa90c 100644 Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ diff --git a/core/__pycache__/pexels.cpython-311.pyc b/core/__pycache__/pexels.cpython-311.pyc new file mode 100644 index 0000000..c9dbc46 Binary files /dev/null and b/core/__pycache__/pexels.cpython-311.pyc differ diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index fbac9b4..b7cf384 100644 Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 984f4c3..ddf0125 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/migrations/0005_project_duration_project_estimated_budget_and_more.py b/core/migrations/0005_project_duration_project_estimated_budget_and_more.py new file mode 100644 index 0000000..087e4a8 --- /dev/null +++ b/core/migrations/0005_project_duration_project_estimated_budget_and_more.py @@ -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), + ), + ] diff --git a/core/migrations/__pycache__/0005_project_duration_project_estimated_budget_and_more.cpython-311.pyc b/core/migrations/__pycache__/0005_project_duration_project_estimated_budget_and_more.cpython-311.pyc new file mode 100644 index 0000000..072b818 Binary files /dev/null and b/core/migrations/__pycache__/0005_project_duration_project_estimated_budget_and_more.cpython-311.pyc differ diff --git a/core/models.py b/core/models.py index b24f06b..9a958fb 100644 --- a/core/models.py +++ b/core/models.py @@ -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()})" \ No newline at end of file + return f"{self.name} ({self.get_asset_type_display()})" diff --git a/core/pexels.py b/core/pexels.py new file mode 100644 index 0000000..3be1c33 --- /dev/null +++ b/core/pexels.py @@ -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 diff --git a/core/templates/base.html b/core/templates/base.html index e035667..5d2a961 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -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; + } {% block extra_head %}{% endblock %} @@ -67,10 +102,13 @@ Command Center + @@ -81,7 +119,7 @@ {% if messages %}
{% for message in messages %} -
+
{{ message }}
{% endfor %} @@ -92,7 +130,7 @@
-

© 2026 CGI Virtual Studio. Secure Command Center.

+

© 2026 CGI Virtual Studio. Powered by AI Technology.

@@ -100,4 +138,4 @@ {% block extra_js %}{% endblock %} - + \ No newline at end of file diff --git a/core/templates/core/production_library.html b/core/templates/core/production_library.html new file mode 100644 index 0000000..9c98bed --- /dev/null +++ b/core/templates/core/production_library.html @@ -0,0 +1,90 @@ +{% extends 'base.html' %} +{% load static %} + +{% block content %} +
+
+
+

BIBLIOTECA STUDIO AI

+

Sua coleção de Super Produções geradas por Inteligência Artificial.

+
+ + NOVA PRODUÇÃO + +
+ +
+ {% for production in productions %} +
+
+
+ {% if production.thumbnail_url %} + {{ production.title }} + {% else %} +
+ +
+ {% endif %} +
+ {{ production.get_project_type_display }} +
+
+ {{ production.duration }} +
+
+
+
+

{{ production.title }}

+ {{ production.rating }} +
+

{{ production.category }} • {{ production.created_at|date:"d/m/Y" }}

+

{{ production.description }}

+
+ + ASSISTIR EXPERIÊNCIA + +
+ + Pipeline + + AI Generation v2.0 +
+
+
+
+
+ {% empty %} +
+
+
+ +
+

Ainda não há produções na sua biblioteca.

+

O Studio AI está pronto para transformar suas ideias em obras-primas.

+ COMEÇAR AGORA +
+
+ {% endfor %} +
+
+ + +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/watch_production.html b/core/templates/core/watch_production.html new file mode 100644 index 0000000..b907d8d --- /dev/null +++ b/core/templates/core/watch_production.html @@ -0,0 +1,223 @@ +{% extends 'base.html' %} +{% load static %} + +{% block content %} + +
+ {% if project.thumbnail_url %} + {{ project.title }} + {% else %} +
+ {% endif %} + +
+
+
+ {{ project.get_project_type_display }} + {{ project.category }} + {{ project.rating }} +
+

{{ project.title }}

+
+ {{ project.duration }} + ORÇAMENTO: {{ project.estimated_budget }} +
+

{{ project.description }}

+
+ + +
+
+
+
+ +
+ +
+

ELENCO PRINCIPAL

+
+ {% for asset in project.assets.all %} + {% if asset.asset_type == 'CHAR' %} +
+
+
+
+ +
+

{{ asset.name }}

+

{{ asset.physical_description }}

+
+
+
+ {% endif %} + {% endfor %} +
+
+ + +
+

GALERIA DE CENAS

+
+ {% for scene in project.scenes.all %} +
+
+ {% if scene.image_url %} + {{ scene.title }} + {% else %} +
+ {% endif %} + +
+ CENA {{ scene.number }} +
{{ scene.title }}
+

{{ scene.description }}

+
+
+
+ {% endfor %} +
+
+ + +
+
+
+

ROTEIRO ORIGINAL

+
+
+
+ {{ project.full_script }} +
+
+
+
+ + +
+ + +
+
+ +
+
+

+

+
+
+ +
+ +
+ CENA 1 / {{ project.scenes.count }} +
+ +
+
+ + + + +{% endblock %} \ No newline at end of file diff --git a/core/urls.py b/core/urls.py index 9faee05..170c09e 100644 --- a/core/urls.py +++ b/core/urls.py @@ -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//', views.watch_production, name='watch_production'), path('project//', views.project_detail, name='project_detail'), -] \ No newline at end of file +] diff --git a/core/views.py b/core/views.py index 0f8f1ef..0232785 100644 --- a/core/views.py +++ b/core/views.py @@ -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) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index e22994c..80c2164 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ Django==5.2.7 mysqlclient==2.2.7 python-dotenv==1.1.1 +httpx