VLC Playlist mit Python

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
Exceed
User
Beiträge: 7
Registriert: Montag 13. Mai 2024, 11:50

Hallo zusammen,

ich versuche gerade mit Python auf dem Raspi 4 ein Programm zu schreiben, das sobald ein USB-Stick eingesteckt wird ausliest, wie der Stick heißt, zu prüfen welche Video/Bild Dateien darauf sind, die Pfade der Dateien dann in einer .txt Datei speichert und dann die Dateien mit dem VLC -Player abspielt. mein erstes Programm, was nur ein Video abspielen sollte hat funktioniert. Stick rein, VLC öffnet sich und das Video läuft.

Jetzt will ich aber noch Bilder dabei haben und das funktioniert nicht. Die Playlist .txt wird zwar erstellt und die Pfade stehe korrekt drin. Der VLC-Player öffnet sich auch, jedoch wird nichts abgespielt.

Main:

Code: Alles auswählen

if __name__ == "__main__":
    directory_path = "/media/spinoza"
    
    
    # Using the DeviceOperations class
    device_name = DeviceOperations.get_device_name(directory_path)
    
    if device_name is not None:
        print("Device:", device_name)
    else:
        print("Keine Ausgabe")
        
    
    file_directory = os.path.join(directory_path, device_name)
    playlist_file = os.path.join(file_directory, 'playlist.txt')
    #playlist_file = "/media/spinoza/03E3-D170/playlist.txt"

    
    print("Video directory:", file_directory)
    
    
    Playlist.create(file_directory, playlist_file)
    VideoPlayer.play_videos(playlist_file)
Device Operations

Code: Alles auswählen

import subprocess

class DeviceOperations:
    @staticmethod
    def get_device_name(directory_path):
        result = subprocess.run(['ls', directory_path], capture_output=True, text=True)
        
        if result.returncode == 0:
            return result.stdout.strip()
        else:
            print("Fehler beim Ausführen des ls-Befehls")
            return None
Playlist Operations

Code: Alles auswählen

import os
import subprocess
import pygame
import glob



class Playlist:
	@staticmethod
	def create(file_directory, playlist_file):

		
		media_files = glob.glob(os.path.join(file_directory, '*.*'))
		media_files = [file for file in media_files if file.lower().endswith(('.mp4','.avi', '.mkv', '.mov', '.png', '.jpeg'))]
		
		print("Media files found: ", media_files)
		
		with open(playlist_file, 'w') as file:
			for media_file in media_files:
				file.write(media_file + '\n')
				
		print("Playlist file created: ", os.path.join(os.getcwd(), playlist_file))		
			 
Video Player

Code: Alles auswählen

import os
import glob
import subprocess
import time
from playlist_operations import Playlist

class VideoPlayer:
    @staticmethod
    def play_videos(playlist_file):
        try:
            subprocess.run(["vlc", "--version"], check = True)
        except subprocess.CalledProcessError:
            print("VLC is not installed or cannor be found.")
            return
            
        try:
            subprocess.run(["vlc", "--fullscreen", "--playlist-enqueue", playlist_file], check = True)
        except subprocess.CalledProcessError as e:
            print(f"Error launching VLC: {e}")
            return
        
Ich hoffe ihr könnt mir weiterhelfen, ich komm echt nicht mehr weiter :D
Viele dank im voraus
Benutzeravatar
__blackjack__
User
Beiträge: 13682
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Exceed: Das gehört komplett entsorgt und neu geschrieben. Drei Module mit jeweils *einer* ”Klasse” die jeweils *eine* statische Methode, also letzlich Funktion enthält, macht so gar keinen Sinn. Wie kommt man auf so eine komische Idee?

Dann verwendet man in neuem Code `pathlib` statt `os.path` und `glob`.

Es gibt keinen sinnvollen Grund ``ls`` als Unterprozess von Python aus zu starten. Den Inhalt von Verzeichnissen kann man auch in Python auflisten, ebenso kann man die Existenz eines Verzeichnisses ohne externe Programme testen.

Eine Datei ist etwas anderes als ein Datei*name*. Wenn etwas `file` heisst, erwartet der Leser so etwas wie `read()`, `write()`, oder `close()` als Methoden.

Beim öffnen von Textdateien sollte man immer die Kodierung mit angeben in der die Datei geschrieben/gelesen werden soll.

Der Ansatz mit der simplen Playlist fällt auf die Nase wenn in den Dateinamen Zeilenendezeichen vorkommen. Es gibt eine VLC-Anbindung auch als Python-Modul.

Die unnötigen weil unbenutzten Importe sollten verschwinden, und das Hauptprogramm gehört in eine Funktion, damit keine Variablen im Modul definiert werden.
„Incorrect documentation is often worse than no documentation.“ — Bertrand Meyer
Exceed
User
Beiträge: 7
Registriert: Montag 13. Mai 2024, 11:50

Hehe :D Ich hab mit Java angefangen zu programmieren und da haben wir immer alles in Klassen "Objektorientiert" in "Module" gesteckt. Ich kenn das nur aus IntelliJ, da war eben für jede Klasse ein eigener Reiter, was jetzt hier die die "Module" sind. In Java Foren habe ich immer zu hören bekommen "Wir programmieren nicht in der Main" finde ich auch deutlich übersichtlicher als alles in ein Modul zu klatschen :0

Die anderen Sachen schaue ich mir jetzt mal an. Danke erstmal !
Benutzeravatar
sparrow
User
Beiträge: 4394
Registriert: Freitag 17. April 2009, 10:28

"Objektorientiert" bedeutet nicht, dass man je Klasse eine Datei schreibt (wie in Java üblich).
Und eine Klasse mit nur einer statischen Methode macht keinen Sinn. Das ist einfach eine Funktion.
Das solltest du dir also ebenso wie den Rest anschauen und einmal alles neu machen.
Benutzeravatar
__blackjack__
User
Beiträge: 13682
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Ergänzend: In „objektorientiert“ kommt auch nur das Wort Objekt vor, nicht das Wort Klasse, man braucht also gar keine Klassen um objektorientiert programmieren zu können. Es gibt OOP-Programmiersprachen die haben gar keine Klassen sondern nur Objekte. Io und Self beispielsweise und auch JavaScript ist sehr lange ohne Klassen ausgekommen. Closures und/oder Prototypen sind hier die Stichworte.
„Incorrect documentation is often worse than no documentation.“ — Bertrand Meyer
Benutzeravatar
__blackjack__
User
Beiträge: 13682
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
from pathlib import Path
from threading import Event

from vlc import MediaList, MediaListPlayer, EventType

MEDIA_SUFFIXES = {".mp4", ".avi", ".mkv", ".mov", ".png", ".jpeg"}


def get_arbitrary_mount_path(media_path):
    try:
        return next(path for path in media_path.iterdir() if path.is_mount())
    except StopIteration:
        raise FileNotFoundError("no mount path found") from None


def main():
    media_path = Path("/media/bj")
    try:
        mount_path = get_arbitrary_mount_path(media_path)
        mount_path = Path("/home/bj/otvr")
    except FileNotFoundError:
        print(f"No mount path found under {media_path}")
    else:
        print("Video directory:", mount_path)

        playing_stopped = Event()
        media_list = MediaList(
            [
                str(path)
                for path in mount_path.iterdir()
                if path.is_file() and path.suffix.lower() in MEDIA_SUFFIXES
            ]
        )
        media_list_player = MediaListPlayer()
        media_list_player.set_media_list(media_list)
        player = media_list_player.get_media_player()
        player.set_fullscreen(True)
        media_list_player.event_manager().event_attach(
            EventType.MediaListPlayerStopped,
            lambda _event: playing_stopped.set(),
        )
        media_list_player.play()

        playing_stopped.wait()


if __name__ == "__main__":
    main()
„Incorrect documentation is often worse than no documentation.“ — Bertrand Meyer
Benutzeravatar
DeaD_EyE
User
Beiträge: 1141
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Hehe :D Ich hab mit Java angefangen zu programmieren
Genau das wollte ich fragen. Paradigmen von einer Sprache auf eine andere Sprache zu übertragen, ist meistens kontraproduktiv.

Diesen Fehler machen viele.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Exceed
User
Beiträge: 7
Registriert: Montag 13. Mai 2024, 11:50

Vielen Dank für die Unterstützung und die Infos!
@__blackjack__ Das Programm schau ich mir direkt mal an. Eigentlich wollte ich mich selbst daran arbeiten aber jetzt hab ich es schon gesehen. XD Ich kann mir das ja dennoch selbst erarbeiten und mich an deine Vorlage halten.
Exceed
User
Beiträge: 7
Registriert: Montag 13. Mai 2024, 11:50

Ist auf jeden fall auch schonmal gut, zu sehen wie ein python Programm strukturiert ist!
Exceed
User
Beiträge: 7
Registriert: Montag 13. Mai 2024, 11:50

@__blackjack__ Ich habe das Programm mal getestet und es läuft wunderbar :D Und ist um ein vielfaches aufgeräumter als meins und das was ich vorhatte XD
Ich muss aber noch ein paar änderungen vornehmen, wie das das Programm in einer "Warteschleife bleibt, solange kein Stick eingesteckt ist, das die Medialist im Loop läuft, dass der Gerätename vom Stick automatisch erkannt wird (sodass ich ohne änderungen am Code jeden Stick einstecken kann) und das die Bilder auf Bildschrimgröße gestreck werden. Gibt es eine Art Liste mit allen Befehlen, die es für das VLC-Modul gibt ?
Ich habe bisher nur diese API-Dokumentation gefunden, die ich etwas unübersichtlich finde https://www.olivieraubert.net/vlc/python-ctypes/doc/
Benutzeravatar
__blackjack__
User
Beiträge: 13682
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Exceed: Es gibt die offizielle API-Dokumentation von libvlc. Die Python-Anbindung ist ja mehr oder weniger automagisch generiert. Und daraus ist dann die API-Dokumentation für Python generiert (mit Epydoc).

Das mit dem auf anstecken reagieren, könnte man vielleicht ausserhalb mit udev-Regeln lösen. Also das man nicht im Programm wartet, sondern das Programm durch das anstecken startet. Ansonsten vielleicht inotify nutzen um auf das auftauchen von neuen Verzeichnissen zu reagieren. Oder einfach pollen. Wäre natürlich die uneleganteste Lösung.

Wie man am besten auf das abziehen eines USB-Sticks im laufenden betrieb reagiert — keine Ahnung ob das mit udev-Regeln geht. Das Programm müsste dann auf jeden Fall robust gegen die Fehler sein, die VLC dann auslöst wenn mitten beim Abspielen das Medium verschwindet. Die feine Art ist das ja nicht gerade und auf vielen Dateisystemen wird dann vermerkt bleiben, dass das Dateisystem nicht sauber ausgehängt wurde.

Es bleibt auch noch das Problem was ich durch den Funktionsnamen `get_arbitrary_mount_path()` ausgedrückt habe: Was ist wenn da mehrere Speichermedien eingehängt sind‽

Und im Moment ist die Reihenfolge der Medien undefiniert. Vielleicht möchte man die noch nach Pfadnamen oder auch irgendeinem anderen Kriterium sortieren.
„Incorrect documentation is often worse than no documentation.“ — Bertrand Meyer
Exceed
User
Beiträge: 7
Registriert: Montag 13. Mai 2024, 11:50

Code: Alles auswählen

#!/usr/bin/env python3
from pathlib import Path
from threading import Event
import os
import time

from vlc import MediaList, MediaListPlayer, EventType

MEDIA_SUFFIXES = {".mp4", ".avi", ".mkv", ".mov", ".png", ".jpg"}



def get_arbitrary_mount_path(media_path):
    try:
        return next(path for path in media_path.iterdir() if path.is_mount())
    except StopIteration:
        raise FileNotFoundError("no mount path found") from None


def check_device_mount():
        return bool(os.listdir('/media/spinoza'))

		

def main():
	while True:
		if check_device_mount():  
			media_path = Path("/media/spinoza")
			try:
				mount_path = get_arbitrary_mount_path(media_path)
				mount_path = Path("/media/spinoza", mount_path)
			except FileNotFoundError:
				print(f"No mount path found under {media_path}")
			else:
				print("Video directory:", mount_path)

				playing_stopped = Event()
				media_list = MediaList(
					[
						str(path)
						for path in mount_path.iterdir()
						if path.is_file() and path.suffix.lower() in MEDIA_SUFFIXES
					]
				)
				media_list_player = MediaListPlayer()
				media_list_player.set_media_list(media_list)
				media_list_player.set_playback_mode(1) # _enum_names_ = {0: 'default', 1: 'loop', 2: 'repeat'}
        
				player = media_list_player.get_media_player()
				player.set_fullscreen(True)
				media_list_player.event_manager().event_attach(
					EventType.MediaListPlayerStopped,
					lambda _event: playing_stopped.set(),
				)
				media_list_player.play()

				playing_stopped.wait()
		else:
					print("No USB device connected.")
					time.sleep(1)  # Wait for USB device to be connected		


if __name__ == "__main__":
    main()

Das ist jetzt der aktuelle stand.

Code: Alles auswählen

media_list_player.set_playback_mode(1) # _enum_names_ = {0: 'default', 1: 'loop', 2: 'repeat'}
Damit konnte ich die Videos ganz einfach im loop laufen lassen

Code: Alles auswählen

			try:
				mount_path = get_arbitrary_mount_path(media_path)
				mount_path = Path("/media/spinoza", mount_path)
			except FileNotFoundError:
				print(f"No mount path found under {media_path}")
			else:
				print("Video directory:", mount_path)
Das hat das Problem mit dem finden des Gerätenamens gelöst. Es soll sowieso nur ein Stick eingesteckt werden, daher muss man sich keine Gedanken darum machen, ob ein weiterer Stick eingesteckt ist. Der Raspi wird rein als Multimedia-Center verwendet.

Code: Alles auswählen

def check_device_mount():
        return bool(os.listdir('/media/spinoza'))

Code: Alles auswählen

if check_device_mount(): 
			media_path = Path("/media/spinoza")
			try:
				mount_path = get_arbitrary_mount_path(media_path)
				mount_path = Path("/media/spinoza", mount_path)
			except FileNotFoundError:
				print(f"No mount path found under {media_path}")
			else:
				print("Video directory:", mount_path)

				playing_stopped = Event()
				media_list = MediaList(
					[
						str(path)
						for path in mount_path.iterdir()
						if path.is_file() and path.suffix.lower() in MEDIA_SUFFIXES
					]
				)
				media_list_player = MediaListPlayer()
				media_list_player.set_media_list(media_list)
				media_list_player.set_playback_mode(1) # _enum_names_ = {0: 'default', 1: 'loop', 2: 'repeat'}
        
				player = media_list_player.get_media_player()
				player.set_fullscreen(True)
				media_list_player.event_manager().event_attach(
					EventType.MediaListPlayerStopped,
					lambda _event: playing_stopped.set(),
				)
				media_list_player.play()

				playing_stopped.wait()
		else:
					print("No USB device connected.")
					time.sleep(1)  # Wait for USB device to be connected
So habe ich das mit dem warten auf einen eingesteckten USB-Stick gelöst. Das Programm wird jetzt einfach einmal gestartet, das mache ich später über ein Autostart-Script und sollte dann noch kein USB-Stick drin sein, wartet das Programm solange, bis ein USB-Stick vorhanden ist.

Wegen der Reihenfolge, habe ich probiert die Dateinamen einfach mit (1. Dateiname || a. Dateiname) zu benennen was aber nicht funktioniert hat..

Ein Problem, was ich jetzt noch habe ist, dass ich die Bilder nicht auf die Größe des Bilschirmes gestreckt bekomme. Ich habe es schon mit "video_set_aspect_ratio" probiert. Das bringt mich aber nicht weiter und sowas wie "video_set_height" u. "video_set_width" gibt es leider nicht. Das einzige was mir einfällt wäre einfach die Bilder in entsprechendem Format bereit zu stellen.

Edit: Das mit dem strecken der Bilder lag wohl tatsächlich nur an der Bildgröße. Mit einem Screenshot vom Desktop z.b. wird es richtig in Vollbild angezeigt.
Exceed
User
Beiträge: 7
Registriert: Montag 13. Mai 2024, 11:50

So jetzt klappt das mit der reihenfolge auch:

Code: Alles auswählen

#!/usr/bin/env python3
from pathlib import Path
from threading import Event
import os
import time

from vlc import MediaList, MediaListPlayer, EventType

MEDIA_SUFFIXES = {".mp4", ".avi", ".mkv", ".mov", ".png", ".jpg"}



def get_arbitrary_mount_path(media_path):
    try:
        return next(path for path in media_path.iterdir() if path.is_mount())
    except StopIteration:
        raise FileNotFoundError("no mount path found") from None


def check_device_mount():
        return bool(os.listdir('/media/spinoza'))

		

def main():
    while True:
        if check_device_mount():  
            media_path = Path("/media/spinoza")
            try:
                mount_path = get_arbitrary_mount_path(media_path)
                mount_path = Path("/media/spinoza", mount_path)
            except FileNotFoundError:
                print(f"No mount path found under {media_path}")
            else:
                print("Video directory:", mount_path)

                playing_stopped = Event()
                media_files = sorted(
                    [
                        str(path)
                        for path in mount_path.iterdir()
                        if path.is_file() and path.suffix.lower() in MEDIA_SUFFIXES
                    ]
                )
                media_list = MediaList(media_files)
                media_list_player = MediaListPlayer()
                media_list_player.set_media_list(media_list)
                media_list_player.set_playback_mode(1)  # loop mode

                player = media_list_player.get_media_player()
                player.set_fullscreen(True)
                media_list_player.event_manager().event_attach(
                    EventType.MediaListPlayerStopped,
                    lambda _event: playing_stopped.set(),
                )
                media_list_player.play()

                playing_stopped.wait()
        else:
            print("No USB device connected.")
            time.sleep(1)  # Wait for USB device to be connected
	


if __name__ == "__main__":
    main()

Benutzeravatar
__blackjack__
User
Beiträge: 13682
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Exceed: Eingerückt wird per Konvention mit vier Leerzeichen pro Ebene, und keine Tabulatorzeichen. Siehe auch den Style Guide for Python Code.

`media_path` ändert sich in der Schleife nie, kann also vor die Schleife gezogen werden.

Wie gesagt `os` benutzt man nicht mehr wenn es da auch was in `pathlib` für gibt. Aber `check_device_mount()` ist auch überflüssig, denn dieser Test ist in `get_arbitrary_mount_path()` mit enthalten.

Das mit `mount_path` kann so nicht funktioneren. Das ist bereits der komplette Pfad zum Mountpoint. Da darf man nicht noch mal das Basisverzeichnis für die Mount-Pfade davor hängen. Das wäre bei `os.listdir()` nötig gewesen, weil das nur die Datei und Verzeichnisnamen ohne den Pfad dort hin liefert. `Path.iterdir()` enthält dagegeben auch den Pfad und nicht nur den Namen.

Wenn dem nicht so wäre: "/media/spinoza" sollte als Wert nur ein einziges mal im Quelltext stehen, damit man das nicht an mehreren Stellen ändern muss, wenn sich beispielsweise der Benutzername mal ändern soll. Das wäre auch ein guter Kandidat für eine Konstante.

Wie gesagt liefern die Funktionen zum auflisten von Verzeichnissen das Ergebnis in der Reihenfolge wie das System/der Dateisystemtreiber das liefert. Wenn man da eine bestimmte Sortierung haben möchte, muss man selber sortieren. Zum Beispiel mit der `sorted()`-Funktion. Ohne eine Funktion für den Sortierschlüssel, werden Pfade beim Sortieren einfach lexikografisch verglichen.

Statt `set_playback_mode()` eine nichtssagende Zahl zu übergeben und zu kommentieren was die bedeutet, würde man besser die entsprechende Konstante übergeben.

Man könnte das auf mehr Funktionen verteilen.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
import time
from pathlib import Path
from threading import Event

from vlc import EventType, MediaList, MediaListPlayer, PlaybackMode

MOUNT_POINTS_BASE_PATH = Path("/media/spinoza")
MEDIA_SUFFIXES = {".mp4", ".avi", ".mkv", ".mov", ".png", ".jpg"}


def get_arbitrary_mount_path(mount_points_base_path):
    try:
        return next(
            path
            for path in mount_points_base_path.iterdir()
            if path.is_mount()
        )
    except StopIteration:
        raise FileNotFoundError("no mount path found") from None


def wait_for_mount_path(mount_points_base_path, poll_intervall=1):
    while True:
        try:
            return get_arbitrary_mount_path(mount_points_base_path)
        except FileNotFoundError:
            time.sleep(poll_intervall)


def play_media_list(media_path, media_list, poll_intervall=1):
    playing_stopped = Event()
    media_list_player = MediaListPlayer()
    media_list_player.set_media_list(media_list)
    media_list_player.set_playback_mode(PlaybackMode.loop)
    player = media_list_player.get_media_player()
    player.set_fullscreen(True)
    media_list_player.event_manager().event_attach(
        EventType.MediaListPlayerStopped,
        lambda _event: playing_stopped.set(),
    )
    media_list_player.play()

    while not playing_stopped.wait(poll_intervall):
        if not media_path.exists():  # External storage device is gone.
            media_list_player.stop()
            break


def main():
    while True:
        mount_path = wait_for_mount_path(MOUNT_POINTS_BASE_PATH)
        print("Video directory:", mount_path)
        media_list = MediaList(
            sorted(
                str(path)
                for path in mount_path.iterdir()
                if path.is_file() and path.suffix.lower() in MEDIA_SUFFIXES
            )
        )
        play_media_list(mount_path, media_list)


if __name__ == "__main__":
    main()
„Incorrect documentation is often worse than no documentation.“ — Bertrand Meyer
Antworten