diff --git a/.travis.yml b/.travis.yml index 07289ba..282d5e1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,11 +23,6 @@ install: # Testing dependencies - pip install twisted flake8 - # Build bundled C utils - - cd src - - sudo make install - - cd .. - # Installing - python setup.py install diff --git a/setup.py b/setup.py index bbcfa0b..051d0e6 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -from setuptools import setup, find_packages +from setuptools import setup, find_packages, Extension setup( name="whipper", @@ -11,6 +11,11 @@ setup( python_requires='>=2.7,<3', packages=find_packages(), setup_requires=['setuptools_scm'], + ext_modules=[ + Extension('accuraterip', + libraries=['sndfile'], + sources=['src/accuraterip-checksum.c']) + ], entry_points={ 'console_scripts': [ 'whipper = whipper.command.main:main' diff --git a/src/.gitignore b/src/.gitignore deleted file mode 100644 index 84cb642..0000000 --- a/src/.gitignore +++ /dev/null @@ -1 +0,0 @@ -accuraterip-checksum diff --git a/src/Makefile b/src/Makefile deleted file mode 100644 index f1c6d4a..0000000 --- a/src/Makefile +++ /dev/null @@ -1,47 +0,0 @@ -# See LICENSE file for copyright and license details. - -include config.mk - -SRC = accuraterip-checksum.c -OBJ = ${SRC:.c=.o} - -all: options accuraterip-checksum - -options: - @echo accuraterip-checksum build options: - @echo "CFLAGS = ${CFLAGS}" - @echo "LDFLAGS = ${LDFLAGS}" - @echo "CC = ${CC}" - -.c.o: - @echo CC $< - @${CC} -c ${CFLAGS} $< - -accuraterip-checksum: ${OBJ} - @echo CC -o $@ - @${CC} -o $@ ${OBJ} ${LDFLAGS} - -clean: - @echo cleaning - @rm -f accuraterip-checksum ${OBJ} accuraterip-checksum-${VERSION}.tar.gz - -dist: clean - @echo creating dist tarball - @mkdir -p accuraterip-checksum-${VERSION} - @cp -R Makefile README.md config.mk \ - ${SRC} accuraterip-checksum-${VERSION} - @tar -cf accuraterip-checksum-${VERSION}.tar accuraterip-checksum-${VERSION} - @gzip accuraterip-checksum-${VERSION}.tar - @rm -rf accuraterip-checksum-${VERSION} - -install: all - @echo installing executable file to ${DESTDIR}${PREFIX}/bin - @mkdir -p ${DESTDIR}${PREFIX}/bin - @cp -f accuraterip-checksum ${DESTDIR}${PREFIX}/bin - @chmod 755 ${DESTDIR}${PREFIX}/bin/accuraterip-checksum - -uninstall: - @echo removing executable file from ${DESTDIR}${PREFIX}/bin - @rm -f ${DESTDIR}${PREFIX}/bin/accuraterip-checksum - -.PHONY: all options clean dist install uninstall diff --git a/src/accuraterip-checksum.c b/src/accuraterip-checksum.c index 68c9dd5..ee784d9 100644 --- a/src/accuraterip-checksum.c +++ b/src/accuraterip-checksum.c @@ -1,12 +1,10 @@ /* ============================================================================ Name : accuraterip-checksum.c - Author : Leo Bogert (http://leo.bogert.de) - Git : https://github.com/leo-bogert/accuraterip-checksum - Version : See global variable "version" - Copyright : GPLv3 - 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 + Authors : Leo Bogert (http://leo.bogert.de), Andreas Oberritter + License : GPLv3 + Description : A Python C extension to compute the AccurateRip checksum of WAV or FLAC tracks. + Implemented according to http://www.hydrogenaudio.org/forums/index.php?showtopic=97603 ============================================================================ */ @@ -17,10 +15,10 @@ #include #include #include +#include -const char *const version = "1.5"; - -bool check_fileformat(const SF_INFO* sfinfo) { +static bool check_fileformat(const SF_INFO *sfinfo) +{ #ifdef DEBUG printf("Channels: %i\n", sfinfo->channels); printf("Format: %X\n", sfinfo->format); @@ -30,27 +28,25 @@ bool check_fileformat(const SF_INFO* sfinfo) { 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; + switch (sfinfo->format & SF_FORMAT_TYPEMASK) { + case SF_FORMAT_WAV: + case SF_FORMAT_FLAC: + return (sfinfo->channels == 2) && + (sfinfo->samplerate == 44100) && + ((sfinfo->format & SF_FORMAT_SUBMASK) == SF_FORMAT_PCM_16); + } - return true; + return false; } -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)); +static void *load_full_audiodata(SNDFILE *sndfile, const SF_INFO *sfinfo, size_t size) +{ + void *data = malloc(size); if(data == NULL) return NULL; - if(sf_readf_short(sndfile, (short*)data, sfinfo->frames) != sfinfo->frames) { + if(sf_readf_short(sndfile, data, sfinfo->frames) != sfinfo->frames) { free(data); return NULL; } @@ -58,170 +54,100 @@ uint32_t* load_full_audiodata(SNDFILE* sndfile, const SF_INFO* sfinfo) { 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 +static void compute_checksums(const uint32_t *audio_data, size_t audio_data_size, size_t track_number, size_t total_tracks, uint32_t *v1, uint32_t *v2) +{ + uint32_t csum_hi = 0; + uint32_t csum_lo = 0; + uint32_t AR_CRCPosCheckFrom = 0; + size_t Datauint32_tSize = audio_data_size / sizeof(uint32_t); + uint32_t AR_CRCPosCheckTo = Datauint32_tSize; + const size_t SectorBytes = 2352; // each sector + uint32_t MulBy = 1; + size_t i; - 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 + if (track_number == 1) // first? + AR_CRCPosCheckFrom += ((SectorBytes * 5) / sizeof(uint32_t)); + if (track_number == total_tracks) // last? + AR_CRCPosCheckTo -= ((SectorBytes * 5) / sizeof(uint32_t)); - //---------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 QWORD 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]; - - QWORD CalcCRCNEW = (QWORD)Value * (QWORD)MulBy; - DWORD LOCalcCRCNEW = (DWORD)(CalcCRCNEW & (QWORD)0xFFFFFFFF); - DWORD HICalcCRCNEW = (DWORD)(CalcCRCNEW / (QWORD)0x100000000); - AC_CRCNEW+=HICalcCRCNEW; - AC_CRCNEW+=LOCalcCRCNEW; + for (i = 0; i < Datauint32_tSize; i++) { + if (MulBy >= AR_CRCPosCheckFrom && MulBy <= AR_CRCPosCheckTo) { + uint64_t product = (uint64_t)audio_data[i] * (uint64_t)MulBy; + csum_hi += (uint32_t)(product >> 32); + csum_lo += (uint32_t)(product); } - MulBy++; + MulBy++; } - return AC_CRCNEW; + *v1 = csum_lo; + *v2 = csum_lo + csum_hi; } -void print_syntax_to_stderr() { - fprintf(stderr, "Syntax: accuraterip-checksum [--version / --accuraterip-v1 / --accuraterip-v2 (default)] filename track_number total_tracks\n"); -} +static PyObject *accuraterip_compute(PyObject *self, PyObject *args) +{ + const char *filename; + unsigned int track_number; + unsigned int total_tracks; + uint32_t v1, v2; + void *audio_data; + size_t size; + SF_INFO sfinfo; + SNDFILE *sndfile = NULL; -int main(int argc, const char** argv) { - int arg_offset; - bool use_v1; + if (!PyArg_ParseTuple(args, "sII", &filename, &track_number, &total_tracks)) + goto err; - 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) { + if (track_number < 1 || track_number > total_tracks) { fprintf(stderr, "Invalid track_number!\n"); - return EXIT_FAILURE; + goto err; } - if(total_tracks < 1 || total_tracks > 99) { + if (total_tracks < 1 || total_tracks > 99) { fprintf(stderr, "Invalid total_tracks!\n"); - return EXIT_FAILURE; + goto err; } #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) { + memset(&sfinfo, 0, sizeof(sfinfo)); + 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; + goto err; } - if(!check_fileformat(&sfinfo)) { + if (!check_fileformat(&sfinfo)) { fprintf(stderr, "check_fileformat failed!\n"); - sf_close(sndfile); - return EXIT_FAILURE; + goto err; } - uint32_t* audio_data = load_full_audiodata(sndfile, &sfinfo); - if(audio_data == NULL) { + size = sfinfo.frames * sfinfo.channels * sizeof(uint16_t); + audio_data = load_full_audiodata(sndfile, &sfinfo, size); + if (audio_data == NULL) { fprintf(stderr, "load_full_audiodata failed!\n"); - sf_close(sndfile); - return EXIT_FAILURE; + goto err; } - 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); + compute_checksums(audio_data, size, track_number, total_tracks, &v1, &v2); free(audio_data); + sf_close(sndfile); - return EXIT_SUCCESS; + return Py_BuildValue("II", v1, v2); + +err: + if (sndfile) + sf_close(sndfile); + return Py_BuildValue("OO", Py_None, Py_None); +} + +static PyMethodDef accuraterip_methods[] = { + { "compute", accuraterip_compute, METH_VARARGS, "Compute AccurateRip v1 and v2 checksums" }, + { NULL, NULL, 0, NULL }, +}; + +PyMODINIT_FUNC initaccuraterip(void) +{ + Py_InitModule("accuraterip", accuraterip_methods); } diff --git a/src/config.mk b/src/config.mk deleted file mode 100644 index 283d745..0000000 --- a/src/config.mk +++ /dev/null @@ -1,11 +0,0 @@ -VERSION = 1.4 - -# paths -PREFIX = /usr/local - -# flags -CFLAGS = -std=c99 -LDFLAGS = -lsndfile - -# compiler and linker -CC = cc diff --git a/whipper/command/offset.py b/whipper/command/offset.py index 9b59bf6..9e1f61f 100644 --- a/whipper/command/offset.py +++ b/whipper/command/offset.py @@ -190,12 +190,7 @@ CD in the AccurateRip database.""" track, offset) runner.run(t) - v1 = arc.accuraterip_checksum( - path, track, len(table.tracks), wave=True, v2=False - ) - v2 = arc.accuraterip_checksum( - path, track, len(table.tracks), wave=True, v2=True - ) + v1, v2 = arc.accuraterip_checksum(path, track, len(table.tracks)) os.unlink(path) return "%08x" % v1, "%08x" % v2 diff --git a/whipper/common/accurip.py b/whipper/common/accurip.py index 40199a6..6133ad7 100644 --- a/whipper/common/accurip.py +++ b/whipper/common/accurip.py @@ -110,19 +110,14 @@ def calculate_checksums(track_paths): logger.debug('checksumming %d tracks', track_count) # This is done sequentially because it is very fast. for i, path in enumerate(track_paths): - v1_sum = accuraterip_checksum( - path, i+1, track_count, wave=True, v2=False - ) - if not v1_sum: + v1_sum, v2_sum = accuraterip_checksum(path, i+1, track_count) + if v1_sum is None: logger.error('could not calculate AccurateRip v1 checksum ' 'for track %d %r', i + 1, path) v1_checksums.append(None) else: v1_checksums.append("%08x" % v1_sum) - v2_sum = accuraterip_checksum( - path, i+1, track_count, wave=True, v2=True - ) - if not v2_sum: + if v2_sum is None: logger.error('could not calculate AccurateRip v2 checksum ' 'for track %d %r', i + 1, path) v2_checksums.append(None) diff --git a/whipper/program/arc.py b/whipper/program/arc.py index 46b8fdf..9e6134a 100644 --- a/whipper/program/arc.py +++ b/whipper/program/arc.py @@ -1,54 +1,5 @@ -from subprocess import Popen, PIPE - -import logging -logger = logging.getLogger(__name__) - -ARB = 'accuraterip-checksum' -FLAC = 'flac' +import accuraterip -def _execute(cmd, **redirects): - logger.debug('executing %r', cmd) - return Popen(cmd, **redirects) - - -def accuraterip_checksum(f, track_number, total_tracks, wave=False, v2=False): - v = '--accuraterip-v1' - if v2: - v = '--accuraterip-v2' - - track_number, total_tracks = str(track_number), str(total_tracks) - - if wave: - cmd = [ARB, v, f, track_number, total_tracks] - redirects = dict(stdout=PIPE, stderr=PIPE) - else: - flac = _execute([FLAC, '-cds', f], stdout=PIPE) - cmd = [ARB, v, '/dev/stdin', track_number, total_tracks] - redirects = dict(stdin=flac.stdout, stdout=PIPE, stderr=PIPE) - arc = _execute(cmd, **redirects) - - if not wave: - flac.stdout.close() - - out, _ = arc.communicate() - - if not wave: - flac.wait() - if flac.returncode != 0: - logger.warning('ARC calculation failed: flac ' - 'return code is non zero: %r', flac.returncode) - return None - - if arc.returncode != 0: - logger.warning('ARC calculation failed: ' - 'arc return code is non zero: %r', arc.returncode) - return None - - try: - checksum = int('0x%s' % out.strip(), base=16) - logger.debug('returned %r', checksum) - return checksum - except ValueError: - logger.warning('ARC output is not usable') - return None +def accuraterip_checksum(f, track_number, total_tracks): + return accuraterip.compute(f.encode('utf-8'), track_number, total_tracks)