Java CD-ID auslesen und Informationen von musicbrainz.org auslesen

CPU

Lieutenant
Registriert
Jan. 2006
Beiträge
704
Hallo,

für ein Projekt muss ich via Java-Applet die CD-Id auslesen und bei musicbrainz.org die Daten zur CD besorgen.
Soweit die Anforderungen. Dann habe ich mal gegoogelt, ob es soetwas bereits gibt, bin jedoch auf kein Ergebnis gestoßen. Also habe ich mir gedacht, dass ich es selbst machen muss und bin auf diese nette kleine Beschreibung gestoßen: http://musicbrainz.org/doc/DiscIDCalculation

Das Grundkonzept ist mir klar, mir sind jedoch noch nicht die Schritte klar, die nötig sind. Und da könnt Ihr mir vielleicht helfen:

The CD Index algorithm simply takes the following pieces of data and runs them through the SHA-1 hash function:

* First track number (normally one): 1 byte
* Last track number: 1 byte
* Lead-out track offset: 4 bytes
* 99 frame offsets: 4 bytes for each track

* If there are less than 99 tracks (almost certainly), the value 0 will be used instead.

Before the data is fed through the SHA-1 hash, it is converted to upper-case hex ASCII using printf("%02X", value); for single-byte values and printf("%08X", value); for 4-byte values.
Code:
sprintf(temp, "%02X", pCDInfo->First*Track);
sha_update(&sha, (unsigned char*) temp, strlen(temp));

sprintf(temp, "%02X", pCDInfo->Last*Track);
sha_update(&sha, (unsigned char*) temp, strlen(temp));
//Note that the lead-out track is stored in pCDInfo->Frame*Offset[0]. 
for (i = 0; i < 100; i++) {
    sprintf(temp, "%08X", pCDInfo->Frame*Offset[i]);
    sha_update(&sha, (unsigned char*) temp, strlen(temp));
}
sha_final(digest, &sha);

Nun zunächst, was ist die "First/Last Track number" und was ist der "Lead-out track offset"?

Verstehe ich das Richtig, dass Einfach nur der First-Track, Last-Track und der FrameOffset (von 0-tracklänge) in einen String gepackt wird und der dann mit sha1 gehashed wird? Und was bedeutet "If there are less than 99 tracks (almost certainly), the value 0 will be used instead. "?

Gruß,
CPU - hoffe Ihr könnt mir helfen ... :(
 
Hast du die gesamte Seite gelesen, die du oben verlinkt hast? Denn da wird es doch weiter oben genau erklärt...
First Track ist halt die Nummer des ersten Tracks, in aller Regel 1. Und Last Track eben die des letzten Tracks, also z.B. 20, wenn die CD 20 Lieder hat.
Der Lead-Out-Track ist die Spur, die auf die letzte Musik-Spur folgt.
Das mit den 99 frame offsets: Du erzeugst ein Array mit 99 Einträgen. Jeder Eintrag enthält das Offset des jeweiligen Tracks (also array[5]=Offset des Track 5). Da aber CDs nur selten wirklich 99 Tracks haben, füllst du alle weiteren Einträge mit 0 auf.
 
Genau da liegt das Problem: ich habe die Seite durch gelesen und auch die mathematischen Operationen verstanden. Aber was ich nicht verstehe, sind z.B. die Zahlen für "First Track" (ist das "1"?) oder "Last Track" (ist das der letzte Track?).

Denn auf der Seite steht ja bei dem Beispiel:
Code:
track 1:       150    (150 + 0)
track 2:       15363  (150 + 15213)
track 3:       32314  (150 + 32164)
track 4:       46592  (150 + 46442)
track 5:       63414  (150 + 63264)
track 6:       80489  (150 + 80339)
lead-out track: 95462  (150 + 95312)
Was hat das zu bedeuten (und die 150)?

Außerdem zum Offset-Array: ich fülle einfach die Einträge mit dem jeweiligen Offset (also array[6]=Offset des Track 6) und die restlichen Felder mit "0" und hänge das dann einfach an den String dran, der später mit SHA-1 gehashed werden soll. Aber was ist genau der Offset.

Vielleicht würde ich es verstehen, wenn jemand den "Rechenweg" an einem Beispiel aufschreiben würde, also:
Code:
SHA1(FIRST_TRAK + LAST_TRACK + LEAD_OUT_TRACK + OFFSET1-n)

Gruß,
CPU
 
Der Sinn der 150 wird doch auch auf der Seite erklärt....

Also anhand der Daten, die du da gepostet hast, ergeben sich folgende Werte:
First Track = 1
Last Track = 6
Lead Out Track Offset = 95462
99 Frame Offsets: [150,15363,32314,46592,63414,80489,0,0,0,0,0...]

Ich bin mir allerdings nicht sicher, ob der Lead-Out-Track noch in die Liste der 99 Frame Offsets reinmuss oder nicht. Das ist da irgendwie unklar. Am besten lädst du dir den Source-Code der Client-Library und schaust nach.
 
Also, ich habe noch mal den gesamten Artikel durchgelesen und mit den Antworten versucht das mal an dem Beispiel - bei dem ja auch das Endergebnis bekannt sein soll (49HHV7Eb8UKF3aQiNmu1GR8vKTY-) - mit einem Java programm durchzurechnen.

Aber leider funktioniert es nicht richtig ... um nicht zu sagen, dass es garnicht funktioniert. Folgendes weiß ich auch nicht, ob ich es richtig umgesetzt habe:
  • "sprintf(temp, "%02X", pCDInfo->First*Track);" was bewirkt das? (nur hexadezimale Darstellung mit zwei Stellen)
  • "sprintf(temp, "%08X", pCDInfo->Frame*Offset);" was bewirkt das?
    [*]"sha_update(&sha, (unsigned char*) temp, strlen(temp));" wird hier nur ein String aneinader gehangen, oder wird der SHA1-Hash von dem aktuellen gebildet
    [*]"The resulting 20 byte SHA-1 signature is converted ..." habe ich das auch umgesetzt?


Gruß,
CPU :(

Code:
public class Test {

	public static void main(String[] args) {
		String sha1Data = "";
		// INPUT DATA
		int firstTrack = 1;
		int lastTrack = 6;
		int[] frameOffset = new int[100];
		for (int i = 0; i < frameOffset.length; i++) frameOffset[i] = 0;
		
		// Note that the lead-out track is stored in pCDInfo->Frame*Offset[0]
		frameOffset[0] = 95462;
		frameOffset[1] = 150;
		frameOffset[2] = 15363;
		frameOffset[3] = 32314;
		frameOffset[4] = 46592;
		frameOffset[5] = 63414;
		frameOffset[6] = 80489;
		
		// CODE
		// sprintf(temp, "%02X", pCDInfo->First*Track);
		sha1Data += convertToHex(new byte[]{(byte) firstTrack});
		// sprintf(temp, "%02X", pCDInfo->Last*Track);
		sha1Data += convertToHex(new byte[]{(byte) lastTrack});

		for (int i = 0; i < 100; i++) {
			// sprintf(temp, "%08X", pCDInfo->Frame*Offset[i]);
			sha1Data += Integer.toHexString(frameOffset[i]);
		}
		System.out.println(sha1Data);
		sha1Data = sha1(sha1Data);
		System.out.println(sha1Data);
		System.out.println(encodeBase64(sha1Data.getBytes()));
		// result: 49HHV7Eb8UKF3aQiNmu1GR8vKTY-
	}
	
	public static String sha1(String text) {
		try {
		    MessageDigest md;
		    md = MessageDigest.getInstance("SHA-1");
		    byte[] sha1hash = new byte[40];
		    md.update(text.getBytes("iso-8859-1"), 0, text.length());
		    sha1hash = md.digest();
		    return convertToHex(sha1hash);
		} catch (Exception e) {
			return null;
		}
	}
	
	private static String convertToHex(byte[] data) {
		StringBuffer buf = new StringBuffer();
		for (int i = 0; i < data.length; i++) {
			int halfbyte = (data[i] >>> 4) & 0x0F;
			int two_halfs = 0;
			do {
				if ((0 <= halfbyte) && (halfbyte <= 9))
					buf.append((char) ('0' + halfbyte));
				else
					buf.append((char) ('a' + (halfbyte - 10)));
				halfbyte = data[i] & 0x0F;
			} while (two_halfs++ < 1);
		}
		return buf.toString();
	}
	
    private static char[] encodeBase64(byte[] data) {
		// original: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=
		// replaced:
    	char[] alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.-_".toCharArray();
        char[] out = new char[((data.length + 2) / 3) * 4];

        //
        // 3 bytes encode to 4 chars.  Output is always an even
        // multiple of 4 characters.
        //
        for (int i = 0, index = 0; i < data.length; i += 3, index += 4) {
            boolean quad = false;
            boolean triple = false;
            //convert to unsigned byte
            int val = (0xFF & (int) data[i]);
            val <<= 8;
            if ((i + 1) < data.length) {
                val |= (0xFF & (int) data[i + 1]);
                triple = true;
            }
            val <<= 8;
            if ((i + 2) < data.length) {
                val |= (0xFF & (int) data[i + 2]);
                quad = true;
            }
            out[index + 3] = alphabet[(quad ? (val & 0x3F) : 64)];
            val >>= 6;
            out[index + 2] = alphabet[(triple ? (val & 0x3F) : 64)];
            val >>= 6;
            out[index + 1] = alphabet[val & 0x3F];
            val >>= 6;
            out[index + 0] = alphabet[val & 0x3F];
        }
        return out;
    }
	
}
 
CPU schrieb:
"sprintf(temp, "%02X", pCDInfo->First*Track);" was bewirkt das? (nur hexadezimale Darstellung mit zwei Stellen)
Ja, glaub schon. Also z.B. "00" oder "4F", etc.
"sprintf(temp, "%08X", pCDInfo->Frame*Offset);" was bewirkt das?

Ich denke das gibt nen Hex-String aus, der immer 8 Stellen hat. Also "0049ACD0" oder so.
Das hast du bei dir im Code falsch, weil toHexString() unter Umständen auch kürzere Strings ausgibt. (Und nicht vergessen, den String noch in UpperCase umzuwandeln).
"sha_update(&sha, (unsigned char*) temp, strlen(temp));" wird hier nur ein String aneinader gehangen, oder wird der SHA1-Hash von dem aktuellen gebildet
Ich glaub, es macht keinen Unterschied, ob man den String erst zusammensetzt (so wie du es macht) oder eben die Einzelstücke nacheinander in die sha_update Funktion übergibt.
"The resulting 20 byte SHA-1 signature is converted ..." habe ich das auch umgesetzt?
Glaub schon.
 
Zuletzt bearbeitet:
Hallo,

ich habe mich mal in dem SVN repository umgeschaut (hier: http://svn.musicbrainz.org/libmusicbrainz/trunk/), jedoch leider kein Ergebniss gefunden, da ich c++ nicht kann ... Aber es gibt ja da ein Beispiel, dass die Nummer berechnet: http://svn.musicbrainz.org/libmusicbrainz/trunk/examples/cdlookup.cpp

Also habe ich mir gedacht, könnte ich mir ein kleines c++ Programm zusammenstellen (d.h. einfach die benötigten dateien drin sind, zu einer exe kompilieren und dann in die jar packen und via "Runtime.getRuntime().exec(...)" aufrufen. Dann könnte ich das noch unter Linux und Mac kompilieren und schon wäre die Plattformunabhängigkeit größtenteils wieder hergestellt. Doch leider ist mir das nicht gelungen. Das kompilieren scheitert immer wieder. Also bin ich zum ursprünglichen Java-Programm zurückgegkehrt und habe alle Änderungen eingearbeitet, die cx01 genannt hat. Doch leider kommt immer noch nicht das richtige Ergebnis raus. Woran kann dass denn noch liegen? Der Code ist doch fast identisch?

Außerdem:
  • es macht doch einen Unterschied, wenn ich nach jedem hinzufügen zum sha1 string den SHA1-Hash bilde oder nicht
  • der Base-64 String am Ende ist viel zu lang. Mein Ergebnis: "ZTNkMWM3NTdiMTFiZjE0Mjg1ZGRhNDIyMzY2YmI1MTkxZjJmMjkzNg__", das korrekte Ergebnis "49HHV7Eb8UKF3aQiNmu1GR8vKTY-"

Gruß,
CPU

Code:
public class Test {

	public static void main(String[] args) {
		String sha1Data = "";
		// INPUT DATA
		int firstTrack = 1;
		int lastTrack = 6;
		int[] frameOffset = new int[100];
		for (int i = 0; i < frameOffset.length; i++) frameOffset[i] = 0;
		
		// Note that the lead-out track is stored in pCDInfo->Frame*Offset[0]
		frameOffset[0] = 95462;
		frameOffset[1] = 150;
		frameOffset[2] = 15363;
		frameOffset[3] = 32314;
		frameOffset[4] = 46592;
		frameOffset[5] = 63414;
		frameOffset[6] = 80489;
		
		// CODE
		// sprintf(temp, "%02X", pCDInfo->First*Track);
		sha1Data += convertToHex(new byte[]{(byte) firstTrack});
		// sprintf(temp, "%02X", pCDInfo->Last*Track);
		sha1Data += convertToHex(new byte[]{(byte) lastTrack});

		for (int i = 0; i < 100; i++) {
			// sprintf(temp, "%08X", pCDInfo->Frame*Offset[i]);
			String hex = hex(frameOffset[i], 8);
			//System.out.println(frameOffset[i] + " => " + hex + " => " + Integer.parseInt(hex, 16));
			sha1Data += hex;
		}
		
		// alles zu uppercase
		sha1Data = sha1Data.toUpperCase();
		System.out.println(sha1Data);
		sha1Data = sha1(sha1Data);
		System.out.println(sha1Data);
		System.out.println(encodeBase64(sha1Data.getBytes()));
		// result: 49HHV7Eb8UKF3aQiNmu1GR8vKTY-
	}
	
	public static String hex(int n, int l) {
		String s = Integer.toHexString(n);
		if (s.length() < l) {
			int runs = (l-s.length());
			for (int i = 0; i < runs; i++) {
				s = "0" + s;
			}
		}
		return s;
	}
	
	public static String sha1(String text) {
		try {
		    MessageDigest md;
		    md = MessageDigest.getInstance("SHA-1");
		    byte[] sha1hash = new byte[40];
		    md.update(text.getBytes("iso-8859-1"), 0, text.length());
		    sha1hash = md.digest();
		    return convertToHex(sha1hash);
		} catch (Exception e) {
			return null;
		}
	}
	
	private static String convertToHex(byte[] data) {
		StringBuffer buf = new StringBuffer();
		for (int i = 0; i < data.length; i++) {
			int halfbyte = (data[i] >>> 4) & 0x0F;
			int two_halfs = 0;
			do {
				if ((0 <= halfbyte) && (halfbyte <= 9))
					buf.append((char) ('0' + halfbyte));
				else
					buf.append((char) ('a' + (halfbyte - 10)));
				halfbyte = data[i] & 0x0F;
			} while (two_halfs++ < 1);
		}
		return buf.toString();
	}
	
    private static char[] encodeBase64(byte[] data) {
		// original: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=
		// replaced:
    	char[] alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.-_".toCharArray();
        char[] out = new char[((data.length + 2) / 3) * 4];

        //
        // 3 bytes encode to 4 chars.  Output is always an even
        // multiple of 4 characters.
        //
        for (int i = 0, index = 0; i < data.length; i += 3, index += 4) {
            boolean quad = false;
            boolean triple = false;
            //convert to unsigned byte
            int val = (0xFF & (int) data[i]);
            val <<= 8;
            if ((i + 1) < data.length) {
                val |= (0xFF & (int) data[i + 1]);
                triple = true;
            }
            val <<= 8;
            if ((i + 2) < data.length) {
                val |= (0xFF & (int) data[i + 2]);
                quad = true;
            }
            out[index + 3] = alphabet[(quad ? (val & 0x3F) : 64)];
            val >>= 6;
            out[index + 2] = alphabet[(triple ? (val & 0x3F) : 64)];
            val >>= 6;
            out[index + 1] = alphabet[val & 0x3F];
            val >>= 6;
            out[index + 0] = alphabet[val & 0x3F];
        }
        return out;
    }
	
}
 
Da waren noch 2 Fehler drin:
- Du hattest in der sha Methode den Rückgabewert in Hex umgewandelt, was nicht gefordert ist
- Du hattest in der Sha Methode das Array auf 40 Zeichen gemacht, aber es sind nur 20 nötig
Mit den Fixes funzt es jetzt.
Code:
import java.security.MessageDigest;


public class Test {

	public static void main(String[] args) {
		String sha1Data = "";
		// INPUT DATA
		int firstTrack = 1;
		int lastTrack = 6;
		int[] frameOffset = new int[100];
		for (int i = 0; i < frameOffset.length; i++) frameOffset[i] = 0;
		
		// Note that the lead-out track is stored in pCDInfo->Frame*Offset[0]
		frameOffset[0] = 95462;
		frameOffset[1] = 150;
		frameOffset[2] = 15363;
		frameOffset[3] = 32314;
		frameOffset[4] = 46592;
		frameOffset[5] = 63414;
		frameOffset[6] = 80489;
		
		// CODE
		// sprintf(temp, "%02X", pCDInfo->First*Track);
		sha1Data += convertToHex(new byte[]{(byte) firstTrack});
		// sprintf(temp, "%02X", pCDInfo->Last*Track);
		sha1Data += convertToHex(new byte[]{(byte) lastTrack});

		for (int i = 0; i < 100; i++) {
			// sprintf(temp, "%08X", pCDInfo->Frame*Offset[i]);
			String hex = hex(frameOffset[i], 8);
			//System.out.println(frameOffset[i] + " => " + hex + " => " + Integer.parseInt(hex, 16));
			sha1Data += hex;
		}
		
		// alles zu uppercase
		sha1Data = sha1Data.toUpperCase();
		System.out.println(sha1Data);
		byte[] shaResult = sha1(sha1Data);
		System.out.println(shaResult);
		System.out.println(encodeBase64(shaResult));
		// result: 49HHV7Eb8UKF3aQiNmu1GR8vKTY-
	}
	
	public static String hex(int n, int l) {
		String s = Integer.toHexString(n);
		if (s.length() < l) {
			int runs = (l-s.length());
			for (int i = 0; i < runs; i++) {
				s = "0" + s;
			}
		}
		return s;
	}
	
	public static byte[] sha1(String text) {
		try {
		    MessageDigest md;
		    md = MessageDigest.getInstance("SHA-1");
		    byte[] sha1hash = new byte[20];
		    md.update(text.getBytes("iso-8859-1"), 0, text.length());
		    sha1hash = md.digest();
		    return sha1hash;
		} catch (Exception e) {
			return null;
		}
	}
	
	private static String convertToHex(byte[] data) {
		StringBuffer buf = new StringBuffer();
		for (int i = 0; i < data.length; i++) {
			int halfbyte = (data[i] >>> 4) & 0x0F;
			int two_halfs = 0;
			do {
				if ((0 <= halfbyte) && (halfbyte <= 9))
					buf.append((char) ('0' + halfbyte));
				else
					buf.append((char) ('a' + (halfbyte - 10)));
				halfbyte = data[i] & 0x0F;
			} while (two_halfs++ < 1);
		}
		return buf.toString();
	}
	
    private static char[] encodeBase64(byte[] data) {
		// original: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=
		// replaced:
    	char[] alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.-_".toCharArray();
        char[] out = new char[((data.length + 2) / 3) * 4];

        //
        // 3 bytes encode to 4 chars.  Output is always an even
        // multiple of 4 characters.
        //
        for (int i = 0, index = 0; i < data.length; i += 3, index += 4) {
            boolean quad = false;
            boolean triple = false;
            //convert to unsigned byte
            int val = (0xFF & (int) data[i]);
            val <<= 8;
            if ((i + 1) < data.length) {
                val |= (0xFF & (int) data[i + 1]);
                triple = true;
            }
            val <<= 8;
            if ((i + 2) < data.length) {
                val |= (0xFF & (int) data[i + 2]);
                quad = true;
            }
            out[index + 3] = alphabet[(quad ? (val & 0x3F) : 64)];
            val >>= 6;
            out[index + 2] = alphabet[(triple ? (val & 0x3F) : 64)];
            val >>= 6;
            out[index + 1] = alphabet[val & 0x3F];
            val >>= 6;
            out[index + 0] = alphabet[val & 0x3F];
        }
        return out;
    }

}

Oh und ich glaube, deine Base64 Methode ist falsch, denn im Alphabet String müssten '-' und '_' getauscht werden.
 
Das ist ja echt toll :) DANKE!!!!

@Base-64-Fehler:
Stimmt, da habe ich die zwei Positionen vertaust. Gefixed und funktioniert! :)

Nun muss ich nur noch eine Sache erledigen: das Bestimmen der Zahlen die eingegeben werden!

  • "firstTrack" und "lastTrack" sind ja jeweils nur die Nummern des ersten Tracks und des letzen ... das ist ja einfach (wobei: auf der Seite steht ja bei "firstTrack" "normally one"; eigentlich kann es doch niemals anders sein, oder?)
  • "leadOutTrack" und "FrameOffsets". Was genau sind dies für Zahlen? Was geben diese Zahlen an? Und außerdem steht ja da, dass weil der erste Track nach 2 Minuten anfängt man auf alle Zahlen 150 rechnen muss. Kann das auch anders sein, dass es z.B. nach 3 Minuten anfängt? Habt Ihr spontan eine Idee, wie ich da mit Java dran komme? Bisher stelle ich mir diese TOC wie eine Partitionstabelle vor ...

Gruß,
CPU

======================
Edit: da ist nur ein Problem: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4246778

Edit: man könnte ja mit "cdrecord" arbeiten. Doch die für Windows kompilierte Version hat noch eine cygwin1.dll dabei, die 2MB groß ist. Da gibt es doch sicherlich auch so ein kleines c++ Progrämmchen in einer kurzen Datei??
 
Zuletzt bearbeitet:
Ich denke mal, dass der First Track theoretisch auch ungleich 1 sein kann, aber das wird in der Praxis eben nie vorkommen.
Die FrameOffsets sind die Offsets, wo der jeweilige Track beginnt, angegeben in Blöcken. Ein Block ist 1/75 Sekunde. Daher hast du bei 2 Sekunden Lead-In eben ein Offset von 150 Blöcken. Ich denke mal, dass der Lead-In auch unterschiedlich lang sein kann. Musst halt einfach dessen Länge in Sekunden mit 75 multiplizieren.

Auslesen in Java kannst du die ganze Sache ja eh nicht. Also wirst du auf ein Konsolenprogramm wie "cdrecord" zurückgreifen müssen und dessen Rückgabe irgendwie parsen.
 
"cdrecord" beleibt für mich ersteinmal letzte Wahl, denn es ist eigentlich viel zu groß, da lädt das Applet ja eine halbe Evigkeit (für Computermaßstäbe) ...

Aber ich habe das hier gefunden: http://www.codeproject.com/KB/audio-video/SimpleAudioCD.aspx nun muss ich es nur noch schaffen das zusammenzusetzen ... irgendwie ... :(

Gruß,
CPU :)

======================
Edit: Ich bekomme es nicht hin, das heruntergeladene Projekt mit Dev C++/Eclipse C/C++ zu bauen. Gibt es dazu irgendwo ein Tutorial? Wie man vorgeht, wenn man ein Projekt erstellen will und das bauen möchte?
 
Zuletzt bearbeitet:
Zurück
Oben