Einführung
Das Zusammenfügen von Videodateien kann eine Herausforderung sein, besonders wenn man sie nummerieren und mit Untertiteln versehen möchte – sei es für Präsentationen, Tutorials oder persönliche Projekte. Der Python-Code „VideoMergerGUI“ bietet eine elegante Lösung: Er kombiniert MP4-Videos aus einem Ordner, fügt automatisch Untertitel mit Nummerierung und Dateinamen hinzu und präsentiert das Ganze in einer modernen Benutzeroberfläche basierend auf PyQt6. In diesem Beitrag stelle ich den Code vor, erkläre seine Funktionalität und zeige, wo er eingesetzt werden kann.
Was macht der VideoMergerGUI?
Der „VideoMergerGUI“ ist ein Werkzeug, das Videobearbeitung vereinfacht. Hier sind die Kernfunktionen im Überblick:
- Videoverarbeitung: Er lädt mehrere MP4-Dateien und fügt sie in der gewählten Reihenfolge zu einem einzigen Video zusammen.
- Automatische Untertitel: Jedes Video erhält einen Text am unteren Rand mit einer Nummer (z. B. „1: dateiname“), der während der gesamten Dauer des Clips sichtbar ist.
- Moderne GUI: Eine PyQt6-Oberfläche ermöglicht das Hinzufügen von Dateien, die Auswahl eines Ausgabepfads und die Verfolgung des Fortschritts über einen Balken.
- Echtzeit-Feedback: Ein Statuslabel informiert über den aktuellen Stand, und eine Nachricht bestätigt den Abschluss.
- Ausgabe: Das fertige Video wird als MP4-Datei gespeichert, standardmäßig als „zusammengefuegtes_video.mp4“.
Die Oberfläche ist intuitiv: Nutzer wählen Videos aus, legen den Ausgabeort fest und starten die Verarbeitung per Knopfdruck. Die Verarbeitung läuft im Hintergrund, sodass die GUI reaktiv bleibt.
Technische Details
Der Code nutzt folgende Bibliotheken:
- moviepy: Für die Videoverarbeitung (Laden, Zusammenfügen, Untertitel).
- PIL (Pillow): Zum Erstellen der Untertitelbilder.
- PyQt6: Für die moderne Benutzeroberfläche.
- tempfile: Für effizientes Management temporärer Dateien.
- os: Für Dateipfad-Operationen.
Die Optimierung umfasst:
- Klassenstruktur: VideoWorker für die Verarbeitung im Hintergrund, VideoMergerGUI für die GUI.
- Temporäres Dateimanagement: Untertitelbilder werden dynamisch erstellt und nach Nutzung gelöscht.
- Threading: Die Verarbeitung läuft in einem separaten Thread, um die GUI nicht zu blockieren.
- Design: Helles Farbschema (Hintergrund #f4f5f7, Buttons #1a73e8) mit abgerundeten Ecken für einen modernen Look.
Installation und Nutzung
Die Installation erfordert einige Abhängigkeiten:
bash
pip install moviepy pillow pyqt6
Zusätzlich muss ffmpeg installiert sein (für moviepy):
- Windows: Von der offiziellen Seite herunterladen und zum PATH hinzufügen.
- Linux: sudo apt-get install ffmpeg
- macOS: brew install ffmpeg
Speichern Sie den Code in einer Datei (z. B. video_merger_gui.py) und starten Sie ihn mit:
bash
python video_merger_gui.py
Die Bedienung ist einfach:
- Öffnen Sie das Programm – eine moderne GUI erscheint.
- Klicken Sie auf „Videos hinzufügen“ und wählen Sie MP4-Dateien aus.
- Geben Sie den Ausgabepfad ein oder wählen Sie einen Ordner über „Ordner wählen“.
- Klicken Sie auf „Videos zusammenfügen“. Der Fortschrittsbalken zeigt den Status, und eine Nachricht bestätigt den Abschluss.
Ein Beispiel: Sie fügen „video1.mp4“ und „video2.mp4“ hinzu. Das fertige Video zeigt „1: video1“ während des ersten Clips und „2: video2“ während des zweiten.
Einsatzszenarien
Der „VideoMergerGUI“ ist vielseitig einsetzbar. Hier sind einige praktische Anwendungen:
- Bildung und Tutorials:
- Lehrer könnten mehrere Unterrichtsvideos zu einem zusammenhängenden Clip kombinieren, mit Untertiteln zur Unterscheidung der Themen (z. B. „1: Einführung“, „2: Beispiele“).
- Online-Kurs-Ersteller könnten Lektionen zusammenfassen und nummerieren.
- Präsentationen:
- Unternehmen könnten Produktvideos oder Demo-Clips zu einer nahtlosen Präsentation vereinen, wobei die Untertitel den jeweiligen Abschnitt klar kennzeichnen.
- Konferenzredner könnten mehrere kurze Clips für eine Keynote vorbereiten.
- Content Creation:
- YouTuber oder Streamer könnten Clips aus verschiedenen Aufnahmen kombinieren, etwa für „Best-of“-Videos, mit automatischen Titeln für jede Szene.
- Hobby-Filmemacher könnten Urlaubsvideos zu einem Film mit Kapiteln zusammenfassen.
- Archivierung:
- Familien könnten alte Videoclips (z. B. von Feiern) in einem einzigen Video bündeln, wobei die Untertitel die Ereignisse identifizieren („1: Geburtstag 2020“).
- Archive könnten historische Aufnahmen zusammenführen und kennzeichnen.
- Technische Analyse:
- Entwickler könnten Testvideos von Software-Demos kombinieren, um Fehler oder Features zu dokumentieren, mit klarer Nummerierung für die Nachverfolgung.
Stärken und Grenzen
Stärken:
- Benutzerfreundlichkeit: Die GUI ist intuitiv und ansprechend, auch für Nutzer ohne Programmierkenntnisse.
- Automatisierung: Untertitel und Zusammenfügen erfolgen ohne manuelle Eingriffe.
- Flexibilität: Mehrere Videos können in beliebiger Reihenfolge verarbeitet werden.
- Visuelles Feedback: Der Fortschrittsbalken zeigt den Status in Echtzeit.
Grenzen:
- Formate: Nur MP4 wird unterstützt – andere Formate wie AVI oder MOV erfordern eine Konvertierung.
- Performance: Bei sehr großen oder vielen Videos kann die Verarbeitung lange dauern, da keine Parallelisierung genutzt wird.
- Anpassung: Schriftgröße und Farbe der Untertitel sind festgelegt (60pt, weiß) – keine GUI-Option zur Änderung.
- Abhängigkeiten: Erfordert ffmpeg, was bei der Installation zusätzlichen Aufwand bedeuten kann.
Mögliche Erweiterungen
Der Code lässt Raum für Verbesserungen:
- Weitere Formate: Unterstützung für AVI, MOV etc. durch erweiterte moviepy-Optionen.
- Anpassbare Untertitel: GUI-Felder für Schriftgröße, Farbe oder Position.
- Vorschau: Eine Miniaturansicht der Videos in der Liste.
- Drag-and-Drop: Für schnelleres Hinzufügen von Dateien.
- Übergänge: Fade-Effekte zwischen Clips für einen flüssigeren Übergang.
Praktisches Beispiel
Stellen Sie sich vor, Sie haben drei kurze Videos von einem Familienurlaub: „strand.mp4“, „stadt.mp4“ und „essen.mp4“. Sie laden sie in die GUI, wählen „urlaub_2023.mp4“ als Ausgabe und starten die Verarbeitung. Das Ergebnis ist ein Video, das nacheinander den Strand (mit „1: strand“), die Stadt („2: stadt“) und das Essen („3: essen“) zeigt – perfekt für eine Präsentation oder zum Teilen mit Freunden.
Fazit
Der „VideoMergerGUI“ ist ein praktisches Werkzeug für alle, die Videos schnell kombinieren und mit Untertiteln versehen möchten. Die moderne PyQt6-Oberfläche hebt ihn von einfachen Skripten ab und macht ihn zugänglich für ein breites Publikum – von Lehrern über Content Creators bis hin zu Hobbyisten. Während er in seiner aktuellen Form solide Ergebnisse liefert, bietet er Potenzial für Erweiterungen, um spezifische Bedürfnisse zu erfüllen. Für Nutzer mit Python-Grundkenntnissen ist er ein idealer Ausgangspunkt, um Videoprojekte effizient umzusetzen.
Quellcode
import os
from moviepy.editor import VideoFileClip, concatenate_videoclips, CompositeVideoClip, ImageClip
from PIL import Image, ImageDraw, ImageFont
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QPushButton, QFileDialog, QListWidget, QLabel, QLineEdit,
QMessageBox, QProgressBar)
from PyQt6.QtCore import Qt, QThread, pyqtSignal
from PyQt6.QtGui import QFont
import sys
import tempfile
class VideoWorker(QThread):
progress_signal = pyqtSignal(int)
finished_signal = pyqtSignal(str)
def __init__(self, files, output_file, fontsize=60, color='white'):
super().__init__()
self.files = files
self.output_file = output_file
self.fontsize = fontsize
self.color = color
def create_text_clip(self, text, duration):
"""Erstellt einen Textclip mit optimiertem temporären Dateimanagement."""
with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as tmp:
img = Image.new('RGBA', (640, 100), (0, 0, 0, 0))
d = ImageDraw.Draw(img)
try:
font = ImageFont.truetype("Arial.ttf", self.fontsize)
except IOError:
font = ImageFont.load_default()
bbox = d.textbbox((0, 0), text, font=font)
text_width, text_height = bbox[2] - bbox[0], bbox[3] - bbox[1]
d.text(((640 - text_width) // 2, (100 - text_height) // 2), text, fill=self.color, font=font)
img.save(tmp.name)
clip = ImageClip(tmp.name).set_duration(duration).set_position(('left', 'bottom')).margin(left=10, bottom=10)
os.unlink(tmp.name) # Temporäre Datei löschen
return clip
def run(self):
clips = []
for i, file_path in enumerate(self.files, 1):
try:
clip = VideoFileClip(file_path)
title = f"{i}: {os.path.splitext(os.path.basename(file_path))[0]}"
text_clip = self.create_text_clip(title, clip.duration)
video_with_subtitle = CompositeVideoClip([clip, text_clip])
clips.append(video_with_subtitle)
self.progress_signal.emit(int((i / len(self.files)) * 100))
except Exception as e:
self.finished_signal.emit(f"Fehler bei {file_path}: {str(e)}")
return
if clips:
final_video = concatenate_videoclips(clips, method="compose")
final_video.write_videofile(self.output_file, codec="libx264", fps=24, logger=None)
self.finished_signal.emit(f"Video erfolgreich erstellt: {self.output_file}")
else:
self.finished_signal.emit("Keine Videos verarbeitet.")
class VideoMergerGUI(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Video Merger with Subtitles")
self.setGeometry(100, 100, 800, 500)
self.init_ui()
def init_ui(self):
widget = QWidget()
self.setCentralWidget(widget)
layout = QVBoxLayout()
widget.setLayout(layout)
# Stil
self.setStyleSheet("""
QWidget { background-color: #f4f5f7; font-family: 'Segoe UI', sans-serif; }
QPushButton { background-color: #1a73e8; color: white; padding: 8px; border: none; border-radius: 4px; }
QPushButton:hover { background-color: #1557b0; }
QLineEdit { padding: 6px; border: 1px solid #dadce0; border-radius: 4px; }
QListWidget { border: 1px solid #dadce0; border-radius: 4px; padding: 5px; }
QLabel { font-size: 14px; color: #202124; }
QProgressBar { border: 1px solid #dadce0; border-radius: 4px; text-align: center; }
""")
# Dateiauswahl
layout.addWidget(QLabel("Videos auswählen:"))
self.file_list = QListWidget()
layout.addWidget(self.file_list)
add_file_btn = QPushButton("Videos hinzufügen")
add_file_btn.clicked.connect(self.add_files)
layout.addWidget(add_file_btn)
# Ausgabeordner und Dateiname
output_layout = QHBoxLayout()
output_layout.addWidget(QLabel("Ausgabedatei:"))
self.output_file = QLineEdit("zusammengefuegtes_video.mp4")
output_layout.addWidget(self.output_file)
choose_folder_btn = QPushButton("Ordner wählen")
choose_folder_btn.clicked.connect(self.choose_output_folder)
output_layout.addWidget(choose_folder_btn)
layout.addLayout(output_layout)
# Start-Button
start_btn = QPushButton("Videos zusammenfügen")
start_btn.clicked.connect(self.start_merging)
start_btn.setFont(QFont("Segoe UI", 12, QFont.Weight.Bold))
layout.addWidget(start_btn)
# Fortschrittsbalken
self.progress_bar = QProgressBar()
self.progress_bar.setValue(0)
layout.addWidget(self.progress_bar)
# Statusanzeige
self.status_label = QLabel("Bereit")
layout.addWidget(self.status_label)
def add_files(self):
files, _ = QFileDialog.getOpenFileNames(self, "Videos auswählen", "", "Video Files (*.mp4)")
for file in files:
self.file_list.addItem(file)
def choose_output_folder(self):
folder = QFileDialog.getExistingDirectory(self, "Ausgabeordner wählen")
if folder:
self.output_file.setText(os.path.join(folder, "zusammengefuegtes_video.mp4"))
def start_merging(self):
files = [self.file_list.item(i).text() for i in range(self.file_list.count())]
output_file = self.output_file.text()
if not files:
QMessageBox.warning(self, "Eingabe fehlt", "Bitte wählen Sie mindestens ein Video aus.")
return
if not output_file:
QMessageBox.warning(self, "Ausgabe fehlt", "Bitte geben Sie eine Ausgabedatei an.")
return
self.progress_bar.setValue(0)
self.status_label.setText("Verarbeitung läuft...")
self.worker = VideoWorker(files, output_file)
self.worker.progress_signal.connect(self.update_progress)
self.worker.finished_signal.connect(self.merging_finished)
self.worker.start()
def update_progress(self, value):
self.progress_bar.setValue(value)
def merging_finished(self, message):
self.status_label.setText(message)
self.progress_bar.setValue(100)
QMessageBox.information(self, "Fertig", message)
def main():
app = QApplication(sys.argv)
window = VideoMergerGUI()
window.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()
Schreibe einen Kommentar