Mac- oder Unix-kompatibles Dienstprogramm zum Berechnen und Vergleichen von LAME MusicCRC in MP3s?

925
kine

Der LAME-Encoder speichert eine CRC16-Prüfsumme des Audiostroms im Header aller von ihm codierten MP3-Dateien. Die "tatsächliche" Audio-Prüfsumme kann dann berechnet und zu einem späteren Zeitpunkt mit dem ursprünglichen Wert verglichen werden, um zu überprüfen, ob das Audio beschädigt wurde (ohne sich um ID3-Tags und dergleichen sorgen zu müssen, die den berechneten Wert ändern).

Unter Windows gab es ein Befehlszeilenprogramm namens LameTag, mit dem die Prüfsumme berechnet und mit dem Original verglichen werden konnte. Leider ist es aufgegeben und wahrscheinlich nicht leicht auf OS X portierbar, was ich natürlich verwende. Ich denke, EncSpot ist in der Lage, dasselbe zu tun, aber es ist wieder nur Windows.

Meine Frage ist: Gibt es Dienstprogramme wie diese, die mit Mac, Linux, BSD oder ähnlichem kompatibel sind?

Es gibt einige, die ich gefunden habe, die den ursprünglichen CRC (wie eyeD3) darstellen können, aber sie können den aktuellen nicht berechnen. Es gibt auch mehrere Dienstprogramme, die behaupten, in MP3s nach Korruption zu suchen, aber ich habe keine gefunden, die den MusicCRC-Frame tatsächlich verwenden - die meisten scheinen eine allgemeinere Methode der Überprüfung zu verwenden, oder sie verwenden Frame-CRCs (welche sind standardmäßig in LAME deaktiviert und können nicht als verlässlich angesehen werden).

edit:
Ich glaube, ich habe meine eigene Frage beantwortet. Bei diesem Versuch bin ich auf ein Python-Skript für Mutagen, die Audio-Metadaten-Bibliothek von QuodLibet, gestoßen. Das Skript soll LAMEs Info-Tag lesen. Obwohl es sich nicht speziell mit den CRC-Feldern befasst, konnte ich etwas erstellen, das auf seinem Beispiel basiert. Nach ein paar Stunden mit den Dingen (ich bin ein schrecklicher Programmierer und weiß absolut nichts über Python) habe ich es endlich geschafft, etwas zu schreiben, das, obwohl nichtssagend und langsam, die ursprünglichen CRCs zurückgibt und die neuen berechnet:

# Known good track kapche-lanka:test % ../mp3crc.py "10 - CLAW FINGER.mp3" 10 - CLAW FINGER.mp3: Original MusicCRC: 8171 Computed MusicCRC: 8171 Original Info Tag CRC: AEFD Computed Info Tag CRC: AEFD  # Known bad track kapche-lanka:test % ../mp3crc.py "10 - Griffons Never Die.mp3" 10 - Griffons Never Die.mp3: Original MusicCRC: 2014 Computed MusicCRC: BCF1 Original Info Tag CRC: DF02 Computed Info Tag CRC: DF02 

Ich werde diesen Beitrag noch einmal aktualisieren, um einen Link zum Skript hinzuzufügen, wann immer ich es auf eine ernsthaftere Weise bekomme.

Vielen Dank!

edit2:
Ich habe unten zu meinem Skript einen Link hinzugefügt (siehe akzeptierte Antwort). Es heißt, mp3crcund obwohl es nicht fachkundig gestaltet ist, scheint es größtenteils zu funktionieren:

https://github.com/ohkine/mp3crc

3
Empfehlen Sie dieses Tool: http://phwip.wordpress.com/home/audio/ es ist leider in Pascal geschrieben. dlamblin vor 12 Jahren 0
Leider liegt es weit außerhalb meiner Möglichkeiten, Delphi-Anwendungen (oder möglicherweise auch andere) nach UNIX zu portieren. : / kine vor 12 Jahren 0
Ja, ich habe den Free Pascal Complier im Delphi-Modus ausprobiert und sagte mir, dass er APPTYPE unter Linux nicht unterstützt und TnTClasses nicht finden konnte. dlamblin vor 12 Jahren 0
Sie müssen wahrscheinlich die $ APPTYPE-Deklaration entfernen, und TnTClasses ist Teil der [TNT Unicode Controls] (http://yunqa.de/delphi/doku.php/products/tntunicodecontrols/index). afrazier vor 12 Jahren 0
@afrazier Nun, Sie müssten TntSystem und TntSysUtils durch etwas ersetzen, das die gleiche Funktionalität bietet. Wir könnten dies nach C, D, Perl, Python, Java portieren? Oder legen Sie einfach Ersatz ein. TntClasses erfordert Windows, das nicht vorhanden ist, vorausgesetzt, LameTag.dpr erfordert auch Windows. dlamblin vor 12 Jahren 0
@dlamblin: I've got no familiarity with FPC, though a fair amount with Delphi, so this is all guessing on my part. I'd expect that the TnT bits could all be refactored out with similar routines from the FPC RTL (or a current Delphi RTL) relatively easily. In fact, it took me about 10 minutes with Delphi XE to strip out the TnT stuff and get a build that seems to work. All the version info stuff is broken though, since that all relied on TnT and I didn't feel like finding an implementation right now. afrazier vor 12 Jahren 0

3 Antworten auf die Frage

2
trevor

Hier eine Bash-Shell-Funktion lameCRC(), die LAME musicCRC und CRC-16 des Xing / Info-LAME-Header-Frames (wie in den Spezifikationen für Mp3-Info-Tags Version 1 - Entwurf 0 angegeben ) mithilfe des Apple- afinfoBefehls und des crcBefehlszeilentools von Hampa berechnet Hug, http://www.hampa.ch/misc-utils/index.html .

Wenn der afinfoBefehl von Apple nicht verfügbar ist, ddwird verwendet (was jedoch zu einem Geschwindigkeitsüberfall führt).

(Hinweis: Ich habe bewusst auf die internen String-Funktionen von Bash verzichtet, um die Portabilität zu erleichtern).

lameCRC() { # Bash shell function  # lameCRC() uses the crc command line tool from http://www.hampa.ch/misc-utils/index.html. # lameCRC() is partly inspired by the output of Apple's afinfo command and  # the C source code of Audio-Scan-0.93 and MP3-Cut-Gapless-0.03 by Andy Grundman: # https://metacpan.org/author/AGRUNDMA # Audio-Scan-0.93/src/mp3.c (GNU General Public License Version 2, June 1991 or later) # Audio-Scan-0.93/include/mp3.h ( ditto ) # MP3-Cut-Gapless-0.03/src/mp3cut.c ( ditto )  # usage: lameCRC lame.mp3  # Basic information:  # Mp3 Info Tag rev 1 specifications, http://gabriel.mp3-tech.org/mp3infotag.html # LAME info header zone A has a length of 120 bytes (or 240 chars in xxd hex output). # The "LAMEx." string is followed by 30 bytes (or 60 chars in xxd hex output) according to the  # "Suggested Info Tag extension fields + layout" in the Mp3 Info Tag rev 1 specifications.  local n n1 n2 lines plus crcs hexchar lame_start_idx xinginfo_start_idx PATH  PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin  [[ ! -x '/usr/local/bin/crc' ]] &&  { printf '%s\n' 'No crc command line tool in /usr/local/bin!' 'See: http://www.hampa.ch/misc-utils/index.html' 1>&2; }  # get Xing|Info|LAME strings and their offsets in binary file lines="$(strings -a -n 4 -t d "$1" | grep -E --line-buffered 'Xing|Info|LAME.\.' | head -n 2)"   [[ $(echo "$lines" | grep -E -c 'Xing|Info') -ne 1 ]] || [[ $(echo "$lines" | grep -E -c 'LAME[^ ]') -ne 1 ]] && { echo 'No Xing|Info string or correct LAME encoder version string (e.g. LAME3.98r) found!' 1>&2;  echo "$lines" 1>&2; return 1;  }  # get offset index numbers of Xing|Info|LAME strings lame_start_idx="$(printf '%s' "$lines" | awk '/LAME/' )" xinginfo_start_idx="$(printf '%s' "$lines" | awk '/Xing|Info/' )"  # get possible offset of LAME string in output of strings command # LAME version string should consist of 9 chars, but may have a prefix in output of strings command # example: 9LAME3.98r instead of LAME3.98r # example: 7LAME3.88 (beta) instead of LAME3.88 (beta) #plus="$(printf '%s' "$lines" | sed -n 's/^[^ ]*[ ][ ]*\([^ ]*\)LAME[^ ]\.*/\1/p' | tr -d '\n' | wc -c)" # use [^ ]\ ? plus="$(printf '%s' "$lines" | sed -n 's/^[^ ]*[ ][ ]*\([^ ]*\)LAME.*/\1/p' | tr -d '\n' | wc -c)"  lame_start_idx=$(( $lame_start_idx + $plus ))  [[ $(( $lame_start_idx - $xinginfo_start_idx )) -ne 120 ]] &&  { echo 'No 120 bytes between Xing / Info and LAME string. Exiting ...' 1>&2; return 1; }  # get entire LAME info tag #dd if="$1" bs=1 skip="$lame_start_idx" count=36 2>/dev/null | LC_ALL=C od -A n -cv; return 0  # get bytes $BC-$BD (MusicCRC) and bytes $BE-$BF (CRC-16 of Info Tag) (as described in http://gabriel.mp3-tech.org/mp3infotag.html) crcs="$(dd if="$1" bs=1 skip="$(( $lame_start_idx + 32 ))" count=4 2>/dev/null | xxd -p | tr -d '\n')"  [[ -z "$crcs" ]] && { echo 'No LAME musicCRC and CRC-16 of Info Tag found!' 1>&2; return 1; }  lameMusicLengthPlusCRCs="$(dd if="$1" bs=1 skip=$(( $lame_start_idx + 28 )) count=8 2>/dev/null | xxd -p | tr -d '\n')" lameMusicLength="$(echo "$lameMusicLengthPlusCRCs" | cut -b 1-8 )" lameMusicCRC1="$(echo "$lameMusicLengthPlusCRCs" | cut -b 9-10 )" # cf. http://gabriel.mp3-tech.org/mp3infotag#musiccrc lameMusicCRC2="$(echo "$lameMusicLengthPlusCRCs" | cut -b 11-12 )" lameInfoTagCRC16="$(echo "$lameMusicLengthPlusCRCs" | cut -b 13-16 )"  # LAME MusicLength consists of:  # [LAME Tag frame][complete mp3 music data] lameMusicLengthByteSize=$(printf '%d' "0x$")  [[ $lameMusicLengthByteSize -le 0 ]] && { echo 'lameMusicLengthByteSize <= 0. Exiting ...' 1>&2; return 1; }   if [[ -x '/usr/bin/sw_vers' ]] && [[ "$(/usr/bin/sw_vers -productName)" == "Mac OS X" ]] && [[ -x '/usr/bin/afinfo' ]]; then  # get audio_bytes, i. e. [complete mp3 music data] - [LAME Tag frame]  #id3v2 --delete-all "$1" 1>/dev/null # for testing purposes; edits file in-place! # afinfo seems to be only available on Mac OS X  # afinfo alternative: Perl module Audio-Scan-0.93 by Andy Grundman  # perl -e 'use Audio::Scan; my $offset = Audio::Scan->find_frame($ARGV[1],0); print "$offset\n";' _ file.mp3 audioinfo="$(afinfo "$1")"  audio_bytes="$(echo "$audioinfo" | awk -F" " '/audio bytes:/' )" audio_data_file_offset="$(echo "$audioinfo" | awk -F" " '/audio data file offset:/')" xingInfoLameTagFrameSize=$(( lameMusicLengthByteSize - audio_bytes ))  [[ $audio_bytes -le 0 ]] && { echo 'audio_bytes <= 0. Exiting ...' 1>&2; return 1; }  # 0..xingInfoLameTagFrameOffset (match first 0xff byte in mp3 file) n=0 hexchar="" until [[ "$hexchar" == 'ff' ]]; do hexchar="$(dd if="$1" bs=1 skip=$n count=1 2>/dev/null | xxd -p)" n=$(( n + 1)) done xingInfoLameTagFrameOffset=$(( n - 1 ))  else # dd speed bump  # get xingInfoLameTagFrameSize # for mp3 magic numbers (\xFF\xFB) see:  # http://www.digitalpreservation.gov/formats/fdd/fdd000105.shtml  # n1 # count bytes from: 0xff...<--...$xinginfo_start_idx hexchar="" n=$xinginfo_start_idx until [[ "$hexchar" == 'ff' ]]; do n=$(( n - 1)) hexchar="$(dd if="$1" bs=1 skip=$n count=1 2>/dev/null | xxd -p)" done xingInfoLameTagFrameOffset=$n n1=$(( xinginfo_start_idx - n ))  # n2 # count bytes from: $xinginfo_start_idx+120+36...-->...0xff hexchar="" n=$((xinginfo_start_idx + 120 + 36)) until [[ "$hexchar" == 'ff' ]]; do hexchar="$(dd if="$1" bs=1 skip=$n count=1 2>/dev/null | xxd -p)" n=$(( n + 1)) done n2=$(( n - xinginfo_start_idx - 120 - 36 - 1 )) # - 1 because the trailing 0xff got counted by $n  xingInfoLameTagFrameSize=$(( $n1 + $n2 + 120 + 36 )) audio_data_file_offset=$((xingInfoLameTagFrameOffset + xingInfoLameTagFrameSize))  # get audio_bytes, i. e. [complete mp3 music data] - [LAME Tag frame] audio_bytes=$( printf "%s\n" "scale = 0; $ - $" | bc )  fi  old_lameInfoTagCRC16="$lameInfoTagCRC16" new_lameInfoTagCRC16="$(head -c $(( xingInfoLameTagFrameOffset + xingInfoLameTagFrameSize )) "$1" |  tail -c $ | head -c 190 | crc -R -r -g crc16)"  old_lameMusicCRC16="$$" new_lameMusicCRC16="$(head -c $(( $ + $ )) "$1" |  tail -c $ | crc -R -r -g crc16)"  echo printf '%s\n' "old_lameInfoTagCRC16: $" "new_lameInfoTagCRC16: $" echo printf '%s\n' "old_lameMusicCRC16: $" "new_lameMusicCRC16: $" echo  return 0 } 
1
carlo

There seems to be a C port of LameTag_Source_0.4.1/CRC16.pas called mp3_check-1.98/crctest.c (which is a command line tool).

Here's a hacked version of mp3_check-1.98/crctest.c that that will calculate the CRC16 checksum of a given mp3 file.

/* modified version of source code taken from: mp3_check-1.98/crctest.c, http://sourceforge.net/projects/mp3check/ NOTE: compare mp3_check-1.98/crctest.c with LameTag_Source_0.4.1/CRC16.pas from http://phwip.wordpress.com/home/audio/ See also: mp3check - check mp3 files for integrity, http://jo.ath.cx/soft/mp3check/ gcc -Wall -Wextra -03 -o crctest crctest.c ./crctest *.mp3 printf '%d\n' $(./crctest *.mp3) */ #include <stdio.h> #include <stdlib.h> int crcbuf(crc, len, buf) register int crc; /* running CRC value */ register unsigned long len; register char *buf; { static short crc_table[] = { 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, 0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440, 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, 0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841, 0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40, 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41, 0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641, 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040, 0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441, 0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, 0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840, 0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41, 0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40, 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640, 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041, 0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240, 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441, 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, 0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840, 0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, 0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, 0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640, 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041, 0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241, 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440, 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841, 0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, 0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, 0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641, 0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040 }; register unsigned long i; for (i=0; i<len; i++) crc = ((crc >> 8) & 0xff) ^ crc_table[(crc ^ *buf++) & 0xff]; return (crc); } int main (int argc, char * argv []) { if (argc != 2) return(1); int crc = 0; int newcrc = 0; // cf. http://www.linuxquestions.org/questions/programming-9/c-howto-read-binary-file-into-buffer-172985/ char *name = argv[1]; FILE *file; char *buffer; unsigned long fileLen; //Open file file = fopen(name, "rb"); if (!file) { fprintf(stderr, "Unable to open file %s", name); return(1); } //Get file length fseek(file, 0, SEEK_END); fileLen=ftell(file); fseek(file, 0, SEEK_SET); //Allocate memory buffer=(char *)malloc(fileLen+1); if (!buffer) { fprintf(stderr, "Memory error!"); fclose(file); return(1); } //Read file contents into buffer fread(buffer, fileLen, 1, file); fclose(file); newcrc = crcbuf(crc, fileLen, buffer); printf("0x%x\n", newcrc); free(buffer); return(0); } 
Vielen Dank! Ich glaube jedoch nicht, dass ich die Antwort in Betracht ziehen kann, denn so funktioniert MusicCRC nicht. Es berechnet den CRC16 des * Audiostreams * und nicht die * Datei *. Dies ist das Tolle daran. Wenn Sie die gesamte Datei erstellen, ändert sich der CRC immer aufgrund von Änderungen der ID3-Tags usw. Der Audiostream sollte jedoch immer derselbe bleiben, egal was passiert (es sei denn, Sie haben ihn absichtlich mit MP3Gain oder etwas durcheinander gebracht). Ich denke, ich hätte vielleicht meine eigene Frage beantwortet: Siehe die Bearbeitung meines Beitrags! kine vor 12 Jahren 0
0
kine

I'll answer my own question here:

In trying to research this, i stumbled across a Python script for mutagen, QuodLibet's audio meta-data library. The script is designed to read LAME's Info Tag, and although it does not deal with either of the CRC fields specifically, i was able to create something that does based off of its example. After a few hours of messing with things (i'm a terrible programmer and know absolutely nothing about Python) i finally managed to write something that, although featureless and slow, does return the original CRCs and compute the new ones. It's still a little buggy, but on my own library it turned out to be at least 90% accurate, so i'll 'release' it i guess. It's called mp3crc and is available on GitHub:

https://github.com/ohkine/mp3crc

The script should run on UNIX and Windows, although there is currently a Windows-only Unicode issue that needs fixed. It also requires crcmod and mutagen to be installed (i include them in the repository but you can install them however).

As mentioned i'm not a very good programmer, so i apologise in advance for how embarrassing the code probably is. But it mostly works :)