This commit is contained in:
clemens 2025-06-09 13:53:19 +00:00
commit 048c182b57

372
main.py Normal file
View File

@ -0,0 +1,372 @@
import sys
import os
import subprocess
import tempfile
import re
import srt
import folium
import vlc
from math import radians, sin, cos, sqrt, atan2
from PyQt5.QtWidgets import (
QApplication, QMainWindow, QLabel, QPushButton, QVBoxLayout,
QWidget, QFileDialog, QHBoxLayout, QSlider, QSizePolicy, QFrame
)
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtGui import QFont
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineSettings
def haversine(lat1, lon1, lat2, lon2):
R = 6371.0
lat1_rad = radians(lat1)
lon1_rad = radians(lon1)
lat2_rad = radians(lat2)
lon2_rad = radians(lon2)
dlon = lon2_rad - lon1_rad
dlat = lat2_rad - lat1_rad
a = sin(dlat / 2)**2 + cos(lat1_rad) * cos(lat2_rad) * sin(dlon / 2)**2
c = 2 * atan2(sqrt(a), sqrt(1 - a))
distance = R * c
return distance
def extract_srt_from_mp4(mp4_path):
temp_srt = tempfile.NamedTemporaryFile(suffix=".srt", delete=False)
temp_srt.close()
cmd = [
"ffmpeg", "-y", "-i", mp4_path, "-map", "0:s:0", "-c:s", "srt", temp_srt.name
]
try:
subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return temp_srt.name
except subprocess.CalledProcessError:
os.unlink(temp_srt.name)
return None
def parse_telemetry_srt(srt_path):
with open(srt_path, encoding="utf-8") as f:
srt_content = f.read()
subs = list(srt.parse(srt_content))
telemetry = []
for sub in subs:
data = {
"start": sub.start.total_seconds(),
"end": sub.end.total_seconds(),
}
text = sub.content.replace('\n', ' ')
m = re.search(r'GPS \(([-\d.]+),\s*([-\d.]+),\s*(\d+)\)', text)
if m:
data["lat"] = float(m.group(2))
data["lon"] = float(m.group(1))
m = re.search(r'H\s*([-\d.]+)m', text)
if m:
data["height"] = float(m.group(1))
m = re.search(r'H\.S\s*([-\d.]+)m/s', text)
if m:
data["h_speed"] = float(m.group(1))
telemetry.append(data)
return telemetry
class MapWidget(QWebEngineView):
def __init__(self, lat, lon):
super().__init__()
self.map = folium.Map(location=[lat, lon], zoom_start=16)
self.start_marker = folium.Marker([lat, lon], popup="Start", icon=folium.Icon(color='green'))
self.start_marker.add_to(self.map)
self.current_marker = folium.Marker([lat, lon], icon=folium.Icon(color='red'))
self.current_marker.add_to(self.map)
self.path_line = folium.PolyLine([[lat, lon]], color="blue", weight=3, opacity=0.8)
self.path_line.add_to(self.map)
self.map_name = self.map.get_name()
self.marker_name = self.current_marker.get_name()
self.line_name = self.path_line.get_name()
html_content = self.map.get_root().render()
self.setHtml(html_content)
def update_marker(self, lat, lon):
js_code = f"""
var new_latlng = L.latLng({lat}, {lon});
{self.marker_name}.setLatLng(new_latlng);
{self.line_name}.addLatLng(new_latlng);
{self.map_name}.panTo(new_latlng);
"""
self.page().runJavaScript(js_code)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("DJI Drone Telemetry Viewer")
self.setGeometry(100, 100, 1280, 720)
self.setStyleSheet("""
QMainWindow { background-color: #23272b; }
QLabel { color: #fff; }
QPushButton { background: #3c4148; border: 1px solid #555; padding: 8px 16px; border-radius: 4px; color: white; }
QPushButton:hover { background: #4a5058; }
QPushButton:pressed { background: #2c3138; }
QSlider::groove:horizontal { height: 8px; background: #333; border-radius: 4px; }
QSlider::handle:horizontal { background: #4f8dcb; width: 16px; margin: -4px 0; border-radius: 8px; }
QFrame { background: #111; }
""")
self.video_frame = QFrame()
self.video_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.video_frame.setStyleSheet("background: #111; border-radius: 8px;")
right_panel = QVBoxLayout()
self.map_placeholder = QLabel("Karte wird geladen...")
self.map_placeholder.setAlignment(Qt.AlignCenter)
self.map_placeholder.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.map_placeholder.setStyleSheet("background: #222; border-radius: 8px; color: #888;")
self.map_widget = None
right_panel.addWidget(self.map_placeholder, stretch=3)
self.gps_label = QLabel("<b>GPS</b><br>--")
self.alt_label = QLabel("<b>Höhe</b><br>--")
self.speed_label = QLabel("<b>Geschw.</b><br>--")
self.dist_label = QLabel("<b>Distanz</b><br>--")
hud_font = QFont("Arial", 11)
for label in [self.gps_label, self.alt_label, self.speed_label, self.dist_label]:
label.setFont(hud_font)
label.setStyleSheet("margin-top: 10px; color: #fff;")
right_panel.addWidget(label)
right_panel.addStretch(1)
self.play_btn = QPushButton("▶ Play")
self.pause_btn = QPushButton("⏸ Pause")
self.unload_btn = QPushButton("Unload")
self.slider = QSlider(Qt.Horizontal)
self.slider.setRange(0, 1000)
self.time_label = QLabel("00:00 / 00:00")
self.open_btn = QPushButton("Öffnen")
main_layout = QHBoxLayout()
main_layout.addWidget(self.video_frame, 3)
right_panel_widget = QWidget()
right_panel_widget.setLayout(right_panel)
right_panel_widget.setMinimumWidth(280)
right_panel_widget.setMaximumWidth(400)
right_panel_widget.setStyleSheet("background: #181b1f; border-radius: 10px; padding: 10px;")
main_layout.addWidget(right_panel_widget, 1)
player_layout = QHBoxLayout()
player_layout.addWidget(self.play_btn)
player_layout.addWidget(self.pause_btn)
player_layout.addWidget(self.slider)
player_layout.addWidget(self.time_label)
player_layout.addWidget(self.open_btn)
player_layout.addWidget(self.unload_btn)
vbox = QVBoxLayout()
vbox.addLayout(main_layout)
vbox.addLayout(player_layout)
container = QWidget()
container.setLayout(vbox)
self.setCentralWidget(container)
self.video_path = None
self.telemetry = []
self.start_coords = None
self.telemetry_idx = 0
self.slider_is_pressed = False
self.vlc_instance = vlc.Instance("--no-xlib")
self.media_player = self.vlc_instance.media_player_new()
self.telemetry_timer = QTimer(self)
self.telemetry_timer.setInterval(33)
self.telemetry_timer.timeout.connect(self.update_telemetry_and_progress)
self.open_btn.clicked.connect(self.open_video)
self.play_btn.clicked.connect(self.play_video)
self.pause_btn.clicked.connect(self.pause_video)
self.unload_btn.clicked.connect(self.unload_video)
self.slider.sliderPressed.connect(self.slider_pressed)
self.slider.sliderReleased.connect(self.slider_released)
self.play_btn.setEnabled(False)
self.pause_btn.setEnabled(False)
self.unload_btn.setEnabled(False)
self.slider.setEnabled(False)
def open_video(self):
fname, _ = QFileDialog.getOpenFileName(self, "Video auswählen", "", "MP4 Files (*.mp4)")
if not fname:
return
self.unload_video()
self.video_path = fname
srt_path = extract_srt_from_mp4(fname)
if not srt_path or not os.path.exists(srt_path):
self.gps_label.setText("Keine Telemetriedaten (SRT) gefunden.")
return
self.telemetry = parse_telemetry_srt(srt_path)
os.unlink(srt_path)
if not self.telemetry or "lat" not in self.telemetry[0]:
self.gps_label.setText("Keine GPS-Daten in der Telemetrie.")
return
self.start_coords = (self.telemetry[0]["lat"], self.telemetry[0]["lon"])
if self.map_widget:
self.map_widget.deleteLater()
self.map_widget = MapWidget(self.start_coords[0], self.start_coords[1])
right_layout = self.centralWidget().layout().itemAt(0).layout().itemAt(1).widget().layout()
right_layout.replaceWidget(self.map_placeholder, self.map_widget)
self.map_placeholder.hide()
self.map_widget.show()
media = self.vlc_instance.media_new(self.video_path)
self.media_player.set_media(media)
self.media_player.set_hwnd(self.video_frame.winId())
self.play_btn.setEnabled(True)
self.pause_btn.setEnabled(True)
self.unload_btn.setEnabled(True)
self.slider.setEnabled(True)
self.play_video()
def play_video(self):
if self.media_player.get_media():
self.media_player.play()
self.telemetry_timer.start()
self.play_btn.setEnabled(False)
self.pause_btn.setEnabled(True)
def pause_video(self):
if self.media_player.is_playing():
self.media_player.pause()
self.telemetry_timer.stop()
self.play_btn.setEnabled(True)
self.pause_btn.setEnabled(False)
def unload_video(self):
self.media_player.stop()
self.telemetry_timer.stop()
self.video_path = None
self.telemetry = []
self.start_coords = None
self.telemetry_idx = 0
if self.map_widget:
self.map_widget.hide()
right_layout = self.centralWidget().layout().itemAt(0).layout().itemAt(1).widget().layout()
right_layout.replaceWidget(self.map_widget, self.map_placeholder)
self.map_widget.deleteLater()
self.map_widget = None
self.map_placeholder.show()
self.gps_label.setText("<b>GPS</b><br>--")
self.alt_label.setText("<b>Höhe</b><br>--")
self.speed_label.setText("<b>Geschw.</b><br>--")
self.dist_label.setText("<b>Distanz</b><br>--")
self.time_label.setText("00:00 / 00:00")
self.slider.setValue(0)
self.play_btn.setEnabled(False)
self.pause_btn.setEnabled(False)
self.unload_btn.setEnabled(False)
self.slider.setEnabled(False)
def update_telemetry_and_progress(self):
duration_ms = self.media_player.get_length()
pos_ms = self.media_player.get_time()
if duration_ms <= 0:
return
pos_sec = pos_ms / 1000.0
if not self.slider_is_pressed:
self.slider.setValue(int((pos_ms / duration_ms) * 1000))
self.time_label.setText(f"{self.format_time(pos_sec)} / {self.format_time(duration_ms / 1000.0)}")
while (self.telemetry_idx + 1 < len(self.telemetry) and
self.telemetry[self.telemetry_idx + 1]["start"] <= pos_sec):
self.telemetry_idx += 1
data = self.telemetry[self.telemetry_idx] if self.telemetry_idx < len(self.telemetry) else {}
self.update_hud(data)
lat = data.get("lat")
lon = data.get("lon")
if lat is not None and lon is not None and self.map_widget:
self.map_widget.update_marker(lat, lon)
if self.media_player.get_state() == vlc.State.Ended:
self.video_finished()
def update_hud(self, data):
lat = data.get('lat')
lon = data.get('lon')
alt = data.get('height', '--')
speed_ms = data.get('h_speed', 0.0)
if lat is not None and lon is not None:
self.gps_label.setText(f"<b>GPS</b><br>{lat:.5f}°, {lon:.5f}°")
if self.start_coords:
dist_km = haversine(self.start_coords[0], self.start_coords[1], lat, lon)
self.dist_label.setText(f"<b>Distanz</b><br>{dist_km*1000:.1f} m")
if isinstance(alt, float):
self.alt_label.setText(f"<b>Höhe</b><br>{alt:.1f} m")
speed_kmh = speed_ms * 3.6
self.speed_label.setText(f"<b>Geschw.</b><br>{speed_kmh:.1f} km/h")
def slider_pressed(self):
if self.media_player.is_playing():
self.slider_is_pressed = True
self.media_player.pause()
self.telemetry_timer.stop()
def slider_released(self):
if self.slider_is_pressed:
seek_ratio = self.slider.value() / 1000.0
self.media_player.set_position(seek_ratio)
new_time_sec = (self.media_player.get_length() / 1000.0) * seek_ratio
self.telemetry_idx = 0
while (self.telemetry_idx + 1 < len(self.telemetry) and
self.telemetry[self.telemetry_idx + 1]["start"] <= new_time_sec):
self.telemetry_idx += 1
self.media_player.play()
self.telemetry_timer.start()
self.slider_is_pressed = False
self.play_btn.setEnabled(False)
self.pause_btn.setEnabled(True)
def video_finished(self):
self.telemetry_timer.stop()
self.play_btn.setEnabled(True)
self.pause_btn.setEnabled(False)
self.slider.setValue(1000)
def format_time(self, sec):
return f"{int(sec)//60:02}:{int(sec)%60:02}"
def closeEvent(self, event):
self.media_player.stop()
self.media_player.release()
event.accept()
if __name__ == "__main__":
app = QApplication(sys.argv)
try:
QWebEngineSettings.globalSettings().setAttribute(QWebEngineSettings.WebAttribute.WebGLEnabled, False)
except Exception:
pass
window = MainWindow()
window.show()
sys.exit(app.exec_())