Seite 1 von 1

CD auslesen und abspielen

Verfasst: Samstag 28. Juli 2018, 13:15
von Bueroklammer_xy
Hallo,

ich möchte CDs auslesen und abspielen können. Leider komme ich nicht sehr weit (bin Python-Laie).

Ich habe es mit dem Pygame-Paket versucht (zum erstmal nur Abspielen):

Code: Alles auswählen

    pygame.mixer.init()
    pygame.mixer.music.load("D:\Track01.cda")
    pygame.mixer.music.play()   
Leider verursacht dies einen Fehler: "Module format not recognized"
(Das Abspielen einer .wav-Datei funktioniert.)

Ich wollte eigentlich auch pymedia ausprobieren. Jedoch habe ich etwas gelesen, dass dieses veraltet ist.
Ich habe Python 3.6 installiert und pymedia ist (anscheinend?) für V2.7?

Wie könnte man mein Problem lösen? (Mit Pygame oder einem anderen Modul.)
Sorry falls mein Problem so simpel ist.

Danke im Voraus.

Re: CD auslesen und abspielen

Verfasst: Samstag 28. Juli 2018, 13:29
von ThomasL
Hi, das wird so nicht funktionieren.
Hintergrundinfos hier: https://praxistipps.chip.de/cda-datei-o ... ehts_95208
Wenn du die Audiospuren der CD(s) mit nem Ripper als .wav Dateien speicherst,
kannst du sie so wie du es bereits versucht hast per Pygame abspielen.

Re: CD auslesen und abspielen

Verfasst: Samstag 28. Juli 2018, 13:30
von __blackjack__
Falls das eine Audio-CD ist, dafür ist pygame.cdrom.CD gedacht.

Die Dateien die Du da siehst, sind von Windows ”erzeugte” virtuelle Dateien die keine Audiodaten enthalten. Damit kann nur Windows etwas anfangen.

Re: CD auslesen und abspielen

Verfasst: Samstag 28. Juli 2018, 13:36
von ThomasL
@blackjack Danke für diesen Hinweis, ich habe ja schon viel mit Pygame gemacht, aber .cdrom.CD ist an mir vorbei gegangen

Re: CD auslesen und abspielen

Verfasst: Samstag 28. Juli 2018, 15:15
von __blackjack__
Das Computerspiele auf CD-ROM kommen und erwarten, dass die CD-ROM zum während das Spiel läuft, im Laufwerk liegt, damit von dort Audiotracks als Begleit-/Hintergrundmusik abgespielt werden können, ist ja auch nicht mehr ganz so der Stand der Technik. :-)

Re: CD auslesen und abspielen

Verfasst: Samstag 28. Juli 2018, 16:52
von Bueroklammer_xy
__blackjack__ hat geschrieben: Samstag 28. Juli 2018, 13:30 Falls das eine Audio-CD ist, dafür ist pygame.cdrom.CD gedacht.

Die Dateien die Du da siehst, sind von Windows ”erzeugte” virtuelle Dateien die keine Audiodaten enthalten. Damit kann nur Windows etwas anfangen.
Da hatte ich wohl einen blinden Fleck. Es klappt nun und ich kann im Grunde schon alles machen, was ich mir vorgestellt habe: Anzahl der Tracks auflisten und abspielen, wahlweise ab einer bestimmten Stelle. :)

Noch eine Frage: Würde es denn gehen, den Titel und den Interpreten einer Audiodatei auszulesen?
Mit Pygame scheint das nicht möglich zu sein.

Re: CD auslesen und abspielen

Verfasst: Samstag 28. Juli 2018, 16:56
von __deets__
Mir wäre nicht bekannt, das die in einer CD irgendwo abgelegt sind. Du kannst sowas hier benutzen:

https://de.wikipedia.org/wiki/Freedb

Ob das wirklich klappt, bei all den Lizenz-Streitigkeiten - 🤔

Re: CD auslesen und abspielen

Verfasst: Samstag 28. Juli 2018, 17:00
von Bueroklammer_xy
Oh, naja, das muss ja auch nicht unbedingt sein.

Re: CD auslesen und abspielen

Verfasst: Samstag 28. Juli 2018, 17:51
von __blackjack__
@__deets__: Man kann bei Audio-CDs Text mit Informationen zu Titeln, Interpreten usw. hinterlegen. Der Standard dafür heisst CD-Text: https://de.wikipedia.org/wiki/CD-Text

Wie weit das bei kommerziellen CDs verbreitet ist weiss ich nicht. Der Wikipedia-Artikel behauptet Sony macht das. Die haben das wohl auch spezifiziert.

Re: CD auslesen und abspielen

Verfasst: Samstag 28. Juli 2018, 19:26
von __deets__
Na die von mir genannten Projekte wären dann ja sinnlos 🤔

Ich höre eh nur Spotify, da kommen alle Metadaten mit 🤗

Re: CD auslesen und abspielen

Verfasst: Freitag 17. August 2018, 15:40
von Bueroklammer_xy
Hallo,

ich habe nun noch ein Problem und möchte eine Frage nachschieben:

Ich möchte gerne mehrere Tracks einer Audio-CD auswählen und abspielen können. Also z.B. Nr. 2, 4 und 6 von 10.

Ich wollte das folgendermaßen mit einem Timer machen:

Code: Alles auswählen

def cd_playbutton():
    
    trackauswahl = lbox_tracks.curselection() # Listbox
    startzeitpunkt = 0
    
    for track_id in trackauswahl:
        
        z = Timer(startzeitpunkt, cd_abspielen, args=[track_id])
        z.start()       
        
        trackdauer = pygame.cdrom.CD(0).get_track_length(track_id)
        startzeitpunkt += trackdauer    

    
def cd_abspielen(track_id):
    pygame.cdrom.CD(0).play(track_id)
Bei direktem Aufruf der Funktion zum Abspielen klappt es. Jedoch sobald ich einen Timer benutze, erscheint folgende Fehlermeldung:
IndexError: Invalid track number
Gibt es eine Möglichkeit, nicht nur ab einem bestimmten Track bis zum Ende der CD durchlaufen zu lassen, sondern eine Auswahl von Tracks abspielen zu lassen?

Danke schon mal.

Re: CD auslesen und abspielen

Verfasst: Freitag 17. August 2018, 17:16
von __deets__
Da das kein vollstaendiges Code-Beispiel ist, kann man da nur mit den Achseln zucken. Hast halt einen Fehler programmiert.

Was direkt auffaellt, und 100%ig in die Hose geht: die Verwendung von threads. pygame erlaubt nicht, dass man da mit mehreren Threads rumfuhrwerkt. Und es ist doch auch ueberhaupt nicht notwendig. Du hast doch in pygame selbst die Aufgabe, den mainloop zu treiben. Und kannst darin problemlos zB die verflossene Zeit ermitteln, und dann bei Bedarf etwas neues abspielen. Oder statt Zeit vielleicht besser mit einem Abspielstatus arbeiten, wobei ich da jetzt genauer in die API schauen muesste.

Re: CD auslesen und abspielen

Verfasst: Samstag 18. August 2018, 16:14
von Bueroklammer_xy
Ich habe nun eine Weile gebastelt und das unten ist dabei herausgekommen.

Es läuft, und damit bin ich eigentlich glücklich. :K

Super, dass man hier fragen kann.

Code: Alles auswählen

from tkinter import *
from tkinter import ttk
from pygame import cdrom 


def cd_tracks_holen():
    '''Tracks von CD auslesen.'''
    liste_tracks = []

    for track_id in range(0, cdrom.CD(0).get_numtracks()):
        trackname = "Track" + str(track_id + 1)
        trackdauer = str(int(cdrom.CD(0).get_track_length(track_id)))
        liste_tracks.append(trackname + " - " + trackdauer)
    return liste_tracks


def abspielen():
    '''Abspielen vorbereiten.'''
    global playflag
    global lbox_index
    global track_ids    
    track_ids = lbox_tracks.curselection()
    if not len(track_ids) == 0: # sofern etwas selektiert ist
        if cdrom.CD(0).get_busy():
            cdrom.CD(0).stop()
        playflag = True
        lbox_index = 0


def fenster_schließen():
    global fenster_offen
    fenster_offen = False    
    root.destroy()
        

cdrom.init()
cdrom.CD(0).init()

root = Tk()
root.protocol("WM_DELETE_WINDOW", fenster_schließen)

mainframe = ttk.Frame(root, padding="5 5 5 5")
mainframe.grid()

playbutton = ttk.Button(mainframe, text='Play', command=abspielen)
playbutton.grid(column=0, row=0, sticky=(N))

# Trackliste
cd_tracks = cd_tracks_holen()
tracks = StringVar(value=cd_tracks)
lbox_tracks = Listbox(mainframe, height=12, selectmode="extended", listvariable=tracks)
lbox_tracks.grid(column=1, row=0, sticky=(N))

fenster_offen = True
playflag = False
lbox_index = 0
track_ids = []

# Hauptschleife Tkinter
while True:  
    root.update_idletasks()
    root.update()
    
    if fenster_offen == False:
        break
    
    if playflag == False:
        continue

    track_id = track_ids[lbox_index]

    track_info = cdrom.CD(0).get_current()
    
    if not cdrom.CD(0).get_busy():
        cdrom.CD(0).play(track_id)
        print("Track" + str(track_id), cdrom.CD(0).get_track_length(track_id))
    elif int(track_info[1]) >= int(cdrom.CD(0).get_track_length(track_id)):
        cdrom.CD(0).stop()
        lbox_index += 1    
    
    if lbox_index >= len(track_ids):
        playflag = False    

Re: CD auslesen und abspielen

Verfasst: Sonntag 19. August 2018, 13:43
von __blackjack__
@Bueroklammer_xy: Das ist aber keine wirklich empfehlenswerte Lösung die Tk-Hauptschleife nicht zu verwenden. Wenn man periodisch etwas machen möchte, gibt es dafür die `after()`-Methode auf Widgets.

Der Sternchen-Import holt ca. 190 Namen in das Modul von denen nur ganz wenige verwendet werden. Es ist schwieriger nachzuvollziehen wo Namen definiert wurden und es besteht die Gefahr von Namenskollisionen.

Funktionen (und Methoden) sollten alles was sie ausser Konstanten benötigen als Argument übergeben bekommen und nicht auf magische Weise aus der Umgebung nehmen. Globale Variablen und ``global`` sollte man nicht verwenden. Das macht Programme unnötig schwer nachvollziehbar, weil man plötzlich das gesamte Programm auf einmal kennen muss, um zu verstehen wann welche globale Variable unter welchen Umständen von wo aus verändert wird.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

Das hier ist ein Programm bei dem man ohne objektorientierte Programmierung nicht mehr wirklich auskommt.

Es werden sehr sehr viele `CD`-Objekte erstellt, die alle das selbe CD-Laufwerk repräsentieren. Man braucht eigentlich nur ein einziges dieser Objekte im ganzen Programm.

Der Test auf das Track-Ende ist unzuverlässig, denn wenn man Pech hat, bekommt man den Track-Wechsel nicht mit. Man darf nicht nur auf die Zeit prüfen, sondern muss auch den Track-Index berücksichtigen.

Zusammenstückeln von Zeichenketten und Werten mittels ``+`` und `str()` ist mehr BASIC als Python. In Python gibt es dafür Zeichenkettenformatierung mit der `format()`-Methode.

Es wird bei der Erstellung der Trackliste kein Test gemacht ob es sich überhaupt um einen Audio-Track handelt.

Statt dort alle Tracks einzeln abzufragen, würde sich die `CD.get_all()`-Methode anbieten.

Explizite Vergleiche mit `True` und `False` sind unnötig umständlich. Da kommt ja nur wieder ein Wahrheitswert heraus, entweder den, den man sowieso schon hatte, oder das Gegenteil. Also kann man auch gleich den Wert oder seine Negation mit `not` verwenden.

Ungetesteter Zwischenstand:

Code: Alles auswählen

from functools import partial
import tkinter as tk
from tkinter import ttk

from pygame import cdrom 


def cd_tracks_holen(drive):
    """Tracks von CD auslesen."""
    return [
        'Track{} - {}'.format(number, int(length))
        for number, (is_audio, _, _, length) in enumerate(drive.get_all(), 1)
        #
        # TODO Check if track is audio track and filter those out.
        #   Needs changes in the GUI code because then the index into
        #   this list isn't neccessarily the index of the `Listbox`.
        # 
    ]


class App(tk.Tk):
    
    def __init__(self, drive):
        tk.Tk.__init__(self)
        self.drive = drive
        self.is_playing = False
        self.current_selection_index = 0
        self.selected_track_indices = []

        main_frame = ttk.Frame(self, padding=5)
        main_frame.grid()

        ttk.Button(
            main_frame, text='Play', command=self.abspielen
        ).grid(column=0, row=0, sticky=tk.N)

        tracks = tk.StringVar(value=cd_tracks_holen(drive))
        self.tracks_listbox = tk.Listbox(
            main_frame, height=12, selectmode=tk.EXTENDED, listvariable=tracks
        )
        self.tracks_listbox.grid(column=1, row=0, sticky=tk.N)
        self.check_state()

    def check_state(self):
        if self.is_playing:
            track_index = self.selected_track_indices[
                self.current_selection_index
            ]
            playing_index, length = self.drive.get_current()
            if not self.drive.get_busy():
                self.drive.play(track_index)
                print(
                    'Track{} {}'.format(
                        track_index, self.drive.get_track_length(track_index)
                    )
                )
            elif (
                track_index != playing_index or
                int(length) >= int(self.drive.get_track_length(track_index))
            ):
                self.drive.stop()
                self.current_selection_index += 1    
            
            if self.current_selection_index >= len(self.selected_track_indices):
                self.is_playing = False
        
        self.after(100, self.check_state)

    def abspielen(self):
        """Abspielen vorbereiten."""
        self.selected_track_indices = self.tracks_listbox.curselection()
        if self.selected_track_indices:
            if self.drive.get_busy():
                self.drive.stop()
            self.is_playing = True
            self.current_selection_index = 0


def main():
    cdrom.init()
    drive = cdrom.CD(0)
    drive.init()
    
    app = App(drive)
    app.mainloop()


if __name__ == '__main__':
    main()

Re: CD auslesen und abspielen

Verfasst: Samstag 25. August 2018, 16:19
von Bueroklammer_xy
@__blackjack__: Danke für deine Ausführungen und dein Script. Ich habe es durchgearbeitet (und auch soweit verstanden :) ). Werde zukünftig auf alle Fälle versuchen mich daran zu orientieren. Die Lösung der Funktion 'cd_tracks_holen' find ich ziemlich stark. Aber wieso ist diese nicht eine Methode innerhalb der Klasse?

Re: CD auslesen und abspielen

Verfasst: Samstag 25. August 2018, 19:09
von __blackjack__
@Bueroklammer_xy: Die Funktion hängt nicht von der GUI ab, die kann man auch ohne die GUI aufrufen/verwenden.