Merge pull request #37 from MerlijnWajer/fast-accuraterip-checksum
Fast accuraterip checksum
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -14,6 +14,10 @@ missing
|
||||
morituri.spec
|
||||
py-compile
|
||||
REVISION
|
||||
*.o
|
||||
.deps
|
||||
/compile
|
||||
/depcomp
|
||||
|
||||
# For Python development using Eclipse IDE
|
||||
.project
|
||||
|
||||
@@ -3,7 +3,7 @@ AUTOMAKE_OPTIONS = 1.8 dist-bzip2 no-dist-gzip
|
||||
|
||||
ACLOCAL_AMFLAGS = -I m4
|
||||
|
||||
SUBDIRS = morituri bin etc doc m4 misc
|
||||
SUBDIRS = morituri bin etc doc m4 misc src
|
||||
|
||||
EXTRA_DIST = morituri.spec morituri.doap RELEASE README.md HACKING REVISION
|
||||
|
||||
|
||||
@@ -56,6 +56,10 @@ dnl check for pychecker
|
||||
AC_CHECK_PROG(PYCHECKER, pychecker, yes, no)
|
||||
AM_CONDITIONAL(HAVE_PYCHECKER, test "x$PYCHECKER" = "xyes")
|
||||
|
||||
dnl
|
||||
AC_PROG_CC
|
||||
AC_CHECK_LIB(sndfile,sf_open)
|
||||
|
||||
dnl output stuff
|
||||
AC_CONFIG_FILES([bin/rip], [chmod +x bin/rip])
|
||||
AC_CONFIG_FILES([etc/bash_completion.d/bash-compgen],
|
||||
@@ -81,4 +85,5 @@ morituri/test/Makefile
|
||||
doc/Makefile
|
||||
misc/Makefile
|
||||
morituri.spec
|
||||
src/Makefile
|
||||
)
|
||||
|
||||
@@ -32,6 +32,10 @@ from morituri.common import log
|
||||
from morituri.common import task
|
||||
|
||||
from morituri.extern.task import gstreamer
|
||||
from morituri.extern.task import task as etask
|
||||
|
||||
from morituri.program.arc import accuraterip_checksum
|
||||
|
||||
|
||||
# checksums are not CRC's. a CRC is a specific type of checksum.
|
||||
|
||||
@@ -268,6 +272,29 @@ class CRC32Task(ChecksumTask):
|
||||
return zlib.crc32(buf, checksum)
|
||||
|
||||
|
||||
class FastAccurateRipChecksumTask(etask.Task):
|
||||
description = 'Calculating (Fast) AccurateRip checksum'
|
||||
|
||||
def __init__(self, path, trackNumber, trackCount, wave, v2=False):
|
||||
self.path = path
|
||||
self.trackNumber = trackNumber
|
||||
self.trackCount = trackCount
|
||||
self._wave = wave
|
||||
self._v2 = v2
|
||||
self.checksum = None
|
||||
|
||||
def start(self, runner):
|
||||
etask.Task.start(self, runner)
|
||||
self.schedule(0.0, self._arc)
|
||||
|
||||
def _arc(self):
|
||||
arc = accuraterip_checksum(self.path, self.trackNumber, self.trackCount,
|
||||
self._wave, self._v2)
|
||||
self.checksum = arc
|
||||
|
||||
self.stop()
|
||||
|
||||
|
||||
class AccurateRipChecksumTask(ChecksumTask):
|
||||
"""
|
||||
I implement the AccurateRip checksum.
|
||||
|
||||
@@ -715,6 +715,7 @@ class Program(log.Loggable):
|
||||
else:
|
||||
trackResult.ARDBCRC = int(response.checksums[i], 16)
|
||||
|
||||
# TODO MW: Update this further for ARv2 code
|
||||
def getAccurateRipResults(self):
|
||||
"""
|
||||
@rtype: list of str
|
||||
|
||||
@@ -112,6 +112,7 @@ class AccurateRipChecksumTask(log.Loggable, task.MultiSeparateTask):
|
||||
|
||||
description = "Checksumming tracks"
|
||||
|
||||
# TODO MW: Update this further for V2 code
|
||||
def __init__(self, image):
|
||||
task.MultiSeparateTask.__init__(self)
|
||||
|
||||
@@ -134,10 +135,10 @@ class AccurateRipChecksumTask(log.Loggable, task.MultiSeparateTask):
|
||||
# here to avoid import gst eating our options
|
||||
from morituri.common import checksum
|
||||
|
||||
checksumTask = checksum.AccurateRipChecksumTask(path,
|
||||
checksumTask = checksum.FastAccurateRipChecksumTask(path,
|
||||
trackNumber=trackIndex + 1, trackCount=len(cue.table.tracks),
|
||||
sampleStart=index.relative * common.SAMPLES_PER_FRAME,
|
||||
sampleLength=length * common.SAMPLES_PER_FRAME)
|
||||
wave=True, v2=False)
|
||||
|
||||
self.addTask(checksumTask)
|
||||
|
||||
def stop(self):
|
||||
|
||||
@@ -4,5 +4,6 @@ morituridir = $(PYTHONLIBDIR)/morituri/program
|
||||
|
||||
morituri_PYTHON = \
|
||||
__init__.py \
|
||||
arc.py \
|
||||
cdparanoia.py \
|
||||
cdrdao.py
|
||||
|
||||
50
morituri/program/arc.py
Normal file
50
morituri/program/arc.py
Normal file
@@ -0,0 +1,50 @@
|
||||
import logging
|
||||
from subprocess import Popen, PIPE
|
||||
from os.path import exists
|
||||
|
||||
ARB = 'accuraterip-checksum'
|
||||
FLAC = 'flac'
|
||||
|
||||
def accuraterip_checksum(f, track, tracks, wave=False, v2=False):
|
||||
v = '--accuraterip-v1'
|
||||
if v2:
|
||||
v = '--accuraterip-v2'
|
||||
|
||||
track, tracks = str(track), str(tracks)
|
||||
|
||||
if not wave:
|
||||
flac = Popen([FLAC, '-cds', f], stdout=PIPE)
|
||||
|
||||
arc = Popen([ARB, v, '/dev/stdin', track, tracks],
|
||||
stdin=flac.stdout, stdout=PIPE, stderr=PIPE)
|
||||
else:
|
||||
arc = Popen([ARB, v, f, track, tracks],
|
||||
stdout=PIPE, stderr=PIPE)
|
||||
|
||||
if not wave:
|
||||
flac.stdout.close()
|
||||
|
||||
out, err = arc.communicate()
|
||||
|
||||
if not wave:
|
||||
flac.wait()
|
||||
flac_rc = flac.returncode
|
||||
|
||||
arc_rc = arc.returncode
|
||||
|
||||
if not wave and flac_rc != 0:
|
||||
logging.warning('ARC calculation failed: flac return code is non zero')
|
||||
return None
|
||||
|
||||
if arc_rc != 0:
|
||||
logging.warning('ARC calculation failed: arc return code is non zero')
|
||||
return None
|
||||
|
||||
out = out.strip()
|
||||
try:
|
||||
outh = int('0x%s' % out, base=16)
|
||||
except ValueError:
|
||||
logging.warning('ARC output is not usable')
|
||||
return None
|
||||
|
||||
return outh
|
||||
@@ -207,6 +207,7 @@ CD in the AccurateRip database."""
|
||||
self.stdout.write('No matching offset found.\n')
|
||||
self.stdout.write('Consider trying again with a different disc.\n')
|
||||
|
||||
# TODO MW: Update this further for ARv2 code
|
||||
def _arcs(self, runner, table, track, offset):
|
||||
# rips the track with the given offset, return the arcs checksum
|
||||
self.debug('Ripping track %r with offset %d ...', track, offset)
|
||||
@@ -226,8 +227,9 @@ CD in the AccurateRip database."""
|
||||
# here to avoid import gst eating our options
|
||||
from morituri.common import checksum
|
||||
|
||||
t = checksum.AccurateRipChecksumTask(path, trackNumber=track,
|
||||
trackCount=len(table.tracks))
|
||||
# TODO MW: Update this to also use the v2 checksum(s)
|
||||
t = checksum.FastAccurateRipChecksumTask(path, trackNumber=track,
|
||||
trackCount=len(table.tracks), wave=True, v2=False)
|
||||
runner.run(t)
|
||||
|
||||
os.unlink(path)
|
||||
|
||||
1
src/.gitignore
vendored
Normal file
1
src/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
accuraterip-checksum
|
||||
4
src/Makefile.am
Normal file
4
src/Makefile.am
Normal file
@@ -0,0 +1,4 @@
|
||||
bin_PROGRAMS = accuraterip-checksum
|
||||
accuraterip_checksum_SOURCES = accuraterip-checksum.c
|
||||
accuraterip_checksum_CFLAGS = -std=c99
|
||||
accuraterip_checksum_LDADD = -lsndfile
|
||||
43
src/README.md
Normal file
43
src/README.md
Normal file
@@ -0,0 +1,43 @@
|
||||
accuraterip-checksum
|
||||
====================
|
||||
|
||||
# Description:
|
||||
A C99 commandline program to compute the AccurateRip checksum of singletrack WAV files.
|
||||
Implemented according to
|
||||
|
||||
http://www.hydrogenaudio.org/forums/index.php?showtopic=97603
|
||||
|
||||
# Syntax:
|
||||
accuraterip-checksum [--version / --accuraterip-v1 / --accuraterip-v2 (default)] filename track_number total_tracks
|
||||
|
||||
# Output:
|
||||
By default, the V2 (AccurateRip version 2) checksum will be printed.
|
||||
You can also obtain the V1 checksum with the "--accuraterip-v1" parameter.
|
||||
|
||||
You can obtain the version of accuraterip-checksum using the "--version" parameter. This is not to be confused with the AccurateRip version!
|
||||
|
||||
The version of accuraterip-checksum should be added to audio files which are tagged using the output of accuraterip-checksum. If any severe bugs are ever found in accuraterip-checksum, this will allow you to identify files which were tagged using affected version.
|
||||
|
||||
|
||||
# Compiling:
|
||||
libsndfile is used for reading the WAV files.
|
||||
Therefore, on Ubuntu 12.04, make sure you have the following packages installed:
|
||||
|
||||
libsndfile1 (should be installed by default)
|
||||
libsndfile1-dev
|
||||
|
||||
The configuration files of an Eclipse project are included.
|
||||
You can use "EGit" (Eclipse git) to import the whole repository.
|
||||
It should as well ask you to import the project configuration then.
|
||||
|
||||
# Author:
|
||||
Leo Bogert (http://leo.bogert.de)
|
||||
|
||||
# Version:
|
||||
1.4
|
||||
|
||||
# Donations:
|
||||
bitcoin:14kPd2QWsri3y2irVFX6wC33vv7FqTaEBh
|
||||
|
||||
# License:
|
||||
GPLv3
|
||||
227
src/accuraterip-checksum.c
Normal file
227
src/accuraterip-checksum.c
Normal file
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
============================================================================
|
||||
Name : accuraterip-checksum.c
|
||||
Author : Leo Bogert (http://leo.bogert.de)
|
||||
Git : http://leo.bogert.de/accuraterip-checksum
|
||||
Version : See global variable "version"
|
||||
Copyright : GPL
|
||||
Description : A C99 commandline program to compute the AccurateRip checksum of singletrack WAV files.
|
||||
Implemented according to http://www.hydrogenaudio.org/forums/index.php?showtopic=97603
|
||||
============================================================================
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <sndfile.h>
|
||||
|
||||
const char *const version = "1.4";
|
||||
|
||||
bool check_fileformat(const SF_INFO* sfinfo) {
|
||||
#ifdef DEBUG
|
||||
printf("Channels: %i\n", sfinfo->channels);
|
||||
printf("Format: %X\n", sfinfo->format);
|
||||
printf("Frames: %li\n", sfinfo->frames);
|
||||
printf("Samplerate: %i\n", sfinfo->samplerate);
|
||||
printf("Sections: %i\n", sfinfo->sections);
|
||||
printf("Seekable: %i\n", sfinfo->seekable);
|
||||
#endif
|
||||
|
||||
if(sfinfo->channels != 2) return false;
|
||||
if((sfinfo->format & SF_FORMAT_TYPEMASK & SF_FORMAT_WAV) != SF_FORMAT_WAV) return false;
|
||||
if((sfinfo->format & SF_FORMAT_SUBMASK & SF_FORMAT_PCM_16) != SF_FORMAT_PCM_16) return false;
|
||||
//if((sfinfo->format & SF_FORMAT_ENDMASK & SF_ENDIAN_LITTLE) != SF_ENDIAN_LITTLE) return false;
|
||||
if(sfinfo->samplerate != 44100) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t get_full_audiodata_size(const SF_INFO* sfinfo) {
|
||||
// 16bit = samplesize, 8 bit = bitcount in byte
|
||||
return sfinfo->frames * sfinfo->channels * (16 / 8);
|
||||
}
|
||||
|
||||
uint32_t* load_full_audiodata(SNDFILE* sndfile, const SF_INFO* sfinfo) {
|
||||
uint32_t* data = (uint32_t*)malloc(get_full_audiodata_size(sfinfo));
|
||||
|
||||
if(data == NULL)
|
||||
return NULL;
|
||||
|
||||
if(sf_readf_short(sndfile, (short*)data, sfinfo->frames) != sfinfo->frames) {
|
||||
free(data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
uint32_t compute_v1_checksum(const uint32_t* audio_data, const size_t audio_data_size, const int track_number, const int total_tracks) {
|
||||
#define DWORD uint32_t
|
||||
|
||||
const DWORD *pAudioData = audio_data; // this should point entire track audio data
|
||||
int DataSize = audio_data_size; // size of the data
|
||||
int TrackNumber = track_number; // actual track number on disc, note that for the first & last track the first and last 5 sectors are skipped
|
||||
int AudioTrackCount = total_tracks; // CD track count
|
||||
|
||||
//---------AccurateRip CRC checks------------
|
||||
DWORD AR_CRC = 0, AR_CRCPosMulti = 1;
|
||||
DWORD AR_CRCPosCheckFrom = 0;
|
||||
DWORD AR_CRCPosCheckTo = DataSize / sizeof(DWORD);
|
||||
#define SectorBytes 2352 // each sector
|
||||
if (TrackNumber == 1) // first?
|
||||
AR_CRCPosCheckFrom+= ((SectorBytes * 5) / sizeof(DWORD));
|
||||
if (TrackNumber == AudioTrackCount) // last?
|
||||
AR_CRCPosCheckTo-=((SectorBytes * 5) / sizeof(DWORD));
|
||||
|
||||
|
||||
int DataDWORDSize = DataSize / sizeof(DWORD);
|
||||
for (int i = 0; i < DataDWORDSize; i++)
|
||||
{
|
||||
if (AR_CRCPosMulti >= AR_CRCPosCheckFrom && AR_CRCPosMulti <= AR_CRCPosCheckTo)
|
||||
AR_CRC+=(AR_CRCPosMulti * pAudioData[i]);
|
||||
|
||||
AR_CRCPosMulti++;
|
||||
}
|
||||
|
||||
return AR_CRC;
|
||||
}
|
||||
|
||||
uint32_t compute_v2_checksum(const uint32_t* audio_data, const size_t audio_data_size, const int track_number, const int total_tracks) {
|
||||
#define DWORD uint32_t
|
||||
#define __int64 uint64_t
|
||||
|
||||
const DWORD *pAudioData = audio_data; // this should point entire track audio data
|
||||
int DataSize = audio_data_size; // size of the data
|
||||
int TrackNumber = track_number; // actual track number on disc, note that for the first & last track the first and last 5 sectors are skipped
|
||||
int AudioTrackCount = total_tracks; // CD track count
|
||||
|
||||
//---------AccurateRip CRC checks------------
|
||||
DWORD AR_CRCPosCheckFrom = 0;
|
||||
DWORD AR_CRCPosCheckTo = DataSize / sizeof(DWORD);
|
||||
#define SectorBytes 2352 // each sector
|
||||
if (TrackNumber == 1) // first?
|
||||
AR_CRCPosCheckFrom+= ((SectorBytes * 5) / sizeof(DWORD));
|
||||
if (TrackNumber == AudioTrackCount) // last?
|
||||
AR_CRCPosCheckTo-=((SectorBytes * 5) / sizeof(DWORD));
|
||||
|
||||
int DataDWORDSize = DataSize / sizeof(DWORD);
|
||||
|
||||
DWORD AC_CRCNEW = 0;
|
||||
DWORD MulBy = 1;
|
||||
for (int i = 0; i < DataDWORDSize; i++)
|
||||
{
|
||||
if (MulBy >= AR_CRCPosCheckFrom && MulBy <= AR_CRCPosCheckTo)
|
||||
{
|
||||
DWORD Value = pAudioData[i];
|
||||
|
||||
uint64_t CalcCRCNEW = (uint64_t)Value * (uint64_t)MulBy;
|
||||
DWORD LOCalcCRCNEW = (DWORD)(CalcCRCNEW & (uint64_t)0xFFFFFFFF);
|
||||
DWORD HICalcCRCNEW = (DWORD)(CalcCRCNEW / (uint64_t)0x100000000);
|
||||
AC_CRCNEW+=HICalcCRCNEW;
|
||||
AC_CRCNEW+=LOCalcCRCNEW;
|
||||
}
|
||||
MulBy++;
|
||||
}
|
||||
|
||||
return AC_CRCNEW;
|
||||
}
|
||||
|
||||
void print_syntax_to_stderr() {
|
||||
fprintf(stderr, "Syntax: accuraterip-checksum [--version / --accuraterip-v1 / --accuraterip-v2 (default)] filename track_number total_tracks\n");
|
||||
}
|
||||
|
||||
int main(int argc, const char** argv) {
|
||||
int arg_offset;
|
||||
bool use_v1;
|
||||
|
||||
switch(argc) {
|
||||
case 2:
|
||||
if(strcmp(argv[1], "--version") != 0) {
|
||||
print_syntax_to_stderr();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
printf("accuraterip-checksum version %s\n", version);
|
||||
return EXIT_SUCCESS;
|
||||
case 4:
|
||||
arg_offset = 0;
|
||||
use_v1 = false;
|
||||
break;
|
||||
case 5:
|
||||
arg_offset = 1;
|
||||
if(!strcmp(argv[1], "--accuraterip-v1")) {
|
||||
use_v1 = true;
|
||||
} else if(!strcmp(argv[1], "--accuraterip-v2")) {
|
||||
use_v1 = false;
|
||||
} else {
|
||||
print_syntax_to_stderr();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
print_syntax_to_stderr();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
const char* filename = argv[1 + arg_offset];
|
||||
const char* track_number_string = argv[2 + arg_offset];
|
||||
const char* total_tracks_string = argv[3 + arg_offset];
|
||||
|
||||
const int track_number = atoi(track_number_string);
|
||||
const int total_tracks = atoi(total_tracks_string);
|
||||
|
||||
if(track_number < 1 || track_number > total_tracks) {
|
||||
fprintf(stderr, "Invalid track_number!\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if(total_tracks < 1 || total_tracks > 99) {
|
||||
fprintf(stderr, "Invalid total_tracks!\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("Reading %s\n", filename);
|
||||
#endif
|
||||
|
||||
SF_INFO sfinfo;
|
||||
sfinfo.channels = 0;
|
||||
sfinfo.format = 0;
|
||||
sfinfo.frames = 0;
|
||||
sfinfo.samplerate = 0;
|
||||
sfinfo.sections = 0;
|
||||
sfinfo.seekable = 0;
|
||||
|
||||
SNDFILE* sndfile = sf_open(filename, SFM_READ, &sfinfo);
|
||||
|
||||
if(sndfile == NULL) {
|
||||
fprintf(stderr, "sf_open failed! sf_error==%i\n", sf_error(NULL));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if(!check_fileformat(&sfinfo)) {
|
||||
fprintf(stderr, "check_fileformat failed!\n");
|
||||
sf_close(sndfile);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
uint32_t* audio_data = load_full_audiodata(sndfile, &sfinfo);
|
||||
if(audio_data == NULL) {
|
||||
fprintf(stderr, "load_full_audiodata failed!\n");
|
||||
sf_close(sndfile);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
const int checksum = use_v1 ?
|
||||
compute_v1_checksum(audio_data, get_full_audiodata_size(&sfinfo), track_number, total_tracks)
|
||||
: compute_v2_checksum(audio_data, get_full_audiodata_size(&sfinfo), track_number, total_tracks);
|
||||
|
||||
printf("%08X\n", checksum);
|
||||
|
||||
sf_close(sndfile);
|
||||
free(audio_data);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
Reference in New Issue
Block a user