Spaces:
Runtime error
Runtime error
Update video_analyzer_keyframes.py
Browse files- video_analyzer_keyframes.py +44 -43
video_analyzer_keyframes.py
CHANGED
|
@@ -31,88 +31,89 @@ class VideoSceneAnalyzer:
|
|
| 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"
|
| 38 |
return False
|
| 39 |
|
| 40 |
-
def describe_image_and_generate_prompt(self, frame,
|
| 41 |
-
|
| 42 |
-
Image.fromarray(np.uint8(frame)).save(
|
| 43 |
-
|
| 44 |
-
|
|
|
|
| 45 |
out = blip_model.generate(**inputs)
|
| 46 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
-
descricao = f"Imagem da cena {scene_number}: {caption}."
|
| 49 |
prompt = (
|
| 50 |
-
f"A cinematic,
|
| 51 |
-
"Captured with
|
| 52 |
-
"
|
|
|
|
|
|
|
| 53 |
"--ar 16:9 --v 6 --style photorealistic --quality 2"
|
| 54 |
)
|
| 55 |
-
|
| 56 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 65 |
-
|
| 66 |
success, frame = cap.read()
|
|
|
|
| 67 |
|
| 68 |
while success:
|
| 69 |
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
| 70 |
-
if
|
| 71 |
-
|
| 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_{
|
| 78 |
Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)).save(img_path)
|
| 79 |
-
|
| 80 |
-
cv2.cvtColor(frame, cv2.COLOR_BGR2RGB),
|
| 81 |
)
|
| 82 |
-
|
| 83 |
-
'scene_number':
|
| 84 |
'time': timestamp,
|
| 85 |
'image_path': img_path,
|
| 86 |
-
'descricao_detalhada':
|
| 87 |
-
'prompt_ia':
|
| 88 |
-
'negative_prompt':
|
| 89 |
-
}
|
| 90 |
-
|
| 91 |
-
|
| 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':
|
| 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):
|
|
|
|
| 31 |
'aspect_ratio': self.clip.size[0] / self.clip.size[1],
|
| 32 |
'total_frames': int(self.clip.duration * self.clip.fps)
|
| 33 |
}
|
|
|
|
| 34 |
return True
|
| 35 |
except Exception as e:
|
| 36 |
+
print(f"Erro ao carregar vídeo: {e}")
|
| 37 |
return False
|
| 38 |
|
| 39 |
+
def describe_image_and_generate_prompt(self, frame, idx):
|
| 40 |
+
tmp_path = f"temp_{idx:02d}.jpg"
|
| 41 |
+
Image.fromarray(np.uint8(frame)).save(tmp_path)
|
| 42 |
+
img = Image.open(tmp_path).convert("RGB")
|
| 43 |
+
|
| 44 |
+
inputs = blip_processor(images=img, return_tensors="pt")
|
| 45 |
out = blip_model.generate(**inputs)
|
| 46 |
+
cap = blip_processor.decode(out[0], skip_special_tokens=True).strip().capitalize()
|
| 47 |
+
|
| 48 |
+
descricao = (
|
| 49 |
+
f"Cena {idx}: {cap}. "
|
| 50 |
+
"Registrada ao entardecer com luz dourada suave, profundidade de campo rasa desfocando o fundo. "
|
| 51 |
+
"O ambiente apresenta uma paisagem urbana vibrante com vegetação e construções ao longe, "
|
| 52 |
+
"enquanto o sujeito exibe expressão serena e postura confiante. "
|
| 53 |
+
"Atmosfera de leve nostalgia e contemplação."
|
| 54 |
+
)
|
| 55 |
|
|
|
|
| 56 |
prompt = (
|
| 57 |
+
f"A cinematic, warm golden-hour shot of {cap}. "
|
| 58 |
+
"Captured with a 35mm lens at f/1.8 for shallow depth of field, "
|
| 59 |
+
"soft backlighting, and gentle handheld motion. "
|
| 60 |
+
"Background features a vibrant urban setting with trees and distant buildings, "
|
| 61 |
+
"evoking a sense of nostalgia and calm. "
|
| 62 |
"--ar 16:9 --v 6 --style photorealistic --quality 2"
|
| 63 |
)
|
| 64 |
+
|
| 65 |
+
negative = (
|
| 66 |
+
"--no (CGI artifacts, plastic textures, overexposed skies, cartoonish colors, static poses, low detail)"
|
| 67 |
+
)
|
| 68 |
+
|
| 69 |
+
return descricao, prompt, negative
|
| 70 |
|
| 71 |
def extract_keyframes(self, threshold=30.0):
|
|
|
|
| 72 |
output_dir = f"keyframes_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
| 73 |
os.makedirs(output_dir, exist_ok=True)
|
| 74 |
|
| 75 |
cap = cv2.VideoCapture(self.video_path)
|
| 76 |
+
prev = None
|
| 77 |
+
count = 0
|
| 78 |
success, frame = cap.read()
|
| 79 |
+
self.scenes = []
|
| 80 |
|
| 81 |
while success:
|
| 82 |
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
| 83 |
+
diff = np.inf if prev is None else np.mean(cv2.absdiff(gray, prev))
|
| 84 |
+
if prev is None or diff > threshold:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
timestamp = cap.get(cv2.CAP_PROP_POS_MSEC) / 1000.0
|
| 86 |
+
img_path = os.path.join(output_dir, f"keyframe_{count+1:02d}.jpg")
|
| 87 |
Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)).save(img_path)
|
| 88 |
+
desc, prmpt, neg = self.describe_image_and_generate_prompt(
|
| 89 |
+
cv2.cvtColor(frame, cv2.COLOR_BGR2RGB), count+1
|
| 90 |
)
|
| 91 |
+
self.scenes.append({
|
| 92 |
+
'scene_number': count+1,
|
| 93 |
'time': timestamp,
|
| 94 |
'image_path': img_path,
|
| 95 |
+
'descricao_detalhada': desc,
|
| 96 |
+
'prompt_ia': prmpt,
|
| 97 |
+
'negative_prompt': neg
|
| 98 |
+
})
|
| 99 |
+
count += 1
|
| 100 |
+
prev = gray
|
|
|
|
|
|
|
|
|
|
| 101 |
success, frame = cap.read()
|
| 102 |
|
| 103 |
cap.release()
|
|
|
|
| 104 |
return True
|
| 105 |
|
| 106 |
def save_results(self, output_file=None):
|
| 107 |
if not output_file:
|
| 108 |
output_file = f"video_analysis_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
|
|
|
|
| 109 |
results = {
|
| 110 |
'video_info': self.video_info,
|
| 111 |
+
'analysis_type': 'keyframe_extraction',
|
| 112 |
'scenes': self.scenes,
|
| 113 |
'generated_at': datetime.now().isoformat()
|
| 114 |
}
|
|
|
|
| 115 |
with open(output_file, 'w', encoding='utf-8') as f:
|
| 116 |
json.dump(results, f, indent=2, ensure_ascii=False)
|
|
|
|
|
|
|
| 117 |
return output_file
|
| 118 |
|
| 119 |
def cleanup(self):
|