# -*- Mode: Python; test-case-name: whipper.test.test_common_accurip -*- # vi:si:et:sw=4:sts=4:ts=4 # Copyright (C) 2017 Samantha Baldwin # Copyright (C) 2009 Thomas Vander Stichele # This file is part of whipper. # # whipper is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # whipper is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with whipper. If not, see . import struct import whipper from os import makedirs from os.path import dirname, exists, join from urllib.error import URLError, HTTPError from urllib.request import urlopen, Request from whipper.common import directory from whipper.program.arc import accuraterip_checksum import logging logger = logging.getLogger(__name__) ACCURATERIP_URL = "http://www.accuraterip.com/accuraterip/" _CACHE_DIR = join(directory.cache_path(), 'accurip') class EntryNotFound(Exception): pass class _AccurateRipResponse: """ An AccurateRip response contains a collection of metadata identifying a particular digital audio compact disc. For disc level metadata it contains the track count, two internal disc IDs, and the CDDB disc ID. A checksum and a confidence score is stored sequentially for each track in the disc index, which excludes any audio hidden in track pre-gaps (such as HTOA). The response is stored as a packed binary structure. """ def __init__(self, data): """ The checksums and confidences arrays are indexed by relative track position, so track 1 will have array index 0, track 2 will have array index 1, and so forth. HTOA and other hidden tracks are not included. """ self.num_tracks = data[0] self.discId1 = "%08x" % struct.unpack(" track.AR[v]['DBConfidence']): track.AR[v]['DBCRC'] = r.checksums[i] track.AR[v]['DBConfidence'] = r.confidences[i] logger.debug( 'track %d matched response %s in AccurateRip' ' database: %s crc %s confidence %s', i, r.cddbDiscId, v, track.AR[v]['DBCRC'], track.AR[v]['DBConfidence']) return any(( all([t.AR['v1']['DBCRC'] for t in tracks]), all([t.AR['v2']['DBCRC'] for t in tracks]) )) def verify_result(result, responses, checksums): """ Verify track AccurateRip checksums against database responses. Stores track checksums and database values on result. """ if not (result and responses and checksums): return False # exclude HTOA from AccurateRip verification # NOTE: if pre-gap hidden audio support is expanded to include # tracks other than HTOA, this is invalid. tracks = [t for t in result.tracks if t.number != 0] if not tracks: return False _assign_checksums_and_confidences(tracks, checksums, responses) return _match_responses(tracks, responses) def print_report(result): """ Print AccurateRip verification results. """ for _, track in enumerate(result.tracks): status = 'rip NOT accurate' conf = '(not found)' db = 'notfound' if track.AR['DBMaxConfidence'] is not None: db = track.AR['DBMaxConfidenceCRC'] conf = '(max confidence %3d)' % track.AR['DBMaxConfidence'] if track.AR['v1']['DBCRC'] or track.AR['v2']['DBCRC']: status = 'rip accurate' db = ', '.join([_f for _f in ( track.AR['v1']['DBCRC'], track.AR['v2']['DBCRC'] ) if _f]) max_conf = max( [track.AR[v]['DBConfidence'] for v in ('v1', 'v2') if track.AR[v]['DBConfidence'] is not None], default=None ) if max_conf: if max_conf < track.AR['DBMaxConfidence']: conf = '(confidence %3d of %3d)' % ( max_conf, track.AR['DBMaxConfidence'] ) # htoa tracks (i == 0) do not have an ARCRC if track.number == 0: print('track 0: unknown (not tracked)') continue if not (track.AR['v1']['CRC'] or track.AR['v2']['CRC']): logger.error('no track AR CRC on non-HTOA track %d', track.number) print('track %2d: unknown (error)' % track.number) else: print('track %2d: %-16s %-23s v1 [%s], v2 [%s], DB [%s]' % ( track.number, status, conf, track.AR['v1']['CRC'], track.AR['v2']['CRC'], db ))