mci zum auslesen von audiocd -> freedb abfragen

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
genrich
User
Beiträge: 91
Registriert: Sonntag 27. Juni 2004, 17:46

Hab das Projekt gefunden:
http://sourceforge.net/projects/cddb-py/
(Wobei es mittlerweile mit freeDB funktioniert)

Bei diesem Projekt wird eine Audio CD mit mci.dll (eine in C geschriebenes Modul) ausgelesen, um die DiscID zu erhalten...

Leider ist diese Modul für Python 2.0 kompiliert... Nun hab ich bei http://www.pkshiu.com/tech/cddb.html folgendes gefunden:
The currently CDDB project works well under *NIX and Python 2.0. However, the pre-compiled mci.dll for windows is built for Python 2.0. To make it work with 2.1, edit it with an editor (emacs works nicely) and search for reference to the python 2.0 dll. Change 2.0 to 2.1. ALso the python source files have some references to UNIX only API calls that is not in the Windows platform (geteuid). Comment out that line will make the software work.
Leider kann ich das Beschrieben aber nicht umsetzen. Hab keinen blassen schimmer, wie man mit C kompiliert...

Generell wäre es auch schön, wenn man system unabhängig an's CD-ROM dran kommt, um die DicsID zu bestimmen... Gibt es da was???
genrich
User
Beiträge: 91
Registriert: Sonntag 27. Juni 2004, 17:46

Hab was gefunden!

http://www.pygame.org

Ist zwar eigentlich für Spiele...
Hat aber ein CDROM Modul:
http://www.pygame.org/docs/ref/pygame_cdrom.html

Hier ein Tutorial:
http://www.pygame.org/pcr/cd_tutorial/index.php


Das ganze gibt es Windows, verschiedene Unix Distributions, MacOS und BeOS.


Jetzt muß ich mir nur was basten, das ich aus den Tack-Information die freeDB DiscID berechnen kann...
genrich
User
Beiträge: 91
Registriert: Sonntag 27. Juni 2004, 17:46

Habe mir jetzt aus verschiedenen Quellen ein skript zusammengebastelt, welches eigentlich eine DiscID einer Audio CD generieren soll... Es klappt aber nicht wie erwartet, die DiscID ist einfach falsch :(
Das liegt wohl daran, das ich die gefundenen Codestücke nicht richtig verstanden habe...



[alter CODE gelöscht, s. aktueller Version unten]



ich bekomme auch einen Fehler, mit dem ich nicht's anfangen kann:
FutureWarning: x<<y losing bits or changing sign will return a long in Python 2.4 and up
Zuletzt geändert von genrich am Dienstag 9. November 2004, 18:06, insgesamt 1-mal geändert.
genrich
User
Beiträge: 91
Registriert: Sonntag 27. Juni 2004, 17:46

Hier eine aktualisierte Version... Doch leider stimmt die DiscID immer noch nicht :(

[alter CODE gelöscht, s. aktueller Version unten]
Zuletzt geändert von genrich am Dienstag 9. November 2004, 18:06, insgesamt 1-mal geändert.
Dookie
Python-Forum Veteran
Beiträge: 2010
Registriert: Freitag 11. Oktober 2002, 18:00
Wohnort: Salzburg
Kontaktdaten:

Hi,

in der Pythonmailingliste die Nummer stimmt aber.

zum Umwandeln in Hex kannst Du auch die eingebaute Funktion hex() nutzen:

Code: Alles auswählen

>>> hex(-1102853104)
... '0xbe43cc10'
das 0x bekommst du einfach so weg:

Code: Alles auswählen

>>> hex(-1102853104)[2:]
... 'be43cc10'

Gruß

Dookie
[code]#!/usr/bin/env python
import this[/code]
genrich
User
Beiträge: 91
Registriert: Sonntag 27. Juni 2004, 17:46

Code: Alles auswählen

"%08lx" % RAWdiscID
ist doch das selbe wie

Code: Alles auswählen

hex(-1102853104)[2:]
Zur eigentlichen Datenbankabfrage nutze ich ja http://sourceforge.net/projects/cddb-py/


Ich hab per Hand nach der CD gesucht... Es kommen nur folgende IDs in Frage:
d40e9010
db0e9210
d60e5910
Also es liegt wohl noch ein Grundlegendes Problem vor :(
Ich habe mit noch mal genau die Spezifikationen angeschaut:
http://www.freedb.org/modules.php?name= ... le&artid=6

Ganz merkwürdig finde ich die berechnung von t:
t = ((cdtoc[tot_trks].min * 60) + cdtoc[tot_trks].sec) -
((cdtoc[0].min * 60) + cdtoc[0].sec);
Und was mich auch mulmig stimmt sind die:
Important note for clients using the MS-Windows MCI interface
Ich weiß leider nicht ob pygames eigentlich nur auf das MCI zurückgreift...
Naja die Trackzeiten mit Sek und Frames stimmen ja zumindest mit den angaben von EAC überein...

Gibt es vielleicht doch nicht etwas fertiges in der Richtung??? Ich schein das ja nicht auf die Kette zu bekommen :oops:
genrich
User
Beiträge: 91
Registriert: Sonntag 27. Juni 2004, 17:46

Ach so, hier noch mal eine aktualisierte Version:

Code: Alles auswählen

from pygame import cdrom

cdrom.init()
print "CDROM initialized: ", cdrom.get_init()
print "Number of CDROMs:  ", cdrom.get_count()


def GetHMS(s):
    h,s = divmod(s,3600)
    m,s=divmod(s,60)
    return "%02.f:%02.f:%02.f" % (h, m, s)

def Sek_Frames(s):
    nurSek = int( s )
    Frames = int( round( (s - nurSek) * 75 ) )
    return nurSek, Frames


def cddb_sum(n):
    # cddb_sum(123456789) => 1+2+3+4+5+6+7+8+9 = 45
    ret = 0
    while n > 0:
        ret += (n % 10)
        n = n / 10
    return ret


def DiscID(cd_object):
    print "\n Nr.   Länge"
    print "--------------------------"

    checksum=0
    TotalFrames=0
    TotalSek=0
    for i in range(TrackAnzahl):
        track_length = cd_object.get_track_length(i)

        nurSek, Frames = Sek_Frames( track_length )
        checksum += cddb_sum(nurSek)

        print " %2d   %s.%2d Frames" % (i, GetHMS(nurSek), Frames)

        TotalSek += nurSek

    nurSek, Frames = Sek_Frames( TotalSek )
    print "\nTotalZeit:",nurSek,"sek"
    print "TotalZeit: %s.%d" % (GetHMS(nurSek),Frames)

    sek_LetzterTrack = Sek_Frames(cd_object.get_track_length(TrackAnzahl-1))[0]
    sek_ErsterTrack  = Sek_Frames(cd_object.get_track_length(0))[0]

    t=sek_LetzterTrack - sek_ErsterTrack

    RAWdiscID=( checksum % 0xff << 24 | t << 8 | TrackAnzahl )



for i in range(cdrom.get_count()):
    cd_object = cdrom.CD(i)
    print "\nNr.:",i," Laufwerk:",cd_object.get_name()

    print "Initialisierte..."
    cd_object.init()

    TrackAnzahl = cd_object.get_numtracks()

    if TrackAnzahl!=0:
        RAWdiscID = DiscID(cd_object)
        print "\nRAW discID..:", RAWdiscID
        print "freedb ID...: %08lx" % RAWdiscID 
verbesserte Version
Zuletzt geändert von genrich am Dienstag 9. November 2004, 18:28, insgesamt 1-mal geändert.
Dookie
Python-Forum Veteran
Beiträge: 2010
Registriert: Freitag 11. Oktober 2002, 18:00
Wohnort: Salzburg
Kontaktdaten:

ich denke mal hier liegt der Fehler:

Code: Alles auswählen

    sek_LetzterTrack = Sek_Frames(cd_object.get_track_length(i))[0] 
i zeigt da nicht auf den letzten Track sondern hat den wert TrackAnzahl
nimm besser

Code: Alles auswählen

    sek_LetzterTrack = Sek_Frames(cd_object.get_track_length(TrackAnzahl-1))[0]


Gruß

Dookie
[code]#!/usr/bin/env python
import this[/code]
genrich
User
Beiträge: 91
Registriert: Sonntag 27. Juni 2004, 17:46

Hast recht, danke...

Hab ich verbessert... Jetzt bekomme ich als ID "be00b210"... Stimmt leider auch nicht :(
Dookie
Python-Forum Veteran
Beiträge: 2010
Registriert: Freitag 11. Oktober 2002, 18:00
Wohnort: Salzburg
Kontaktdaten:

Hi genrich,

firsttrack muss auch nicht unbeding track0 sein.

noch ein Tipp, lad dir das CDDB-1.3.tar.gz runter und entpacke es, dann kannst Du in das DiskID.py reinschauen, da ist auch eine Funktion disc_id drinnen mit der Berechnung.


Gruß

Dookie
[code]#!/usr/bin/env python
import this[/code]
Dookie
Python-Forum Veteran
Beiträge: 2010
Registriert: Freitag 11. Oktober 2002, 18:00
Wohnort: Salzburg
Kontaktdaten:

Du brauchsts nichtmal runterladen, einfach hier gucken:
http://csl.cse.ucsc.edu/~ben/python/CDDB/DiscID.py


Gruß

Dookie
[code]#!/usr/bin/env python
import this[/code]
Dookie
Python-Forum Veteran
Beiträge: 2010
Registriert: Freitag 11. Oktober 2002, 18:00
Wohnort: Salzburg
Kontaktdaten:

nochwas,

in dem source findest Du öfter solche Zeilen:

Code: Alles auswählen

(first, last) = cdrom.toc_header(device)
mach da am besten die Klammern vorne weg:

Code: Alles auswählen

first, last = cdrom.toc_header(device)
Dann klappts auch mit Python2.3


Gruß

Dookie
[code]#!/usr/bin/env python
import this[/code]
genrich
User
Beiträge: 91
Registriert: Sonntag 27. Juni 2004, 17:46

Darauf basiert mein Code... In diesem Projekt ist eigentlich alles enthalten, nur es benutze eine eigene C-Lib um an die TOC Informationen der Audio CD zu gelangen... Es ist auch eigentlich ein fertig kompilierte DLL dabei, die läuft allerdings nur mit Python v2.0 :(

Auf der Mailingliste von pygames habe ich ein anderes Problem erfahren:
Und zwar braucht man eigentlich die "lead in" und "lead out" Daten... Die sind glaube ich nicht immer die selben, wie die normalen Start und Längen angaben... Kenne mich da aber auch nicht so dolle aus...
Ich habe auch ein Source mitgeschickt bekommen... Ich habs mal ein wenig angepasst, sodas es direkt läuft, wenn man pygame installiert hat:

Altes Listing entfernt... s.unten

Bei meiner "Depeche Mode" - "Speak & Spell" CD bekomme ich damit folgende Werte:

Code: Alles auswählen

RAW_ID......: 1660956944
freedb ID...: 63003110
Zuletzt geändert von genrich am Mittwoch 10. November 2004, 06:36, insgesamt 1-mal geändert.
genrich
User
Beiträge: 91
Registriert: Sonntag 27. Juni 2004, 17:46

Mit dem Kommandozeilen Programm http://www.freedb.org/software/cddbidgen.zip kann ich nun entlich sichergehen, welchen DiscID der richtige ist:
dd0e8e10
spuckt es aus und das passt: http://www.freedb.org/freedb/data/dd0e8e10

Für mein Skript bringt mich das aber nicht weiter :(
Dookie
Python-Forum Veteran
Beiträge: 2010
Registriert: Freitag 11. Oktober 2002, 18:00
Wohnort: Salzburg
Kontaktdaten:

warum, du kannst das Tool ja mit system oder os.popen oder sonstwas aufrufen und das Ergebnis so erhalten.


Gruß

Dookie
[code]#!/usr/bin/env python
import this[/code]
genrich
User
Beiträge: 91
Registriert: Sonntag 27. Juni 2004, 17:46

So jetzt habe ich es... Das Listing aus der Mailingliste war nicht ganz vollständig...

Code: Alles auswählen


from pygame import cdrom

def getDiskId( tracksInfo ):
    """
    Returns disc id based on tracksInfo which is tuple of ( track_start, track_length )
    """
    # check sum
    def sumDigits( val ):
        return int( reduce( lambda x, y: int( y )+ int( x ), str( val ) ) )

    # check sum
    checkSum= reduce( lambda x, y: x+ y, map( lambda x: sumDigits( int( x[ 0 ]/75 )), tracksInfo ) )
    # total length
    totalTime= reduce( lambda x, y: x+ y, map( lambda x: x[ 1 ], tracksInfo ) )
    return ( ( long(checkSum % 0xff) << 24 ) + ( long( totalTime/75 ) << 8 ) + len( tracksInfo ) )




cdrom.init()
cd=cdrom.CD(0)

print "Initialisierte..."
cd.init()

RAW_ID = getDiskId( [ ( cd.get_track_start( x )*75,cd.get_track_length( x )*75 ) for x in xrange( cd.get_numtracks() ) ] )

print "RAW_ID......:",RAW_ID
print "freedb ID...: %08lx" % RAW_ID
Nun ist das Ergebniss korrekt:

Code: Alles auswählen

RAW_ID......: 3708718608
freedb ID...: dd0e8e10
Obwohl das Listing schön kompakt ist, will ich es etwas Normalisieren... Die ganzen lambda Dinger verstehe ich nämlich nicht wirklich...
genrich
User
Beiträge: 91
Registriert: Sonntag 27. Juni 2004, 17:46

So... Ich hab meine Variante mit der kompakten Varianten von oben gekreutzt...
Nun hab ich ein Listing, welches ich auch verstehe :lol:
Und es funktioniert!!! :D

Code: Alles auswählen

from pygame import cdrom

def DiscID(cd_object):
    def getHMS_f(Frames):
        # Umwandung von Frames in h:m:s.frames
        s,Frames = divmod(Frames,75)
        h,s = divmod(s,3600)
        m,s=divmod(s,60)
        return "%02.f:%02.f:%02.f.%02.f" % (h, m, s, Frames)

    def sumDigits( n ):
        # Berechnet die Quersumme
        # n=123456789 => 1+2+3+4+5+6+7+8+9 = 45
        ret = 0
        for i in str(n):
            ret += int(i)
        return ret

    checksum=0
    total_length = 0
    for i in range(TrackAnzahl):
        track_length = cd_object.get_track_length(i)*75
        print "%2d - %s" % (i, getHMS_f(track_length))
        total_length += track_length

        checksum += sumDigits( int(cd_object.get_track_start( i )) )

    print "\nTotalZeit: %s -> %dsek." % (getHMS_f(total_length), round(total_length/75))

    return (long(checksum % 0xff) << 24) + (long(total_length/75) << 8) + TrackAnzahl

cdrom.init()
cd_object=cdrom.CD(0)

print "Initialisierte..."
cd_object.init()

RAW_ID = getDiskId( cd_object )

print "RAW_ID......:",RAW_ID
print "freedb ID...: %08lx" % RAW_ID
Wobei... Ist es eigentlich ok, wenn man eine Funktion in einer Funktion packt??? Ich meine, eigentlich ist es praktisch, wenn man diese Funktion nur "lokal" braucht...
Um das selbe zu erreichen hätte ich normalerweise jetzt wieder eine Klasse draus gemacht...


Jetzt muß ich "nur" noch die eigentliche freedb Abfrage hinbekommen... Leider klappt das mit diesem alten CDDB.py spontan nicht... Ich erhalte immer 500 None zurück :(
genrich
User
Beiträge: 91
Registriert: Sonntag 27. Juni 2004, 17:46

So... Hab nun eine Abfrage fertig...

Code: Alles auswählen

#!/usr/bin/python
# -*- coding: cp1252 -*-


import urllib, string


__version__="0.1"

UserEMail="test+test"

MaxReadLines=50 # Max. zu lesende Zeilen

# Use protocol version 5 to get DYEAR and DGENRE fields.
protocol = 5
default_server = 'http://freedb.freedb.org/~cddb/cddb.cgi'

ClientInfo = "&hello=%s+%s+%s&proto=%i" % (UserEMail, __file__, __version__, protocol)


def parseFreedbInfo(txt):
    """
    Wandelt die freedb Daten in einen Dictionary um
    """
    Info={}
    for i in txt:
        zeile=string.strip(i)
        if zeile==".": break

        if zeile[0]!="#":
            Item, txt = string.split( zeile , "=", 1)
            if txt!="":
                Info[Item]=txt

    return Info


def readHeader(response):
    Line = response.readline()
    header = string.strip( Line )
    code, msg = string.split( header , " ", 1)
    return code, msg




def GenreList():
    """
    Fragt die Liste aller verfügbarer Gernes bei freedb ab
    """
    url = default_server+"?cmd=cddb+lscat"+ ClientInfo
    #~ print url
    print "Frage Liste aller Gernes ab...",
    response = urllib.urlopen(url)

    code, msg = readHeader(response)
    print code

    # alles auslesen, bis auf die letzte Zeile
    #~ lines = response.readlines()[:-1]
    Genres = []
    for i in response.readlines()[:-1]:
        Genres.append(string.strip(i))
    return Genres


def DatenLesen(discID,AllItems=False):
    """
    Fragt die Daten zur CD bei freedb ab.
    AllItems=True  -> Es werden alle Gernes abgefragt
    AllItems=False -> Fragt nur das erste verfügbare Gerne ab
    """
    freedbInfo={}
    for Genre in GenreList():
        url = default_server+"?cmd=cddb+read+"+Genre+"+"+discID+ ClientInfo
        response = urllib.urlopen(url)

        code, msg = readHeader(response)

        print "%14s" % Genre,"-",

        if code=="210":
            print "Eintrag vorhanden!"

            freedbInfo[Genre] = parseFreedbInfo(response.readlines())

            if AllItems==False:
                # Nach dem ersten gefunden Eintrag -> abbruch
                break
        else:
            print "kein Eintrag vorhanden"

    return freedbInfo
Die Abfrage geht mit DatenLesen(freedbID)
Dookie
Python-Forum Veteran
Beiträge: 2010
Registriert: Freitag 11. Oktober 2002, 18:00
Wohnort: Salzburg
Kontaktdaten:

H genrich,
Wobei... Ist es eigentlich ok, wenn man eine Funktion in einer Funktion packt??? Ich meine, eigentlich ist es praktisch, wenn man diese Funktion nur "lokal" braucht...
Lokale Funktionen braucht man nicht oft, sind aber schon ok. Besonders wenn damit die Lesbarkeit eines Programmes erleichtert wird.


Gruß

Dookie
[code]#!/usr/bin/env python
import this[/code]
Antworten