Merge pull request #37 from MerlijnWajer/fast-accuraterip-checksum

Fast accuraterip checksum
This commit is contained in:
JoeLametta
2016-10-15 20:34:51 +02:00
committed by GitHub
13 changed files with 372 additions and 6 deletions

4
.gitignore vendored
View File

@@ -14,6 +14,10 @@ missing
morituri.spec
py-compile
REVISION
*.o
.deps
/compile
/depcomp
# For Python development using Eclipse IDE
.project

View File

@@ -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

View File

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

View File

@@ -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.

View File

@@ -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

View File

@@ -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):

View File

@@ -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
View 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

View File

@@ -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
View File

@@ -0,0 +1 @@
accuraterip-checksum

4
src/Makefile.am Normal file
View 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
View 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
View 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;
}