diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 3efa90c..8e9ab81 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 index c9dbc46..69816d0 100644 Binary files a/core/__pycache__/pexels.cpython-311.pyc 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 b7cf384..fecd489 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 ddf0125..0266ed7 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/migrations/0006_cgiasset_voice_preset_project_banner_url_and_more.py b/core/migrations/0006_cgiasset_voice_preset_project_banner_url_and_more.py new file mode 100644 index 0000000..ac9d033 --- /dev/null +++ b/core/migrations/0006_cgiasset_voice_preset_project_banner_url_and_more.py @@ -0,0 +1,63 @@ +# Generated by Django 5.2.7 on 2026-02-16 02:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0005_project_duration_project_estimated_budget_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='cgiasset', + name='voice_preset', + field=models.CharField(choices=[('v_male_1', 'Actor 1 (Adult Male)'), ('v_male_2', 'Actor 2 (Mature Male)'), ('v_female_1', 'Actress 1 (Adult Female)'), ('v_female_2', 'Actress 2 (Young Female)')], default='v_male_1', max_length=50), + ), + migrations.AddField( + model_name='project', + name='banner_url', + field=models.URLField(blank=True, max_length=500), + ), + migrations.AddField( + model_name='project', + name='video_url', + field=models.CharField(blank=True, help_text='Path to generated video file', max_length=500), + ), + migrations.AddField( + model_name='project', + name='voice_preset', + field=models.CharField(choices=[('male_1', 'James (Natural Male)'), ('male_2', 'Robert (Deep Narrative)'), ('female_1', 'Emma (Soft Female)'), ('female_2', 'Sophia (Professional)'), ('robot', 'CGI Assist (Neural)')], default='male_1', max_length=50), + ), + migrations.AddField( + model_name='scene', + name='video_url', + field=models.CharField(blank=True, max_length=500), + ), + migrations.AlterField( + model_name='cgiasset', + name='file_location', + field=models.CharField(blank=True, max_length=500), + ), + migrations.AlterField( + model_name='cgiasset', + name='physical_description', + field=models.TextField(blank=True), + ), + migrations.AlterField( + model_name='pipelinestep', + name='progress', + field=models.PositiveIntegerField(default=0), + ), + migrations.AlterField( + model_name='project', + name='project_type', + field=models.CharField(choices=[('MOVIE', 'Feature Film'), ('SERIES', 'TV Series'), ('DOCUMENTARY', 'Documentary'), ('SHORT', 'Short Film')], default='MOVIE', max_length=20), + ), + migrations.AlterField( + model_name='project', + name='thumbnail_url', + field=models.URLField(blank=True, max_length=500), + ), + ] diff --git a/core/migrations/__pycache__/0006_cgiasset_voice_preset_project_banner_url_and_more.cpython-311.pyc b/core/migrations/__pycache__/0006_cgiasset_voice_preset_project_banner_url_and_more.cpython-311.pyc new file mode 100644 index 0000000..241ef06 Binary files /dev/null and b/core/migrations/__pycache__/0006_cgiasset_voice_preset_project_banner_url_and_more.cpython-311.pyc differ diff --git a/core/models.py b/core/models.py index 9a958fb..48f4d03 100644 --- a/core/models.py +++ b/core/models.py @@ -11,7 +11,6 @@ class StudioConfig(models.Model): def save(self, *args, **kwargs): if not self.admin_access_key: - # Using the key requested by the user as default self.admin_access_key = "61823dbc-ee05-455f-8924-764f15104fc1" super().save(*args, **kwargs) @@ -22,6 +21,7 @@ class Project(models.Model): TYPES = ( ('MOVIE', 'Feature Film'), ('SERIES', 'TV Series'), + ('DOCUMENTARY', 'Documentary'), ('SHORT', 'Short Film'), ) STATUS_CHOICES = ( @@ -30,15 +30,25 @@ class Project(models.Model): ('POST', 'Post-Production'), ('DONE', 'Completed'), ) + VOICE_CHOICES = ( + ('male_1', 'James (Natural Male)'), + ('male_2', 'Robert (Deep Narrative)'), + ('female_1', 'Emma (Soft Female)'), + ('female_2', 'Sophia (Professional)'), + ('robot', 'CGI Assist (Neural)'), + ) title = models.CharField(max_length=255) slug = models.SlugField(unique=True, blank=True) - project_type = models.CharField(max_length=10, choices=TYPES, default='MOVIE') + project_type = models.CharField(max_length=20, choices=TYPES, default='MOVIE') category = models.CharField(max_length=100, default='Sci-Fi') status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='PRE') description = models.TextField(blank=True) full_script = models.TextField(blank=True) - thumbnail_url = models.URLField(blank=True, help_text="URL to a representative image") + thumbnail_url = models.URLField(blank=True, max_length=500) + banner_url = models.URLField(blank=True, max_length=500) + video_url = models.CharField(max_length=500, blank=True, help_text="Path to generated video file") + voice_preset = models.CharField(max_length=50, choices=VOICE_CHOICES, default='male_1') created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) is_ai_generated = models.BooleanField(default=False) @@ -72,6 +82,7 @@ class Scene(models.Model): description = models.TextField() visual_prompt = models.TextField(blank=True) image_url = models.CharField(max_length=500, blank=True) + video_url = models.CharField(max_length=500, blank=True) class Meta: ordering = ['number'] @@ -81,25 +92,22 @@ class Scene(models.Model): class PipelineStep(models.Model): STAGES = ( - # Pre-Production ('SCRIPT', 'Roteiro & Storyboard'), ('CONCEPT', 'Concept Art'), ('ANIMATIC', 'Animatic'), - # Production ('MODELING', 'Modelagem 3D'), ('TEXTURING', 'Texturização'), ('RIGGING', 'Rigging'), ('ANIMATION', 'Animação'), ('LIGHTING', 'Iluminação'), ('FX', 'Simulação (FX)'), - # Post-Production ('RENDERING', 'Renderização'), ('COMPOSITING', 'Composição'), ('EDITING', 'Edição & Sonoplastia'), ) project = models.ForeignKey(Project, related_name='steps', on_delete=models.CASCADE) name = models.CharField(max_length=20, choices=STAGES) - progress = models.PositiveIntegerField(default=0, help_text="Progress from 0 to 100") + progress = models.PositiveIntegerField(default=0) is_completed = models.BooleanField(default=False) updated_at = models.DateTimeField(auto_now=True) @@ -115,15 +123,22 @@ class CgiAsset(models.Model): ('PROP', 'Prop'), ('ENV', 'Environment'), ) + VOICE_CHOICES = ( + ('v_male_1', 'Actor 1 (Adult Male)'), + ('v_male_2', 'Actor 2 (Mature Male)'), + ('v_female_1', 'Actress 1 (Adult Female)'), + ('v_female_2', 'Actress 2 (Young Female)'), + ) project = models.ForeignKey(Project, related_name='assets', on_delete=models.CASCADE) name = models.CharField(max_length=255) asset_type = models.CharField(max_length=10, choices=ASSET_TYPES) is_realistic = models.BooleanField(default=True) - physical_description = models.TextField(blank=True, help_text="Detailed physical appearance based on real humans") + physical_description = models.TextField(blank=True) + voice_preset = models.CharField(max_length=50, choices=VOICE_CHOICES, default='v_male_1') current_stage = models.CharField(max_length=100, default='Modeling') version = models.PositiveIntegerField(default=1) - file_location = models.CharField(max_length=500, blank=True, help_text="Path or URL to the digital file") + file_location = models.CharField(max_length=500, blank=True) 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()})" \ No newline at end of file diff --git a/core/pexels.py b/core/pexels.py index 3be1c33..c8736af 100644 --- a/core/pexels.py +++ b/core/pexels.py @@ -3,7 +3,8 @@ import requests from pathlib import Path API_KEY = os.getenv("PEXELS_KEY", "Vc99rnmOhHhJAbgGQoKLZtsaIVfkeownoQNbTj78VemUjKh08ZYRbf18") -CACHE_DIR = Path("static/images/pexels") +IMAGE_CACHE_DIR = Path("static/images/pexels") +VIDEO_CACHE_DIR = Path("static/videos/pexels") def fetch_first(query: str, orientation: str = "landscape") -> dict | None: if not API_KEY: @@ -23,10 +24,10 @@ def fetch_first(query: str, orientation: str = "landscape") -> dict | None: 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" + IMAGE_CACHE_DIR.mkdir(parents=True, exist_ok=True) + target = IMAGE_CACHE_DIR / f"{photo['id']}.jpg" - if src: + if src and not target.exists(): img_resp = requests.get(src, timeout=15) img_resp.raise_for_status() target.write_bytes(img_resp.content) @@ -35,8 +36,56 @@ def fetch_first(query: str, orientation: str = "landscape") -> dict | None: "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}") + print(f"Error fetching image from Pexels: {e}") return None + +def fetch_video(query: str, orientation: str = "landscape") -> dict | None: + if not API_KEY: + return None + + headers = {"Authorization": API_KEY} + url = "https://api.pexels.com/videos/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() + + video_data = (data.get("videos") or [None])[0] + if not video_data: + return None + + # Get the best HD video link + video_files = video_data.get("video_files", []) + # Prefer HD/Full HD mp4 + best_file = None + for f in video_files: + if f.get("file_type") == "video/mp4": + if not best_file or f.get("width", 0) > best_file.get("width", 0): + best_file = f + + if not best_file: + return None + + src = best_file["link"] + VIDEO_CACHE_DIR.mkdir(parents=True, exist_ok=True) + target = VIDEO_CACHE_DIR / f"{video_data['id']}.mp4" + + if src and not target.exists(): + vid_resp = requests.get(src, timeout=30, stream=True) + vid_resp.raise_for_status() + with open(target, 'wb') as f: + for chunk in vid_resp.iter_content(chunk_size=8192): + f.write(chunk) + + return { + "id": video_data["id"], + "local_path": f"videos/pexels/{video_data['id']}.mp4", + "user": video_data.get("user", {}).get("name"), + } + except Exception as e: + print(f"Error fetching video from Pexels: {e}") + return None \ No newline at end of file diff --git a/core/templates/core/edit_production.html b/core/templates/core/edit_production.html new file mode 100644 index 0000000..5cb339c --- /dev/null +++ b/core/templates/core/edit_production.html @@ -0,0 +1,63 @@ +{% extends 'base.html' %} +{% load static %} + +{% block content %} +
+
+
+
+
+

Editar Produção

+
+
+
+ {% csrf_token %} + +
+ + +
+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ CANCELAR + +
+
+
+
+
+
+
+ + +{% endblock %} diff --git a/core/templates/core/production_library.html b/core/templates/core/production_library.html index 9c98bed..7aae47f 100644 --- a/core/templates/core/production_library.html +++ b/core/templates/core/production_library.html @@ -2,89 +2,108 @@ {% load static %} {% block content %} -
+
-

BIBLIOTECA STUDIO AI

-

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

+

Biblioteca CGI Studio

+

Minhas Super Produções Geradas por IA

- - NOVA PRODUÇÃO + + Criar Nova Produção
+ {% if productions %}
- {% for production in productions %} -
-
-
- {% if production.thumbnail_url %} - {{ production.title }} + {% for prod in productions %} +
+
+
+ {% if prod.thumbnail_url %} + {{ prod.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 }}

- -
- {% empty %} -
-
-
- +
+
+ {{ prod.category }} + {{ prod.rating }} +
+
{{ prod.title }}
+
+ {{ prod.duration }} + {{ prod.estimated_budget }} +
-

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 %}
+ {% else %} +
+
+ +

Nenhuma produção gerada ainda.

+

Vá ao Studio AI para criar sua primeira obra prima!

+ IR AO STUDIO AI +
+
+ {% endif %}
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/core/templates/core/studio_ai.html b/core/templates/core/studio_ai.html index ebe8adc..98fc83c 100644 --- a/core/templates/core/studio_ai.html +++ b/core/templates/core/studio_ai.html @@ -4,76 +4,104 @@ {% block content %}
-
-
+
+
+
+

🚀 Studio AI Automático

+

CGI Studio - Cinema Digital Inteligente

+
-
- -

STUDIO AI AUTOMÁTICO

-

Crie Super-Produções (Filmes e Séries) totalmente automatizadas com IA.

-
- -
+ {% csrf_token %} +
- - + + + +
- - + +
- - -
A IA criará personagens reais, cenas e roteiro completo com base nisso.
+ + +
Vozes baseadas em padrões reais de cinema.
-
-
- -
-

A IA está gerando o roteiro, personagens e cenas... Aguarde o processamento cinematográfico.

+ +
+
+

A IA está Gerando sua Super Produção...

+

Criando roteiro, escalando personagens, gerando vídeos cinematográficos e integrando vozes reais.

+

Isso pode levar até 2 minutos devido ao processamento de vídeo.

+ +
-{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/watch_production.html b/core/templates/core/watch_production.html index b907d8d..8227e3b 100644 --- a/core/templates/core/watch_production.html +++ b/core/templates/core/watch_production.html @@ -2,222 +2,265 @@ {% 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' %} -
-
-
-
- + +
+ + +
+ +
+
+ {% for scene in project.scenes.all %} +
+
+ {% if scene.image_url %} + {{ scene.title }} + {% else %} +
+ +
+ {% endif %} +
+ Cena {{ scene.number }} +
{{ scene.title }}
+
+ VER VÍDEO +
+
-

{{ asset.name }}

-

{{ asset.physical_description }}

+ {% endfor %} +
+
+ + +
+
+ {% for char in project.assets.all %} +
+
+
+
+ +
+
{{ char.name }}
+

Protagonista CGI

+

{{ char.physical_description|truncatechars:100 }}

+ +
+
+
+ {% endfor %}
- {% 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 }} + +
+
+

Roteiro Cinematográfico

+
+
{{ project.full_script }}
- -
- - -
-
- + + - - -{% endblock %} \ No newline at end of file + + +{% endblock %} diff --git a/core/urls.py b/core/urls.py index 170c09e..c494356 100644 --- a/core/urls.py +++ b/core/urls.py @@ -5,10 +5,15 @@ urlpatterns = [ path('', views.home, name='home'), path('admin-login/', views.admin_login, name='admin_login'), path('admin-logout/', views.admin_logout, name='admin_logout'), - 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('studio-ai/generate/', views.generate_production, name='generate_production'), + path('library/', views.production_library, name='production_library'), - path('watch//', views.watch_production, name='watch_production'), + path('library/watch//', views.watch_production, name='watch_production'), + path('library/edit//', views.edit_production, name='edit_production'), + path('library/delete//', views.delete_production, name='delete_production'), + + path('assets/', views.asset_library, name='asset_library'), 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 0232785..da5c2f0 100644 --- a/core/views.py +++ b/core/views.py @@ -7,7 +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 +from .pexels import fetch_first, fetch_video def studio_admin_required(view_func): """Decorator to restrict access to studio admin only.""" @@ -44,15 +44,20 @@ def home(request): @studio_admin_required def studio_ai(request): """Page to configure and launch AI-automated productions.""" - return render(request, "core/studio_ai.html") + 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 automatically.""" + """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. @@ -60,33 +65,33 @@ def generate_production(request): Requirements: 1. Unique Title and a compelling description. - 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). + 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 the result ONLY in JSON format with the following structure: + Return JSON: {{ "title": "...", "description": "...", "full_script": "...", - "thumbnail_query": "...", + "banner_query": "...", "budget": "...", "rating": "...", "duration": "...", "characters": [ - {{"name": "...", "description": "...", "type": "CHAR"}} + {{"name": "...", "description": "...", "voice_preset": "..."}} ], "scenes": [ - {{"title": "...", "description": "...", "visual_query": "..."}} + {{"title": "...", "description": "...", "video_query": "..."}} ] }} """ response = LocalAIApi.create_response({ "input": [ - {"role": "system", "content": "You are an expert Hollywood Producer and AI Cinema Director specialized in Super Productions."}, + {"role": "system", "content": "You are a Hollywood AI Director creating cinematic video-based productions."}, {"role": "user", "content": prompt}, ], "text": {"format": {"type": "json_object"}}, @@ -96,11 +101,9 @@ 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'] + # 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( @@ -109,56 +112,93 @@ def generate_production(request): category=category, description=data['description'], full_script=data.get('full_script', ''), - thumbnail_url=thumbnail_url, + thumbnail_url=banner_url, + banner_url=banner_url, is_ai_generated=True, status='DONE', - estimated_budget=data.get('budget', '$150M'), + voice_preset=voice_preset, + estimated_budget=data.get('budget', '$200M'), rating=data.get('rating', 'PG-13'), - duration=data.get('duration', '120 min') + duration=data.get('duration', '130 min') ) # Create default Pipeline Steps - stages = [s[0] for s in PipelineStep.STAGES] - for stage in stages: + for stage in [s[0] for s in PipelineStep.STAGES]: PipelineStep.objects.create(project=project, name=stage, progress=100, is_completed=True) - # Create Characters (Assets) + # Characters for char in data['characters']: CgiAsset.objects.create( project=project, name=char['name'], asset_type='CHAR', - physical_description=char['description'] + physical_description=char['description'], + voice_preset=char.get('voice_preset', 'v_male_1') ) - # Create Scenes and fetch images + # Scenes + Videos 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'] + 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=scene_data.get('visual_query', ''), - image_url=scene_image_url + visual_prompt=v_query, + image_url=image_path, + video_url=video_path ) - messages.success(request, f"Super Produção '{project.title}' criada e salva na Biblioteca!") + # 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 resposta da AI: {str(e)}") + messages.error(request, f"Erro ao processar: {str(e)}") else: - messages.error(request, f"Erro na API de IA: {response.get('error')}") + 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.""" @@ -202,21 +242,9 @@ def asset_library(request): 'PROP': assets.filter(asset_type='PROP'), 'ENV': assets.filter(asset_type='ENV'), } - - context = { - "assets": assets, - "asset_types": asset_types, - } - return render(request, "core/asset_library.html", context) + 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) - - context = { - "project": project, - "steps": project.steps.all(), - "assets": project.assets.all(), - "scenes": project.scenes.all(), - } - return render(request, "core/project_detail.html", context) \ No newline at end of file + return render(request, "core/project_detail.html", {"project": project}) diff --git a/static/images/pexels/13245968.jpg b/static/images/pexels/13245968.jpg new file mode 100644 index 0000000..c1cb0c5 Binary files /dev/null and b/static/images/pexels/13245968.jpg differ diff --git a/static/images/pexels/13595070.jpg b/static/images/pexels/13595070.jpg new file mode 100644 index 0000000..5aa81bd Binary files /dev/null and b/static/images/pexels/13595070.jpg differ diff --git a/static/images/pexels/25000648.jpg b/static/images/pexels/25000648.jpg new file mode 100644 index 0000000..5f0e1c7 Binary files /dev/null and b/static/images/pexels/25000648.jpg differ diff --git a/static/images/pexels/31730155.jpg b/static/images/pexels/31730155.jpg new file mode 100644 index 0000000..2c6e619 Binary files /dev/null and b/static/images/pexels/31730155.jpg differ diff --git a/static/images/pexels/33987758.jpg b/static/images/pexels/33987758.jpg new file mode 100644 index 0000000..2943344 Binary files /dev/null and b/static/images/pexels/33987758.jpg differ diff --git a/static/images/pexels/34177179.jpg b/static/images/pexels/34177179.jpg new file mode 100644 index 0000000..56744ca Binary files /dev/null and b/static/images/pexels/34177179.jpg differ diff --git a/static/images/pexels/3760790.jpg b/static/images/pexels/3760790.jpg new file mode 100644 index 0000000..85f8d1b Binary files /dev/null and b/static/images/pexels/3760790.jpg differ diff --git a/static/images/pexels/5504684.jpg b/static/images/pexels/5504684.jpg new file mode 100644 index 0000000..7d89539 Binary files /dev/null and b/static/images/pexels/5504684.jpg differ diff --git a/static/images/pexels/5965516.jpg b/static/images/pexels/5965516.jpg new file mode 100644 index 0000000..d831f7a Binary files /dev/null and b/static/images/pexels/5965516.jpg differ diff --git a/static/images/pexels/6828563.jpg b/static/images/pexels/6828563.jpg new file mode 100644 index 0000000..514f2bf Binary files /dev/null and b/static/images/pexels/6828563.jpg differ diff --git a/static/images/pexels/8371738.jpg b/static/images/pexels/8371738.jpg new file mode 100644 index 0000000..4b1d779 Binary files /dev/null and b/static/images/pexels/8371738.jpg differ diff --git a/static/images/pexels/8898601.jpg b/static/images/pexels/8898601.jpg new file mode 100644 index 0000000..9030e18 Binary files /dev/null and b/static/images/pexels/8898601.jpg differ