Shads229 commited on
Commit
aa8f25d
·
verified ·
1 Parent(s): 6d18217

Upload 16 files

Browse files
backend/core/__pycache__/engine.cpython-314.pyc CHANGED
Binary files a/backend/core/__pycache__/engine.cpython-314.pyc and b/backend/core/__pycache__/engine.cpython-314.pyc differ
 
backend/core/engine.py CHANGED
@@ -141,40 +141,77 @@ class VideoProcessor:
141
  self.video_path, self.output_dir = video_path, output_dir
142
  self.output_dir.mkdir(parents=True, exist_ok=True)
143
 
144
- def extract_keyframes(self, max_frames: int = 50) -> List[Frame]:
 
 
 
 
 
 
145
  try:
146
  from decord import VideoReader, cpu
147
  vr = VideoReader(str(self.video_path), ctx=cpu(0))
148
  total = len(vr)
149
- step = max(1, total // max_frames)
150
- indices = range(0, total, step)[:max_frames]
151
- frames_data = vr.get_batch(indices).asnumpy()
152
  fps = vr.get_avg_fps()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  extracted = []
154
  for i, idx in enumerate(indices):
155
  img = cv2.cvtColor(frames_data[i], cv2.COLOR_RGB2BGR)
156
  ts = idx / fps
157
  p = self.output_dir / f"f_{idx}.jpg"
158
- cv2.imwrite(str(p), img, [cv2.IMWRITE_JPEG_QUALITY, 85])
159
  extracted.append(Frame(path=p, timestamp=ts, metrics=self.get_frame_metrics(img)))
 
 
160
  return extracted
161
  except Exception as e:
162
  logger.warning(f"Decord failed, fallback to CV2: {e}")
163
  cap = cv2.VideoCapture(str(self.video_path))
164
  fps = cap.get(cv2.CAP_PROP_FPS) or 30.0
165
  total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) or 1000
166
- step = max(1, total // max_frames)
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  extracted = []
168
  for idx in range(0, total, step):
169
- if len(extracted) >= max_frames: break
170
  cap.set(cv2.CAP_PROP_POS_FRAMES, idx)
171
  ret, img = cap.read()
172
  if ret:
173
  ts = idx / fps
174
  p = self.output_dir / f"f_{idx}.jpg"
175
- cv2.imwrite(str(p), img, [cv2.IMWRITE_JPEG_QUALITY, 85])
176
  extracted.append(Frame(path=p, timestamp=ts, metrics=self.get_frame_metrics(img)))
177
  cap.release()
 
178
  return extracted
179
 
180
  class AudioProcessor:
@@ -183,7 +220,8 @@ class AudioProcessor:
183
  if WHISPER_AVAILABLE and self.model is None:
184
  try:
185
  device = "cuda" if torch.cuda.is_available() else "cpu"
186
- self.model = WhisperModel("base", device=device, compute_type="int8")
 
187
  except: pass
188
  def transcribe(self, p: Path) -> str:
189
  self.initialize()
@@ -273,13 +311,13 @@ class ZenithAnalyzer:
273
 
274
  if self.yolo:
275
  all_paths = [str(f.path) for f in frames]
276
- batch_size = 10
277
  for i in range(0, len(all_paths), batch_size):
278
  batch = all_paths[i:i+batch_size]
279
- results = await loop.run_in_executor(executor, lambda: self.yolo(batch, verbose=False, imgsz=320, stream=False))
280
  for j, res in enumerate(results):
281
  idx = i + j
282
- objs = [res.names[int(b.cls[0])] for b in res.boxes if b.conf > 0.25]
283
  ambiance = f"Ambiance: {'Sombre' if frames[idx].metrics['brightness'] < 50 else 'Lumineuse'}"
284
  frames[idx].vision_content = f"{ambiance}, Objets: " + ", ".join([f"{v}x {k}" for k,v in Counter(objs).items()])
285
 
@@ -307,11 +345,20 @@ class ZenithAnalyzer:
307
 
308
  Produis un rapport TECHNIQUE, FACTUEL et STRUCTURÉ en Markdown."""
309
 
310
- # Encodage parallèle des images
311
- selected_frames = [frames[i] for i in range(0, len(frames), max(1, len(frames)//10))][:10]
 
 
 
 
 
 
 
312
  def encode_f(f):
313
  img = cv2.imread(str(f.path))
314
- _, buf = cv2.imencode('.jpg', img, [cv2.IMWRITE_JPEG_QUALITY, 70])
 
 
315
  return {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{base64.b64encode(buf).decode()}"}}
316
 
317
  with concurrent.futures.ThreadPoolExecutor() as executor:
 
141
  self.video_path, self.output_dir = video_path, output_dir
142
  self.output_dir.mkdir(parents=True, exist_ok=True)
143
 
144
+ def extract_keyframes(self, max_frames: int = 30) -> List[Frame]:
145
+ """
146
+ Extraction intelligente de keyframes avec échantillonnage adaptatif.
147
+ - Vidéos courtes (<2min) : 1 frame toutes les 3-4s
148
+ - Vidéos moyennes (2-10min) : 1 frame toutes les 10-15s
149
+ - Vidéos longues (>10min) : 1 frame toutes les 20-30s
150
+ """
151
  try:
152
  from decord import VideoReader, cpu
153
  vr = VideoReader(str(self.video_path), ctx=cpu(0))
154
  total = len(vr)
 
 
 
155
  fps = vr.get_avg_fps()
156
+ duration_seconds = total / fps
157
+
158
+ # Échantillonnage adaptatif basé sur la durée
159
+ if duration_seconds < 120: # < 2 minutes
160
+ target_interval = 3 # 1 frame toutes les 3 secondes
161
+ elif duration_seconds < 600: # 2-10 minutes
162
+ target_interval = 12 # 1 frame toutes les 12 secondes
163
+ else: # > 10 minutes
164
+ target_interval = 25 # 1 frame toutes les 25 secondes
165
+
166
+ # Calculer le nombre de frames optimal
167
+ optimal_frames = min(int(duration_seconds / target_interval), max_frames)
168
+ optimal_frames = max(optimal_frames, 10) # Minimum 10 frames
169
+
170
+ step = max(1, total // optimal_frames)
171
+ indices = range(0, total, step)[:optimal_frames]
172
+ frames_data = vr.get_batch(indices).asnumpy()
173
+
174
  extracted = []
175
  for i, idx in enumerate(indices):
176
  img = cv2.cvtColor(frames_data[i], cv2.COLOR_RGB2BGR)
177
  ts = idx / fps
178
  p = self.output_dir / f"f_{idx}.jpg"
179
+ cv2.imwrite(str(p), img, [cv2.IMWRITE_JPEG_QUALITY, 70])
180
  extracted.append(Frame(path=p, timestamp=ts, metrics=self.get_frame_metrics(img)))
181
+
182
+ logger.info(f"✅ Extraction adaptative : {len(extracted)} frames pour {duration_seconds:.1f}s de vidéo (1 frame/{target_interval}s)")
183
  return extracted
184
  except Exception as e:
185
  logger.warning(f"Decord failed, fallback to CV2: {e}")
186
  cap = cv2.VideoCapture(str(self.video_path))
187
  fps = cap.get(cv2.CAP_PROP_FPS) or 30.0
188
  total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) or 1000
189
+ duration_seconds = total / fps
190
+
191
+ # Même logique adaptative pour le fallback CV2
192
+ if duration_seconds < 120:
193
+ target_interval = 3
194
+ elif duration_seconds < 600:
195
+ target_interval = 12
196
+ else:
197
+ target_interval = 25
198
+
199
+ optimal_frames = min(int(duration_seconds / target_interval), max_frames)
200
+ optimal_frames = max(optimal_frames, 10)
201
+
202
+ step = max(1, total // optimal_frames)
203
  extracted = []
204
  for idx in range(0, total, step):
205
+ if len(extracted) >= optimal_frames: break
206
  cap.set(cv2.CAP_PROP_POS_FRAMES, idx)
207
  ret, img = cap.read()
208
  if ret:
209
  ts = idx / fps
210
  p = self.output_dir / f"f_{idx}.jpg"
211
+ cv2.imwrite(str(p), img, [cv2.IMWRITE_JPEG_QUALITY, 70])
212
  extracted.append(Frame(path=p, timestamp=ts, metrics=self.get_frame_metrics(img)))
213
  cap.release()
214
+ logger.info(f"✅ Extraction CV2 adaptative : {len(extracted)} frames pour {duration_seconds:.1f}s de vidéo")
215
  return extracted
216
 
217
  class AudioProcessor:
 
220
  if WHISPER_AVAILABLE and self.model is None:
221
  try:
222
  device = "cuda" if torch.cuda.is_available() else "cpu"
223
+ # Utiliser tiny au lieu de base pour plus de rapidité
224
+ self.model = WhisperModel("tiny", device=device, compute_type="int8")
225
  except: pass
226
  def transcribe(self, p: Path) -> str:
227
  self.initialize()
 
311
 
312
  if self.yolo:
313
  all_paths = [str(f.path) for f in frames]
314
+ batch_size = 20
315
  for i in range(0, len(all_paths), batch_size):
316
  batch = all_paths[i:i+batch_size]
317
+ results = await loop.run_in_executor(executor, lambda: self.yolo(batch, verbose=False, imgsz=256, stream=False))
318
  for j, res in enumerate(results):
319
  idx = i + j
320
+ objs = [res.names[int(b.cls[0])] for b in res.boxes if b.conf > 0.35]
321
  ambiance = f"Ambiance: {'Sombre' if frames[idx].metrics['brightness'] < 50 else 'Lumineuse'}"
322
  frames[idx].vision_content = f"{ambiance}, Objets: " + ", ".join([f"{v}x {k}" for k,v in Counter(objs).items()])
323
 
 
345
 
346
  Produis un rapport TECHNIQUE, FACTUEL et STRUCTURÉ en Markdown."""
347
 
348
+ # Encodage parallèle des images - Sélection intelligente et équilibrée
349
+ # On prend des images réparties uniformément sur toute la durée
350
+ num_images_to_send = min(8, len(frames)) # Max 8 images pour l'IA
351
+ if len(frames) > 0:
352
+ step = max(1, len(frames) // num_images_to_send)
353
+ selected_frames = [frames[i] for i in range(0, len(frames), step)][:num_images_to_send]
354
+ else:
355
+ selected_frames = []
356
+
357
  def encode_f(f):
358
  img = cv2.imread(str(f.path))
359
+ # Redimensionner pour réduire la taille tout en gardant la qualité visuelle
360
+ img = cv2.resize(img, (800, 450), interpolation=cv2.INTER_AREA)
361
+ _, buf = cv2.imencode('.jpg', img, [cv2.IMWRITE_JPEG_QUALITY, 65])
362
  return {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{base64.b64encode(buf).decode()}"}}
363
 
364
  with concurrent.futures.ThreadPoolExecutor() as executor: