Narzędzie kompatybilne z Mac lub Unix do obliczania i porównywania LAME MusicCRC w plikach MP3?

3

Koder LAME przechowuje sumę kontrolną CRC16 strumienia audio w nagłówku każdego kodowanego pliku MP3. „Rzeczywista” suma kontrolna audio może być następnie obliczona i porównana z pierwotną wartością w późniejszym terminie, aby sprawdzić, czy dźwięk został uszkodzony (bez konieczności martwienia się o znaczniki ID3 i podobne zmiany wartości obliczonej).

W systemie Windows istniało narzędzie wiersza polecenia o nazwie LameTag, które było w stanie obliczyć sumę kontrolną i porównać ją z oryginałem. Niestety, jest porzucony i prawdopodobnie nie jest łatwo przenośny na OS X, co oczywiście jest tym, czego używam. Myślę, że EncSpot jest w stanie zrobić to samo, ale znowu jest tylko Windows.

Moje pytanie brzmi: czy istnieją takie narzędzia, które są kompatybilne z komputerami Mac, Linux, BSD lub podobnymi?

Znalazłem kilka, które mogą pokazać oryginalny CRC (jak eyeD3 ), ale nie mogą obliczyć bieżącego. Istnieje również kilka narzędzi, które twierdzą, że sprawdzają korupcję w plikach MP3, ale nie znalazłem żadnych, które rzeczywiście używają ramki MusicCRC - większość z nich wydaje się używać bardziej ogólnej metody sprawdzania, lub używają CRC ramek (które są domyślnie wyłączone w LAME i nie można na nich polegać).

edytować:
Myślę, że odpowiedziałem na moje pytanie. Próbując to zbadać, natknąłem się skrypt Pythona dla mutagen , Biblioteka metadanych audio QuodLibet. Skrypt został zaprojektowany do odczytu tagu informacyjnego LAME i chociaż nie dotyczy konkretnie żadnego z pól CRC, udało mi się stworzyć coś, co opiera się na jego przykładzie. Po kilku godzinach bałaganu z rzeczami (jestem okropnym programistą i nie wiem absolutnie nic o Pythonie) udało mi się w końcu napisać coś, co, mimo że jest wolne i wolne, zwraca oryginalne CRC i oblicz nowe:

# 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

Zaktualizuję ten post jeszcze raz, aby dodać link do skryptu, gdy tylko zacznę działać w bardziej poważny sposób.

Dzięki!

edytuj2:
Dodałem link do mojego skryptu poniżej (zobacz zaakceptowaną odpowiedź). To jest nazwane mp3crc i choć nie jest fachowo zaprojektowany, wydaje się, że działa w większości:

https://github.com/ohkine/mp3crc

kine
źródło
Polecam port tego narzędzia: phwip.wordpress.com/home/audio jest niestety napisane w Pascalu.
dlamblin
Niestety, znacznie wykracza poza moje możliwości przenoszenia aplikacji Delphi (lub prawdopodobnie innych) do UNIX. : /
kine
Tak, próbowałem Free Pascal Complier w trybie Delphi i powiedział mi, że nie obsługuje APPTYPE na Linuksie i nie mógł znaleźć TnTClasses.
dlamblin
Prawdopodobnie będziesz musiał usunąć $APPTYPE deklaracja, a TnTClasses jest częścią Sterowanie Unicode TNT .
afrazier
@afrazier dobrze, musisz zastąpić TntSystem i TntSysUtils czymś, co zapewnia taką samą funkcjonalność. Możemy przenieść to do C, D, Perl, Python, Java? Lub po prostu jakoś upuść zamienniki. TntClasses wymaga systemu Windows, którego tam nie ma, przyznany LameTag.dpr wymaga także systemu Windows.
dlamblin

Odpowiedzi:

2

Oto funkcja powłoki Bash o nazwie lameCRC() który oblicza LCR musicCRC i CRC-16 ramki nagłówka Xing / Info-LAME (zgodnie z określeniem przez Mp3 Info Tag rev 1 specyfikacje - wersja robocza 0 ) za pomocą Apple'a afinfo polecenie i crc narzędzie linii poleceń Hampa Hug, http://www.hampa.ch/misc-utils/index.html .

Jeśli Apple afinfo polecenie jest niedostępne, dd zostanie użyty (co jednak doprowadzi do zwiększenia prędkości).

(Uwaga: celowo unikałem wewnętrznych funkcji Basha, aby ułatwić przenoszenie).

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[^ ]{5}') -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/{print $1}' )"
   xinginfo_start_idx="$(printf '%s' "$lines" | awk '/Xing|Info/{print $1}' )"

   # 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[^ ]\{5\}.*/\1/p' | tr -d '\n' | wc -c)"   # use [^ ]\{5\} ?
   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${lameMusicLength}")

   [[ $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:/{print $NF}' )"
      audio_data_file_offset="$(echo "$audioinfo" | awk -F" " '/audio data file offset:/{print $NF}')"
      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; ${lameMusicLengthByteSize} - ${xingInfoLameTagFrameSize}" | bc )

   fi

   old_lameInfoTagCRC16="$lameInfoTagCRC16"
   new_lameInfoTagCRC16="$(head -c $(( xingInfoLameTagFrameOffset + xingInfoLameTagFrameSize )) "$1" | 
          tail -c ${xingInfoLameTagFrameSize} | head -c 190 | crc -R -r -g crc16)"

   old_lameMusicCRC16="${lameMusicCRC1}${lameMusicCRC2}"
   new_lameMusicCRC16="$(head -c $(( ${audio_data_file_offset} + ${audio_bytes} )) "$1" | 
          tail -c ${audio_bytes} | crc -R -r -g crc16)"

   echo
   printf '%s\n' "old_lameInfoTagCRC16: ${old_lameInfoTagCRC16}" "new_lameInfoTagCRC16: ${new_lameInfoTagCRC16}"
   echo
   printf '%s\n' "old_lameMusicCRC16: ${old_lameMusicCRC16}" "new_lameMusicCRC16: ${new_lameMusicCRC16}"
   echo

   return 0
}
trevor
źródło
1

Wygląda na to, że jest port C LameTag_Source_0.4.1 / CRC16.pas nazywa mp3_check-1.98 / crctest.c (które jest narzędziem wiersza poleceń).

Oto zhakowana wersja mp3_check-1.98/crctest.c który obliczy sumę kontrolną CRC16 danego pliku mp3.

/*

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);

}
carlo
źródło
Dziękuję Ci! Jednak nie sądzę, żebym mógł uznać to za odpowiedź, ponieważ nie do końca tak działa MusicCRC. Oblicza CRC16 strumień audio , nie plik , co jest wielką zaletą - jeśli zrobisz cały plik, CRC zawsze się zmieni ze względu na zmiany tagów ID3 i tak dalej. Strumień audio powinien być zawsze taki sam, niezależnie od tego, co (chyba, że ​​celowo zepsujesz to MP3Gain lub czymś). Myślę, że mógłbym odpowiedzieć na moje pytanie: zobacz edycję na moim poście!
kine
0

Odpowiem na moje pytanie tutaj:

Próbując to zbadać, natknąłem się skrypt Pythona dla mutagen, Biblioteka metadanych audio QuodLibet. Skrypt został zaprojektowany do odczytu tagu informacyjnego LAME i chociaż nie dotyczy konkretnie żadnego z pól CRC, udało mi się stworzyć coś, co opiera się na jego przykładzie. Po kilku godzinach błądzenia (jestem okropnym programistą i nie wiem absolutnie nic o Pythonie) w końcu udało mi się napisać coś, co, mimo że jest wolne i wolne, zwraca oryginalne CRC i oblicza nowe. To wciąż trochę buggy, ale w mojej własnej bibliotece okazało się, że jest co najmniej w 90% dokładne, więc chyba „wypuszczę” to. To jest nazwane mp3crc i jest dostępny na GitHub:

https://github.com/ohkine/mp3crc

Skrypt powinien działać w systemach UNIX i Windows, chociaż obecnie istnieje problem z Unicode tylko dla systemu Windows, który wymaga naprawy. Wymaga to również crcmod i mutagen do zainstalowania (włączam je do repozytorium, ale możesz je zainstalować).

Jak już wspomniałem, nie jestem zbyt dobrym programistą, więc z góry przepraszam za to, jak prawdopodobnie krępujący jest kod. Ale to działa głównie :)

kine
źródło