CD auslesen und abspielen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Bueroklammer_xy
User
Beiträge: 8
Registriert: Samstag 5. Mai 2018, 11:18

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.
Benutzeravatar
ThomasL
User
Beiträge: 1366
Registriert: Montag 14. Mai 2018, 14:44
Wohnort: Kreis Unna NRW

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.
Zuletzt geändert von ThomasL am Samstag 28. Juli 2018, 13:32, insgesamt 1-mal geändert.
Ich bin Pazifist und greife niemanden an, auch nicht mit Worten.
Für alle meine Code Beispiele gilt: "There is always a better way."
https://projecteuler.net/profile/Brotherluii.png
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

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.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
ThomasL
User
Beiträge: 1366
Registriert: Montag 14. Mai 2018, 14:44
Wohnort: Kreis Unna NRW

@blackjack Danke für diesen Hinweis, ich habe ja schon viel mit Pygame gemacht, aber .cdrom.CD ist an mir vorbei gegangen
Ich bin Pazifist und greife niemanden an, auch nicht mit Worten.
Für alle meine Code Beispiele gilt: "There is always a better way."
https://projecteuler.net/profile/Brotherluii.png
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

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. :-)
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Bueroklammer_xy
User
Beiträge: 8
Registriert: Samstag 5. Mai 2018, 11:18

__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.
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

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 - 🤔
Bueroklammer_xy
User
Beiträge: 8
Registriert: Samstag 5. Mai 2018, 11:18

Oh, naja, das muss ja auch nicht unbedingt sein.
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@__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.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Na die von mir genannten Projekte wären dann ja sinnlos 🤔

Ich höre eh nur Spotify, da kommen alle Metadaten mit 🤗
Bueroklammer_xy
User
Beiträge: 8
Registriert: Samstag 5. Mai 2018, 11:18

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.
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

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.
Bueroklammer_xy
User
Beiträge: 8
Registriert: Samstag 5. Mai 2018, 11:18

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    
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@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()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Bueroklammer_xy
User
Beiträge: 8
Registriert: Samstag 5. Mai 2018, 11:18

@__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?
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Bueroklammer_xy: Die Funktion hängt nicht von der GUI ab, die kann man auch ohne die GUI aufrufen/verwenden.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten