cngsm commited on
Commit
0140812
·
verified ·
1 Parent(s): f54fd04

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +43 -0
  2. video_analyzer_keyframes.py +120 -0
app.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import gradio as gr
3
+ import os
4
+ from video_analyzer_keyframes import VideoSceneAnalyzer
5
+ import shutil
6
+
7
+ def process_video_keyframes(video_file):
8
+ video_path = video_file
9
+ output_dir = "output"
10
+
11
+ if not os.path.exists(video_path):
12
+ return "❌ Erro: vídeo não encontrado.", None
13
+
14
+ try:
15
+ analyzer = VideoSceneAnalyzer(video_path)
16
+ if not analyzer.load_video():
17
+ return "❌ Erro ao carregar vídeo", None
18
+
19
+ analyzer.extract_keyframes()
20
+ result_file = analyzer.save_results()
21
+ analyzer.cleanup()
22
+
23
+ if not os.path.exists(output_dir):
24
+ os.makedirs(output_dir)
25
+ shutil.copy(result_file, os.path.join(output_dir, os.path.basename(result_file)))
26
+
27
+ return f"✅ Análise por quadros-chave concluída! Resultados salvos em: {result_file}", os.path.join(output_dir, os.path.basename(result_file))
28
+ except Exception as e:
29
+ return f"❌ Erro inesperado: {e}", None
30
+
31
+ with gr.Blocks(title="🎬 Veo3 Keyframe Prompt Generator") as demo:
32
+ gr.Markdown("## 🎞️ Geração de prompts cinematográficos a partir de quadros-chave do vídeo")
33
+
34
+ with gr.Row():
35
+ video_input = gr.Video(label="📹 Envie seu vídeo (MP4)")
36
+
37
+ submit_btn = gr.Button("🚀 Analisar quadros-chave")
38
+ output_text = gr.Textbox(label="📝 Resultado")
39
+ output_file = gr.File(label="📄 JSON com prompts", visible=True)
40
+
41
+ submit_btn.click(fn=process_video_keyframes, inputs=[video_input], outputs=[output_text, output_file])
42
+
43
+ demo.launch()
video_analyzer_keyframes.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import moviepy.editor as mp
3
+ import cv2
4
+ import numpy as np
5
+ from PIL import Image
6
+ import os
7
+ import json
8
+ from datetime import datetime
9
+ from transformers import BlipProcessor, BlipForConditionalGeneration
10
+ import torch
11
+
12
+ # BLIP setup
13
+ blip_processor = BlipProcessor.from_pretrained("Salesforce/blip-image-captioning-base")
14
+ blip_model = BlipForConditionalGeneration.from_pretrained("Salesforce/blip-image-captioning-base")
15
+
16
+ class VideoSceneAnalyzer:
17
+ def __init__(self, video_path, scene_duration=8):
18
+ self.video_path = video_path
19
+ self.scene_duration = scene_duration
20
+ self.clip = None
21
+ self.video_info = {}
22
+ self.scenes = []
23
+
24
+ def load_video(self):
25
+ try:
26
+ self.clip = mp.VideoFileClip(self.video_path)
27
+ self.video_info = {
28
+ 'duration': self.clip.duration,
29
+ 'fps': self.clip.fps,
30
+ 'size': self.clip.size,
31
+ 'aspect_ratio': self.clip.size[0] / self.clip.size[1],
32
+ 'total_frames': int(self.clip.duration * self.clip.fps)
33
+ }
34
+ print(f"✅ Vídeo carregado: {self.video_info}")
35
+ return True
36
+ except Exception as e:
37
+ print(f"❌ Erro ao carregar vídeo: {e}")
38
+ return False
39
+
40
+ def describe_image_and_generate_prompt(self, frame, scene_number):
41
+ temp_img_path = f"temp_scene_{scene_number:02d}.jpg"
42
+ Image.fromarray(np.uint8(frame)).save(temp_img_path)
43
+ image = Image.open(temp_img_path).convert("RGB")
44
+ inputs = blip_processor(images=image, return_tensors="pt")
45
+ out = blip_model.generate(**inputs)
46
+ caption = blip_processor.decode(out[0], skip_special_tokens=True).strip().capitalize()
47
+
48
+ descricao = f"Imagem da cena {scene_number}: {caption}."
49
+ prompt = (
50
+ f"A cinematic, naturalistic shot showing: {caption}. "
51
+ "Captured with shallow depth of field, soft natural light, and handheld motion. "
52
+ "Realistic skin texture, clean background separation, true-to-life tone. "
53
+ "--ar 16:9 --v 6 --style photorealistic --quality 2"
54
+ )
55
+ negative_prompt = "--no (CGI skin, artificial lighting, cartoon textures, overexposed highlights, static pose, low detail)"
56
+ return descricao, prompt, negative_prompt
57
+
58
+ def extract_keyframes(self, threshold=30.0):
59
+ print("🔍 Iniciando extração de quadros-chave inteligentes...")
60
+ output_dir = f"keyframes_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
61
+ os.makedirs(output_dir, exist_ok=True)
62
+
63
+ cap = cv2.VideoCapture(self.video_path)
64
+ prev_frame = None
65
+ saved_count = 0
66
+ success, frame = cap.read()
67
+
68
+ while success:
69
+ gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
70
+ if prev_frame is None:
71
+ diff = float('inf')
72
+ else:
73
+ diff = np.mean(cv2.absdiff(gray, prev_frame))
74
+
75
+ if prev_frame is None or diff > threshold:
76
+ timestamp = cap.get(cv2.CAP_PROP_POS_MSEC) / 1000.0
77
+ img_path = os.path.join(output_dir, f"keyframe_{saved_count+1:02d}.jpg")
78
+ Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)).save(img_path)
79
+ descricao, prompt, negative_prompt = self.describe_image_and_generate_prompt(
80
+ cv2.cvtColor(frame, cv2.COLOR_BGR2RGB), saved_count + 1
81
+ )
82
+ scene_info = {
83
+ 'scene_number': saved_count + 1,
84
+ 'time': timestamp,
85
+ 'image_path': img_path,
86
+ 'descricao_detalhada': descricao,
87
+ 'prompt_ia': prompt,
88
+ 'negative_prompt': negative_prompt
89
+ }
90
+ self.scenes.append(scene_info)
91
+ saved_count += 1
92
+ print(f"✅ Keyframe {saved_count} salvo (diferença: {diff:.2f})")
93
+
94
+ prev_frame = gray
95
+ success, frame = cap.read()
96
+
97
+ cap.release()
98
+ print(f"🎞️ {saved_count} quadros-chave extraídos com sucesso.")
99
+ return True
100
+
101
+ def save_results(self, output_file=None):
102
+ if not output_file:
103
+ output_file = f"video_analysis_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
104
+
105
+ results = {
106
+ 'video_info': self.video_info,
107
+ 'analysis_type': "keyframe_extraction",
108
+ 'scenes': self.scenes,
109
+ 'generated_at': datetime.now().isoformat()
110
+ }
111
+
112
+ with open(output_file, 'w', encoding='utf-8') as f:
113
+ json.dump(results, f, indent=2, ensure_ascii=False)
114
+
115
+ print(f"💾 Resultados salvos em: {output_file}")
116
+ return output_file
117
+
118
+ def cleanup(self):
119
+ if self.clip:
120
+ self.clip.close()