commit 048c182b5756444cbd8d18fad4255212e912b54b Author: clemens Date: Mon Jun 9 13:53:19 2025 +0000 main.py diff --git a/main.py b/main.py new file mode 100644 index 0000000..2e6547f --- /dev/null +++ b/main.py @@ -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("GPS
--") + self.alt_label = QLabel("Höhe
--") + self.speed_label = QLabel("Geschw.
--") + self.dist_label = QLabel("Distanz
--") + 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("GPS
--") + self.alt_label.setText("Höhe
--") + self.speed_label.setText("Geschw.
--") + self.dist_label.setText("Distanz
--") + 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"GPS
{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"Distanz
{dist_km*1000:.1f} m") + + if isinstance(alt, float): + self.alt_label.setText(f"Höhe
{alt:.1f} m") + + speed_kmh = speed_ms * 3.6 + self.speed_label.setText(f"Geschw.
{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_()) \ No newline at end of file