
Transkription von Videodateien
Als Reaktion auf Lucas Erstlingswerk für die „Sunday Projects“ habe ich sein Projekt direkt als Lösungsansatz wiederverwendet, um Folgendes, welches mich immer wieder auf der Arbeit wurmt, zu erledigen: das Verarbeiten von großen Informationsmengen und das Organisieren von Wissen.
Das Problem
Ich bin ein Datenmessie und wende deshalb einen beachtlichen Anteil meiner Zeit dafür auf, die entsprechenden Informationen für meine Arbeit zu suchen, zu organisieren und zu verteilen. Und dementsprechend viel lese ich.
Es macht so einen beachtlichen Anteil aus, dass ich mich beim Schreiben dieser paar Zeilen gefragt habe, weshalb ich keine Karriere als Bibliothekar angestrebt habe.
Das geht so weit, dass ich letztens von einem ehemaligen Kollegen angeschrieben wurde, um seinen Kollegen bei Wien Energie für ein Projekt einen Crashkurs zu einem spezifischen Thema zu (inkl. Folder mit Quellen).
Und ja, ich war überrascht, dass ein Energieversorger keine interne Expertise zum Thema Energiespeicher hatte.
Aber zurück zur Problemstellung: Auf meinem Arbeitsnotebook horte ich 65 GB an Publikationen, Büchern, Videos, E-Mails und Aufzeichnungen. Insgesamt handelt es sich um 28.585 Dateien, organisiert in 2834 Ordnern (es ist etwas ungenau, weil 183 Ordner durch Zotero angelegt wurden, um die dort gespeicherten Publikationen zu verwalten). Während nun die meisten PDFs eigentlich ganz einfach zu durchsuchen sind, wird es aber mit den ca. 660 Videodateien problematisch, weil keine einzige davon Untertitel hat.
Die Lösung?
Dank Lucas Post konnte ich mich recht großzügig beim Code bedienen und ihn entsprechend für meine Zwecke wiederverwenden (den Code habe ich einfach mittels ChatGPT kommentieren lassen). Im Endeffekt gehe ich durch meinen Literaturordner, suche nach Videodateien und schicke diese durch Whisper. Aus irgendeinem Grund war es anfangs nicht möglich, direkt die Videodateien zu verwenden, weshalb ich erstmal MP3s extrahiert habe, um anschließend alles durch Whisper zu schicken. Danach funktionierte es wieder direkt per Video, also vermute ich, dass da eine Datei das Problem verursacht hat. Aus Faulheit habe ich aber auch keine Fallback-Lösungen eingebaut (z. B. zuerst Video, dann Audio), da ich nicht beabsichtige, das Skript jeden Tag unbeaufsichtigt ausführen zu lassen (oder vielleicht doch irgendwann?).
import osimport subprocessimport globimport torchimport whisperfrom tqdm import tqdmfolder_path = 'WHATEVER'# Load the Modelmodel = whisper.load_model("base")def extract_audio(video_path, audio_path):# Use ffmpeg to extract the audio from the videosubprocess.run(['ffmpeg', '-y', '-loglevel', 'panic', '-i', video_path, '-f', 'mp3', '-ab', '192000', '-vn', audio_path])def transcribe_videos(folder_path):# Initialize the progress barfiles_list = glob.glob(os.path.join(folder_path, '**/*.mp4'), recursive=True) + \glob.glob(os.path.join(folder_path, '**/*.avi'), recursive=True) + \glob.glob(os.path.join(folder_path, '**/*.mkv'), recursive=True)total_files = len(files_list)progress_bar = tqdm(files_list, total=total_files, desc='Processing')# Iterate through all files in the folder (including subfolders)for video_path in progress_bar:audio_path = os.path.splitext(video_path)[0] + '.mp3'transcript_path = os.path.splitext(video_path)[0] + '.txt'# Check if transcript file already exists and has contentif os.path.exists(transcript_path) and os.path.getsize(transcript_path) > 0:continue# Extract audio from the videoextract_audio(video_path, audio_path)# Transcribe the extracted audio using Whispertranscript = model.transcribe(audio_path, fp16=False)# Save the transcript to a text file next to the video filefile_name = os.path.splitext(video_path)[0]with open(transcript_path, 'w', encoding="utf-8") as f:f.write(transcript['text'])# Delete the temporary audio fileos.remove(audio_path)transcribe_videos(folder_path)
Aus Mangel an Rechenressourcen habe ich im Gegensatz zu Luca das Base- anstatt das Large-Modell von OpenAI verwendet. Das sah in den ersten Ergebnissen auch alles ganz okay aus. Hier ein Ausschnitt (insgesamt 118.290 Zeichen):
So let's look at a typical silicon carbide or any vertical device for that matter. There is a thick layer, a drift layer that holds the high voltage. And if you want to make a device of a specific voltage, then the thickness is inversely proportional to the critical electric field. So if the critical electric field is 10 times higher, then the thickness of your device is 10 times smaller. If you look at the device resistance, the equation right below, it's inversely proportional to the third power now of the critical electric field. So if your critical electric field is 10 times higher, then the resistance of that layer will be 1000 times lower. …
Zusatzaufgabe: Erstmal Platz schaffen
Das funktionierte alles soweit auch echt super, bis ich auf ein kleines Detail gestoßen bin: Ich habe die Daten in OneDrive gespeichert und während also das Skript durchlief und brav die Videodateien beim Zugriff heruntergeladen hat, wurde allmählich mein freier Speicherplatz immer geringer, bis ich bei 0 Byte gelandet bin. Blöd. Aber dank FFmpeg und FFprobe ist das auch gar kein Problem (danke ChatGPT, das meinen Code refactored hat). FFmpeg ist eine Bibliothek zum Konvertieren von Videodateien und erlaubt alles Mögliche. Extrahieren von Audio-Dateien, Konvertieren, Filter usw. FFprobe ist davon Bestandteil und ermöglicht es herauszufinden, welchen Codec die Videodateien verwenden. Was ich also tue, ist somit Folgendes:
- Hey, erstell mir mal eine Liste aller Videodateien.
- Gehe durch alle Videodateien und prüfe, ob sie h.265 verwenden.
- Jetzt wäre es irgendwie Leiwand, wenn du sie mit der CPU* alle umwandelst.
- Lösch auch den alten Müll einfach.
- Voila, Videos sind nur noch ein Bruchteil so groß (teilweise 1/10).
- Leiwand.
- CPU, da NVenc meiner Erfahrung nach sowohl bei der Videoqualität als auch beim Komprimieren deutlich schlechter ist.
import osimport subprocessimport shutilimport globfrom tqdm import tqdm# Specify the folder where you want to search for video filesfolder_path = 'WHATEVER'def is_h265_encoded(file_path):# Execute ffprobe command to gather video stream informationprocess = subprocess.Popen(["ffprobe", "-v", "error", "-select_streams", "v:0","-show_entries", "stream=codec_name", "-of", "default=nw=1:nk=1",file_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)output, error = process.communicate()# Check if the output contains "hevc" indicating H.265 encodingreturn output.strip().decode("utf-8") == "hevc"def convert_to_mkv(file_path):# Replace .mp4 extension with .mkv for the output fileoutput_path = file_path.replace('.mp4', '.mkv')# Use ffmpeg to convert the video to MKV format with H.265 encodingresult = subprocess.call(['ffmpeg', '-i', file_path, '-c:v', 'libx265','-hide_banner', '-loglevel', 'panic','-crf', '28', '-c:a', 'copy', output_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)# Return the path of the converted MKV file if conversion was successful, otherwise return Noneif result == 0 and os.path.exists(output_path):return output_pathelse:return Nonedef search_and_convert_videos(folder_path):# Use glob to get a list of video files (with .mp4 extension) in the folder and its subfoldersvideo_files = glob.glob(os.path.join(folder_path, '**/*.mp4'), recursive=True) + \glob.glob(os.path.join(folder_path, '**/*.avi'), recursive=True)# Create a progress bar using tqdmprogress_bar = tqdm(video_files, unit='file')for file_path in progress_bar:# Check if the video is already encoded in H.265 formatif not is_h265_encoded(file_path):# Construct the output path for the converted MKV fileoutput_path = file_path.replace('.mp4', '.mkv')# Check if the output file already existsif os.path.exists(output_path):# Update the progress bar descriptionprogress_bar.set_description(f"Skipped {os.path.basename(file_path)} (already converted)")else:# Convert the video to MKV format and get the output pathoutput_path = convert_to_mkv(file_path)if output_path is not None:# Remove the original video fileos.remove(file_path)# Update the progress bar descriptionprogress_bar.set_description(f"Converted {os.path.basename(file_path)} to {os.path.basename(output_path)}")else:progress_bar.set_description(f"Error converting {os.path.basename(file_path)}")else:progress_bar.set_description(f"{os.path.basename(file_path)} is already in H.265 format, skipping conversion")# Call the function to search for video files and convert them to MKVsearch_and_convert_videos(folder_path)
Ausblick
Was soll eigentlich der ganze Terz, wenn der Text immer noch nicht richtig leserlich ist? Nun ja, es gibt noch ein anderes Projekt, an dem ich nebenbei etwas arbeite: Mein eigener Chatbot mithilfe von PrivateGPT, um es als Suchmaschine zu verwenden. Das läuft auch soweit, wenn auch noch sehr langsam, und ich bin gerade dabei, es auf eine neuere Version zu aktualisieren. Danach kann ich mir dann Gedanken darüber machen, wie ich es für die Kollegen zugänglich machen kann. ;-)
Ciao Kakao, Adrian