Nur hab ich Probleme Audio und Frames bis auf den letzten frame zu synchronisieren. Das Programm hängt sich auf am ende eines Videos manchmal oder am Anfang eines Videos, wenn es wenige Frames hat. Kann mir jemand den Code verbessern oder die Werte ?
Code: Alles auswählen
import glob
import cv2
import sys
import time
import traceback
import os
from ffpyplayer.player import MediaPlayer
import logging
# Logger-Konfiguration (nur Konsole)
logging.basicConfig(level=logging.ERROR,
format="%(asctime)s:%(levelname)s:%(message)s",
handlers=[logging.StreamHandler(sys.stdout)])
# Pfad zum Ordner 'videos' auf dem Desktop
desktop_path = os.path.join(os.path.expanduser("~"), "Desktop")
videos_folder = os.path.join(desktop_path, "videos")
# Alle MP4-Dateien im Ordner finden
try:
video_files = glob.glob(os.path.join(videos_folder, "*.mp4"))
if not video_files:
print(f"Fehler: Keine MP4-Dateien im Ordner {videos_folder} gefunden.")
sys.exit(1)
except Exception as e:
logging.error("Fehler beim Suchen von Videodateien: %s", str(e))
logging.error(traceback.format_exc())
sys.exit(1)
# Globale Variablen für Steuerung
paused = False
fullscreen = True
next_video_requested = False # Steuerung für das Überspringen zum nächsten Video
seek_value = 0 # Initialer Wert für die Seekbar
cap = None # Globale Variable für den Video-Capture
seekbar_created = False # Flag, um festzustellen, ob die Seekbar bereits erstellt wurde
previous_video_path = None # Variable zum Speichern des Pfads des vorherigen Videos
current_video_path = None # Variable für das aktuelle Video
sync_issue_start_time = None # Variable für die Zeit, ab wann das Synchronisationsproblem beginnt
# Funktion zum Ändern der Frame-Position über die Seekbar
def on_trackbar(val):
global seek_value
seek_value = val
# Mausereignis-Funktion (mit Überspringen und Löschen)
def mouse_callback(event, x, y, flags, param):
global paused, fullscreen, next_video_requested, current_video_path
if event == cv2.EVENT_LBUTTONDOWN:
paused = not paused
param.toggle_pause() # Pausiere/Setze Audio fort
if event == cv2.EVENT_LBUTTONDBLCLK:
fullscreen = not fullscreen
if fullscreen:
cv2.setWindowProperty("Video abspielen", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
else:
cv2.setWindowProperty("Video abspielen", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_NORMAL)
# Rechtsklick -> Überspringe zum nächsten Video
if event == cv2.EVENT_RBUTTONDOWN:
next_video_requested = True # Setze Flag zum Überspringen des Videos
# Funktion zum Einblenden der Synchronisierungsinformationen
def display_sync_info(frame, video_time, audio_time, position, total_duration):
font = cv2.FONT_HERSHEY_SIMPLEX
text_video = f"Video-Zeit: {video_time:.2f}s"
text_audio = f"Audio-Zeit: {audio_time:.2f}s"
text_position = f"Position: {position}/{total_duration} Frames"
# Zeige die Video- und Audio-Timestamps auf dem Video-Frame an
cv2.putText(frame, text_video, (10, 30), font, 1, (0, 255, 0), 2, cv2.LINE_AA)
cv2.putText(frame, text_audio, (10, 70), font, 1, (0, 255, 0), 2, cv2.LINE_AA)
cv2.putText(frame, text_position, (10, 110), font, 1, (0, 255, 0), 2, cv2.LINE_AA)
# Funktion zum Vorladen von Videos mit Hardwarebeschleunigung
def preload_video_with_progress(video_path):
try:
cap = cv2.VideoCapture(video_path, cv2.CAP_FFMPEG)
cap.set(cv2.CAP_PROP_HW_ACCELERATION, cv2.VIDEO_ACCELERATION_ANY)
if not cap.isOpened():
raise Exception(f"Fehler beim Öffnen des Videos: {video_path}")
fps = cap.get(cv2.CAP_PROP_FPS)
if fps == 0:
fps = 30 # Fallback, falls FPS nicht verfügbar ist
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
return cap, fps, total_frames
except Exception as e:
logging.error("Fehler beim Vorladen des Videos: %s", str(e))
logging.error(traceback.format_exc())
return None, None, None
# Funktion, um das Video und den Audio-Player synchron abzuspielen
def play_video(video_path, cap, fps, total_frames):
global paused, fullscreen, next_video_requested, seek_value, seekbar_created, current_video_path, sync_issue_start_time
try:
player = MediaPlayer(video_path)
cv2.namedWindow("Video abspielen", cv2.WND_PROP_FULLSCREEN)
time.sleep(0.1)
if fullscreen:
cv2.setWindowProperty("Video abspielen", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
cv2.setMouseCallback("Video abspielen", mouse_callback, param=player)
# Seekbar erstellen, wenn noch nicht erstellt
if not seekbar_created:
cv2.createTrackbar('Position', 'Video abspielen', 0, total_frames, on_trackbar)
seekbar_created = True
else:
cv2.setTrackbarMax('Position', 'Video abspielen', total_frames)
cv2.setTrackbarPos('Position', 'Video abspielen', 0)
frame_duration = 1000 / fps # Millisekunden pro Frame
while cap.isOpened():
if not paused:
if seek_value != int(cap.get(cv2.CAP_PROP_POS_FRAMES)):
cap.set(cv2.CAP_PROP_POS_FRAMES, seek_value)
new_audio_time = seek_value / fps
player.seek(new_audio_time, relative=False) # Setze die Audio-Position
ret, frame = cap.read()
if not ret:
next_video_requested = True
break
# Bildschirmauflösung holen
screen_res = (cv2.getWindowImageRect("Video abspielen")[2], cv2.getWindowImageRect("Video abspielen")[3])
# Berechnung des Seitenverhältnisses des Videos
video_aspect_ratio = frame.shape[1] / frame.shape[0]
screen_aspect_ratio = screen_res[0] / screen_res[1]
if video_aspect_ratio > screen_aspect_ratio:
# Das Video ist breiter als der Bildschirm, skalieren auf Bildschirmbreite
scale_width = screen_res[0] / frame.shape[1]
window_width = screen_res[0]
window_height = int(frame.shape[0] * scale_width)
else:
# Das Video ist höher als der Bildschirm, skalieren auf Bildschirmhöhe
scale_height = screen_res[1] / frame.shape[0]
window_width = int(frame.shape[1] * scale_height)
window_height = screen_res[1]
frame_resized = cv2.resize(frame, (window_width, window_height))
# Zentrieren des Videos mit schwarzen Balken
x_offset = (screen_res[0] - window_width) // 2
y_offset = (screen_res[1] - window_height) // 2
black_background = cv2.copyMakeBorder(
frame_resized, y_offset, y_offset, x_offset, x_offset, cv2.BORDER_CONSTANT, value=[0, 0, 0]
)
video_time = cap.get(cv2.CAP_PROP_POS_MSEC) / 1000.0
audio_time = player.get_pts()
# Synchronitätsprüfung: Video anpassen, falls Unterschied > 45ms
time_diff = (video_time - audio_time) * 1000 # in Millisekunden
if abs(time_diff) > 0.1: # Wenn die Differenz größer als 45 ms ist
if time_diff < 0:
# Video ist zu langsam, Frames überspringen
while abs(time_diff) > 40:
ret, frame = cap.read() # Lese mehr Frames, um Video zu beschleunigen
video_time = cap.get(cv2.CAP_PROP_POS_MSEC) / 1000.0
time_diff = (video_time - audio_time) * 1000
else:
# Video ist zu schnell, verlangsamen
time.sleep(abs(time_diff) / 1000.0) # Warte, um Video zu verlangsamen
# Synchronisations-Problembehandlung
if abs(time_diff) > 100: # Zeitdifferenz zu groß
if sync_issue_start_time is None:
sync_issue_start_time = time.time() # Timer starten
elif time.time() - sync_issue_start_time > 3: # Problem hält seit 5 Sekunden an
print("Synchronisation nicht möglich, zurücksetzen auf den aktuellen Frame...")
# Den aktuellen Frame erhalten
current_frame = int(cap.get(cv2.CAP_PROP_POS_FRAMES))
# Setze das Video auf den aktuellen Frame zurück
cap.set(cv2.CAP_PROP_POS_FRAMES, current_frame)
# Berechne die neue Audio-Zeit auf Grundlage des aktuellen Frames
new_audio_time = current_frame / fps
player.seek(new_audio_time, relative=False) # Setze die Audio-Position auf den aktuellen Frame
# Timer zurücksetzen
sync_issue_start_time = None
else:
sync_issue_start_time = None # Timer zurücksetzen, wenn das Problem behoben ist
display_sync_info(black_background, video_time, audio_time, int(cap.get(cv2.CAP_PROP_POS_FRAMES)), total_frames)
cv2.imshow("Video abspielen", black_background)
current_frame = int(cap.get(cv2.CAP_PROP_POS_FRAMES))
cv2.setTrackbarPos('Position', 'Video abspielen', current_frame)
if current_frame >= total_frames - 1 or abs(audio_time - total_frames / fps) < 0.1:
next_video_requested = True
break
audio_frame, val = player.get_frame()
if audio_frame is not None:
frame_pts = audio_frame[1]
audio_delay = frame_pts - player.get_pts()
if audio_delay > 0:
time.sleep(audio_delay / 1000)
key = cv2.waitKey(int(frame_duration)) & 0xFF
if key == ord(' '):
paused = not paused
player.toggle_pause()
elif key == 27:
fullscreen = not fullscreen
if fullscreen:
cv2.setWindowProperty("Video abspielen", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
else:
cv2.setWindowProperty("Video abspielen", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_NORMAL)
if next_video_requested:
break
if cv2.getWindowProperty("Video abspielen", cv2.WND_PROP_VISIBLE) < 1:
sys.exit(0)
player.close_player()
cap.release()
return True
except Exception as e:
logging.error("Fehler beim Abspielen des Videos: %s", str(e))
logging.error(traceback.format_exc())
return False
# Funktion zum Löschen der Datei
def delete_previous_video(previous_video_path):
if previous_video_path and os.path.exists(previous_video_path):
try:
time.sleep(1)
os.remove(previous_video_path)
print(f"Gelöscht: {previous_video_path}")
except Exception as e:
logging.error(f"Fehler beim Löschen von {previous_video_path}: {str(e)}")
logging.error(traceback.format_exc())
# Hauptschleife zum Abspielen aller Videos
previous_video_path = None
for i, video_path in enumerate(video_files):
try:
current_video_path = video_path
cap, fps, total_frames = preload_video_with_progress(video_path)
if cap is not None:
print(f"\nSpiele Video {i + 1} von {len(video_files)} ab: {video_path}")
play_video(video_path, cap, fps, total_frames)
if next_video_requested:
next_video_requested = False
time.sleep(0.1)
delete_previous_video(previous_video_path)
previous_video_path = video_path
except Exception as e:
logging.error("Hauptschleifenfehler: %s", str(e))
logging.error(traceback.format_exc())