pax_global_header00006660000000000000000000000064135717322440014522gustar00rootroot0000000000000052 comment=b1718a2a688f7890580540a24702eb0e0225e7fc whipper-0.9.0/000077500000000000000000000000001357173224400132065ustar00rootroot00000000000000whipper-0.9.0/.github/000077500000000000000000000000001357173224400145465ustar00rootroot00000000000000whipper-0.9.0/.github/workflows/000077500000000000000000000000001357173224400166035ustar00rootroot00000000000000whipper-0.9.0/.github/workflows/greetings.yml000066400000000000000000000013441357173224400213170ustar00rootroot00000000000000name: Greetings on: [pull_request, issues] jobs: greeting: runs-on: ubuntu-latest steps: - uses: actions/first-interaction@v1 with: repo-token: ${{ secrets.GITHUB_TOKEN }} issue-message: | 👋 Thanks for opening your first issue here! If you're reporting a 🐞 bug, please make sure you include steps to reproduce it. We get a lot of issues on this repo, so please be patient and we will get back to you as soon as we can. To help make it easier for us to investigate your issue, please follow the [contributing instructions](https://github.com/whipper-team/whipper#bug-reports--feature-requests). pr-message: '💖 Thanks for opening your first pull request here! 💖' whipper-0.9.0/.gitignore000066400000000000000000000003161357173224400151760ustar00rootroot00000000000000*.pyc INSTALL py-compile REVISION *.o # For Python development using Eclipse IDE .project .pydevproject # setup.py install generated files build/ dist/ whipper.egg-info/ # From coverage report .coverage whipper-0.9.0/.travis.yml000066400000000000000000000015331357173224400153210ustar00rootroot00000000000000dist: xenial sudo: required language: python python: - "3.5" virtualenv: system_site_packages: false cache: pip env: - FLAKE8=false - FLAKE8=true install: # Dependencies - sudo apt-get -qq update - pip install --upgrade -qq pip - sudo apt-get -qq install cdparanoia cdrdao flac gir1.2-glib-2.0 libcdio-dev libgirepository1.0-dev libiso9660-dev libsndfile1-dev sox swig libcdio-utils # newer version of pydcio requires newer version of libcdio than travis has - pip install pycdio==0.21 # install rest of dependencies - pip install -r requirements.txt # Testing dependencies - pip install twisted flake8 # Installing - python setup.py install script: - if [ ! "$FLAKE8" = true ]; then python -m unittest discover; fi - if [ "$FLAKE8" = true ]; then flake8 --benchmark --statistics; fi whipper-0.9.0/CHANGELOG.md000066400000000000000000001062131357173224400150220ustar00rootroot00000000000000# Change Log ## [Unreleased](https://github.com/whipper-team/whipper/tree/HEAD) [Full Changelog](https://github.com/whipper-team/whipper/compare/v0.9.0...HEAD) ## [v0.9.0](https://github.com/whipper-team/whipper/tree/v0.9.0) (2019-11-04) [Full Changelog](https://github.com/whipper-team/whipper/compare/v0.8.0...v0.9.0) **Fixed bugs:** - Fix regression introduced due to Python 3 port [\#424](https://github.com/whipper-team/whipper/issues/424) - Properly tagging releases on dockerhub [\#423](https://github.com/whipper-team/whipper/issues/423) - Test failure when building a release [\#420](https://github.com/whipper-team/whipper/issues/420) - Dockerfile is missing ruamel.yaml [\#419](https://github.com/whipper-team/whipper/issues/419) - Port to Python 3 [\#78](https://github.com/whipper-team/whipper/issues/78) **Closed issues:** - Why is CD-Text if found not used for naming Disk and Tracks? [\#397](https://github.com/whipper-team/whipper/issues/397) **Merged pull requests:** - Python 3 port [\#411](https://github.com/whipper-team/whipper/pull/411) ([ddevault](https://github.com/ddevault)) ## [v0.8.0](https://github.com/whipper-team/whipper/tree/v0.8.0) (2019-10-27) [Full Changelog](https://github.com/whipper-team/whipper/compare/v0.7.3...v0.8.0) **Implemented enhancements:** - Include MusicBrainz Release ID in the log file [\#381](https://github.com/whipper-team/whipper/issues/381) - Specify supported version\(s\) of Python in setup.py [\#378](https://github.com/whipper-team/whipper/pull/378) ([Freso](https://github.com/Freso)) **Fixed bugs:** - whipper bails out if MusicBrainz release group doesn’t have a type [\#396](https://github.com/whipper-team/whipper/issues/396) - object has no attribute 'working\_directory' when running cd info [\#375](https://github.com/whipper-team/whipper/issues/375) - Failure to rip CD: "ValueError: could not convert string to float: " [\#374](https://github.com/whipper-team/whipper/issues/374) - "AttributeError: Program instance has no attribute '\_presult'" when ripping [\#369](https://github.com/whipper-team/whipper/issues/369) - Drive analysis fails [\#361](https://github.com/whipper-team/whipper/issues/361) - Eliminate warning "eject: CD-ROM tray close command failed" [\#354](https://github.com/whipper-team/whipper/issues/354) - Flac file permissions [\#284](https://github.com/whipper-team/whipper/issues/284) **Closed issues:** - Separate out Release in log into two value map [\#416](https://github.com/whipper-team/whipper/issues/416) - Network issue [\#412](https://github.com/whipper-team/whipper/issues/412) - RequestsDependencyWarning: urllib3 \(1.25.2\) or chardet \(3.0.4\) doesn't match a supported version [\#400](https://github.com/whipper-team/whipper/issues/400) - Add git/mercurial dependency to the README [\#386](https://github.com/whipper-team/whipper/issues/386) - Doesn't eject - "eject: unable to eject" \(but manual eject works\) [\#355](https://github.com/whipper-team/whipper/issues/355) - Note in the whipper output/log if development version was used [\#337](https://github.com/whipper-team/whipper/issues/337) - fedora 29, whipper 0.72, Error While Executing Any Command [\#332](https://github.com/whipper-team/whipper/issues/332) - read-toc progress information [\#299](https://github.com/whipper-team/whipper/issues/299) - ripping fails frequently, but not repeatably [\#290](https://github.com/whipper-team/whipper/issues/290) - Look into adding more MusicBrainz identifiers to ripped files [\#200](https://github.com/whipper-team/whipper/issues/200) **Merged pull requests:** - Fix ripping discs with less than ten tracks [\#418](https://github.com/whipper-team/whipper/pull/418) ([mtdcr](https://github.com/mtdcr)) - Make getFastToc\(\) fast again [\#417](https://github.com/whipper-team/whipper/pull/417) ([mtdcr](https://github.com/mtdcr)) - Use ruamel.yaml for formatting and outputting rip .log file [\#415](https://github.com/whipper-team/whipper/pull/415) ([itismadness](https://github.com/itismadness)) - Handle missing self.options for whipper cd info [\#410](https://github.com/whipper-team/whipper/pull/410) ([JoeLametta](https://github.com/JoeLametta)) - Fix erroneous result message for whipper drive analyze [\#409](https://github.com/whipper-team/whipper/pull/409) ([JoeLametta](https://github.com/JoeLametta)) - Report eject's failures as logger warnings [\#408](https://github.com/whipper-team/whipper/pull/408) ([JoeLametta](https://github.com/JoeLametta)) - Set FLAC files permissions to 0644 [\#407](https://github.com/whipper-team/whipper/pull/407) ([JoeLametta](https://github.com/JoeLametta)) - Fix offset find command [\#406](https://github.com/whipper-team/whipper/pull/406) ([vmx](https://github.com/vmx)) - Make whipper not break on missing release type [\#398](https://github.com/whipper-team/whipper/pull/398) ([Freso](https://github.com/Freso)) - Set default for eject to: success [\#392](https://github.com/whipper-team/whipper/pull/392) ([gorgobacka](https://github.com/gorgobacka)) - Use eject value of the class again [\#391](https://github.com/whipper-team/whipper/pull/391) ([gorgobacka](https://github.com/gorgobacka)) - Convert documentation from epydoc to reStructuredText [\#387](https://github.com/whipper-team/whipper/pull/387) ([JoeLametta](https://github.com/JoeLametta)) - Include MusicBrainz Release URL in log output [\#382](https://github.com/whipper-team/whipper/pull/382) ([Freso](https://github.com/Freso)) - Fix critical regressions introduced in 3e79032 and 16b0d8d [\#371](https://github.com/whipper-team/whipper/pull/371) ([JoeLametta](https://github.com/JoeLametta)) - Use git to get whipper's version [\#370](https://github.com/whipper-team/whipper/pull/370) ([Freso](https://github.com/Freso)) - Handle artist MBIDs as multivalue tags [\#367](https://github.com/whipper-team/whipper/pull/367) ([Freso](https://github.com/Freso)) - Add Track, Release Group, and Work MBIDs to ripped files [\#366](https://github.com/whipper-team/whipper/pull/366) ([Freso](https://github.com/Freso)) - Refresh MusicBrainz JSON responses used for testing [\#365](https://github.com/whipper-team/whipper/pull/365) ([Freso](https://github.com/Freso)) - Clean up MusicBrainz nomenclature [\#364](https://github.com/whipper-team/whipper/pull/364) ([Freso](https://github.com/Freso)) - Fix misaligned output in command.mblookup [\#363](https://github.com/whipper-team/whipper/pull/363) ([Freso](https://github.com/Freso)) - Update accuraterip-checksum [\#362](https://github.com/whipper-team/whipper/pull/362) ([Freso](https://github.com/Freso)) - Require Developer Certificate of Origin sign-off [\#358](https://github.com/whipper-team/whipper/pull/358) ([JoeLametta](https://github.com/JoeLametta)) - Address warnings/errors from various static analysis tools [\#357](https://github.com/whipper-team/whipper/pull/357) ([JoeLametta](https://github.com/JoeLametta)) - Clarify format option for disc template [\#353](https://github.com/whipper-team/whipper/pull/353) ([rekh127](https://github.com/rekh127)) - Refactor cdrdao toc/table functions into Task and provide progress output [\#345](https://github.com/whipper-team/whipper/pull/345) ([jtl999](https://github.com/jtl999)) - accuraterip-checksum: convert to python C extension [\#274](https://github.com/whipper-team/whipper/pull/274) ([mtdcr](https://github.com/mtdcr)) ## [v0.7.3](https://github.com/whipper-team/whipper/tree/v0.7.3) (2018-12-14) [Full Changelog](https://github.com/whipper-team/whipper/compare/v0.7.2...v0.7.3) **Fixed bugs:** - Error when parsing log file due to left pad track number [\#340](https://github.com/whipper-team/whipper/issues/340) - Failing AccurateRipResponse tests [\#333](https://github.com/whipper-team/whipper/issues/333) - Disc template KeyError [\#279](https://github.com/whipper-team/whipper/issues/279) - Unicode issues [\#215](https://github.com/whipper-team/whipper/issues/215) - whipper offset find exception [\#208](https://github.com/whipper-team/whipper/issues/208) - ZeroDivisionError: float division by zero [\#202](https://github.com/whipper-team/whipper/issues/202) - Allow plugins from system directories [\#135](https://github.com/whipper-team/whipper/issues/135) **Closed issues:** - On Ubuntu 18.10 cd-paranoia binary is called cdparanoia [\#347](https://github.com/whipper-team/whipper/issues/347) - WARNING:whipper.common.program:network error: NetworkError\(\) [\#338](https://github.com/whipper-team/whipper/issues/338) - Can not install [\#314](https://github.com/whipper-team/whipper/issues/314) - use standard logging [\#303](https://github.com/whipper-team/whipper/issues/303) - Write musicbrainz\_discid tag when disc is unknown [\#280](https://github.com/whipper-team/whipper/issues/280) - pycdio & libcdio issues [\#238](https://github.com/whipper-team/whipper/issues/238) - Write .toc files in addition to .cue files to support cdrdao and non-compliant .cue sheets [\#214](https://github.com/whipper-team/whipper/issues/214) **Merged pull requests:** - Discover plugins in system directories too [\#348](https://github.com/whipper-team/whipper/pull/348) ([JoeLametta](https://github.com/JoeLametta)) - Avoid zero padding in logger track numbers [\#341](https://github.com/whipper-team/whipper/pull/341) ([itismadness](https://github.com/itismadness)) - Update failing AccurateRipResponse tests [\#334](https://github.com/whipper-team/whipper/pull/334) ([JoeLametta](https://github.com/JoeLametta)) - Replace sys.std{out,err} statements with logger/print calls [\#331](https://github.com/whipper-team/whipper/pull/331) ([JoeLametta](https://github.com/JoeLametta)) - Add Probot apps to improve workflow [\#329](https://github.com/whipper-team/whipper/pull/329) ([JoeLametta](https://github.com/JoeLametta)) - Raise exception when cdparanoia can't read any frames [\#328](https://github.com/whipper-team/whipper/pull/328) ([JoeLametta](https://github.com/JoeLametta)) - Prevent exception in offset find [\#327](https://github.com/whipper-team/whipper/pull/327) ([JoeLametta](https://github.com/JoeLametta)) - Fix template validation error [\#325](https://github.com/whipper-team/whipper/pull/325) ([JoeLametta](https://github.com/JoeLametta)) - Fix UnicodeEncodeError with non ASCII MusicBrainz's catalog numbers [\#323](https://github.com/whipper-team/whipper/pull/323) ([JoeLametta](https://github.com/JoeLametta)) - Raise exception if template has invalid variables [\#322](https://github.com/whipper-team/whipper/pull/322) ([JoeLametta](https://github.com/JoeLametta)) - Preserve ToC file generated by cdrdao [\#321](https://github.com/whipper-team/whipper/pull/321) ([JoeLametta](https://github.com/JoeLametta)) ## [v0.7.2](https://github.com/whipper-team/whipper/tree/v0.7.2) (2018-10-31) [Full Changelog](https://github.com/whipper-team/whipper/compare/v0.7.1...v0.7.2) **Fixed bugs:** - UnicodeEncodeError: 'ascii' codec can't encode characters in position 17-18: ordinal not in range\(128\) [\#315](https://github.com/whipper-team/whipper/issues/315) **Closed issues:** - Add whipper to Hydrogen Audio wiki's "Comparison of CD rippers" [\#317](https://github.com/whipper-team/whipper/issues/317) - Make 0.7.1 release \(before GCI 😅\) [\#312](https://github.com/whipper-team/whipper/issues/312) - automatically build Docker images [\#301](https://github.com/whipper-team/whipper/issues/301) **Merged pull requests:** - Explicitly encode path as UTF-8 in truncate\_filename\(\) [\#319](https://github.com/whipper-team/whipper/pull/319) ([Freso](https://github.com/Freso)) - Add AppStream metainfo.xml file [\#318](https://github.com/whipper-team/whipper/pull/318) ([Freso](https://github.com/Freso)) ## [v0.7.1](https://github.com/whipper-team/whipper/tree/v0.7.1) (2018-10-23) [Full Changelog](https://github.com/whipper-team/whipper/compare/v0.7.0...v0.7.1) **Fixed bugs:** - TypeError on whipper offset find [\#263](https://github.com/whipper-team/whipper/issues/263) - Remove whipper's retag feature [\#262](https://github.com/whipper-team/whipper/issues/262) - ImportError: libcdio.so.16: cannot open shared object file: No such file or directory [\#229](https://github.com/whipper-team/whipper/issues/229) - Catch DNS error [\#206](https://github.com/whipper-team/whipper/issues/206) - Limit length of filenames [\#197](https://github.com/whipper-team/whipper/issues/197) - Loggers [\#117](https://github.com/whipper-team/whipper/issues/117) **Closed issues:** - Disable eject button when ripping [\#308](https://github.com/whipper-team/whipper/issues/308) - Transfer repository ownership to GitHub organization [\#306](https://github.com/whipper-team/whipper/issues/306) - Variable offset detected [\#295](https://github.com/whipper-team/whipper/issues/295) - Github repo [\#293](https://github.com/whipper-team/whipper/issues/293) - pre emphasis documentation [\#275](https://github.com/whipper-team/whipper/issues/275) - Add cdparanoia version to log file [\#267](https://github.com/whipper-team/whipper/issues/267) - Add a requirements.txt file [\#221](https://github.com/whipper-team/whipper/issues/221) **Merged pull requests:** - Limit length of filenames [\#311](https://github.com/whipper-team/whipper/pull/311) ([JoeLametta](https://github.com/JoeLametta)) - Add a requirements.txt file [\#310](https://github.com/whipper-team/whipper/pull/310) ([JoeLametta](https://github.com/JoeLametta)) - Reorder Dockerfile for performance [\#305](https://github.com/whipper-team/whipper/pull/305) ([anarcat](https://github.com/anarcat)) - Handle FreeDB server errors gracefully [\#304](https://github.com/whipper-team/whipper/pull/304) ([anarcat](https://github.com/anarcat)) - Fix Docker invocation [\#300](https://github.com/whipper-team/whipper/pull/300) ([anarcat](https://github.com/anarcat)) - Document Docker usage in the README [\#297](https://github.com/whipper-team/whipper/pull/297) ([thomas-mc-work](https://github.com/thomas-mc-work)) - switch CDDB implementation to freedb.py from python-audio-tools [\#276](https://github.com/whipper-team/whipper/pull/276) ([mtdcr](https://github.com/mtdcr)) - task: implement logging [\#272](https://github.com/whipper-team/whipper/pull/272) ([mtdcr](https://github.com/mtdcr)) - Switch to PyGObject by default [\#271](https://github.com/whipper-team/whipper/pull/271) ([mtdcr](https://github.com/mtdcr)) - Remove whipper's image retag feature [\#269](https://github.com/whipper-team/whipper/pull/269) ([JoeLametta](https://github.com/JoeLametta)) - Incremental code modernization for \(future\) Python 3 port [\#268](https://github.com/whipper-team/whipper/pull/268) ([JoeLametta](https://github.com/JoeLametta)) - Remove dead code from program.getFastToc [\#264](https://github.com/whipper-team/whipper/pull/264) ([mtdcr](https://github.com/mtdcr)) - Add Dockerfile [\#237](https://github.com/whipper-team/whipper/pull/237) ([thomas-mc-work](https://github.com/thomas-mc-work)) ## [v0.7.0](https://github.com/whipper-team/whipper/tree/v0.7.0) (2018-04-09) [Full Changelog](https://github.com/whipper-team/whipper/compare/v0.6.0...v0.7.0) **Implemented enhancements:** - Simple message while reading TOC [\#257](https://github.com/whipper-team/whipper/issues/257) **Fixed bugs:** - cd rip is not able to rip the last track [\#203](https://github.com/whipper-team/whipper/issues/203) - Various ripping issues [\#179](https://github.com/whipper-team/whipper/issues/179) - whipper not picking up all settings in whipper.conf [\#99](https://github.com/whipper-team/whipper/issues/99) **Closed issues:** - How to choose device \(if there are more\)? [\#241](https://github.com/whipper-team/whipper/issues/241) - Make a 0.6.0 release [\#219](https://github.com/whipper-team/whipper/issues/219) - flac settings [\#184](https://github.com/whipper-team/whipper/issues/184) - Remove connection to parent fork. [\#79](https://github.com/whipper-team/whipper/issues/79) **Merged pull requests:** - Small readme cleanups [\#250](https://github.com/whipper-team/whipper/pull/250) ([RecursiveForest](https://github.com/RecursiveForest)) - Remove debug commands, add mblookup command [\#249](https://github.com/whipper-team/whipper/pull/249) ([RecursiveForest](https://github.com/RecursiveForest)) - Remove reference to Copr repository [\#248](https://github.com/whipper-team/whipper/pull/248) ([mruszczyk](https://github.com/mruszczyk)) - Revert "Convert docstrings to reStructuredText" [\#246](https://github.com/whipper-team/whipper/pull/246) ([RecursiveForest](https://github.com/RecursiveForest)) - remove -T/--toc-pickle [\#245](https://github.com/whipper-team/whipper/pull/245) ([RecursiveForest](https://github.com/RecursiveForest)) - credit four major developers by line count [\#243](https://github.com/whipper-team/whipper/pull/243) ([RecursiveForest](https://github.com/RecursiveForest)) - remove radon reports [\#242](https://github.com/whipper-team/whipper/pull/242) ([RecursiveForest](https://github.com/RecursiveForest)) - read command parameters from config sections [\#240](https://github.com/whipper-team/whipper/pull/240) ([RecursiveForest](https://github.com/RecursiveForest)) - fix CI build error with latest pycdio [\#233](https://github.com/whipper-team/whipper/pull/233) ([thomas-mc-work](https://github.com/thomas-mc-work)) - Removed reference to unused "profile = flac" config option \(issue \#99\) [\#231](https://github.com/whipper-team/whipper/pull/231) ([calumchisholm](https://github.com/calumchisholm)) ## [v0.6.0](https://github.com/whipper-team/whipper/tree/v0.6.0) (2018-02-02) [Full Changelog](https://github.com/whipper-team/whipper/compare/v0.5.1...v0.6.0) **Implemented enhancements:** - Declare supported Python version [\#152](https://github.com/whipper-team/whipper/issues/152) **Fixed bugs:** - Error: NotFoundException message displayed while ripping an unknown disc [\#198](https://github.com/whipper-team/whipper/issues/198) - whipper doesn't name files .flac, which leads to it not being able to find ripped files [\#194](https://github.com/whipper-team/whipper/issues/194) - Issues with finding offset [\#182](https://github.com/whipper-team/whipper/issues/182) - cdparanoia toc does not agree with cdrdao-toc, cd-paranoia also reports different \(but better\) lengths [\#175](https://github.com/whipper-team/whipper/issues/175) - failing unittests in systemd-nspawn container [\#157](https://github.com/whipper-team/whipper/issues/157) - Update doc/release or remove it [\#149](https://github.com/whipper-team/whipper/issues/149) - Test HTOA peak value against 0 \(integer equality\) [\#143](https://github.com/whipper-team/whipper/issues/143) - Regression: Unable to resume a failed rip [\#136](https://github.com/whipper-team/whipper/issues/136) - "Catalog Number" incorrectly appended to "artist" instead of the Album name. [\#127](https://github.com/whipper-team/whipper/issues/127) - Track "can't be ripped" but EAC can :\) [\#116](https://github.com/whipper-team/whipper/issues/116) - ERROR: stopping task which is already stopped [\#59](https://github.com/whipper-team/whipper/issues/59) - can't find accuraterip-checksum binary in morituri-uninstalled mode [\#47](https://github.com/whipper-team/whipper/issues/47) **Closed issues:** - ImportError - CDDB on Solus. [\#209](https://github.com/whipper-team/whipper/issues/209) - rename milestone 101010 to backlog [\#190](https://github.com/whipper-team/whipper/issues/190) - .log, .cue, and .m3u file names [\#180](https://github.com/whipper-team/whipper/issues/180) - using your own MusicBrainz server [\#172](https://github.com/whipper-team/whipper/issues/172) - Use 'Artist as credited' in filename instead of 'Artist in MusicBrainz' \(e.g. to solve \[unknown\]\) [\#155](https://github.com/whipper-team/whipper/issues/155) - Identify media type in log file \(ie CD vs CD-R\) [\#137](https://github.com/whipper-team/whipper/issues/137) - Rename the Python module [\#100](https://github.com/whipper-team/whipper/issues/100) - libcdio-paranoia instead of cdparanoia [\#87](https://github.com/whipper-team/whipper/issues/87) - Release, Tags, NEWS? [\#63](https://github.com/whipper-team/whipper/issues/63) - Support both AccurateRip V1 and AccurateRip V2 at the same time [\#18](https://github.com/whipper-team/whipper/issues/18) **Merged pull requests:** - Test HTOA peak value against 0 \(integer comparison\) [\#224](https://github.com/whipper-team/whipper/pull/224) ([JoeLametta](https://github.com/JoeLametta)) - Fix appearance of template description text. [\#223](https://github.com/whipper-team/whipper/pull/223) ([calumchisholm](https://github.com/calumchisholm)) - Run whipper without installation [\#222](https://github.com/whipper-team/whipper/pull/222) ([vmx](https://github.com/vmx)) - Remove doc/release [\#218](https://github.com/whipper-team/whipper/pull/218) ([MerlijnWajer](https://github.com/MerlijnWajer)) - Fix resuming previous rips [\#217](https://github.com/whipper-team/whipper/pull/217) ([MerlijnWajer](https://github.com/MerlijnWajer)) - Switch to libcdio-cdparanoia \(from cdparanoia\) [\#213](https://github.com/whipper-team/whipper/pull/213) ([MerlijnWajer](https://github.com/MerlijnWajer)) - Convert docstrings to reStructuredText [\#211](https://github.com/whipper-team/whipper/pull/211) ([JoeLametta](https://github.com/JoeLametta)) - Enable connecting to a custom MusicBrainz server [\#210](https://github.com/whipper-team/whipper/pull/210) ([ghost](https://github.com/ghost)) - Fix recently introduced Python 3 incompatibility [\#199](https://github.com/whipper-team/whipper/pull/199) ([LingMan](https://github.com/LingMan)) - restore .flac extension [\#195](https://github.com/whipper-team/whipper/pull/195) ([RecursiveForest](https://github.com/RecursiveForest)) - Misc fixes [\#188](https://github.com/whipper-team/whipper/pull/188) ([ubitux](https://github.com/ubitux)) - AccurateRip V2 support [\#187](https://github.com/whipper-team/whipper/pull/187) ([RecursiveForest](https://github.com/RecursiveForest)) - Solve all flake8 warnings [\#163](https://github.com/whipper-team/whipper/pull/163) ([JoeLametta](https://github.com/JoeLametta)) - Minor touchups [\#161](https://github.com/whipper-team/whipper/pull/161) ([Freso](https://github.com/Freso)) - Stop allowing flake8 to fail in Travis CI [\#160](https://github.com/whipper-team/whipper/pull/160) ([Freso](https://github.com/Freso)) - Fix division by zero [\#159](https://github.com/whipper-team/whipper/pull/159) ([sqozz](https://github.com/sqozz)) - Fix artist name [\#156](https://github.com/whipper-team/whipper/pull/156) ([gorgobacka](https://github.com/gorgobacka)) - Detect and handle CD-R discs [\#154](https://github.com/whipper-team/whipper/pull/154) ([gorgobacka](https://github.com/gorgobacka)) - Disambiguate on release [\#153](https://github.com/whipper-team/whipper/pull/153) ([Freso](https://github.com/Freso)) - Add flake8 testing to CI [\#151](https://github.com/whipper-team/whipper/pull/151) ([Freso](https://github.com/Freso)) - Clean up files in misc/ [\#150](https://github.com/whipper-team/whipper/pull/150) ([Freso](https://github.com/Freso)) - Update .gitignore [\#148](https://github.com/whipper-team/whipper/pull/148) ([Freso](https://github.com/Freso)) - Fix references to morituri. [\#109](https://github.com/whipper-team/whipper/pull/109) ([Freso](https://github.com/Freso)) ## [v0.5.1](https://github.com/whipper-team/whipper/tree/v0.5.1) (2017-04-24) [Full Changelog](https://github.com/whipper-team/whipper/compare/v0.5.0...v0.5.1) **Fixed bugs:** - 0.5.0 Release init.py version number not updated [\#147](https://github.com/whipper-team/whipper/issues/147) ## [v0.5.0](https://github.com/whipper-team/whipper/tree/v0.5.0) (2017-04-24) [Full Changelog](https://github.com/whipper-team/whipper/compare/v0.4.2...v0.5.0) **Fixed bugs:** - Final track rip failure due to file size mismatch [\#146](https://github.com/whipper-team/whipper/issues/146) - Fails to rip if MB Release doesn't have a release date/year [\#133](https://github.com/whipper-team/whipper/issues/133) - overly verbose warning logging [\#131](https://github.com/whipper-team/whipper/issues/131) - fb271f08cdee877795091065c344dcc902d1dcbf breaks HEAD [\#129](https://github.com/whipper-team/whipper/issues/129) - 'whipper drive list' returns a suggestion to run 'rip offset find' [\#112](https://github.com/whipper-team/whipper/issues/112) - EmptyError\('not a single buffer gotten',\) [\#101](https://github.com/whipper-team/whipper/issues/101) - Julie Roberts bug [\#74](https://github.com/whipper-team/whipper/issues/74) **Closed issues:** - `whipper find offset` still requiring gst [\#141](https://github.com/whipper-team/whipper/issues/141) - Burn FLACs 1:1 CD ? [\#125](https://github.com/whipper-team/whipper/issues/125) - Check that whipper deals properly with CD pre-emphasis [\#120](https://github.com/whipper-team/whipper/issues/120) - Difficulty getting flac encoding working. [\#118](https://github.com/whipper-team/whipper/issues/118) - additional tag creation [\#108](https://github.com/whipper-team/whipper/issues/108) - Remove gstreamer dependency [\#29](https://github.com/whipper-team/whipper/issues/29) **Merged pull requests:** - Remove notes related to GStreamer flacparse [\#140](https://github.com/whipper-team/whipper/pull/140) ([Freso](https://github.com/Freso)) - Prevent a crash if MusicBrainz release date is missing [\#139](https://github.com/whipper-team/whipper/pull/139) ([ribbons](https://github.com/ribbons)) - program: do not fetch 4 times musicbrainz metadata [\#134](https://github.com/whipper-team/whipper/pull/134) ([ubitux](https://github.com/ubitux)) - Fix Travis CI build failures [\#132](https://github.com/whipper-team/whipper/pull/132) ([JoeLametta](https://github.com/JoeLametta)) - Rip out all code that uses gstreamer [\#130](https://github.com/whipper-team/whipper/pull/130) ([MerlijnWajer](https://github.com/MerlijnWajer)) - Add pre-emphasis status reporting to whipper's logfiles [\#124](https://github.com/whipper-team/whipper/pull/124) ([JoeLametta](https://github.com/JoeLametta)) - Add gstreamer-less flac encoder and tagging [\#121](https://github.com/whipper-team/whipper/pull/121) ([MerlijnWajer](https://github.com/MerlijnWajer)) - Replace rip command suggestions with 'whipper' [\#114](https://github.com/whipper-team/whipper/pull/114) ([JoeLametta](https://github.com/JoeLametta)) ## [v0.4.2](https://github.com/whipper-team/whipper/tree/v0.4.2) (2017-01-08) [Full Changelog](https://github.com/whipper-team/whipper/compare/v0.4.1...v0.4.2) **Fixed bugs:** - 0.4.1 Release created but version number in code not bumped [\#105](https://github.com/whipper-team/whipper/issues/105) - Whipper attempts to rip with no CD inserted [\#81](https://github.com/whipper-team/whipper/issues/81) **Closed issues:** - Make a 0.4.1 release [\#104](https://github.com/whipper-team/whipper/issues/104) **Merged pull requests:** - Amend previous tagged release [\#107](https://github.com/whipper-team/whipper/pull/107) ([JoeLametta](https://github.com/JoeLametta)) - Update links to Arch Linux AUR packages in README. [\#103](https://github.com/whipper-team/whipper/pull/103) ([Freso](https://github.com/Freso)) ## [v0.4.1](https://github.com/whipper-team/whipper/tree/v0.4.1) (2017-01-06) [Full Changelog](https://github.com/whipper-team/whipper/compare/v0.4.0...v0.4.1) **Closed issues:** - Please don't stop - despite the recent events \(ANSWERED\) [\#76](https://github.com/whipper-team/whipper/issues/76) - Migrate away from the "rip" command [\#21](https://github.com/whipper-team/whipper/issues/21) **Merged pull requests:** - Small cleanups of setup.py [\#102](https://github.com/whipper-team/whipper/pull/102) ([Freso](https://github.com/Freso)) - Persist False value for defeats\_cache correctly [\#98](https://github.com/whipper-team/whipper/pull/98) ([ribbons](https://github.com/ribbons)) - Update suggested commands given by `drive list` [\#97](https://github.com/whipper-team/whipper/pull/97) ([ribbons](https://github.com/ribbons)) - add url and license to setup.py [\#96](https://github.com/whipper-team/whipper/pull/96) ([RecursiveForest](https://github.com/RecursiveForest)) - remove configure.configure, use \_\_version\_\_, remove getRevision\(\) [\#94](https://github.com/whipper-team/whipper/pull/94) ([RecursiveForest](https://github.com/RecursiveForest)) - cdrdao no-disc ejection & --eject [\#93](https://github.com/whipper-team/whipper/pull/93) ([RecursiveForest](https://github.com/RecursiveForest)) - argparse & logging [\#92](https://github.com/whipper-team/whipper/pull/92) ([RecursiveForest](https://github.com/RecursiveForest)) - Update README.md [\#91](https://github.com/whipper-team/whipper/pull/91) ([pieqq](https://github.com/pieqq)) - Fixed README broken links and added a better changelog [\#90](https://github.com/whipper-team/whipper/pull/90) ([JoeLametta](https://github.com/JoeLametta)) - soxi: remove self.\_path unused variable, mark dep as 'soxi' [\#89](https://github.com/whipper-team/whipper/pull/89) ([RecursiveForest](https://github.com/RecursiveForest)) - Fix spelling mistake in README.md [\#86](https://github.com/whipper-team/whipper/pull/86) ([takeshibaconsuzuki](https://github.com/takeshibaconsuzuki)) - Error reporting enhancements \(conditional-raise-instead-of-assert version\) [\#80](https://github.com/whipper-team/whipper/pull/80) ([chrysn](https://github.com/chrysn)) - Update top level informational files [\#71](https://github.com/whipper-team/whipper/pull/71) ([RecursiveForest](https://github.com/RecursiveForest)) - Use soxi instead of gstreamer to determine a track's length [\#67](https://github.com/whipper-team/whipper/pull/67) ([chrysn](https://github.com/chrysn)) ## [v0.4.0](https://github.com/whipper-team/whipper/tree/v0.4.0) (2016-11-08) [Full Changelog](https://github.com/whipper-team/whipper/compare/v0.3.0...v0.4.0) **Fixed bugs:** - wrong status code when giving up [\#57](https://github.com/whipper-team/whipper/issues/57) - CD-TEXT issue [\#49](https://github.com/whipper-team/whipper/issues/49) **Merged pull requests:** - Invoke whipper by its name + Readme rewrite [\#70](https://github.com/whipper-team/whipper/pull/70) ([JoeLametta](https://github.com/JoeLametta)) - do not recalculate musicbrainz disc id for every getMusicBrainzDiscId… [\#69](https://github.com/whipper-team/whipper/pull/69) ([RecursiveForest](https://github.com/RecursiveForest)) - Directory [\#62](https://github.com/whipper-team/whipper/pull/62) ([RecursiveForest](https://github.com/RecursiveForest)) - undelete overzealously removed plugin initialisation [\#61](https://github.com/whipper-team/whipper/pull/61) ([RecursiveForest](https://github.com/RecursiveForest)) - README.md: drop executable flag [\#55](https://github.com/whipper-team/whipper/pull/55) ([chrysn](https://github.com/chrysn)) - nuke-autohell [\#54](https://github.com/whipper-team/whipper/pull/54) ([RecursiveForest](https://github.com/RecursiveForest)) - standardise program/sox.py formatting, add test case, docstring [\#53](https://github.com/whipper-team/whipper/pull/53) ([RecursiveForest](https://github.com/RecursiveForest)) - replace cdrdao.py with much simpler version [\#52](https://github.com/whipper-team/whipper/pull/52) ([RecursiveForest](https://github.com/RecursiveForest)) - use setuptools, remove autohell, use raw make for src/ [\#51](https://github.com/whipper-team/whipper/pull/51) ([RecursiveForest](https://github.com/RecursiveForest)) ## [v0.3.0](https://github.com/whipper-team/whipper/tree/v0.3.0) (2016-10-17) [Full Changelog](https://github.com/whipper-team/whipper/compare/v0.2.4...v0.3.0) **Fixed bugs:** - UnicodeEncodeError [\#43](https://github.com/whipper-team/whipper/issues/43) - Use a single standard for config/cache/state files [\#24](https://github.com/whipper-team/whipper/issues/24) **Merged pull requests:** - Sox [\#48](https://github.com/whipper-team/whipper/pull/48) ([RecursiveForest](https://github.com/RecursiveForest)) - Fast accuraterip checksum [\#37](https://github.com/whipper-team/whipper/pull/37) ([MerlijnWajer](https://github.com/MerlijnWajer)) ## [v0.2.4](https://github.com/whipper-team/whipper/tree/v0.2.4) (2016-10-09) [Full Changelog](https://github.com/whipper-team/whipper/compare/v0.2.3...v0.2.4) **Implemented enhancements:** - Don't allow ripping without an explicit offset, and make pycdio a required dependency [\#23](https://github.com/whipper-team/whipper/issues/23) **Fixed bugs:** - whipper fails to build on bash-compgen [\#25](https://github.com/whipper-team/whipper/issues/25) - \[musicbrainz\] KeyError: 'disc' [\#22](https://github.com/whipper-team/whipper/issues/22) - NameError: global name 'musicbrainz' is not defined [\#16](https://github.com/whipper-team/whipper/issues/16) - Fix HTOA handling [\#14](https://github.com/whipper-team/whipper/issues/14) - rip offset find seems to fail [\#4](https://github.com/whipper-team/whipper/issues/4) - rip cd info seems to fail [\#3](https://github.com/whipper-team/whipper/issues/3) **Closed issues:** - Error selecting Drive for ripping [\#34](https://github.com/whipper-team/whipper/issues/34) - Offset not saved: could not get device info \(requires pycdio\) [\#33](https://github.com/whipper-team/whipper/issues/33) - On Arch Linux, CDDB does not know how to install morituri. [\#28](https://github.com/whipper-team/whipper/issues/28) - Minimal makedepends for building [\#17](https://github.com/whipper-team/whipper/issues/17) - Delete stale branches [\#7](https://github.com/whipper-team/whipper/issues/7) - get rid of the gstreamer-0.10 dependency [\#2](https://github.com/whipper-team/whipper/issues/2) - Merge 'fork' into 'master' [\#1](https://github.com/whipper-team/whipper/issues/1) **Merged pull requests:** - Issue24 [\#42](https://github.com/whipper-team/whipper/pull/42) ([JoeLametta](https://github.com/JoeLametta)) - Update .travis.yml [\#39](https://github.com/whipper-team/whipper/pull/39) ([JoeLametta](https://github.com/JoeLametta)) - Fix issue \#23 [\#32](https://github.com/whipper-team/whipper/pull/32) ([JoeLametta](https://github.com/JoeLametta)) - Remove thomasvs' python-deps [\#31](https://github.com/whipper-team/whipper/pull/31) ([JoeLametta](https://github.com/JoeLametta)) - Include name of used logger into whipper's txt report [\#30](https://github.com/whipper-team/whipper/pull/30) ([JoeLametta](https://github.com/JoeLametta)) - PRE\_EMPHASIS [\#27](https://github.com/whipper-team/whipper/pull/27) ([RecursiveForest](https://github.com/RecursiveForest)) - Resolve case where \_peakdB is None [\#20](https://github.com/whipper-team/whipper/pull/20) ([chadberg](https://github.com/chadberg)) - Remove old musicbrainz dependency [\#12](https://github.com/whipper-team/whipper/pull/12) ([abendebury](https://github.com/abendebury)) - Travis build fix [\#10](https://github.com/whipper-team/whipper/pull/10) ([abendebury](https://github.com/abendebury)) - Fork [\#6](https://github.com/whipper-team/whipper/pull/6) ([abendebury](https://github.com/abendebury)) ## [v0.2.3](https://github.com/whipper-team/whipper/tree/v0.2.3) (2014-07-16) [Full Changelog](https://github.com/whipper-team/whipper/compare/v0.2.2...v0.2.3) ## [v0.2.2](https://github.com/whipper-team/whipper/tree/v0.2.2) (2013-07-30) [Full Changelog](https://github.com/whipper-team/whipper/compare/v0.2.1...v0.2.2) ## [v0.2.1](https://github.com/whipper-team/whipper/tree/v0.2.1) (2013-07-15) [Full Changelog](https://github.com/whipper-team/whipper/compare/v0.2.0...v0.2.1) ## [v0.2.0](https://github.com/whipper-team/whipper/tree/v0.2.0) (2013-01-20) [Full Changelog](https://github.com/whipper-team/whipper/compare/20421488be8a82606f7ae82a16c9d8bc015b9e01...v0.2.0) \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* whipper-0.9.0/COVERAGE000066400000000000000000000145361357173224400143350ustar00rootroot00000000000000Coverage.py 4.5.4 text report against whipper v0.9.0 $ coverage run --branch --omit='whipper/test/*' --source=whipper -m unittest discover $ coverage report -m Name Stmts Miss Branch BrPart Cover Missing ----------------------------------------------------------------------------- whipper/__init__.py 15 5 4 2 63% 9-12, 16, 18, 15->16, 17->18 whipper/__main__.py 7 7 2 0 0% 4-14 whipper/command/__init__.py 0 0 0 0 100% whipper/command/accurip.py 41 41 18 0 0% 21-90 whipper/command/basecommand.py 69 29 30 8 53% 70, 72, 76, 82-88, 98-102, 107-114, 127, 129, 133, 139, 142-145, 68->70, 71->72, 75->76, 80->82, 96->98, 106->107, 126->127, 128->129 whipper/command/cd.py 227 189 60 0 13% 72-80, 85-196, 199, 212, 236-288, 295-321, 324-493 whipper/command/drive.py 57 57 10 0 0% 21-107 whipper/command/image.py 37 37 6 0 0% 21-75 whipper/command/main.py 68 68 24 0 0% 4-116 whipper/command/mblookup.py 29 3 8 2 86% 21-23, 35->37, 37->28 whipper/command/offset.py 110 110 32 0 0% 21-219 whipper/common/__init__.py 0 0 0 0 100% whipper/common/accurip.py 132 5 62 4 95% 118, 124, 133-135, 113->118, 119->124, 241->247, 251->257 whipper/common/cache.py 100 48 34 5 44% 66-90, 96, 99, 107-110, 113-114, 138-142, 165-172, 196-201, 206-222, 95->96, 98->99, 136->146, 137->138, 164->165 whipper/common/checksum.py 26 14 2 0 43% 41-42, 45-46, 49-64 whipper/common/common.py 150 28 38 6 78% 51-52, 119-120, 143-144, 162-169, 181, 274-279, 286-291, 328-332, 118->119, 131->134, 180->181, 190->197, 271->274, 326->334 whipper/common/config.py 90 8 18 4 89% 104-105, 123-124, 130, 140, 142, 144, 129->130, 139->140, 141->142, 143->144 whipper/common/directory.py 18 5 4 0 68% 42-48 whipper/common/drive.py 31 20 8 0 33% 35-40, 44-50, 54-60, 64-71 whipper/common/encode.py 44 23 2 0 46% 37-38, 41-42, 45-46, 53-56, 59-60, 63-64, 76-77, 80-81, 84-91 whipper/common/mbngs.py 174 52 66 7 70% 38-39, 45, 93-99, 174-175, 180-181, 227, 233, 258-260, 269, 289-344, 159->158, 173->174, 179->180, 226->227, 232->233, 257->258, 266->269 whipper/common/path.py 24 0 8 3 91% 42->45, 52->56, 60->65 whipper/common/program.py 345 267 117 5 19% 85-87, 93-104, 113-147, 156-161, 164, 169-173, 218, 229-230, 232-236, 253-268, 276-386, 397-455, 463-471, 475-490, 501-540, 552-569, 572-590, 593-603, 606-614, 76->79, 215->218, 228->229, 231->232, 238->242 whipper/common/renamer.py 102 2 16 1 97% 133, 156, 58->66 whipper/common/task.py 77 15 14 2 79% 47-52, 86-87, 102, 115-116, 123, 129, 135, 141, 147, 84->86, 99->102 whipper/extern/__init__.py 0 0 0 0 100% whipper/extern/asyncsub.py 112 55 58 11 46% 15-17, 32, 37-38, 47-84, 89-102, 115, 122, 134, 145, 151, 14->15, 35->37, 45->47, 110->113, 114->115, 121->122, 133->134, 139->141, 141->152, 144->145, 148->151 whipper/extern/freedb.py 90 72 42 0 17% 46, 54, 74-153, 160-199 whipper/extern/task/__init__.py 0 0 0 0 100% whipper/extern/task/task.py 270 115 56 11 53% 53, 59, 78, 86, 152-154, 173-175, 183-199, 217-220, 241-242, 283-284, 287-293, 308-309, 317-319, 328-335, 341-358, 362, 365, 372-389, 400-401, 404-407, 411, 414, 429, 432-434, 450, 462, 508-513, 520-525, 534-542, 545-553, 556-557, 565, 570-572, 52->53, 56->59, 65->67, 151->152, 165->exit, 216->217, 230->232, 235->exit, 497->499, 531->534, 569->570 whipper/image/__init__.py 0 0 0 0 100% whipper/image/cue.py 91 9 20 3 89% 98, 115-116, 131-133, 158, 186, 204, 97->98, 114->115, 130->131 whipper/image/image.py 116 93 18 0 17% 49-57, 65-67, 74-107, 121-154, 157-173, 184-214 whipper/image/table.py 394 18 120 16 93% 240, 499, 578, 663-664, 684-685, 694-697, 748, 794-795, 797-798, 842-843, 848-850, 180->183, 498->499, 532->536, 555->558, 577->578, 585->592, 683->684, 692->698, 693->694, 722->726, 726->721, 747->748, 793->794, 796->797, 841->842, 847->848 whipper/image/toc.py 203 16 60 10 90% 133, 260-261, 277-280, 338-340, 362-364, 384, 408, 438, 129->133, 211->219, 259->260, 276->277, 286->291, 322->329, 337->338, 361->362, 371->375, 403->408 whipper/program/__init__.py 0 0 0 0 100% whipper/program/arc.py 3 0 0 0 100% whipper/program/cdparanoia.py 307 179 78 2 39% 48-50, 59-60, 124-126, 198-199, 239-253, 256-306, 309-347, 350-354, 357-393, 447-499, 504-551, 585-588, 591, 598, 606-611, 123->124, 597->598 whipper/program/cdrdao.py 113 74 32 2 28% 33-58, 80-86, 90-105, 108-137, 140-144, 147-160, 167-170, 180-182, 186-188, 179->180, 185->186 whipper/program/flac.py 9 5 0 0 44% 12-19 whipper/program/sox.py 17 4 4 2 71% 18-19, 23-24, 17->18, 22->23 whipper/program/soxi.py 28 2 4 1 91% 36, 49, 48->49 whipper/program/utils.py 23 16 2 0 28% 12-17, 25-31, 42-47 whipper/result/__init__.py 0 0 0 0 100% whipper/result/logger.py 144 23 40 16 78% 68, 84-92, 112, 123, 128, 130, 134-135, 143, 202, 240, 244-245, 252-253, 67->68, 83->84, 111->112, 122->123, 127->128, 129->130, 133->134, 142->143, 201->202, 213->217, 217->222, 222->226, 226->230, 234->244, 236->240, 249->252 whipper/result/result.py 57 13 6 0 70% 115-119, 137, 148-149, 158-165 ----------------------------------------------------------------------------- TOTAL 3950 1727 1123 123 53% whipper-0.9.0/Dockerfile000066400000000000000000000041751357173224400152070ustar00rootroot00000000000000FROM debian:buster RUN apt-get update && apt-get install --no-install-recommends -y \ autoconf \ automake \ cdrdao \ bzip2 \ curl \ eject \ flac \ gir1.2-glib-2.0 \ git \ libiso9660-dev \ libsndfile1-dev \ libtool \ locales \ make \ pkgconf \ python3-dev \ python3-gi \ python3-musicbrainzngs \ python3-mutagen \ python3-pip \ python3-requests \ python3-ruamel.yaml \ python3-setuptools \ sox \ swig \ && apt-get clean && rm -rf /var/lib/apt/lists/* \ && pip3 --no-cache-dir install pycdio==2.1.0 # libcdio-paranoia / libcdio-utils are wrongfully packaged in Debian, thus built manually # see https://github.com/whipper-team/whipper/pull/237#issuecomment-367985625 RUN curl -o - 'https://ftp.gnu.org/gnu/libcdio/libcdio-2.1.0.tar.bz2' | tar jxf - \ && cd libcdio-2.1.0 \ && autoreconf -fi \ && ./configure --disable-dependency-tracking --disable-cxx --disable-example-progs --disable-static \ && make install \ && cd .. \ && rm -rf libcdio-2.1.0 # Install cd-paranoia from tarball RUN curl -o - 'https://ftp.gnu.org/gnu/libcdio/libcdio-paranoia-10.2+2.0.0.tar.bz2' | tar jxf - \ && cd libcdio-paranoia-10.2+2.0.0 \ && autoreconf -fi \ && ./configure --disable-dependency-tracking --disable-example-progs --disable-static \ && make install \ && cd .. \ && rm -rf libcdio-paranoia-10.2+2.0.0 RUN ldconfig # add user RUN useradd -m worker -G cdrom \ && mkdir -p /output /home/worker/.config/whipper \ && chown worker: /output /home/worker/.config/whipper VOLUME ["/home/worker/.config/whipper", "/output"] # setup locales + cleanup RUN echo "LC_ALL=en_US.UTF-8" >> /etc/environment \ && echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen \ && echo "LANG=en_US.UTF-8" > /etc/locale.conf \ && locale-gen en_US.UTF-8 # install whipper RUN mkdir /whipper COPY . /whipper/ RUN cd /whipper && python3 setup.py install \ && rm -rf /whipper \ && whipper -v ENV LC_ALL=en_US.UTF-8 ENV LANG=en_US ENV LANGUAGE=en_US.UTF-8 ENV PYTHONIOENCODING=utf-8 USER worker WORKDIR /output ENTRYPOINT ["whipper"] whipper-0.9.0/HACKING000066400000000000000000000052561357173224400142050ustar00rootroot00000000000000style guide ----------- Where possible when writing new code, try to stay as PEP8 compliant as possible. We understand that large portions of the codebase are not, but part of our ongoing efforts is to clean up whipper. major data structures --------------------- - image.table.Track: A list of track properties, including a list of indexes, the isrc, cdtext, pre-emphasis flag, track number, and track type (audio or data) - image.table.Index: A list of track index properties, including the track's path, relative and absolute offsets within the track & disc, respectively, as well as the track number and counter. - image.table.Table: An ordered list of Track objects, with their leadouts, cdtext, and catalog numbers. - image.cue.CueFile: Generates an image.table.Table from a .cue file from an existing rip. - image.toc.TocFile: Generates an image.table.Table from a .toc file generated by `cdrdao read-toc`. notes ----- test: single rip of kings of leon - only by the night track 1: frame start 0, 17811 CD frames, track 2: frame start 17811, 18481 CD frames ARCue.pl says 2c15499a track 11: frame start 166858, 25103 CD frames (14760564 audio frames) 191961 total CD frames unicode ------- - All text files should be read and written as unicode. - All strings that came from the outside should be converted to unicode objects. - Use asserts liberally to ensure this so we catch problems earlier. - All gst.parse_launch() pipelines should be passed as utf-8; use encode('utf-8') - whipper.extern.log.log is not unicode-safe; don't pass it unicode objects; for example, always use %r to log paths - run with RIP_DEBUG=5 once in a while to catch unicode/logging errors. - Also use unicode prefix/suffix in tempfile.* methods; to force unicode. - filesystems on Unix do not have an encoding. file names are bytes. However, most distros default to a utf-8 interpretation - You can either treat paths as byte strings all the way without interpreting (even when writing them to other files), or assume utf-8 on in and out. - also direct output to a file; redirection sets codec to ASCII and brings out unicode bugs CDROMS ------ PLEXTOR CD-R PX-W8432T Read offset of device is: 355. test discs ---------- Julie Roberts - Julie Roberts: cdparanoia paranoid mode has a false positive jitter correction and silently rips the incorrect track. ripping with -Z rips the correct track. Rush - Test for Echo: has 31 frames of silence in the first track's pregap, test for HTOA detection regressions. The Strokes - Someday (promo): has 1 frame silence marked as SILENCE The Pixies - Surfer Rosa/Come on Pilgrim: has pre-gap, and INDEX 02 on TRACK 11 Florence & The Machine - Lungs: data track whipper-0.9.0/LICENSE000066400000000000000000001045121357173224400142160ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program 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. This program 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 this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read .whipper-0.9.0/README000077700000000000000000000000001357173224400153402README.mdustar00rootroot00000000000000whipper-0.9.0/README.md000066400000000000000000000463221357173224400144740ustar00rootroot00000000000000# Whipper [![license](https://img.shields.io/github/license/whipper-team/whipper.svg)](https://github.com/whipper-team/whipper/blob/master/LICENSE) [![Build Status](https://travis-ci.com/whipper-team/whipper.svg?branch=master)](https://travis-ci.com/whipper-team/whipper) [![GitHub (pre-)release](https://img.shields.io/github/release/whipper-team/whipper/all.svg)](https://github.com/whipper-team/whipper/releases/latest) [![IRC](https://img.shields.io/badge/irc-%23whipper%40freenode-brightgreen.svg)](https://webchat.freenode.net/?channels=%23whipper) [![GitHub Stars](https://img.shields.io/github/stars/whipper-team/whipper.svg)](https://github.com/whipper-team/whipper/stargazers) [![GitHub Issues](https://img.shields.io/github/issues/whipper-team/whipper.svg)](https://github.com/whipper-team/whipper/issues) [![GitHub contributors](https://img.shields.io/github/contributors/whipper-team/whipper.svg)](https://github.com/whipper-team/whipper/graphs/contributors) Whipper is a Python 3 (3.5+) CD-DA ripper based on the [morituri project](https://github.com/thomasvs/morituri) (_CDDA ripper for *nix systems aiming for accuracy over speed_). It started just as a fork of morituri - which development seems to have halted - merging old ignored pull requests, improving it with bugfixes and new features. Nowadays whipper's codebase diverges significantly from morituri's one. Whipper is currently developed and tested _only_ on Linux distributions but _may_ work fine on other *nix OSes too. In order to track whipper's latest changes it's advised to check its commit history (README and [CHANGELOG](#changelog) files may not be comprehensive). ## Table of content - [Rationale](#rationale) - [Features](#features) - [Changelog](#changelog) - [Installation](#installation) * [Docker](#docker) * [Package](#package) - [Building](#building) 1. [Required dependencies](#required-dependencies) 2. [Fetching the source code](#fetching-the-source-code) 3. [Finalizing the build](#finalizing-the-build) - [Usage](#usage) - [Getting started](#getting-started) - [Configuration file documentation](#configuration-file-documentation) - [Running uninstalled](#running-uninstalled) - [Logger plugins](#logger-plugins) - [License](#license) - [Contributing](#contributing) - [Developer Certificate of Origin (DCO)](#developer-certificate-of-origin-dco) - [DCO Sign-Off Methods](#dco-sign-off-methods) - [Bug reports & feature requests](#bug-reports--feature-requests) - [Credits](#credits) - [Links](#links) ## Rationale For a detailed description, see morituri's wiki page: [The Art of the Rip]( https://web.archive.org/web/20160528213242/https://thomas.apestaart.org/thomas/trac/wiki/DAD/Rip). ## Features - Detects correct read offset (in samples) - Detects whether ripped media is a CD-R - Has ability to defeat cache of drives - Performs Test & Copy rips - Verifies rip accuracy using the [AccurateRip database](http://www.accuraterip.com/) - Uses [MusicBrainz](https://musicbrainz.org/doc/About) for metadata lookup - Supports reading the [pre-emphasis](http://wiki.hydrogenaud.io/index.php?title=Pre-emphasis) flag embedded into some CDs (and correctly tags the resulting rip) - _Currently whipper only reports the pre-emphasis flag value stored in the TOC_ - Detects and rips _non digitally silent_ [Hidden Track One Audio](http://wiki.hydrogenaud.io/index.php?title=HTOA) (HTOA) - Provides batch ripping capabilities - Provides templates for file and directory naming - Supports lossless encoding of ripped audio tracks (FLAC) - Allows extensibility through external logger plugins ## Changelog See [CHANGELOG.md](https://github.com/whipper-team/whipper/blob/master/CHANGELOG.md). For detailed information, please check the commit history. ## Installation Whipper still isn't available as an official package in every Linux distributions so, in order to use it, it may be necessary to [build it from its source code](#building). ### Docker You can easily install whipper without needing to care about the required dependencies by making use of the automatically built images hosted on [Docker Hub](https://hub.docker.com/r/whipperteam/whipper): `docker pull whipperteam/whipper` Alternatively, in case you prefer building Docker images locally, just issue the following command (it relies on the [Dockerfile](https://github.com/whipper-team/whipper/blob/master/Dockerfile) included in whipper's repository): `docker build -t whipperteam/whipper` It's recommended to create an alias for a convenient usage: ```bash alias whipper="docker run -ti --rm --device=/dev/cdrom \ -v ~/.config/whipper:/home/worker/.config/whipper \ -v ${PWD}/output:/output \ whipperteam/whipper" ``` You should put this e.g. into your `.bash_aliases`. Also keep in mind to substitute the path definitions to something that fits to your needs (e.g. replace `… -v ${PWD}/output:/output …` with `… -v ${HOME}/ripped:/output \ …`). Make sure you create the configuration directory: `mkdir -p ~/.config/whipper "${PWD}"/output` Finally you can test the correct installation: ``` whipper -v whipper drive list ``` ### Package This is a noncomprehensive summary which shows whipper's packaging status (unofficial repositories are probably not included): [![Packaging status](https://repology.org/badge/vertical-allrepos/whipper.svg)](https://repology.org/metapackage/whipper) There's also an [unoffical snap package on snapcraft](https://snapcraft.io/whipper). In case you decide to install whipper using an unofficial repository just keep in mind it is your responsibility to verify that the provided content is safe to use. ## Building If you are building from a source tarball or checkout, you can choose to use whipper installed or uninstalled _but first install all the required dependencies_. ### Required dependencies Whipper relies on the following packages in order to run correctly and provide all the supported features: - [cd-paranoia](https://github.com/rocky/libcdio-paranoia), for the actual ripping - To avoid bugs it's advised to use `cd-paranoia` versions ≥ **10.2+0.94+2-2** - The package named `libcdio-utils`, available on Debian and Ubuntu, is affected by a bug (except for Debian testing/sid): it doesn't include the `cd-paranoia` binary (needed by whipper). For more details see: [#888053 (Debian)](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=888053), [#889803 (Debian)](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=889803) and [#1750264 (Ubuntu)](https://bugs.launchpad.net/ubuntu/+source/libcdio/+bug/1750264). - [cdrdao](http://cdrdao.sourceforge.net/), for session, TOC, pre-gap, and ISRC extraction - [GObject Introspection](https://wiki.gnome.org/Projects/GObjectIntrospection), to provide GLib-2.0 methods used by `task.py` - [PyGObject](https://pypi.org/project/PyGObject/), required by `task.py` - [musicbrainzngs](https://pypi.org/project/musicbrainzngs/), for metadata lookup - [mutagen](https://pypi.python.org/pypi/mutagen), for tagging support - [setuptools](https://pypi.python.org/pypi/setuptools), for installation, plugins support - [requests](https://pypi.python.org/pypi/requests), for retrieving AccurateRip database entries - [pycdio](https://pypi.python.org/pypi/pycdio/), for drive identification (required for drive offset and caching behavior to be stored in the configuration file). - To avoid bugs it's advised to use the most recent `pycdio` version with the corresponding `libcdio` release or, if stuck to old pycdio versions, **0.20**/**0.21** with `libcdio` ≥ **0.90** ≤ **0.94**. All other combinations won't probably work. - [ruamel.yaml](https://pypi.org/project/ruamel.yaml/), for generating well formed YAML report logfiles - [libsndfile](http://www.mega-nerd.com/libsndfile/), for reading wav files - [flac](https://xiph.org/flac/), for reading flac files - [sox](http://sox.sourceforge.net/), for track peak detection - [git](https://git-scm.com/) or [mercurial](https://www.mercurial-scm.org/) - Required either when running whipper without installing it or when building it from its source code (code cloned from a git/mercurial repository). Some dependencies aren't available in the PyPI. They can be probably installed using your distribution's package manager: - [cd-paranoia](https://github.com/rocky/libcdio-paranoia) - [cdrdao](http://cdrdao.sourceforge.net/) - [GObject Introspection](https://wiki.gnome.org/Projects/GObjectIntrospection) - [libsndfile](http://www.mega-nerd.com/libsndfile/) - [flac](https://xiph.org/flac/) - [sox](http://sox.sourceforge.net/) - [git](https://git-scm.com/) or [mercurial](https://www.mercurial-scm.org/) PyPI installable dependencies are listed in the [requirements.txt](https://github.com/whipper-team/whipper/blob/master/requirements.txt) file and can be installed issuing the following command: `pip install -r requirements.txt` ### Fetching the source code Change to a directory where you want to put whipper source code (for example, `$HOME/dev/ext` or `$HOME/prefix/src`) ```bash git clone https://github.com/whipper-team/whipper.git cd whipper ``` ### Finalizing the build Install whipper: `python3 setup.py install` Note that, depending on the chosen installation path, this command may require elevated rights. ## Usage Whipper currently only has a command-line interface called `whipper` which is self-documenting: `whipper -h` gives you the basic instructions. Whipper implements a tree of commands: for example, the top-level `whipper` command has a number of sub-commands. Positioning of arguments is important: `whipper cd -d (device) rip` is correct, while `whipper cd rip -d (device)` is not, because the `-d` argument applies to the `cd` command. ## Getting started The simplest way to get started making accurate rips is: 1. Pick a relatively popular CD that has a good chance of being in the AccurateRip database 2. Analyze the drive's caching behavior `whipper drive analyze` 3. Find the drive's offset. Consult the [AccurateRip's CD Drive Offset database](http://www.accuraterip.com/driveoffsets.htm) for your drive. Drive information can be retrieved with `whipper drive list`. `whipper offset find -o insert-numeric-value-here` If you omit the `-o` argument, whipper will try a long, popularity-sorted list of drive offsets. If you can not confirm your drive offset value but wish to set a default regardless, set `read_offset = insert-numeric-value-here` in `whipper.conf`. Offsets confirmed with `whipper offset find` are automatically written to the configuration file. If specifying the offset manually, please note that: if positive it must be written as a number without sign (ex: `+102` -> `102`), if negative it must include the sign too (ex: `-102` -> `-102`). 4. Rip the disc by running `whipper cd rip` ## Configuration file documentation The configuration file is stored in `$XDG_CONFIG_HOME/whipper/whipper.conf`, or `$HOME/.config/whipper/whipper.conf` if `$XDG_CONFIG_HOME` is undefined. See [XDG Base Directory Specification](http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html) and [ConfigParser](https://docs.python.org/3/library/configparser.html). The configuration file consists of newline-delineated `[sections]` containing `key = value` pairs. The sections `[main]` and `[musicbrainz]` are special config sections for options not accessible from the command line interface. Sections beginning with `drive` are written by whipper; certain values should not be edited. Example configuration demonstrating all `[main]` and `[musicbrainz]` options: ```INI [main] path_filter_fat = True ; replace FAT file system unsafe characters in filenames with _ path_filter_special = False ; replace special characters in filenames with _ [musicbrainz] server = musicbrainz.org:80 ; use MusicBrainz server at host[:port] [drive:HL-20] defeats_cache = True ; whether the drive is capable of defeating the audio cache read_offset = 6 ; drive read offset in positive/negative frames (no leading +) # do not edit the values 'vendor', 'model', and 'release'; they are used by whipper to match the drive # command line defaults for `whipper cd rip` [whipper.cd.rip] unknown = True output_directory = ~/My Music track_template = new/%%A/%%y - %%d/%%t - %%n ; note: the format char '%' must be represented '%%' disc_template = new/%%A/%%y - %%d/%%A - %%d # ... ``` ## Running uninstalled To make it easier for developers, you can run whipper straight from the source checkout: ```bash python3 -m whipper -h ``` ## Logger plugins Whipper allows using external logger plugins to customize the template of `.log` files. The available plugins can be listed with `whipper cd rip -h`. Specify a logger to rip with by passing `-L loggername`: ```bash whipper cd rip -L eac ``` Whipper searches for logger plugins in the following paths: - `$XDG_DATA_HOME/whipper/plugins` - Paths returned by the following Python instruction: `[x + '/whipper/plugins' for x in site.getsitepackages()]` - If whipper is run in a `virtualenv`, it will use these alternative instructions (from `distutils.sysconfig`): - `get_python_lib(plat_specific=False, standard_lib=False, prefix='/usr/local') + '/whipper/plugins'` - `get_python_lib(plat_specific=False, standard_lib=False) + '/whipper/plugins'` On a default Debian/Ubuntu installation, the following paths are searched by whipper: - `$HOME/.local/share/whipper/plugins` - `/usr/local/lib/python3.X/dist-packages/whipper/plugins` - `/usr/lib/python3.X/dist-packages/whipper/plugins` Where `X` stands for the minor version of the Python 3 release available on the system. ### Official logger plugins I suggest using whipper's default logger unless you've got particular requirements. - [whipper-plugin-eaclogger](https://github.com/whipper-team/whipper-plugin-eaclogger) - a plugin for whipper which provides EAC style log reports ## License Licensed under the [GNU GPLv3 license](http://www.gnu.org/licenses/gpl-3.0). ```Text Copyright (C) 2009 Thomas Vander Stichele Copyright (C) 2016-2019 The Whipper Team: JoeLametta, Samantha Baldwin, Merlijn Wajer, Frederik “Freso” S. Olesen, et al. This program 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. This program 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 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ``` ## Contributing Make sure you have the latest copy from our [git repository](https://github.com/whipper-team/whipper). Where possible, please include tests for new or changed functionality. You can run tests with `python3 -m unittest discover` from your source checkout. ### Developer Certificate of Origin (DCO) To make a good faith effort to ensure licensing criteria are met, this project requires the Developer Certificate of Origin (DCO) process to be followed. The Developer Certificate of Origin (DCO) is a document that certifies you own and/or have the right to contribute the work and license it appropriately. The DCO is used instead of a _much more annoying_ [CLA (Contributor License Agreement)](https://en.wikipedia.org/wiki/Contributor_License_Agreement). With the DCO, you retain copyright of your own work :). The DCO originated in the Linux community, and is used by other projects like Git and Docker. The DCO agreement is shown below and it's also available online: [HERE](https://developercertificate.org/). ``` Developer Certificate of Origin Version 1.1 Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 1 Letterman Drive Suite D4700 San Francisco, CA, 94129 Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Developer's Certificate of Origin 1.1 By making a contribution to this project, I certify that: (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. ``` #### DCO Sign-Off Methods The DCO requires a sign-off message in the following format appear on each commit in the pull request: ``` Signed-off-by: Full Name ``` The DCO text can either be manually added to your commit body, or you can add either `-s` or `--signoff` to your usual Git commit commands. If you forget to add the sign-off you can also amend a previous commit with the sign-off by running `git commit --amend -s`. ### Bug reports & feature requests Please use the [issue tracker](https://github.com/whipper-team/whipper/issues) to report any bugs or to file feature requests. When filing bug reports, please run the failing command with the environment variable `WHIPPER_DEBUG` set. For example: ```bash WHIPPER_DEBUG=DEBUG WHIPPER_LOGFILE=whipper.log whipper offset find gzip whipper.log ``` And attach the gzipped log file to your bug report. Without `WHIPPER_LOGFILE` set, logging messages will go to stderr. `WHIPPER_DEBUG` accepts a string of the [default python logging levels](https://docs.python.org/3/library/logging.html#logging-levels). ## Credits Thanks to: - [Thomas Vander Stichele](https://github.com/thomasvs) - [Joe Lametta](https://github.com/JoeLametta) - [Samantha Baldwin](https://github.com/RecursiveForest) - [Frederik “Freso” S. Olesen](https://github.com/Freso) - [Merlijn Wajer](https://github.com/MerlijnWajer) And to all the [contributors](https://github.com/whipper-team/whipper/graphs/contributors). ## Links You can find us and talk about the project on: - IRC: [freenode](https://webchat.freenode.net/?channels=%23whipper), **#whipper** channel - Matrix (the room is a bridge to freenode IRC) - Access Matrix through the [Riot.im web client](https://riot.im/app/#/room/!wxdgcGzudITUpZMCrn:matrix.org) - Join to the room named `#freenode_#whipper:matrix.org` - [Redacted thread (official)](https://redacted.ch/forums.php?action=viewthread&threadid=150) Other relevant links: - [Whipper - Hydrogenaudio Knowledgebase](https://wiki.hydrogenaud.io/index.php?title=Whipper) - [Repology: versions for whipper](https://repology.org/metapackage/whipper/versions) - [Unattended ripping using whipper (by Thomas McWork)](https://github.com/thomas-mc-work/most-possible-unattended-rip) whipper-0.9.0/TODO000066400000000000000000000107171357173224400137040ustar00rootroot00000000000000TODO: Please see https://github.com/whipper-team/whipper/milestones for further TODO items; this file exists only to have contents individually removed eventually, not to be continually updated. EASY - self.error() invokes exit() which is bad for a gui - change format to be %2d - %performer by default FIXME: why was this again? - at least mention the data track somewhere in the log - handle errors on cdrdao spawning (for example, not having cdrecorder, or not putting the disk in) - add rip offset verify, to verify that the current offset looks coorect - handle not having wav plugin: http://sprunge.us/DYjd DEBUG [13888] "ChecksumTask 0xb5e8b290" ChecksumTask Jul 25 19:09:47 bus_error_cb: bus , message (morituri/extern/task/gstreamer.py:211) DEBUG [13888] "ChecksumTask 0xb5e8b290" ChecksumTask Jul 25 19:09:47 set exception, 'exception GstException at /home/merlijn/archive/morituri/morituri/extern/task/gstreamer.py:217: bus_error_cb(): (, "/var/tmp/portage/media-libs/gst-plugins-base-0.10.36-r1/work/gst-plugins-base-0.10.36/gst/playback/gstdecodebin.c(1003): close_pad_link (): /GstPipeline:pipeline0/GstDecodeBin:decode:\\nNo decoder to handle media type \'audio/x-wav\'")' (morituri/extern/task/task.py:197) MEDIUM - after fixing relative, pregaps, and index 02, check when htoa is 0, and add a setSilence to table to set a counter 0 with no path, and test that the cue file puts a SILENCE/PREGAP - store drive features in a database - try http://www.ime.usp.br/~pjssilva/secure-cdparanoia.py and see if it is better at handling some bad cd's - add AccurateRip validation for ripped images to rip command - consider basing ripping progress not only on read (reaches 100% before writes are done) or writes (very bursty in cdparanoia) but a combo of the two, each counting for half. - rip task should abort on task 4 if checksums don't match - retry cdrdao a few times when it had to load the tray - getting cache results should depend on same drive/offset - do some character mangling so trail of dead is not in a hidden dir HARD - rip the data session - add GUI - write xbmc/plex plugin SPECIFIC RELEASES ISSUES - on ana, Goldfrapp tells me I have offset 0! - discs we should rip: LCD soundsystem disc 2 (data track) - Zita Swoon anthology cd 1 shows track 8 rip NOT accurate, but checksums match NO DECISION YET - possibly figure out how to name releases with credited artist; look at gorky and spiritualized electric mainline - check if cdda2wav or icedax analyze pregaps correctly OLD - check pregaps more than once, to see if results are consistent, or with different methods - check if it's simple to listen to each track in a multitrack completing - save trms to a pickle, after finishing each track - cache results of MusicBrainz lookups - don't keep short HTOA's if their peak level is low (see Pixies Planet of Sound single) - if disk not found in accuraterip, it doesn't mean that it's not accurate - burn ripped images - use a temp dir, until the whole rip is good don't move it, so we easily find half done rips Compare https://musicbrainz.org/cdtoc/MAj3xXf6QMy7G.BIFOyHyq4MySE- with https://musicbrainz.org/cdtoc/USC1utCZbTLZy80aHvQzJw4FASk- Almost same, but second is 2 seconds longer on last track, suggesting it was calculated wrong (150 frame offset done wrong ?) Can't find it in edit history though Write an example document with this cd as an example explaining offsets and id calculations - when shortening file name then reripping, it rerips since it doesn't know the shortened name; see sufjan stevens - if a disc output dir is already there, see if it was properly finished; complain if it was, to not overwrite - if multiple releases with different artist match the disc id, stop and let user continue by choosing one - artist-credit-phrase fabricated by musicbrainzngs only looks at name, not at artist-credit->name (see e.g. Gorky) - fix %r for normal case release name - decide whether output-dir should be part of the relative filenames of things; right now it is; maybe split in to base and output ? whipper-0.9.0/com.github.whipper_team.Whipper.metainfo.xml000066400000000000000000000020671357173224400236750ustar00rootroot00000000000000 com.github.whipper_team.Whipper CC0-1.0 whipper GPL-3.0-or-later The Whipper Team A CD-DA ripper prioritising accuracy over speed

whipper is a command-line CD-DA ripper that focuses on making accurate rips over fast ones.

https://github.com/whipper-team/whipper https://github.com/whipper-team/whipper/issues https://github.com/whipper-team/whipper/blob/master/README.md AudioVideo Audio Music ConsoleOnly whipper whipper
whipper-0.9.0/misc/000077500000000000000000000000001357173224400141415ustar00rootroot00000000000000whipper-0.9.0/misc/header.py000066400000000000000000000014271357173224400157470ustar00rootroot00000000000000# -*- Mode: Python; test-case-name: whipper.test.test_header -*- # vi:si:et:sw=4:sts=4:ts=4 # 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 . whipper-0.9.0/misc/offsets.py000066400000000000000000000027071357173224400161720ustar00rootroot00000000000000# -*- Mode: Python -*- # vi:si:et:sw=4:sts=4:ts=4 # show all possible offsets, in order of popularity, from a download of # http://www.accuraterip.com/driveoffsets.htm import sys from bs4 import BeautifulSoup with open(sys.argv[1]) as f: doc = f.read() soup = BeautifulSoup(doc, features='html.parser') offsets = {} # offset -> total count # skip first two spurious elements rows = soup.findAll('tr')[2:] for row in rows: columns = row.findAll('td') if len(columns) == 4: first, second, third, fourth = columns name = first.find(text=True) offset = second.find(text=True) count = third.find(text=True) # only use numeric offsets try: int(offset) except ValueError: continue if offset not in offsets.keys(): offsets[offset] = 0 offsets[offset] += int(count) # now sort offsets by count counts = [] for offset, count in offsets.items(): counts.append((count, offset)) counts.sort() counts.reverse() offsets = [] for count, offset in counts: offsets.append(offset) # now format it for code inclusion lines = [] line = 'OFFSETS = ("' for offset in offsets: line += offset + ', ' if len(line) > 60: line += '"' lines.append(line) line = ' "' # get last line too, trimming the comma and adding the quote if len(line) > 11: line = line[:-2] + '")' lines.append(line) print('\n'.join(lines)) whipper-0.9.0/requirements.txt000066400000000000000000000001211357173224400164640ustar00rootroot00000000000000musicbrainzngs mutagen pycdio>0.20 PyGObject requests ruamel.yaml setuptools_scm whipper-0.9.0/scripts/000077500000000000000000000000001357173224400146755ustar00rootroot00000000000000whipper-0.9.0/scripts/accuraterip-checksum000066400000000000000000000015411357173224400207230ustar00rootroot00000000000000#!/usr/bin/env python # SPDX-License-Identifier: GPL-3.0-only import accuraterip import sys if len(sys.argv) == 2 and sys.argv[1] == '--version': print('accuraterip-checksum version 2.0') exit(0) use_v1 = None if len(sys.argv) == 4: offset = 0 use_v1 = False elif len(sys.argv) == 5: offset = 1 if sys.argv[1] == '--accuraterip-v1': use_v1 = True elif sys.argv[1] == '--accuraterip-v2': use_v1 = False if use_v1 is None: print('Syntax: accuraterip-checksum [--version / --accuraterip-v1 / --accuraterip-v2 (default)] filename track_number total_tracks') exit(1) filename = sys.argv[offset + 1] track_number = int(sys.argv[offset + 2]) total_tracks = int(sys.argv[offset + 3]) v1, v2 = accuraterip.compute(filename, track_number, total_tracks) if use_v1: print('%08X' % v1) else: print('%08X' % v2) whipper-0.9.0/setup.py000066400000000000000000000015611357173224400147230ustar00rootroot00000000000000from setuptools import setup, find_packages, Extension setup( name="whipper", use_scm_version=True, description="a secure cd ripper preferring accuracy over speed", author=['Thomas Vander Stichele', 'The Whipper Team'], maintainer=['The Whipper Team'], url='https://github.com/whipper-team/whipper', license='GPL3', python_requires='>=3.5', 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' ] }, data_files=[ ('share/metainfo', ['com.github.whipper_team.Whipper.metainfo.xml']), ], scripts=[ 'scripts/accuraterip-checksum', ], ) whipper-0.9.0/src/000077500000000000000000000000001357173224400137755ustar00rootroot00000000000000whipper-0.9.0/src/README.md000066400000000000000000000033331357173224400152560ustar00rootroot00000000000000# accuraterip-checksum ## Description A C99 command line program to compute the [AccurateRip](http://accuraterip.com/) checksum of single track WAV files, i.e. WAV files which contain only a single track of an audio CD. Such files can for example be generated by [Exact Audio Copy](http://exactaudiocopy.de/) and various other CD ripping programs, as listed e.g. [here](http://accuraterip.com/software.htm) and [here](https://wiki.hydrogenaud.io/index.php?title=AccurateRip). Implemented according to [this thread on HydrogenAudio](http://www.hydrogenaudio.org/forums/index.php?showtopic=97603). ## Usage Calculate AccurateRip v2 checksum of track number ```TRACK``` which is contained in WAV file ```TRACK_FILE```, and which was ripped from a disc with a total track count of ```TOTAL_TRACKS```: accuraterip-checksum TRACK_FILE TRACK TOTAL_TRACKS Explicitly choose AccurateRip checksum version, where ```VERSION``` is 1 or 2: accuraterip-checksum --accuraterip-vVERSION TRACK_FILE TRACK TOTAL_TRACKS Show accuraterip-checksum program version (this is **not** the AccurateRip checksum version!): accuraterip-checksum --version The version of accuraterip-checksum should be added to the tags of audio files which were processed 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. ## Dependencies libsndfile is used for reading the WAV files. Therefore, on Ubuntu, make sure you have the following packages installed: libsndfile1 For compiling you need: libsndfile1-dev ## Author Leo Bogert (http://leo.bogert.de) ## Version 1.5 ## Donations bitcoin:14kPd2QWsri3y2irVFX6wC33vv7FqTaEBh ## License GPLv3 whipper-0.9.0/src/accuraterip-checksum.c000066400000000000000000000102341357173224400202430ustar00rootroot00000000000000/* ============================================================================ Name : accuraterip-checksum.c 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 ============================================================================ */ #include #include #include #include #include #include #include #include static 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 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 false; } 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, data, sfinfo->frames) != sfinfo->frames) { free(data); return NULL; } return data; } 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; 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)); 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++; } *v1 = csum_lo; *v2 = csum_lo + csum_hi; } 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; if (!PyArg_ParseTuple(args, "sII", &filename, &track_number, &total_tracks)) goto err; if (track_number < 1 || track_number > total_tracks) { fprintf(stderr, "Invalid track_number!\n"); goto err; } if (total_tracks < 1 || total_tracks > 99) { fprintf(stderr, "Invalid total_tracks!\n"); goto err; } #ifdef DEBUG printf("Reading %s\n", filename); #endif 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)); goto err; } if (!check_fileformat(&sfinfo)) { fprintf(stderr, "check_fileformat failed!\n"); goto err; } 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"); goto err; } compute_checksums(audio_data, size, track_number, total_tracks, &v1, &v2); free(audio_data); sf_close(sndfile); 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 }, }; static struct PyModuleDef accuraterip_module = { .m_base = PyModuleDef_HEAD_INIT, .m_name = "accuraterip", .m_methods = accuraterip_methods, }; PyMODINIT_FUNC PyInit_accuraterip(void) { return PyModule_Create(&accuraterip_module); } whipper-0.9.0/whipper/000077500000000000000000000000001357173224400146645ustar00rootroot00000000000000whipper-0.9.0/whipper/__init__.py000066400000000000000000000013321357173224400167740ustar00rootroot00000000000000import logging import os import sys from pkg_resources import (get_distribution, DistributionNotFound, RequirementParseError) try: __version__ = get_distribution(__name__).version except (DistributionNotFound, RequirementParseError): # not installed as package or is being run from source/git checkout from setuptools_scm import get_version __version__ = get_version() level = logging.INFO if 'WHIPPER_DEBUG' in os.environ: level = os.environ['WHIPPER_DEBUG'].upper() if 'WHIPPER_LOGFILE' in os.environ: logging.basicConfig(filename=os.environ['WHIPPER_LOGFILE'], filemode='w', level=level) else: logging.basicConfig(stream=sys.stderr, level=level) whipper-0.9.0/whipper/__main__.py000066400000000000000000000005571357173224400167650ustar00rootroot00000000000000# -*- Mode: Python -*- # vi:si:et:sw=4:sts=4:ts=4 import os import sys from whipper.command.main import main if __name__ == '__main__': # Make accuraterip_checksum be found automatically if it was built local_arb = os.path.join(os.path.dirname(__file__), '..', 'src') os.environ['PATH'] = ':'.join([os.getenv('PATH'), local_arb]) sys.exit(main()) whipper-0.9.0/whipper/command/000077500000000000000000000000001357173224400163025ustar00rootroot00000000000000whipper-0.9.0/whipper/command/__init__.py000066400000000000000000000000001357173224400204010ustar00rootroot00000000000000whipper-0.9.0/whipper/command/accurip.py000066400000000000000000000061531357173224400203070ustar00rootroot00000000000000# -*- Mode: Python -*- # vi:si:et:sw=4:sts=4:ts=4 # 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 . from whipper.command.basecommand import BaseCommand from whipper.common.accurip import get_db_entry, ACCURATERIP_URL import logging logger = logging.getLogger(__name__) class Show(BaseCommand): summary = "show accuraterip data" description = """ retrieves and display accuraterip data from the given URL """ def add_arguments(self): self.parser.add_argument('url', action='store', help="accuraterip URL to load data from") def do(self): responses = get_db_entry(self.options.url.lstrip(ACCURATERIP_URL)) count = responses[0].num_tracks logger.info("found %d responses for %d tracks", len(responses), count) for (i, r) in enumerate(responses): if r.num_tracks != count: logger.warning("response %d has %d tracks instead of %d", i, r.num_tracks, count) # checksum and confidence by track for track in range(count): print("Track %d:" % (track + 1)) checksums = {} for (i, r) in enumerate(responses): if r.num_tracks != count: continue assert len(r.checksums) == r.num_tracks assert len(r.confidences) == r.num_tracks entry = {"confidence": r.confidences[track], "response": i + 1} checksum = r.checksums[track] if checksum in checksums: checksums[checksum].append(entry) else: checksums[checksum] = [entry, ] # now sort track results in checksum by highest confidence sortedChecksums = [] for checksum, entries in list(checksums.items()): highest = max(d['confidence'] for d in entries) sortedChecksums.append((highest, checksum)) sortedChecksums.sort() sortedChecksums.reverse() for highest, checksum in sortedChecksums: print(" %d result(s) for checksum %s: %s" % ( len(checksums[checksum]), checksum, checksums[checksum])) class AccuRip(BaseCommand): summary = "handle AccurateRip information" description = """ Handle AccurateRip information. Retrieves AccurateRip disc entries and displays diagnostic information. """ subcommands = { 'show': Show } whipper-0.9.0/whipper/command/basecommand.py000066400000000000000000000121541357173224400211300ustar00rootroot00000000000000# -*- Mode: Python -*- # vi:si:et:sw=4:sts=4:ts=4 import argparse import os import sys from whipper.common import config, drive import logging logger = logging.getLogger(__name__) # Q: What about argparse.add_subparsers(), you ask? # A: Unfortunately add_subparsers() does not support specifying the # formatter_class of subparsers, nor does it support epilogs, so # it does not quite fit our use case. # Q: Why not subclass ArgumentParser and extend/replace the relevant # methods? # A: If this can be done in a simpler fashion than this current # implementation, by all means submit a patch. # Q: Why not argparse.parse_known_args()? # A: The prefix matching prevents passing '-h' (and possibly other # options) to the child command. class BaseCommand: """ Register and handle whipper command arguments with ArgumentParser. Register arguments by overriding `add_arguments()` and modifying `self.parser`. Option defaults are read from the dot-separated `prog_name` section of the config file (e.g., 'whipper cd rip' options are read from '[whipper.cd.rip]'). Runs `argparse.parse_args()` then calls `handle_arguments()`. Provides self.epilog() formatting command for argparse. device_option = True adds -d / --device option to current command no_add_help = True removes -h / --help option from current command Overriding formatter_class sets the argparse formatter class. If the 'subcommands' dictionary is set, __init__ searches the arguments for subcommands.keys() and instantiates the class implementing the subcommand as self.cmd, passing all non-understood arguments, the current options namespace, and the full command path name. """ device_option = False no_add_help = False # for rip.main.Whipper formatter_class = argparse.RawDescriptionHelpFormatter def __init__(self, argv, prog_name, opts): self.opts = opts # for Rip.add_arguments() self.prog_name = prog_name self.init_parser() self.add_arguments() config_section = prog_name.replace(' ', '.') defaults = {} for action in self.parser._actions: val = None if isinstance(action, argparse._StoreAction): val = config.Config().get(config_section, action.dest) elif isinstance(action, (argparse._StoreTrueAction, argparse._StoreFalseAction)): val = config.Config().getboolean(config_section, action.dest) if val is not None: defaults[action.dest] = val self.parser.set_defaults(**defaults) if hasattr(self, 'subcommands'): self.parser.add_argument('remainder', nargs=argparse.REMAINDER, help=argparse.SUPPRESS) if self.device_option: # pick the first drive as default drives = drive.getAllDevicePaths() if not drives: msg = 'No CD-DA drives found!' logger.critical(msg) # whipper exited with return code 3 here raise IOError(msg) self.parser.add_argument('-d', '--device', action="store", dest="device", default=drives[0], help="CD-DA device") self.options = self.parser.parse_args(argv, namespace=opts) if self.device_option: # this can be a symlink to another device self.options.device = os.path.realpath(self.options.device) if not os.path.exists(self.options.device): msg = 'CD-DA device %s not found!' % self.options.device logger.critical(msg) raise IOError(msg) self.handle_arguments() if hasattr(self, 'subcommands'): if not self.options.remainder: self.parser.print_help() sys.exit(0) if not self.options.remainder[0] in self.subcommands: logger.critical("incorrect subcommand: %s", self.options.remainder[0]) sys.exit(1) self.cmd = self.subcommands[self.options.remainder[0]]( self.options.remainder[1:], prog_name + " " + self.options.remainder[0], self.options ) def init_parser(self): kw = { 'prog': self.prog_name, 'description': self.description, 'formatter_class': self.formatter_class, } if hasattr(self, 'subcommands'): kw['epilog'] = self.epilog() if self.no_add_help: kw['add_help'] = False self.parser = argparse.ArgumentParser(**kw) def add_arguments(self): pass def handle_arguments(self): pass def do(self): return self.cmd.do() def epilog(self): s = "commands:\n" for com in sorted(self.subcommands.keys()): s += " %s %s\n" % (com.ljust(8), self.subcommands[com].summary) return s whipper-0.9.0/whipper/command/cd.py000066400000000000000000000516211357173224400172470ustar00rootroot00000000000000# -*- Mode: Python -*- # vi:si:et:sw=4:sts=4:ts=4 # 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 argparse import cdio import os import glob import logging from whipper.command.basecommand import BaseCommand from whipper.common import ( accurip, config, drive, program, task ) from whipper.common.common import validate_template from whipper.program import cdrdao, cdparanoia, utils from whipper.result import result logger = logging.getLogger(__name__) SILENT = 0 MAX_TRIES = 5 DEFAULT_TRACK_TEMPLATE = '%r/%A - %d/%t. %a - %n' DEFAULT_DISC_TEMPLATE = '%r/%A - %d/%A - %d' TEMPLATE_DESCRIPTION = ''' Tracks are named according to the track template, filling in the variables and adding the file extension. Variables exclusive to the track template are: - %t: track number - %a: track artist - %n: track title - %s: track sort name Disc files (.cue, .log, .m3u) are named according to the disc template, filling in the variables and adding the file extension. Variables for both disc and track template are: - %A: release artist - %S: release sort name - %d: disc title - %y: release year - %r: release type, lowercase - %R: release type, normal case - %x: audio extension, lowercase - %X: audio extension, uppercase ''' class _CD(BaseCommand): eject = True # XXX: Pylint, parameters differ from overridden 'add_arguments' method @staticmethod def add_arguments(parser): parser.add_argument('-R', '--release-id', action="store", dest="release_id", help="MusicBrainz release id to match to " "(if there are multiple)") parser.add_argument('-p', '--prompt', action="store_true", dest="prompt", help="Prompt if there are multiple " "matching releases") parser.add_argument('-c', '--country', action="store", dest="country", help="Filter releases by country") def do(self): self.config = config.Config() self.program = program.Program(self.config, record=self.options.record) self.runner = task.SyncRunner() # if the device is mounted (data session), unmount it self.device = self.options.device logger.info('checking device %s', self.device) utils.load_device(self.device) utils.unmount_device(self.device) # first, read the normal TOC, which is fast self.ittoc = self.program.getFastToc(self.runner, self.device) # already show us some info based on this self.program.getRipResult(self.ittoc.getCDDBDiscId()) print("CDDB disc id: %s" % self.ittoc.getCDDBDiscId()) self.mbdiscid = self.ittoc.getMusicBrainzDiscId() print("MusicBrainz disc id %s" % self.mbdiscid) print("MusicBrainz lookup URL %s" % self.ittoc.getMusicBrainzSubmitURL()) self.program.metadata = ( self.program.getMusicBrainz(self.ittoc, self.mbdiscid, release=self.options.release_id, country=self.options.country, prompt=self.options.prompt) ) if not self.program.metadata: # fall back to FreeDB for lookup cddbid = self.ittoc.getCDDBValues() cddbmd = self.program.getCDDB(cddbid) if cddbmd: logger.info('FreeDB identifies disc as %s', cddbmd) # also used by rip cd info if not getattr(self.options, 'unknown', False): logger.critical("unable to retrieve disc metadata, " "--unknown argument not passed") return -1 self.program.result.isCdr = cdrdao.DetectCdr(self.device) if (self.program.result.isCdr and not getattr(self.options, 'cdr', False)): logger.critical("inserted disc seems to be a CD-R, " "--cdr not passed") return -1 # Change working directory before cdrdao's task if getattr(self.options, 'working_directory', False): os.chdir(os.path.expanduser(self.options.working_directory)) if hasattr(self.options, 'output_directory'): out_bpath = self.options.output_directory # Needed to preserve cdrdao's tocfile out_fpath = self.program.getPath(out_bpath, self.options.disc_template, self.mbdiscid, self.program.metadata) else: out_fpath = None # now, read the complete index table, which is slower offset = getattr(self.options, 'offset', 0) self.itable = self.program.getTable(self.runner, self.ittoc.getCDDBDiscId(), self.ittoc.getMusicBrainzDiscId(), self.device, offset, out_fpath) assert self.itable.getCDDBDiscId() == self.ittoc.getCDDBDiscId(), \ "full table's id %s differs from toc id %s" % ( self.itable.getCDDBDiscId(), self.ittoc.getCDDBDiscId()) assert self.itable.getMusicBrainzDiscId() == \ self.ittoc.getMusicBrainzDiscId(), \ "full table's mb id %s differs from toc id mb %s" % ( self.itable.getMusicBrainzDiscId(), self.ittoc.getMusicBrainzDiscId()) if self.program.metadata: self.program.metadata.discid = self.ittoc.getMusicBrainzDiscId() # result self.program.result.cdrdaoVersion = cdrdao.version() self.program.result.cdparanoiaVersion = \ cdparanoia.getCdParanoiaVersion() info = drive.getDeviceInfo(self.device) if info: try: self.program.result.cdparanoiaDefeatsCache = \ self.config.getDefeatsCache(*info) except KeyError as e: logger.debug('got key error: %r', (e, )) self.program.result.artist = self.program.metadata \ and self.program.metadata.artist \ or 'Unknown Artist' self.program.result.title = self.program.metadata \ and self.program.metadata.title \ or 'Unknown Title' _, self.program.result.vendor, self.program.result.model, \ self.program.result.release = \ cdio.Device(self.device).get_hwinfo() self.program.result.metadata = self.program.metadata self.doCommand() if (self.options.eject == 'success' and self.eject or self.options.eject == 'always'): utils.eject_device(self.device) return None def doCommand(self): pass class Info(_CD): summary = "retrieve information about the currently inserted CD" description = ("Display MusicBrainz, CDDB/FreeDB, and AccurateRip " "information for the currently inserted CD.") eject = False # Requires opts.device # XXX: Pylint, parameters differ from overridden 'add_arguments' method def add_arguments(self): _CD.add_arguments(self.parser) class Rip(_CD): summary = "rip CD" # see whipper.common.program.Program.getPath for expansion description = """ Rips a CD. %s Paths to track files referenced in .cue and .m3u files will be made relative to the directory of the disc files. All files will be created relative to the given output directory. Log files will log the path to tracks relative to this directory. """ % TEMPLATE_DESCRIPTION formatter_class = argparse.RawTextHelpFormatter # Requires opts.record # Requires opts.device # XXX: Pylint, parameters differ from overridden 'add_arguments' method def add_arguments(self): loggers = list(result.getLoggers()) default_offset = None info = drive.getDeviceInfo(self.opts.device) if info: try: default_offset = config.Config().getReadOffset(*info) logger.info("using configured read offset %d", default_offset) except KeyError: pass _CD.add_arguments(self.parser) self.parser.add_argument('-L', '--logger', action="store", dest="logger", default='whipper', help=("logger to use (choose from: '%s" % "', '".join(loggers) + "')")) self.parser.add_argument('-o', '--offset', action="store", dest="offset", default=default_offset, help="sample read offset") self.parser.add_argument('-x', '--force-overread', action="store_true", dest="overread", default=False, help="Force overreading into the " "lead-out portion of the disc. Works only " "if the patched cdparanoia package is " "installed and the drive " "supports this feature. ") self.parser.add_argument('-O', '--output-directory', action="store", dest="output_directory", default=os.path.relpath(os.getcwd()), help="output directory; will be included " "in file paths in log") self.parser.add_argument('-W', '--working-directory', action="store", dest="working_directory", help="working directory; whipper will " "change to this directory " "and files will be created relative to " "it when not absolute") self.parser.add_argument('--track-template', action="store", dest="track_template", default=DEFAULT_TRACK_TEMPLATE, help="template for track file naming") self.parser.add_argument('--disc-template', action="store", dest="disc_template", default=DEFAULT_DISC_TEMPLATE, help="template for disc file naming") self.parser.add_argument('-U', '--unknown', action="store_true", dest="unknown", help="whether to continue ripping if " "the CD is unknown", default=False) self.parser.add_argument('--cdr', action="store_true", dest="cdr", help="whether to continue ripping if " "the disc is a CD-R", default=False) def handle_arguments(self): self.options.output_directory = os.path.expanduser( self.options.output_directory) self.options.track_template = self.options.track_template validate_template(self.options.track_template, 'track') self.options.disc_template = self.options.disc_template validate_template(self.options.disc_template, 'disc') if self.options.offset is None: raise ValueError("Drive offset is unconfigured.\n" "Please install pycdio and run 'whipper offset " "find' to detect your drive's offset or set it " "manually in the configuration file. It can " "also be specified at runtime using the " "'--offset=value' argument") if self.options.working_directory is not None: self.options.working_directory = os.path.expanduser( self.options.working_directory) if self.options.logger: try: self.logger = result.getLoggers()[self.options.logger]() except KeyError: msg = "No logger named %s found!" % self.options.logger logger.critical(msg) raise ValueError(msg) def doCommand(self): self.program.setWorkingDirectory(self.options.working_directory) self.program.outdir = self.options.output_directory self.program.result.offset = int(self.options.offset) self.program.result.overread = self.options.overread self.program.result.logger = self.options.logger discName = self.program.getPath(self.program.outdir, self.options.disc_template, self.mbdiscid, self.program.metadata) dirname = os.path.dirname(discName) if os.path.exists(dirname): logs = glob.glob(os.path.join(dirname, '*.log')) if logs: msg = ("output directory %s is a finished rip" % dirname) logger.debug(msg) raise RuntimeError(msg) else: logger.info("creating output directory %s", dirname) os.makedirs(dirname) # FIXME: turn this into a method def _ripIfNotRipped(number): logger.debug('ripIfNotRipped for track %d', number) # we can have a previous result trackResult = self.program.result.getTrackResult(number) if not trackResult: trackResult = result.TrackResult() self.program.result.tracks.append(trackResult) else: logger.debug('ripIfNotRipped have trackresult, path %r', trackResult.filename) path = self.program.getPath(self.program.outdir, self.options.track_template, self.mbdiscid, self.program.metadata, track_number=number) + '.flac' logger.debug('ripIfNotRipped: path %r', path) trackResult.number = number assert isinstance(path, str), "%r is not str" % path trackResult.filename = path if number > 0: trackResult.pregap = self.itable.tracks[number - 1].getPregap() trackResult.pre_emphasis = ( self.itable.tracks[number - 1].pre_emphasis ) # FIXME: optionally allow overriding reripping if os.path.exists(path): if path != trackResult.filename: # the path is different (different name/template ?) # but we can copy it logger.debug('previous result %r, expected %r', trackResult.filename, path) logger.info('verifying track %d of %d: %s', number, len(self.itable.tracks), os.path.basename(path)) if not self.program.verifyTrack(self.runner, trackResult): logger.warning('verification failed, reripping...') os.unlink(path) if not os.path.exists(path): logger.debug('path %r does not exist, ripping...', path) tries = 0 # we reset durations for test and copy here trackResult.testduration = 0.0 trackResult.copyduration = 0.0 extra = "" while tries < MAX_TRIES: tries += 1 if tries > 1: extra = " (try %d)" % tries logger.info('ripping track %d of %d%s: %s', number, len(self.itable.tracks), extra, os.path.basename(path)) try: logger.debug('ripIfNotRipped: track %d, try %d', number, tries) self.program.ripTrack(self.runner, trackResult, offset=int(self.options.offset), device=self.device, taglist=self.program.getTagList( number, self.mbdiscid), overread=self.options.overread, what='track %d of %d%s' % ( number, len(self.itable.tracks), extra)) break # FIXME: catching too general exception (Exception) except Exception as e: logger.debug('got exception %r on try %d', e, tries) if tries == MAX_TRIES: logger.critical('giving up on track %d after %d times', number, tries) raise RuntimeError( "track can't be ripped. " "Rip attempts number is equal to 'MAX_TRIES'") if trackResult.testcrc == trackResult.copycrc: logger.info('CRCs match for track %d', number) else: raise RuntimeError( "CRCs did not match for track %d" % number ) print('Peak level: %.6f' % (trackResult.peak / 32768.0)) print('Rip quality: {:.2%}'.format(trackResult.quality)) # overlay this rip onto the Table if number == 0: # HTOA goes on index 0 of track 1 # ignore silence in PREGAP if trackResult.peak == SILENT: logger.debug('HTOA peak %r is equal to the SILENT ' 'threshold, disregarding', trackResult.peak) self.itable.setFile(1, 0, None, self.itable.getTrackStart(1), number) logger.debug('unlinking %r', trackResult.filename) os.unlink(trackResult.filename) trackResult.filename = None logger.info('HTOA discarded, contains digital silence') else: self.itable.setFile(1, 0, trackResult.filename, self.itable.getTrackStart(1), number) else: self.itable.setFile(number, 1, trackResult.filename, self.itable.getTrackLength(number), number) self.program.saveRipResult() # check for hidden track one audio htoa = self.program.getHTOA() if htoa: start, stop = htoa logger.info('found Hidden Track One Audio from frame %d to %d', start, stop) _ripIfNotRipped(0) for i, track in enumerate(self.itable.tracks): # FIXME: rip data tracks differently if not track.audio: logger.warning('skipping data track %d, not implemented', i + 1) # FIXME: make it work for now track.indexes[1].relative = 0 continue _ripIfNotRipped(i + 1) logger.debug('writing cue file for %r', discName) self.program.writeCue(discName) logger.debug('writing m3u file for %r', discName) self.program.write_m3u(discName) try: self.program.verifyImage(self.runner, self.itable) except accurip.EntryNotFound: logger.warning('AccurateRip entry not found') accurip.print_report(self.program.result) self.program.saveRipResult() self.program.writeLog(discName, self.logger) class CD(BaseCommand): summary = "handle CDs" description = "Display and rip CD-DA and metadata." device_option = True subcommands = { 'info': Info, 'rip': Rip } whipper-0.9.0/whipper/command/drive.py000066400000000000000000000074311357173224400177720ustar00rootroot00000000000000# -*- Mode: Python -*- # vi:si:et:sw=4:sts=4:ts=4 # 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 . from whipper.command.basecommand import BaseCommand from whipper.common import config, drive from whipper.extern.task import task from whipper.program import cdparanoia import logging logger = logging.getLogger(__name__) class Analyze(BaseCommand): summary = "analyze caching behaviour of drive" description = """Determine whether cdparanoia can defeat the audio cache of the drive.""" # noqa: E501 device_option = True def do(self): runner = task.SyncRunner() t = cdparanoia.AnalyzeTask(self.options.device) runner.run(t) if t.defeatsCache is None: logger.critical('cannot analyze the drive: is there a CD in it?') return if not t.defeatsCache: logger.info('cdparanoia cannot defeat the audio cache ' 'on this drive') else: logger.info('cdparanoia can defeat the audio cache on this drive') info = drive.getDeviceInfo(self.options.device) if not info: logger.error('drive caching behaviour not saved: ' 'could not get device info') return logger.info('adding drive cache behaviour to configuration file') config.Config().setDefeatsCache( info[0], info[1], info[2], t.defeatsCache) class List(BaseCommand): summary = "list drives" description = """list available CD-DA drives""" def do(self): paths = drive.getAllDevicePaths() self.config = config.Config() if not paths: logger.critical('no drives found. Create /dev/cdrom ' 'if you have a CD drive, or install ' 'pycdio for better detection') return try: import cdio as _ # noqa: F401 (TODO: fix it in a separate PR?) except ImportError: logger.error('install pycdio for vendor/model/release detection') return for path in paths: vendor, model, release = drive.getDeviceInfo(path) print("drive: %s, vendor: %s, model: %s, release: %s" % ( path, vendor, model, release)) try: offset = self.config.getReadOffset( vendor, model, release) print(" Configured read offset: %d" % offset) except KeyError: # Note spaces at the beginning for pretty terminal output logger.warning("no read offset found. " "Run 'whipper offset find'") try: defeats = self.config.getDefeatsCache( vendor, model, release) print(" Can defeat audio cache: %s" % defeats) except KeyError: logger.warning("unknown whether audio cache can be " "defeated. Run 'whipper drive analyze'") class Drive(BaseCommand): summary = "handle drives" description = """Drive utilities.""" subcommands = { 'analyze': Analyze, 'list': List } whipper-0.9.0/whipper/command/image.py000066400000000000000000000046041357173224400177420ustar00rootroot00000000000000# -*- Mode: Python -*- # vi:si:et:sw=4:sts=4:ts=4 # 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 sys from whipper.command.basecommand import BaseCommand from whipper.common import accurip, config, program from whipper.extern.task import task from whipper.image import image from whipper.result import result import logging logger = logging.getLogger(__name__) class Verify(BaseCommand): summary = "verify image" description = """ Verifies the image from the given .cue files against the AccurateRip database. """ def add_arguments(self): self.parser.add_argument('cuefile', nargs='+', action='store', help="cue file to load rip image from") def do(self): prog = program.Program(config.Config()) runner = task.SyncRunner() for arg in self.options.cuefile: cueImage = image.Image(arg) cueImage.setup(runner) # FIXME: this feels like we're poking at internals. prog.cuePath = arg prog.result = result.RipResult() for track in cueImage.table.tracks: tr = result.TrackResult() tr.number = track.number prog.result.tracks.append(tr) verified = False try: verified = prog.verifyImage(runner, cueImage.table) except accurip.EntryNotFound: print('AccurateRip entry not found') accurip.print_report(prog.result) if not verified: sys.exit(1) class Image(BaseCommand): summary = "handle images" description = """ Handle disc images. Disc images are described by a .cue file. Disc images can be verified. """ subcommands = { 'verify': Verify, } whipper-0.9.0/whipper/command/main.py000066400000000000000000000105351357173224400176040ustar00rootroot00000000000000# -*- Mode: Python -*- # vi:si:et:sw=4:sts=4:ts=4 import os import sys import pkg_resources import musicbrainzngs import site import whipper from distutils.sysconfig import get_python_lib from whipper.command import cd, offset, drive, image, accurip, mblookup from whipper.command.basecommand import BaseCommand from whipper.common import common, directory, config from whipper.extern.task import task from whipper.program.utils import eject_device import logging logger = logging.getLogger(__name__) def main(): server = config.Config().get_musicbrainz_server() musicbrainzngs.set_hostname(server) # Find whipper's plugins paths (local paths have higher priority) plugins_p = [directory.data_path('plugins')] # local path (in $HOME) if hasattr(sys, 'real_prefix'): # no getsitepackages() in virtualenv plugins_p.append( get_python_lib(plat_specific=False, standard_lib=False, prefix='/usr/local') + '/whipper/plugins') plugins_p.append(get_python_lib(plat_specific=False, standard_lib=False) + '/whipper/plugins') else: plugins_p += [x + '/whipper/plugins' for x in site.getsitepackages()] # register plugins with pkg_resources distributions, _ = pkg_resources.working_set.find_plugins( pkg_resources.Environment(plugins_p) ) list(map(pkg_resources.working_set.add, distributions)) try: cmd = Whipper(sys.argv[1:], os.path.basename(sys.argv[0]), None) ret = cmd.do() except SystemError as e: logger.critical("SystemError: %s", e) if (isinstance(e, common.EjectError) and cmd.options.eject in ('failure', 'always')): # XXX: Pylint, instance of 'SystemError' has no 'device' member eject_device(e.device) return 255 except RuntimeError as e: print(e) return 1 except KeyboardInterrupt: return 2 except ImportError: raise except task.TaskException as e: if isinstance(e.exception, ImportError): raise ImportError(e.exception) elif isinstance(e.exception, common.MissingDependencyException): logger.critical('missing dependency "%s"', e.exception.dependency) return 255 if isinstance(e.exception, common.EmptyError): logger.debug("EmptyError: %s", e.exception) logger.critical('could not create encoded file') return 255 # in python3 we can instead do `raise e.exception` as that would show # the exception's original context logger.critical(e.exceptionMessage) return 255 return ret if ret else 0 class Whipper(BaseCommand): description = ( "whipper is a CD ripping utility focusing on accuracy over speed.\n\n" "whipper gives you a tree of subcommands to work with.\n" "You can get help on subcommands by using the -h option " "to the subcommand.\n") no_add_help = True subcommands = { 'accurip': accurip.AccuRip, 'cd': cd.CD, 'drive': drive.Drive, 'offset': offset.Offset, 'image': image.Image, 'mblookup': mblookup.MBLookup } def add_arguments(self): self.parser.add_argument('-R', '--record', action='store_true', dest='record', help="record API requests for playback") self.parser.add_argument('-v', '--version', action="store_true", dest="version", help="show version information") self.parser.add_argument('-h', '--help', action="store_true", dest="help", help="show this help message and exit") self.parser.add_argument('-e', '--eject', action="store", dest="eject", default="success", choices=('never', 'failure', 'success', 'always'), help="when to eject disc (default: success)") def handle_arguments(self): if self.options.help: self.parser.print_help() sys.exit(0) if self.options.version: print("whipper %s" % whipper.__version__) sys.exit(0) whipper-0.9.0/whipper/command/mblookup.py000066400000000000000000000031201357173224400205000ustar00rootroot00000000000000from whipper.command.basecommand import BaseCommand from whipper.common.mbngs import musicbrainz class MBLookup(BaseCommand): summary = "lookup MusicBrainz entry" description = """Look up a MusicBrainz disc id and output information. You can get the MusicBrainz disc id with whipper cd info. Example disc id: KnpGsLhvH.lPrNc1PBL21lb9Bg4-""" def add_arguments(self): self.parser.add_argument( 'mbdiscid', action='store', help="MB disc id to look up" ) def do(self): try: discId = str(self.options.mbdiscid) except IndexError: print('Please specify a MusicBrainz disc id.') return 3 metadatas = musicbrainz(discId) print('%d releases' % len(metadatas)) for i, md in enumerate(metadatas): print('- Release %d:' % (i + 1, )) print(' Artist: %s' % md.artist.encode('utf-8')) print(' Title: %s' % md.title.encode('utf-8')) print(' Type: %s' % str(md.releaseType).encode('utf-8')) # noqa: E501 print(' URL: %s' % md.url) print(' Tracks: %d' % len(md.tracks)) if md.catalogNumber: print(' Cat no: %s' % md.catalogNumber) if md.barcode: print(' Barcode: %s' % md.barcode) for j, track in enumerate(md.tracks): print(' Track %2d: %s - %s' % ( j + 1, track.artist.encode('utf-8'), track.title.encode('utf-8') )) return None whipper-0.9.0/whipper/command/offset.py000066400000000000000000000203161357173224400201440ustar00rootroot00000000000000# -*- Mode: Python -*- # vi:si:et:sw=4:sts=4:ts=4 # 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 argparse import os import tempfile import logging from whipper.command.basecommand import BaseCommand from whipper.common import accurip, common, config, drive from whipper.common import task as ctask from whipper.program import arc, cdrdao, cdparanoia, utils from whipper.extern.task import task logger = logging.getLogger(__name__) # see http://www.accuraterip.com/driveoffsets.htm # and misc/offsets.py OFFSETS = ("+6, +667, +48, +102, +12, +30, +103, +618, +96, +594, " "+738, +98, -472, +116, +733, +696, +120, +691, +685, " "+99, +97, +600, +676, +690, +1292, +702, +686, -24, " "+704, +697, +572, +1182, +688, +91, -491, +145, +689, " "+564, +708, +86, +355, +79, -496, +679, -1164, 0, " "+1160, -436, +694, +684, +94, +1194, +106, +681, " "+117, +692, +943, +92, +680, +678, +682, +1268, +1279, " "+1473, -582, -54, +674, +687, +1272, +1263, +1508, " "+675, +534, +740, +122, -489, +974, +976, +1303, " "+108, +1130, +111, +739, +732, -589, -495, -494, " "+975, +961, +935, +87, +668, +234, +1776, +138, +1364, " "+1336, +1262, +1127") class Find(BaseCommand): summary = "find drive read offset" description = """Find drive's read offset by ripping tracks from a CD in the AccurateRip database.""" formatter_class = argparse.ArgumentDefaultsHelpFormatter device_option = True def add_arguments(self): self.parser.add_argument( '-o', '--offsets', action="store", dest="offsets", default=OFFSETS, help="list of offsets, comma-separated, colon-separated for ranges" ) def handle_arguments(self): self._offsets = [] blocks = self.options.offsets.split(',') for b in blocks: if ':' in b: a, b = b.split(':') self._offsets.extend(list(range(int(a), int(b) + 1))) else: self._offsets.append(int(b)) logger.debug('trying with offsets %r', self._offsets) def do(self): runner = ctask.SyncRunner() device = self.options.device # if necessary, load and unmount logger.info('checking device %s', device) utils.load_device(device) utils.unmount_device(device) # first get the Table Of Contents of the CD t = cdrdao.ReadTOCTask(device) runner.run(t) table = t.toc.table logger.debug("CDDB disc id: %r", table.getCDDBDiscId()) try: responses = accurip.get_db_entry(table.accuraterip_path()) except accurip.EntryNotFound: logger.warning("AccurateRip entry not found: drive offset " "can't be determined, try again with another disc") return None if responses: logger.debug('%d AccurateRip responses found.', len(responses)) if responses[0].cddbDiscId != table.getCDDBDiscId(): logger.warning("AccurateRip response discid different: %s", responses[0].cddbDiscId) # now rip the first track at various offsets, calculating AccurateRip # CRC, and matching it against the retrieved ones # archecksums is a tuple of accuraterip checksums: (v1, v2) def match(archecksums, track, responses): for i, r in enumerate(responses): for checksum in archecksums: if checksum == r.checksums[track - 1]: return checksum, i return None, None for offset in self._offsets: logger.info('trying read offset %d...', offset) try: archecksums = self._arcs(runner, table, 1, offset) except task.TaskException as e: # let MissingDependency fall through if isinstance(e.exception, common.MissingDependencyException): raise e if isinstance(e.exception, cdparanoia.FileSizeError): logger.warning('cannot rip with offset %d...', offset) continue logger.warning("unknown task exception for offset %d: %s", offset, e) logger.warning('cannot rip with offset %d...', offset) continue logger.debug('AR checksums calculated: %s', archecksums) c, i = match(archecksums, 1, responses) if c: count = 1 logger.debug('matched against response %d', i) logger.info('offset of device is likely %d, confirming...', offset) # now try and rip all other tracks as well, except for the # last one (to avoid readers that can't do overread for track in range(2, (len(table.tracks) + 1) - 1): try: archecksums = self._arcs(runner, table, track, offset) except task.TaskException as e: if isinstance(e.exception, cdparanoia.FileSizeError): logger.warning('cannot rip with offset %d...', offset) continue c, i = match(archecksums, track, responses) if c: logger.debug('matched track %d against response %d', track, i) count += 1 if count == len(table.tracks) - 1: self._foundOffset(device, offset) return 0 else: logger.warning('only %d of %d tracks matched, ' 'continuing...', count, len(table.tracks)) logger.error('no matching offset found. ' 'Consider trying again with a different disc') return None def _arcs(self, runner, table, track, offset): # rips the track with the given offset, return the arcs checksums logger.debug('ripping track %r with offset %d...', track, offset) fd, path = tempfile.mkstemp( suffix='.track%02d.offset%d.whipper.wav' % ( track, offset)) os.close(fd) t = cdparanoia.ReadTrackTask(path, table, table.getTrackStart( track), table.getTrackEnd(track), overread=False, offset=offset, device=self.options.device) t.description = 'Ripping track %d with read offset %d' % ( track, offset) runner.run(t) v1, v2 = arc.accuraterip_checksum(path, track, len(table.tracks)) os.unlink(path) return "%08x" % v1, "%08x" % v2 @staticmethod def _foundOffset(device, offset): print('\nRead offset of device is: %d.' % offset) info = drive.getDeviceInfo(device) if not info: logger.error('offset not saved: ' 'could not get device info (requires pycdio)') return logger.info('adding read offset to configuration file') config.Config().setReadOffset(info[0], info[1], info[2], offset) class Offset(BaseCommand): summary = "handle drive offsets" description = """ Drive offset detection utility. """ subcommands = { 'find': Find, } whipper-0.9.0/whipper/common/000077500000000000000000000000001357173224400161545ustar00rootroot00000000000000whipper-0.9.0/whipper/common/__init__.py000066400000000000000000000000001357173224400202530ustar00rootroot00000000000000whipper-0.9.0/whipper/common/accurip.py000066400000000000000000000230211357173224400201520ustar00rootroot00000000000000# -*- 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 requests import struct from os import makedirs from os.path import dirname, exists, join 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 )) whipper-0.9.0/whipper/common/cache.py000066400000000000000000000153601357173224400175760ustar00rootroot00000000000000# -*- Mode: Python; test-case-name: whipper.test.test_common_cache -*- # vi:si:et:sw=4:sts=4:ts=4 # 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 os import os.path import glob import tempfile import shutil from whipper.result import result from whipper.common import directory import logging logger = logging.getLogger(__name__) class Persister: """ I wrap an optional pickle to persist an object to disk. Instantiate me with a path to automatically unpickle the object. Call persist to store the object to disk; it will get stored if it changed from the on-disk object. :ivar object: the persistent object """ def __init__(self, path=None, default=None): """ If path is not given, the object will not be persisted. This allows code to transparently deal with both persisted and non-persisted objects, since the persist method will just end up doing nothing. """ self._path = path self.object = None self._unpickle(default) def persist(self, obj=None): """ Persist the given object, if we have a persistence path and the object changed. If object is not given, re-persist our object, always. If object is given, only persist if it was changed. """ # don't pickle if it's already ok if obj and obj == self.object: return # store the object on ourselves if not None if obj is not None: self.object = obj # don't pickle if there is no path if not self._path: return # default to pickling our object again if obj is None: obj = self.object # pickle self.object = obj (fd, path) = tempfile.mkstemp(suffix='.whipper.pickle') handle = os.fdopen(fd, 'wb') import pickle pickle.dump(obj, handle, 2) handle.close() # do an atomic move shutil.move(path, self._path) logger.debug('saved persisted object to %r', self._path) def _unpickle(self, default=None): self.object = default if not self._path: return if not os.path.exists(self._path): return with open(self._path, 'rb') as handle: import pickle try: self.object = pickle.load(handle) logger.debug('loaded persisted object from %r', self._path) # FIXME: catching too general exception (Exception) except Exception as e: # can fail for various reasons; in that case, pretend we didn't # load it logger.debug(e) def delete(self): self.object = None os.unlink(self._path) class PersistedCache: """ I wrap a directory of persisted objects. """ path = None def __init__(self, path): self.path = path os.makedirs(self.path, exist_ok=True) def _getPath(self, key): return os.path.join(self.path, '%s.pickle' % key) def get(self, key): """ Returns the persister for the given key. """ persister = Persister(self._getPath(key)) if persister.object: if hasattr(persister.object, 'instanceVersion'): o = persister.object if o.instanceVersion < o.__class__.classVersion: logger.debug('key %r persisted object version %d ' 'is outdated', key, o.instanceVersion) persister.object = None # FIXME: don't delete old objects atm # persister.delete() return persister class ResultCache: def __init__(self, path=None): self._path = path or directory.cache_path('result') self._pcache = PersistedCache(self._path) def getRipResult(self, cddbdiscid, create=True): """ Retrieve the persistable RipResult either from our cache (from a previous, possibly aborted rip), or return a new one. :rtype: :any:`Persistable` for :any:`result.RipResult` """ presult = self._pcache.get(cddbdiscid) if not presult.object: logger.debug('result for cddbdiscid %r not in cache', cddbdiscid) if not create: logger.debug('returning None') return None logger.debug('creating result') presult.object = result.RipResult() presult.persist(presult.object) else: logger.debug('result for cddbdiscid %r found in cache, reusing', cddbdiscid) return presult def getIds(self): paths = glob.glob(os.path.join(self._path, '*.pickle')) return [os.path.splitext(os.path.basename(path))[0] for path in paths] class TableCache: """ I read and write entries to and from the cache of tables. If no path is specified, the cache will write to the current cache directory and read from all possible cache directories (to allow for pre-0.2.1 cddbdiscid-keyed entries). """ def __init__(self, path=None): if not path: self._path = directory.cache_path('table') else: self._path = path self._pcache = PersistedCache(self._path) def get(self, cddbdiscid, mbdiscid): # Before 0.2.1, we only saved by cddbdiscid, and had collisions # mbdiscid collisions are a lot less likely ptable = self._pcache.get('mbdiscid.' + mbdiscid) if not ptable.object: ptable = self._pcache.get(cddbdiscid) if ptable.object: if ptable.object.getMusicBrainzDiscId() != mbdiscid: logger.debug('cached table is for different mb id %r', ptable.object.getMusicBrainzDiscId()) ptable.object = None else: logger.debug('no valid cached table found for %r', cddbdiscid) if not ptable.object: # get an empty persistable from the writable location ptable = self._pcache.get('mbdiscid.' + mbdiscid) return ptable whipper-0.9.0/whipper/common/checksum.py000066400000000000000000000036051357173224400203340ustar00rootroot00000000000000# -*- Mode: Python; test-case-name: whipper.test.test_common_checksum -*- # vi:si:et:sw=4:sts=4:ts=4 # 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 binascii import wave import tempfile import subprocess import os from whipper.extern.task import task as etask import logging logger = logging.getLogger(__name__) # checksums are not CRC's. a CRC is a specific type of checksum. class CRC32Task(etask.Task): # TODO: Support sampleStart, sampleLength later on (should be trivial, just # add change the read part in _crc32 to skip some samples and/or not # read too far) def __init__(self, path, sampleStart=0, sampleLength=-1, is_wave=True): self.path = path self.is_wave = is_wave def start(self, runner): etask.Task.start(self, runner) self.schedule(0.0, self._crc32) def _crc32(self): if not self.is_wave: _, tmpf = tempfile.mkstemp() try: subprocess.check_call(['flac', '-d', self.path, '-fo', tmpf]) w = wave.open(tmpf) finally: os.remove(tmpf) else: w = wave.open(self.path) d = w._data_chunk.read() self.checksum = binascii.crc32(d) & 0xffffffff self.stop() whipper-0.9.0/whipper/common/common.py000066400000000000000000000230751357173224400200250ustar00rootroot00000000000000# -*- Mode: Python; test-case-name: whipper.test.test_common_common -*- # vi:si:et:sw=4:sts=4:ts=4 # 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 os import os.path import math import re import subprocess import unicodedata from whipper.extern import asyncsub import logging logger = logging.getLogger(__name__) FRAMES_PER_SECOND = 75 SAMPLES_PER_FRAME = 588 # a sample is 2 16-bit values, left and right channel WORDS_PER_FRAME = SAMPLES_PER_FRAME * 2 BYTES_PER_FRAME = SAMPLES_PER_FRAME * 4 class EjectError(SystemError): """ Possibly ejects the drive in command.main. """ def __init__(self, device, *args): """ args is a tuple used by BaseException.__str__ device is the device path to eject """ self.args = args self.device = device def msfToFrames(msf): """ Converts a string value in MM:SS:FF to frames. :param msf: the MM:SS:FF value to convert :type msf: str :rtype: int :returns: number of frames """ if ':' not in msf: return int(msf) m, s, f = msf.split(':') return 60 * FRAMES_PER_SECOND * int(m) \ + FRAMES_PER_SECOND * int(s) \ + int(f) def framesToMSF(frames, frameDelimiter=':'): f = frames % FRAMES_PER_SECOND frames -= f s = (frames / FRAMES_PER_SECOND) % 60 frames -= s * FRAMES_PER_SECOND m = frames / FRAMES_PER_SECOND / 60 return "%02d:%02d%s%02d" % (m, s, frameDelimiter, f) def framesToHMSF(frames): # cdparanoia style f = frames % FRAMES_PER_SECOND frames -= f s = (frames / FRAMES_PER_SECOND) % 60 frames -= s * FRAMES_PER_SECOND m = (frames / FRAMES_PER_SECOND / 60) % 60 frames -= m * FRAMES_PER_SECOND * 60 h = frames / FRAMES_PER_SECOND / 60 / 60 return "%02d:%02d:%02d.%02d" % (h, m, s, f) def formatTime(seconds, fractional=3): """ Nicely format time in a human-readable format, like HH:MM:SS.mmm If fractional is zero, no seconds will be shown. If it is greater than 0, we will show seconds and fractions of seconds. As a side consequence, there is no way to show seconds without fractions. :param seconds: the time in seconds to format. :type seconds: int or float :param fractional: how many digits to show for the fractional part of seconds. :type fractional: int :rtype: string :returns: a nicely formatted time string. """ chunks = [] if seconds < 0: chunks.append('-') seconds = -seconds hour = 60 * 60 hours = seconds / hour seconds %= hour minute = 60 minutes = seconds / minute seconds %= minute chunk = '%02d:%02d' % (hours, minutes) if fractional > 0: chunk += ':%0*.*f' % (fractional + 3, fractional, seconds) chunks.append(chunk) return " ".join(chunks) class MissingDependencyException(Exception): dependency = None def __init__(self, *args): self.args = args self.dependency = args[0] class EmptyError(Exception): pass class MissingFrames(Exception): """ Less frames decoded than expected. """ pass def truncate_filename(path): """ Truncate filename to the max. len. allowed by the path's filesystem """ p, f = os.path.split(os.path.normpath(path)) f, e = os.path.splitext(f) # Get the filename length limit in bytes fn_lim = os.pathconf(p.encode('utf-8'), 'PC_NAME_MAX') f_max = fn_lim - len(e.encode('utf-8')) f = unicodedata.normalize('NFC', f) f_trunc = f.encode()[:f_max].decode('utf-8', errors='ignore') return os.path.join(p, f_trunc + e) def shrinkPath(path): """ Shrink a full path to a shorter version. Used to handle ENAMETOOLONG """ parts = list(os.path.split(path)) length = len(parts[-1]) target = 127 if length <= target: target = pow(2, int(math.log(length, 2))) - 1 name, ext = os.path.splitext(parts[-1]) target -= len(ext) + 1 # split on space, then reassemble words = name.split(' ') length = 0 pieces = [] for word in words: if length + 1 + len(word) <= target: pieces.append(word) length += 1 + len(word) else: break name = " ".join(pieces) # ext includes period parts[-1] = '%s%s' % (name, ext) path = os.path.join(*parts) return path def getRealPath(refPath, filePath): """ Translate a .cue or .toc's FILE argument to an existing path. Does Windows path translation. Will look for the given file name, but with .flac and .wav as extensions. :param refPath: path to the file from which the track is referenced; for example, path to the .cue file in the same directory :type refPath: str :type filePath: str """ assert isinstance(filePath, str), "%r is not str" % filePath if os.path.exists(filePath): return filePath candidatePaths = [] # .cue FILE statements can have Windows-style path separators, so convert # them as one possible candidate # on the other hand, the file may indeed contain a backslash in the name # on linux # FIXME: I guess we might do all possible combinations of splitting or # keeping the slash, but let's just assume it's either Windows # or linux # See https://thomas.apestaart.org/morituri/trac/ticket/107 parts = filePath.split('\\') if parts[0] == '': parts[0] = os.path.sep tpath = os.path.join(*parts) for path in [filePath, tpath]: if path == os.path.abspath(path): candidatePaths.append(path) else: # if the path is relative: # - check relatively to the cue file # - check only the filename part relative to the cue file candidatePaths.append(os.path.join( os.path.dirname(refPath), path)) candidatePaths.append(os.path.join( os.path.dirname(refPath), os.path.basename(path))) # Now look for .wav and .flac files, as .flac files are often named .wav for candidate in candidatePaths: noext, _ = os.path.splitext(candidate) for ext in ['wav', 'flac']: cpath = '%s.%s' % (noext, ext) if os.path.exists(cpath): return cpath raise KeyError("Cannot find file for %r" % filePath) def getRelativePath(targetPath, collectionPath): """ Get a relative path from the directory of collectionPath to targetPath. Used to determine the path to use in .cue/.m3u files """ logger.debug('getRelativePath: target %r, collection %r', targetPath, collectionPath) targetDir = os.path.dirname(targetPath) collectionDir = os.path.dirname(collectionPath) if targetDir == collectionDir: logger.debug('getRelativePath: target and collection in same dir') return os.path.basename(targetPath) rel = os.path.relpath( targetDir + os.path.sep, collectionDir + os.path.sep) logger.debug('getRelativePath: target and collection ' 'in different dir, %r', rel) return os.path.join(rel, os.path.basename(targetPath)) def validate_template(template, kind): """ Raise exception if disc/track template includes invalid variables """ if kind == 'disc': matches = re.findall(r'%[^ARSXdrxy]', template) elif kind == 'track': matches = re.findall(r'%[^ARSXadnrstxy]', template) if '%' in template and matches: raise ValueError(kind + ' template string contains invalid ' 'variable(s): {}'.format(', '.join(matches))) class VersionGetter: """ I get the version of a program by looking for it in command output according to a regexp. """ def __init__(self, dependency, args, regexp, expander): """ :param dependency: name of the dependency providing the program :param args: the arguments to invoke to show the version :type args: list of str :param regexp: the regular expression to get the version :param expander: the expansion string for the version using the regexp group dict """ self._dep = dependency self._args = args self._regexp = regexp self._expander = expander def get(self): version = "(Unknown)" try: with asyncsub.Popen(self._args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) as p: p.wait() output = asyncsub.recv_some(p, e=0, stderr=1).decode() vre = self._regexp.search(output) if vre: version = self._expander % vre.groupdict() except OSError as e: import errno if e.errno == errno.ENOENT: raise MissingDependencyException(self._dep) raise return version whipper-0.9.0/whipper/common/config.py000066400000000000000000000126211357173224400177750ustar00rootroot00000000000000# -*- Mode: Python; test-case-name: whipper.test.test_common_config -*- # vi:si:et:sw=4:sts=4:ts=4 # 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 codecs import configparser import os.path import shutil import tempfile from urllib.parse import urlparse, quote from whipper.common import directory import logging logger = logging.getLogger(__name__) class Config: def __init__(self, path=None): self._path = path or directory.config_path() self._parser = configparser.ConfigParser() self.open() def open(self): # Open the file with the correct encoding if os.path.exists(self._path): with codecs.open(self._path, 'r', encoding='utf-8') as f: self._parser.read_file(f) logger.debug('loaded %d sections from config file', len(self._parser.sections())) def write(self): fd, path = tempfile.mkstemp(suffix='.whipperrc') handle = os.fdopen(fd, 'w') self._parser.write(handle) handle.close() shutil.move(path, self._path) # any section def _getter(self, suffix, section, option): methodName = 'get' + suffix method = getattr(self._parser, methodName) try: return method(section, option) except (configparser.NoSectionError, configparser.NoOptionError): return None def get(self, section, option): return self._getter('', section, option) def getboolean(self, section, option): return self._getter('boolean', section, option) # musicbrainz section def get_musicbrainz_server(self): server = self.get('musicbrainz', 'server') or 'musicbrainz.org' server_url = urlparse('//' + server) if server_url.scheme != '' or server_url.path != '': raise KeyError('Invalid MusicBrainz server: %s' % server) return server # drive sections def setReadOffset(self, vendor, model, release, offset): """ Set a read offset for the given drive. Strips the given strings of leading and trailing whitespace. """ section = self._findOrCreateDriveSection(vendor, model, release) self._parser.set(section, 'read_offset', str(offset)) self.write() def getReadOffset(self, vendor, model, release): """ Get a read offset for the given drive. """ section = self._findDriveSection(vendor, model, release) try: return int(self._parser.get(section, 'read_offset')) except configparser.NoOptionError: raise KeyError("Could not find read_offset for %s/%s/%s" % ( vendor, model, release)) def setDefeatsCache(self, vendor, model, release, defeat): """ Set whether the drive defeats the cache. Strips the given strings of leading and trailing whitespace. """ section = self._findOrCreateDriveSection(vendor, model, release) self._parser.set(section, 'defeats_cache', str(defeat)) self.write() def getDefeatsCache(self, vendor, model, release): section = self._findDriveSection(vendor, model, release) try: return self._parser.get(section, 'defeats_cache') == 'True' except configparser.NoOptionError: raise KeyError("Could not find defeats_cache for %s/%s/%s" % ( vendor, model, release)) def _findDriveSection(self, vendor, model, release): for name in self._parser.sections(): if not name.startswith('drive:'): continue logger.debug('looking at section %r', name) conf = {} for key in ['vendor', 'model', 'release']: locals()[key] = locals()[key].strip() conf[key] = self._parser.get(name, key) logger.debug("%s: '%s' versus '%s'", key, locals()[key], conf[key]) if vendor.strip() != conf['vendor']: continue if model.strip() != conf['model']: continue if release.strip() != conf['release']: continue return name raise KeyError("Could not find configuration section for %s/%s/%s" % ( vendor, model, release)) def _findOrCreateDriveSection(self, vendor, model, release): try: section = self._findDriveSection(vendor, model, release) except KeyError: section = 'drive:' + quote('%s:%s:%s' % ( vendor, model, release)) self._parser.add_section(section) for key in ['vendor', 'model', 'release']: self._parser.set(section, key, locals()[key].strip()) self.write() return self._findDriveSection(vendor, model, release) whipper-0.9.0/whipper/common/directory.py000066400000000000000000000030121357173224400205260ustar00rootroot00000000000000# -*- Mode: Python; test-case-name: whipper.test.test_common_directory -*- # vi:si:et:sw=4:sts=4:ts=4 # Copyright (C) 2013 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 . from os import getenv, makedirs from os.path import join, expanduser def config_path(): path = join(getenv('XDG_CONFIG_HOME') or join(expanduser('~'), '.config'), 'whipper') makedirs(path, exist_ok=True) return join(path, 'whipper.conf') def cache_path(name=None): path = join(getenv('XDG_CACHE_HOME') or join(expanduser('~'), '.cache'), 'whipper') if name: path = join(path, name) makedirs(path, exist_ok=True) return path def data_path(name=None): path = join(getenv('XDG_DATA_HOME') or join(expanduser('~'), '.local/share'), 'whipper') if name: path = join(path, name) makedirs(path, exist_ok=True) return path whipper-0.9.0/whipper/common/drive.py000066400000000000000000000036341357173224400176450ustar00rootroot00000000000000# -*- Mode: Python; test-case-name: whipper.test.test_common_drive -*- # vi:si:et:sw=4:sts=4:ts=4 # 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 os import logging logger = logging.getLogger(__name__) def _listify(listOrString): if isinstance(listOrString, str): return [listOrString, ] return listOrString def getAllDevicePaths(): try: # see https://savannah.gnu.org/bugs/index.php?38477 return [str(dev) for dev in _getAllDevicePathsPyCdio()] except ImportError: logger.info('cannot import pycdio') return _getAllDevicePathsStatic() def _getAllDevicePathsPyCdio(): import pycdio import cdio # using FS_AUDIO here only makes it list the drive when an audio cd # is inserted # ticket 102: this cdio call returns a list of str, or a single str return _listify(cdio.get_devices_with_cap(pycdio.FS_MATCH_ALL, False)) def _getAllDevicePathsStatic(): ret = [] for c in ['/dev/cdrom', '/dev/cdrecorder']: if os.path.exists(c): ret.append(c) return ret def getDeviceInfo(path): try: import cdio except ImportError: return None device = cdio.Device(path) _, vendor, model, release = device.get_hwinfo() return vendor, model, release whipper-0.9.0/whipper/common/encode.py000066400000000000000000000050651357173224400177710ustar00rootroot00000000000000# -*- Mode: Python; test-case-name: whipper.test.test_common_encode -*- # vi:si:et:sw=4:sts=4:ts=4 # 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 . from mutagen.flac import FLAC from whipper.extern.task import task from whipper.program import sox from whipper.program import flac import logging logger = logging.getLogger(__name__) class SoxPeakTask(task.Task): description = 'Calculating peak level' def __init__(self, track_path): self.track_path = track_path self.peak = None def start(self, runner): task.Task.start(self, runner) self.schedule(0.0, self._sox_peak) def _sox_peak(self): self.peak = sox.peak_level(self.track_path) self.stop() class FlacEncodeTask(task.Task): description = 'Encoding to FLAC' def __init__(self, track_path, track_out_path, what="track"): self.track_path = track_path self.track_out_path = track_out_path self.new_path = None self.description = 'Encoding %s to FLAC' % what def start(self, runner): task.Task.start(self, runner) self.schedule(0.0, self._flac_encode) def _flac_encode(self): flac.encode(self.track_path, self.track_out_path) self.stop() class TaggingTask(task.Task): # TODO: Wizzup: Do we really want this as 'Task'...? # I only made it a task for now because that it's easier to integrate in # program/cdparanoia.py - where whipper currently does the tagging. # We should just move the tagging to a more sensible place. description = 'Writing tags to FLAC' def __init__(self, track_path, tags): self.track_path = track_path self.tags = tags def start(self, runner): task.Task.start(self, runner) self.schedule(0.0, self._tag) def _tag(self): w = FLAC(self.track_path) for k, v in list(self.tags.items()): w[k] = v w.save() self.stop() whipper-0.9.0/whipper/common/mbngs.py000066400000000000000000000261441357173224400176430ustar00rootroot00000000000000# -*- Mode: Python; test-case-name: whipper.test.test_common_mbngs -*- # vi:si:et:sw=4:sts=4:ts=4 # Copyright (C) 2009, 2010, 2011 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 . """ Handles communication with the MusicBrainz server using NGS. """ from urllib.error import HTTPError import whipper import logging logger = logging.getLogger(__name__) VA_ID = "89ad4ac3-39f7-470e-963a-56509c546377" # Various Artists class MusicBrainzException(Exception): def __init__(self, exc): self.args = (exc, ) self.exception = exc class NotFoundException(MusicBrainzException): def __str__(self): return "Disc not found in MusicBrainz" class TrackMetadata: artist = None title = None duration = None # in ms mbid = None sortName = None mbidArtist = None mbidRecording = None mbidWorks = [] class DiscMetadata: """ :param artist: artist(s) name :param sortName: release artist sort name :param release: earliest release date, in YYYY-MM-DD :type release: str :param title: title of the disc (with disambiguation) :param releaseTitle: title of the release (without disambiguation) :type tracks: list of :any:`TrackMetadata` """ artist = None sortName = None title = None various = False tracks = None release = None releaseTitle = None releaseType = None mbid = None mbidReleaseGroup = None mbidArtist = None url = None catalogNumber = None barcode = None def __init__(self): self.tracks = [] def _record(record, which, name, what): # optionally record to disc as a JSON serialization if record: import json filename = 'whipper.%s.%s.json' % (which, name) handle = open(filename, 'w') handle.write(json.dumps(what)) handle.close() logger.info('wrote %s %s to %s', which, name, filename) # credit is of the form [dict, str, dict, ... ] # e.g. [ # {'artist': { # 'sort-name': 'Sukilove', # 'id': '5f4af6cf-a1b8-4e51-a811-befed399a1c6', # 'name': 'Sukilove' # }}, ' & ', { # 'artist': { # 'sort-name': 'Blackie and the Oohoos', # 'id': '028a9dc7-f5ef-43c2-866b-08d69ffff363', # 'name': 'Blackie & the Oohoos'}}] # or # [{'artist': # {'sort-name': 'Pixies', # 'id': 'b6b2bb8d-54a9-491f-9607-7b546023b433', 'name': 'Pixies'}}] class _Credit(list): """ I am a representation of an artist-credit in MusicBrainz for a disc or track. """ def joiner(self, attributeGetter, joinString=None): res = [] for item in self: if isinstance(item, dict): res.append(attributeGetter(item)) else: if not joinString: res.append(item) else: res.append(joinString) return "".join(res) def getSortName(self): return self.joiner(lambda i: i.get('artist').get('sort-name', None)) def getName(self): return self.joiner(lambda i: i.get('name', i.get('artist').get('name', None))) def getIds(self): # split()'s the joined string so we get a proper list of MBIDs return self.joiner(lambda i: i.get('artist').get('id', None), joinString=";").split(';') def _getWorks(recording): """Get "performance of" works out of a recording.""" works = [] valid_work_rel_types = [ 'a3005666-a872-32c3-ad06-98af558e99b0', # "Performance" ] if 'work-relation-list' in recording: for work in recording['work-relation-list']: if work['type-id'] in valid_work_rel_types: works.append(work['work']['id']) return works def _getMetadata(release, discid, country=None): """ :type release: dict :param release: a release dict as returned in the value for key release from get_release_by_id :rtype: DiscMetadata or None """ logger.debug('getMetadata for release id %r', release['id']) if not release['id']: logger.warning('no id for release %r', release) return None assert release['id'], 'Release does not have an id' if 'country' in release and country and release['country'] != country: logger.warning('%r was not released in %r', release, country) return None discMD = DiscMetadata() if 'type' in release['release-group']: discMD.releaseType = release['release-group']['type'] discCredit = _Credit(release['artist-credit']) # FIXME: is there a better way to check for VA ? discMD.various = False if discCredit[0]['artist']['id'] == VA_ID: discMD.various = True if len(discCredit) > 1: logger.debug('artist-credit more than 1: %r', discCredit) releaseArtistName = discCredit.getName() # getUniqueName gets disambiguating names like Muse (UK rock band) discMD.artist = releaseArtistName discMD.sortName = discCredit.getSortName() if 'date' not in release: logger.warning("release with ID '%s' (%s - %s) does not have a date", release['id'], discMD.artist, release['title']) else: discMD.release = release['date'] discMD.mbid = release['id'] discMD.mbidReleaseGroup = release['release-group']['id'] discMD.mbidArtist = discCredit.getIds() discMD.url = 'https://musicbrainz.org/release/' + release['id'] discMD.barcode = release.get('barcode', None) lil = release.get('label-info-list', [{}]) if lil: discMD.catalogNumber = lil[0].get('catalog-number') tainted = False duration = 0 # only show discs from medium-list->disc-list with matching discid for medium in release['medium-list']: for disc in medium['disc-list']: if disc['id'] == discid: title = release['title'] discMD.releaseTitle = title if 'disambiguation' in release: title += " (%s)" % release['disambiguation'] count = len(release['medium-list']) if count > 1: title += ' (Disc %d of %d)' % ( int(medium['position']), count) if 'title' in medium: title += ": %s" % medium['title'] discMD.title = title for t in medium['track-list']: track = TrackMetadata() trackCredit = _Credit( t.get('artist-credit', t['recording']['artist-credit'] )) if len(trackCredit) > 1: logger.debug('artist-credit more than 1: %r', trackCredit) # FIXME: leftover comment, need an example # various artists discs can have tracks with no artist track.artist = trackCredit.getName() track.sortName = trackCredit.getSortName() track.mbidArtist = trackCredit.getIds() track.title = t['recording']['title'] track.mbid = t['id'] track.mbidRecording = t['recording']['id'] track.mbidWorks = _getWorks(t['recording']) # FIXME: unit of duration ? track.duration = int(t['recording'].get('length', 0)) if not track.duration: logger.warning('track %r (%r) does not have duration', track.title, track.mbid) tainted = True else: duration += track.duration discMD.tracks.append(track) if not tainted: discMD.duration = duration else: discMD.duration = 0 return discMD # see http://bugs.musicbrainz.org/browser/python-musicbrainz2/trunk/examples/ # ripper.py def musicbrainz(discid, country=None, record=False): """ Based on a MusicBrainz disc id, get a list of DiscMetadata objects for the given disc id. Example disc id: Mj48G109whzEmAbPBoGvd4KyCS4- :type discid: str :rtype: list of :any:`DiscMetadata` """ logger.debug('looking up results for discid %r', discid) import musicbrainzngs logging.getLogger("musicbrainzngs").setLevel(logging.WARNING) musicbrainzngs.set_useragent("whipper", whipper.__version__, "https://github.com/whipper-team/whipper") ret = [] try: result = musicbrainzngs.get_releases_by_discid( discid, includes=["artists", "recordings", "release-groups"]) except musicbrainzngs.ResponseError as e: if isinstance(e.cause, HTTPError): if e.cause.code == 404: raise NotFoundException(e) else: logger.debug('received bad response from the server') raise MusicBrainzException(e) # The result can either be a "disc" or a "cdstub" if result.get('disc'): logger.debug('found %d releases for discid %r', len(result['disc']['release-list']), discid) _record(record, 'releases', discid, result) # Display the returned results to the user. import json for release in result['disc']['release-list']: formatted = json.dumps(release, sort_keys=False, indent=4) logger.debug('result %s: artist %r, title %r', formatted, release['artist-credit-phrase'], release['title']) # to get titles of recordings, we need to query the release with # artist-credits res = musicbrainzngs.get_release_by_id( release['id'], includes=["artists", "artist-credits", "recordings", "discids", "labels", "recording-level-rels", "work-rels", "release-groups"]) _record(record, 'release', release['id'], res) releaseDetail = res['release'] formatted = json.dumps(releaseDetail, sort_keys=False, indent=4) logger.debug('release %s', formatted) md = _getMetadata(releaseDetail, discid, country) if md: logger.debug('duration %r', md.duration) ret.append(md) return ret elif result.get('cdstub'): logger.debug('query returned cdstub: ignored') return None whipper-0.9.0/whipper/common/path.py000066400000000000000000000043431357173224400174660ustar00rootroot00000000000000# -*- Mode: Python; test-case-name: whipper.test.test_common_path -*- # vi:si:et:sw=4:sts=4:ts=4 # 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 re class PathFilter: """ I filter path components for safe storage on file systems. """ def __init__(self, slashes=True, quotes=True, fat=True, special=False): """ :param slashes: whether to convert slashes to dashes :param quotes: whether to normalize quotes :param fat: whether to strip characters illegal on FAT filesystems :param special: whether to strip special characters """ self._slashes = slashes self._quotes = quotes self._fat = fat self._special = special def filter(self, path): if self._slashes: path = re.sub(r'[/\\]', '-', path, re.UNICODE) def separators(path): # replace separators with a space-hyphen or hyphen path = re.sub(r'[:]', ' -', path, re.UNICODE) path = re.sub(r'[|]', '-', path, re.UNICODE) return path # change all fancy single/double quotes to normal quotes if self._quotes: path = re.sub(r'[\xc2\xb4\u2018\u2019\u201b]', "'", path) path = re.sub(r'[\u201c\u201d\u201f]', '"', path) if self._special: path = separators(path) path = re.sub(r'[*?&!\'\"$()`{}\[\]<>]', '_', path) if self._fat: path = separators(path) # : and | already gone, but leave them here for reference path = re.sub(r'[:*?"<>|]', '_', path) return path whipper-0.9.0/whipper/common/program.py000066400000000000000000000533011357173224400201770ustar00rootroot00000000000000# -*- Mode: Python; test-case-name: whipper.test.test_common_program -*- # vi:si:et:sw=4:sts=4:ts=4 # Copyright (C) 2009, 2010, 2011 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 . """ Common functionality and class for all programs using whipper. """ import musicbrainzngs import re import os import time from whipper.common import accurip, cache, checksum, common, mbngs, path from whipper.program import cdrdao, cdparanoia from whipper.image import image from whipper.extern import freedb from whipper.extern.task import task import logging logger = logging.getLogger(__name__) # FIXME: should Program have a runner ? class Program: """ I maintain program state and functionality. :vartype metadata: mbngs.DiscMetadata :cvar result: the rip's result :vartype result: result.RipResult :vartype outdir: str :vartype config: whipper.common.config.Config """ cuePath = None logPath = None metadata = None outdir = None result = None def __init__(self, config, record=False): """ :param record: whether to record results of API calls for playback. """ self._record = record self._cache = cache.ResultCache() self._config = config d = {} for key, default in list({ 'fat': True, 'special': False }.items()): value = None value = self._config.getboolean('main', 'path_filter_' + key) if value is None: value = default d[key] = value self._filter = path.PathFilter(**d) @staticmethod def setWorkingDirectory(workingDirectory): if workingDirectory: logger.info('changing to working directory %s', workingDirectory) os.chdir(workingDirectory) def getFastToc(self, runner, device): """Retrieve the normal TOC table from the drive. Also warn about buggy cdrdao versions. """ from pkg_resources import parse_version as V version = cdrdao.version() if V(version) < V('1.2.3rc2'): logger.warning('cdrdao older than 1.2.3 has a pre-gap length bug.' ' See http://sourceforge.net/tracker/?func=detail&aid=604751&group_id=2171&atid=102171') # noqa: E501 t = cdrdao.ReadTOCTask(device, fast_toc=True) runner.run(t) toc = t.toc.table assert toc.hasTOC() return toc def getTable(self, runner, cddbdiscid, mbdiscid, device, offset, toc_path): """ Retrieve the Table either from the cache or the drive. :rtype: table.Table """ tcache = cache.TableCache() ptable = tcache.get(cddbdiscid, mbdiscid) itable = None tdict = {} # Ignore old cache, since we do not know what offset it used. if isinstance(ptable.object, dict): tdict = ptable.object if offset in tdict: itable = tdict[offset] if not itable: logger.debug('getTable: cddbdiscid %s, mbdiscid %s not in cache ' 'for offset %s, reading table', cddbdiscid, mbdiscid, offset) t = cdrdao.ReadTOCTask(device, toc_path=toc_path) t.description = "Reading table" runner.run(t) itable = t.toc.table tdict[offset] = itable ptable.persist(tdict) logger.debug('getTable: read table %r', itable) else: logger.debug('getTable: cddbdiscid %s, mbdiscid %s in cache ' 'for offset %s', cddbdiscid, mbdiscid, offset) logger.debug('getTable: loaded table %r', itable) assert itable.hasTOC() self.result.table = itable logger.debug('getTable: returning table with mb id %s', itable.getMusicBrainzDiscId()) return itable def getRipResult(self, cddbdiscid): """ Retrieve the persistable RipResult either from our cache (from a previous, possibly aborted rip), or return a new one. :rtype: result.RipResult """ assert self.result is None self._presult = self._cache.getRipResult(cddbdiscid) self.result = self._presult.object return self.result def saveRipResult(self): self._presult.persist() @staticmethod def addDisambiguation(template_part, metadata): """Add disambiguation to template path part string.""" if metadata.catalogNumber: template_part += ' (%s)' % metadata.catalogNumber elif metadata.barcode: template_part += ' (%s)' % metadata.barcode return template_part def getPath(self, outdir, template, mbdiscid, metadata, track_number=None): """ Return disc or track path relative to outdir according to template. Track paths do not include extension. Tracks are named according to the track template, filling in the variables and adding the file extension. Variables exclusive to the track template are: - %t: track number - %a: track artist - %n: track title - %s: track sort name Disc files (.cue, .log, .m3u) are named according to the disc template, filling in the variables and adding the file extension. Variables for both disc and track template are: - %A: release artist - %S: release artist sort name - %d: disc title - %y: release year - %r: release type, lowercase - %R: release type, normal case - %x: audio extension, lowercase - %X: audio extension, uppercase """ assert isinstance(outdir, str), "%r is not str" % outdir assert isinstance(template, str), "%r is not str" % template v = {} v['A'] = 'Unknown Artist' v['d'] = mbdiscid # fallback for title v['r'] = 'unknown' v['R'] = 'Unknown' v['B'] = '' # barcode v['C'] = '' # catalog number v['x'] = 'flac' v['X'] = v['x'].upper() v['y'] = '0000' if track_number is not None: v['a'] = v['A'] v['t'] = '%02d' % track_number if track_number == 0: v['n'] = 'Hidden Track One Audio' else: v['n'] = 'Unknown Track %d' % track_number if metadata: release = metadata.release or '0000' v['y'] = release[:4] v['A'] = self._filter.filter(metadata.artist) v['S'] = self._filter.filter(metadata.sortName) v['d'] = self._filter.filter(metadata.title) v['B'] = metadata.barcode v['C'] = metadata.catalogNumber if metadata.releaseType: v['R'] = metadata.releaseType v['r'] = metadata.releaseType.lower() if track_number is not None and track_number > 0: v['a'] = self._filter.filter( metadata.tracks[track_number - 1].artist) v['s'] = self._filter.filter( metadata.tracks[track_number - 1].sortName) v['n'] = self._filter.filter( metadata.tracks[track_number - 1].title) elif track_number == 0: # htoa defaults to disc's artist v['a'] = self._filter.filter(metadata.artist) template = re.sub(r'%(\w)', r'%(\1)s', template) return os.path.join(outdir, template % v) @staticmethod def getCDDB(cddbdiscid): """ :param cddbdiscid: list of id, tracks, offsets, seconds :rtype: str """ # FIXME: convert to nonblocking? try: md = freedb.perform_lookup(cddbdiscid, 'freedb.freedb.org', 80) logger.debug('CDDB query result: %r', md) return [item['DTITLE'] for item in md if 'DTITLE' in item] or None except ValueError as e: logger.warning("CDDB protocol error: %s", e) except IOError as e: # FIXME: for some reason errno is a str ? if e.errno == 'socket error': logger.warning("CDDB network error: %r", (e, )) else: raise return None def getMusicBrainz(self, ittoc, mbdiscid, release=None, country=None, prompt=False): """ :type ittoc: whipper.image.table.Table """ # look up disc on MusicBrainz print('Disc duration: %s, %d audio tracks' % ( common.formatTime(ittoc.duration() / 1000.0), ittoc.getAudioTracks())) logger.debug('MusicBrainz submit url: %r', ittoc.getMusicBrainzSubmitURL()) metadatas = None for _ in range(0, 4): try: metadatas = mbngs.musicbrainz(mbdiscid, country=country, record=self._record) break except mbngs.NotFoundException as e: logger.warning("release not found: %r", (e, )) break except musicbrainzngs.NetworkError as e: logger.warning("network error: %r", (e, )) break except mbngs.MusicBrainzException as e: logger.warning("musicbrainz exception: %r", (e, )) time.sleep(5) continue if not metadatas: logger.warning('continuing without metadata') if metadatas: deltas = {} print('\nMatching releases:') for metadata in metadatas: print('\nArtist : %s' % metadata.artist) print('Title : %s' % metadata.title) print('Duration: %s' % common.formatTime( metadata.duration / 1000.0)) print('URL : %s' % metadata.url) print('Release : %s' % metadata.mbid) print('Type : %s' % metadata.releaseType) if metadata.barcode: print("Barcode : %s" % metadata.barcode) # TODO: Add test for non ASCII catalog numbers: see issue #215 if metadata.catalogNumber: print("Cat no : %s" % metadata.catalogNumber) delta = abs(metadata.duration - ittoc.duration()) if delta not in deltas: deltas[delta] = [] deltas[delta].append(metadata) lowest = None if not release and len(metadatas) > 1: # Select the release that most closely matches the duration. lowest = min(list(deltas)) if prompt: guess = (deltas[lowest])[0].mbid release = input( "\nPlease select a release [%s]: " % guess) if not release: release = guess if release: metadatas = [m for m in metadatas if m.url.endswith(release)] logger.debug('asked for release %r, only kept %r', release, metadatas) if len(metadatas) == 1: logger.info('picked requested release id %s', release) print('Artist: %s' % metadatas[0].artist) print('Title : %s' % metadatas[0].title) elif not metadatas: logger.warning("requested release id '%s', but none of " "the found releases match", release) return None else: if lowest: metadatas = deltas[lowest] # If we have multiple, make sure they match if len(metadatas) > 1: artist = metadatas[0].artist releaseTitle = metadatas[0].releaseTitle for i, metadata in enumerate(metadatas): if not artist == metadata.artist: logger.warning("artist 0: %r and artist %d: %r are " "not the same", artist, i, metadata.artist) if not releaseTitle == metadata.releaseTitle: logger.warning("title 0: %r and title %d: %r are " "not the same", releaseTitle, i, metadata.releaseTitle) if not release and len(list(deltas)) > 1: logger.warning('picked closest match in duration. ' 'Others may be wrong in MusicBrainz, ' 'please correct') print('Artist : %s' % artist) print('Title : %s' % metadatas[0].title) # Select one of the returned releases. We just pick the first one. ret = metadatas[0] else: print('Submit this disc to MusicBrainz at the above URL.') ret = None print('') return ret def getTagList(self, number, mbdiscid): """ Based on the metadata, get a dict of tags for the given track. :param number: track number (0 for HTOA) :type number: int :rtype: dict """ trackArtist = 'Unknown Artist' releaseArtist = 'Unknown Artist' disc = 'Unknown Disc' title = 'Unknown Track' if self.metadata: trackArtist = self.metadata.artist releaseArtist = self.metadata.artist disc = self.metadata.title mbidRelease = self.metadata.mbid mbidReleaseGroup = self.metadata.mbidReleaseGroup mbidReleaseArtist = self.metadata.mbidArtist if number > 0: try: track = self.metadata.tracks[number - 1] trackArtist = track.artist title = track.title mbidRecording = track.mbidRecording mbidTrack = track.mbid mbidTrackArtist = track.mbidArtist mbidWorks = track.mbidWorks except IndexError as e: logger.error('no track %d found, %r', number, e) raise else: # htoa defaults to disc's artist title = 'Hidden Track One Audio' tags = {} if number > 0: tags['MUSICBRAINZ_DISCID'] = mbdiscid if self.metadata and not self.metadata.various: tags['ALBUMARTIST'] = releaseArtist tags['ARTIST'] = trackArtist tags['TITLE'] = title tags['ALBUM'] = disc tags['TRACKNUMBER'] = '%s' % number if self.metadata: if self.metadata.release is not None: tags['DATE'] = self.metadata.release if number > 0: tags['MUSICBRAINZ_RELEASETRACKID'] = mbidTrack tags['MUSICBRAINZ_TRACKID'] = mbidRecording tags['MUSICBRAINZ_ARTISTID'] = mbidTrackArtist tags['MUSICBRAINZ_ALBUMID'] = mbidRelease tags['MUSICBRAINZ_RELEASEGROUPID'] = mbidReleaseGroup tags['MUSICBRAINZ_ALBUMARTISTID'] = mbidReleaseArtist if len(mbidWorks) > 0: tags['MUSICBRAINZ_WORKID'] = mbidWorks # TODO/FIXME: ISRC tag return tags def getHTOA(self): """ Check if we have hidden track one audio. :returns: tuple of (start, stop), or None """ track = self.result.table.tracks[0] try: index = track.getIndex(0) except KeyError: return None start = index.absolute stop = track.getIndex(1).absolute - 1 return start, stop @staticmethod def verifyTrack(runner, trackResult): is_wave = not trackResult.filename.endswith('.flac') t = checksum.CRC32Task(trackResult.filename, is_wave=is_wave) try: runner.run(t) except task.TaskException as e: if isinstance(e.exception, common.MissingFrames): logger.warning('missing frames for %r', trackResult.filename) return False else: raise ret = trackResult.testcrc == t.checksum logger.debug('verifyTrack: track result crc %r, file crc %r, ' 'result %r', trackResult.testcrc, t.checksum, ret) return ret def ripTrack(self, runner, trackResult, offset, device, taglist, overread, what=None): """ Ripping the track may change the track's filename as stored in trackResult. :param trackResult: the object to store information in. :type trackResult: result.TrackResult """ if trackResult.number == 0: start, stop = self.getHTOA() else: start = self.result.table.getTrackStart(trackResult.number) stop = self.result.table.getTrackEnd(trackResult.number) dirname = os.path.dirname(trackResult.filename) os.makedirs(dirname, exist_ok=True) if not what: what = 'track %d' % (trackResult.number, ) t = cdparanoia.ReadVerifyTrackTask(trackResult.filename, self.result.table, start, stop, overread, offset=offset, device=device, taglist=taglist, what=what) runner.run(t) logger.debug('ripped track') logger.debug('test speed %.3f/%.3f seconds', t.testspeed, t.testduration) logger.debug('copy speed %.3f/%.3f seconds', t.copyspeed, t.copyduration) trackResult.testcrc = t.testchecksum trackResult.copycrc = t.copychecksum trackResult.peak = t.peak trackResult.quality = t.quality trackResult.testspeed = t.testspeed trackResult.copyspeed = t.copyspeed # we want rerips to add cumulatively to the time trackResult.testduration += t.testduration trackResult.copyduration += t.copyduration if trackResult.filename != t.path: trackResult.filename = t.path logger.info('filename changed to %r', trackResult.filename) def verifyImage(self, runner, table): """ verify table against accuraterip and cue_path track lengths Verify our image against the given AccurateRip responses. Needs an initialized self.result. Will set accurip and friends on each TrackResult. Populates self.result.tracks with above TrackResults. """ cueImage = image.Image(self.cuePath) # assigns track lengths verifytask = image.ImageVerifyTask(cueImage) runner.run(verifytask) if verifytask.exception: logger.error(verifytask.exceptionMessage) return False responses = accurip.get_db_entry(table.accuraterip_path()) logger.info('%d AccurateRip response(s) found', len(responses)) checksums = accurip.calculate_checksums([ os.path.join(os.path.dirname(self.cuePath), t.indexes[1].path) for t in [t for t in cueImage.cue.table.tracks if t.number != 0] ]) if not (checksums and any(checksums['v1']) and any(checksums['v2'])): return False return accurip.verify_result(self.result, responses, checksums) def write_m3u(self, discname): m3uPath = common.truncate_filename(discname + '.m3u') with open(m3uPath, 'w') as f: f.write('#EXTM3U\n') for track in self.result.tracks: if not track.filename: # false positive htoa continue if track.number == 0: length = (self.result.table.getTrackStart(1) / common.FRAMES_PER_SECOND) else: length = (self.result.table.getTrackLength(track.number) / common.FRAMES_PER_SECOND) target_path = common.getRelativePath(track.filename, m3uPath) u = '#EXTINF:%d,%s\n' % (length, target_path) f.write(u) u = '%s\n' % target_path f.write(u) def writeCue(self, discName): assert self.result.table.canCue() cuePath = common.truncate_filename(discName + '.cue') logger.debug('write .cue file to %s', cuePath) handle = open(cuePath, 'w') # FIXME: do we always want utf-8 ? handle.write(self.result.table.cue(cuePath)) handle.close() self.cuePath = cuePath return cuePath def writeLog(self, discName, txt_logger): logPath = common.truncate_filename(discName + '.log') handle = open(logPath, 'w') log = txt_logger.log(self.result) handle.write(log) handle.close() self.logPath = logPath return logPath whipper-0.9.0/whipper/common/renamer.py000066400000000000000000000144001357173224400201560ustar00rootroot00000000000000# -*- Mode: Python; test-case-name: whipper.test.test_common_renamer -*- # vi:si:et:sw=4:sts=4:ts=4 # 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 os import tempfile """Rename files on file system and inside metafiles in a resumable way.""" class Operator: def __init__(self, statePath, key): self._todo = [] self._done = [] self._statePath = statePath self._key = key self._resuming = False def addOperation(self, operation): """ Add an operation. """ self._todo.append(operation) def load(self): """ Load state from the given state path using the given key. Verifies the state. """ todo = os.path.join(self._statePath, self._key + '.todo') lines = [] with open(todo, 'r') as handle: for line in handle.readlines(): lines.append(line) name, data = line.split(' ', 1) cls = globals()[name] operation = cls.deserialize(data) self._todo.append(operation) done = os.path.join(self._statePath, self._key + '.done') if os.path.exists(done): with open(done, 'r') as handle: for i, line in enumerate(handle.readlines()): assert line == lines[i], "line %s is different than %s" % ( line, lines[i]) self._done.append(self._todo[i]) # last task done is i; check if the next one might have gotten done. self._resuming = True def save(self): """ Saves the state to the given state path using the given key. """ # only save todo first time todo = os.path.join(self._statePath, self._key + '.todo') if not os.path.exists(todo): with open(todo, 'w') as handle: for o in self._todo: name = o.__class__.__name__ data = o.serialize() handle.write('%s %s\n' % (name, data)) # save done every time done = os.path.join(self._statePath, self._key + '.done') with open(done, 'w') as handle: for o in self._done: name = o.__class__.__name__ data = o.serialize() handle.write('%s %s\n' % (name, data)) def start(self): """ Execute the operations """ def __next__(self): operation = self._todo[len(self._done)] if self._resuming: operation.redo() self._resuming = False else: operation.do() self._done.append(operation) self.save() class FileRenamer(Operator): def addRename(self, source, destination): """ Add a rename operation. :param source: source filename :type source: str :param destination: destination filename :type destination: str """ class Operation: def verify(self): """ Check if the operation will succeed in the current conditions. Consider this a pre-flight check. Does not eliminate the need to handle errors as they happen. """ def do(self): """ Perform the operation. """ pass def redo(self): """ Perform the operation, without knowing if it already has been (partly) performed. """ self.do() def serialize(self): """ Serialize the operation. The return value should bu usable with :any:`deserialize` :rtype: str """ def deserialize(cls, data): """ Deserialize the operation with the given operation data. :type data: str """ raise NotImplementedError deserialize = classmethod(deserialize) class RenameFile(Operation): def __init__(self, source, destination): self._source = source self._destination = destination def verify(self): assert os.path.exists(self._source) assert not os.path.exists(self._destination) def do(self): os.rename(self._source, self._destination) def serialize(self): return '"%s" "%s"' % (self._source, self._destination) def deserialize(cls, data): _, source, __, destination, ___ = data.split('"') return RenameFile(source, destination) deserialize = classmethod(deserialize) def __eq__(self, other): return self._source == other._source \ and self._destination == other._destination class RenameInFile(Operation): def __init__(self, path, source, destination): self._path = path self._source = source self._destination = destination def verify(self): assert os.path.exists(self._path) # check if the source exists in the given file def do(self): with open(self._path) as handle: (fd, name) = tempfile.mkstemp(suffix='.whipper') for s in handle: os.write(fd, s.replace(self._source, self._destination).encode()) os.close(fd) os.rename(name, self._path) def serialize(self): return '"%s" "%s" "%s"' % (self._path, self._source, self._destination) def deserialize(cls, data): _, path, __, source, ___, destination, ____ = data.split('"') return RenameInFile(path, source, destination) deserialize = classmethod(deserialize) def __eq__(self, other): return self._source == other._source \ and self._destination == other._destination \ and self._path == other._path whipper-0.9.0/whipper/common/task.py000066400000000000000000000071531357173224400174760ustar00rootroot00000000000000# -*- Mode: Python -*- # vi:si:et:sw=4:sts=4:ts=4 import os import signal import subprocess from whipper.extern import asyncsub from whipper.extern.task import task import logging logger = logging.getLogger(__name__) class SyncRunner(task.SyncRunner): pass class LoggableTask(task.Task): pass class LoggableMultiSeparateTask(task.MultiSeparateTask): pass class PopenTask(task.Task): """ I am a task that runs a command using Popen. """ logCategory = 'PopenTask' bufsize = 1024 command = None cwd = None def start(self, runner): task.Task.start(self, runner) try: self._popen = asyncsub.Popen(self.command, bufsize=self.bufsize, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True, cwd=self.cwd) except OSError as e: import errno if e.errno == errno.ENOENT: self.commandMissing() raise logger.debug('started %r with pid %d', self.command, self._popen.pid) self.schedule(1.0, self._read, runner) def _read(self, runner): try: read = False ret = self._popen.recv() if ret: logger.debug("read from stdout: %s", ret) self.readbytesout(ret) read = True ret = self._popen.recv_err() if ret: logger.debug("read from stderr: %s", ret) self.readbyteserr(ret) read = True # if we read anything, we might have more to read, so # reschedule immediately if read and self.runner: self.schedule(0.0, self._read, runner) return # if we didn't read anything, give the command more time to # produce output if self._popen.poll() is None and self.runner: # not finished yet self.schedule(1.0, self._read, runner) return self._done() # FIXME: catching too general exception (Exception) except Exception as e: logger.debug('exception during _read(): %s', e) self.setException(e) self.stop() def _done(self): assert self._popen.returncode is not None, "No returncode" if self._popen.returncode >= 0: logger.debug('return code was %d', self._popen.returncode) else: logger.debug('terminated with signal %d', -self._popen.returncode) self.setProgress(1.0) if self._popen.returncode != 0: self.failed() else: self.done() self.stop() return def abort(self): logger.debug('aborting, sending SIGTERM to %d', self._popen.pid) os.kill(self._popen.pid, signal.SIGTERM) # self.stop() def readbytesout(self, bytes_stdout): """ Called when bytes have been read from stdout. """ pass def readbyteserr(self, bytes_stderr): """ Called when bytes have been read from stderr. """ pass def done(self): """ Called when the command completed successfully. """ pass def failed(self): """ Called when the command failed. """ pass def commandMissing(self): """ Called when the command is missing. """ pass whipper-0.9.0/whipper/extern/000077500000000000000000000000001357173224400161715ustar00rootroot00000000000000whipper-0.9.0/whipper/extern/__init__.py000066400000000000000000000000001357173224400202700ustar00rootroot00000000000000whipper-0.9.0/whipper/extern/asyncsub.py000066400000000000000000000101561357173224400203750ustar00rootroot00000000000000# -*- Mode: Python -*- # vi:si:et:sw=4:sts=4:ts=4 # from http://code.activestate.com/recipes/440554/ import os import subprocess import errno import time import sys PIPE = subprocess.PIPE if sys.platform == 'win32': from win32file import ReadFile, WriteFile from win32pipe import PeekNamedPipe import msvcrt else: import select import fcntl class Popen(subprocess.Popen): def recv(self, maxsize=None): return self._recv('stdout', maxsize) def recv_err(self, maxsize=None): return self._recv('stderr', maxsize) def send_recv(self, in_put='', maxsize=None): return self.send(in_put), self.recv(maxsize), self.recv_err(maxsize) def get_conn_maxsize(self, which, maxsize): if maxsize is None: maxsize = 1024 elif maxsize < 1: maxsize = 1 return getattr(self, which), maxsize def _close(self, which): getattr(self, which).close() setattr(self, which, None) if sys.platform == 'win32': def send(self, in_put): if not self.stdin: return None try: x = msvcrt.get_osfhandle(self.stdin.fileno()) (errCode, written) = WriteFile(x, in_put) except ValueError: return self._close('stdin') except (subprocess.pywintypes.error, Exception) as why: if why.args[0] in (109, errno.ESHUTDOWN): return self._close('stdin') raise return written def _recv(self, which, maxsize): conn, maxsize = self.get_conn_maxsize(which, maxsize) if conn is None: return None try: x = msvcrt.get_osfhandle(conn.fileno()) (read, nAvail, nMessage) = PeekNamedPipe(x, 0) if maxsize < nAvail: nAvail = maxsize if nAvail > 0: (errCode, read) = ReadFile(x, nAvail, None) except ValueError: return self._close(which) except (subprocess.pywintypes.error, Exception) as why: if why.args[0] in (109, errno.ESHUTDOWN): return self._close(which) raise if self.universal_newlines: read = self._translate_newlines(read) return read else: def send(self, in_put): if not self.stdin: return None if not select.select([], [self.stdin], [], 0)[1]: return 0 try: written = os.write(self.stdin.fileno(), in_put) except OSError as why: if why.args[0] == errno.EPIPE: # broken pipe return self._close('stdin') raise return written def _recv(self, which, maxsize): conn, maxsize = self.get_conn_maxsize(which, maxsize) if conn is None: return None flags = fcntl.fcntl(conn, fcntl.F_GETFL) if not conn.closed: fcntl.fcntl(conn, fcntl.F_SETFL, flags | os.O_NONBLOCK) try: if not select.select([conn], [], [], 0)[0]: return '' r = conn.read(maxsize) if not r: return self._close(which) if self.universal_newlines: r = self._translate_newlines(r) return r finally: if not conn.closed: fcntl.fcntl(conn, fcntl.F_SETFL, flags) message = "Other end disconnected!" def recv_some(p, t=.1, e=1, tr=5, stderr=0): if tr < 1: tr = 1 x = time.time() + t y = [] r = '' pr = p.recv if stderr: pr = p.recv_err while time.time() < x or r: r = pr() if r is None: if e: raise Exception(message) else: break elif r: y.append(r) else: time.sleep(max((x - time.time()) / tr, 0)) return ''.join(x.decode() for x in y).encode() whipper-0.9.0/whipper/extern/freedb.py000066400000000000000000000162051357173224400177760ustar00rootroot00000000000000# Audio Tools, a module and set of tools for manipulating audio data # Copyright (C) 2007-2016 Brian Langenberger # This program 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 2 of the License, or # (at your option) any later version. # This program 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 this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # USA def digit_sum(i): """returns the sum of all digits for the given integer""" return sum(map(int, str(i))) class DiscID: def __init__(self, offsets, total_length, track_count, playable_length): """offsets is a list of track offsets, in CD frames total_length is the total length of the disc, in seconds track_count is the total number of tracks on the disc playable_length is the playable length of the disc, in seconds the first three items are for generating the hex disc ID itself while the last is for performing queries""" assert(len(offsets) == track_count) for o in offsets: assert(o >= 0) self.offsets = offsets self.total_length = total_length self.track_count = track_count self.playable_length = playable_length def __repr__(self): return "DiscID({})".format( ", ".join(["{}={}".format(attr, getattr(self, attr)) for attr in ["offsets", "total_length", "track_count", "playable_length"]])) def __str__(self): return "{:08X}".format(int(self)) def __int__(self): digit_sum_ = sum([digit_sum(o // 75) for o in self.offsets]) return (((digit_sum_ % 255) << 24) | ((self.total_length & 0xFFFF) << 8) | (self.track_count & 0xFF)) def perform_lookup(disc_id, freedb_server, freedb_port): """performs a web-based lookup using a DiscID on the given freedb_server string and freedb_int port iterates over a list of MetaData objects per successful match, like: [track1, track2, ...], [track1, track2, ...], ... may raise HTTPError if an error occurs querying the server or ValueError if the server returns invalid data """ import re from time import sleep RESPONSE = re.compile(r'(\d{3}) (.+?)[\r\n]+') QUERY_RESULT = re.compile(r'(\S+) ([0-9a-fA-F]{8}) (.+)') FREEDB_LINE = re.compile(r'(\S+?)=(.+?)[\r\n]+') query = freedb_command(freedb_server, freedb_port, "query", *([disc_id.__str__(), "{:d}".format(disc_id.track_count)] + ["{:d}".format(o) for o in disc_id.offsets] + ["{:d}".format(disc_id.playable_length)])) line = next(query) response = RESPONSE.match(line) if response is None: raise ValueError("invalid response from server") else: # a list of (category, disc id, disc title) tuples matches = [] code = int(response.group(1)) if code == 200: # single exact match match = QUERY_RESULT.match(response.group(2)) if match is not None: matches.append((match.group(1), match.group(2), match.group(3))) else: raise ValueError("invalid query result") elif (code == 211) or (code == 210): # multiple exact or inexact matches line = next(query) while not line.startswith("."): match = QUERY_RESULT.match(line) if match is not None: matches.append((match.group(1), match.group(2), match.group(3))) else: raise ValueError("invalid query result") line = next(query) elif code == 202: # no match found pass else: # some error has occurred raise ValueError(response.group(2)) if len(matches) > 0: # for each result, query FreeDB for XMCD file data # XXX: Pylint, redefining argument with the local name 'disc_id' for (category, disc_id, _) in matches: sleep(1) # add a slight delay to keep the server happy query = freedb_command(freedb_server, freedb_port, "read", category, disc_id) response = RESPONSE.match(next(query)) if response is not None: # FIXME: check response code here freedb = {} line = next(query) while not line.startswith("."): if not line.startswith("#"): entry = FREEDB_LINE.match(line) if entry is not None: if entry.group(1) in freedb: freedb[entry.group(1)] += entry.group(2) else: freedb[entry.group(1)] = entry.group(2) line = next(query) yield freedb else: raise ValueError("invalid response from server") def freedb_command(freedb_server, freedb_port, cmd, *args): """given a freedb_server string, freedb_port int, command string and argument strings, yields a list of strings""" from urllib.error import URLError from urllib.request import urlopen from urllib.parse import urlencode from socket import getfqdn from whipper import __version__ as VERSION # some debug type checking assert(isinstance(cmd, str)) for arg in args: assert(isinstance(arg, str)) POST = [] # generate query to post with arguments in specific order if len(args) > 0: POST.append(("cmd", "cddb {} {}".format(cmd, " ".join(args)))) else: POST.append(("cmd", "cddb {}".format(cmd))) POST.append( ("hello", "user {} {} {}".format(getfqdn(), "whipper", VERSION))) POST.append(("proto", "6")) try: # get Request object from post request = urlopen( "http://{}:{:d}/~cddb/cddb.cgi".format(freedb_server, freedb_port), urlencode(POST).encode()) except URLError as e: raise ValueError(str(e)) try: # yield lines of output line = request.readline() while len(line) > 0: yield line.decode("UTF-8", "replace") line = request.readline() finally: request.close() whipper-0.9.0/whipper/extern/task/000077500000000000000000000000001357173224400171335ustar00rootroot00000000000000whipper-0.9.0/whipper/extern/task/ChangeLog000066400000000000000000000027101357173224400207050ustar00rootroot000000000000002012-11-18 Thomas Vander Stichele * gstreamer.py: Only set an exception once in bus_error_cb. Was triggered by morituri's checksum test, but only if multiple tests were run - got the same bus error twice. 2012-07-12 Thomas Vander Stichele * task.py: Add a debug statement. 2011-08-15 Thomas Vander Stichele * task.py: Better logging when scheduling. * gstreamer.py: If paused() returns True, don't go to playing. add a method for querying duration in the common case. 2011-08-08 Thomas Vander Stichele * task.py: Remove scrubFilename call. 2011-08-08 Thomas Vander Stichele * task.py: Pull in getExceptionMessage privately. 2011-08-05 Thomas Vander Stichele * gstreamer.py: * task.py: Don't rely on the log module; users that want to log should first subclass from a log class that implements warning/info/debug/log 2011-08-05 Thomas Vander Stichele * gstreamer.py: Document bus and pipeline. Make bus public. 2011-08-05 Thomas Vander Stichele * gstreamer.py: Add quoteParse() method. 2011-08-05 Thomas Vander Stichele * gstreamer.py: Add getPipeline() method. Base class implementation uses getPipelineDesc(). whipper-0.9.0/whipper/extern/task/__init__.py000066400000000000000000000000001357173224400212320ustar00rootroot00000000000000whipper-0.9.0/whipper/extern/task/task.py000066400000000000000000000414351357173224400204560ustar00rootroot00000000000000# -*- Mode: Python -*- # vi:si:et:sw=4:sts=4:ts=4 # 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 logging import sys from gi.repository import GLib as GLib logger = logging.getLogger(__name__) class TaskException(Exception): """ I wrap an exception that happened during task execution. """ exception = None # original exception def __init__(self, exception, message=None): self.exception = exception self.exceptionMessage = message self.args = (exception, message, ) # lifted from flumotion log module def _getExceptionMessage(exception, frame=-1, filename=None): """ Return a short message based on an exception, useful for debugging. Tries to find where the exception was triggered. """ import traceback stack = traceback.extract_tb(sys.exc_info()[2]) if filename: stack = [f for f in stack if f[0].find(filename) > -1] # badly raised exceptions can come without a stack if stack: (filename, line, func, text) = stack[frame] else: (filename, line, func, text) = ('no stack', 0, 'none', '') exc = exception.__class__.__name__ msg = "" # a shortcut to extract a useful message out of most exceptions # for now if str(exception): msg = ": %s" % str(exception) return "exception %(exc)s at %(filename)s:%(line)s: %(func)s()%(msg)s" \ % locals() class LogStub: """ I am a stub for a log interface. """ @staticmethod def log(message, *args): logger.info(message, *args) @staticmethod def debug(message, *args): logger.debug(message, *args) @staticmethod def warning(message, *args): logger.warning(message, *args) class Task(LogStub): """ I wrap a task in an asynchronous interface. I can be listened to for starting, stopping, description changes and progress updates. I communicate an error by setting self.exception to an exception and stopping myself from running. The listener can then handle the Task.exception. :cvar description: what am I doing :cvar exception: set if an exception happened during the task execution. Will be raised through run() at the end. """ logCategory = 'Task' description = 'I am doing something.' progress = 0.0 increment = 0.01 running = False runner = None exception = None exceptionMessage = None exceptionTraceback = None _listeners = None # subclass methods def start(self, runner): """ Start the task. Subclasses should chain up to me at the beginning. Subclass implementations should raise exceptions immediately in case of failure (using set(AndRaise)Exception) first, or do it later using those methods. If start doesn't raise an exception, the task should run until complete, or setException and stop(). """ self.debug('starting') self.setProgress(self.progress) self.running = True self.runner = runner self._notifyListeners('started') def stop(self): """ Stop the task. Also resets the runner on the task. Subclasses should chain up to me at the end. It is important that they do so in all cases, even when they ran into an exception of their own. Listeners will get notified that the task is stopped, whether successfully or with an exception. """ self.debug('stopping') self.running = False if not self.runner: print('ERROR: stopping task which is already stopped') import traceback traceback.print_stack() self.runner = None self.debug('reset runner to None') self._notifyListeners('stopped') # base class methods def setProgress(self, value): """ Notify about progress changes bigger than the increment. Called by subclass implementations as the task progresses. """ if (value - self.progress > self.increment or value >= 1.0 or value == 0.0): self.progress = value self._notifyListeners('progressed', value) self.debug('notifying progress: %r on %r', value, self.description) def setDescription(self, description): if description != self.description: self._notifyListeners('described', description) self.description = description # FIXME: unify? def setExceptionAndTraceback(self, exception): """ Call this to set a synthetically created exception (and not one that was actually raised and caught) """ import traceback stack = traceback.extract_stack()[:-1] (filename, line, func, text) = stack[-1] exc = exception.__class__.__name__ msg = "" # a shortcut to extract a useful message out of most exceptions # for now if str(exception): msg = ": %s" % str(exception) line = ("exception %(exc)s at %(filename)s:%(line)s: " "%(func)s()%(msg)s" % locals()) self.exception = exception self.exceptionMessage = line self.exceptionTraceback = traceback.format_exc() self.debug('set exception, %r' % self.exceptionMessage) # FIXME: remove setAndRaiseException = setExceptionAndTraceback def setException(self, exception): """ Call this to set a caught exception on the task. """ import traceback self.exception = exception self.exceptionMessage = _getExceptionMessage(exception) self.exceptionTraceback = traceback.format_exc() self.debug('set exception, %r, %r' % ( exception, self.exceptionMessage)) def schedule(self, delta, callable_task, *args, **kwargs): if not self.runner: print("ERROR: scheduling on a task that's altready stopped") import traceback traceback.print_stack() return self.runner.schedule(self, delta, callable_task, *args, **kwargs) def addListener(self, listener): """ Add a listener for task status changes. Listeners should implement started, stopped, and progressed. """ self.debug('Adding listener %r', listener) if not self._listeners: self._listeners = [] self._listeners.append(listener) def _notifyListeners(self, methodName, *args, **kwargs): if self._listeners: for l in self._listeners: method = getattr(l, methodName) try: method(self, *args, **kwargs) # FIXME: catching too general exception (Exception) except Exception as e: self.setException(e) # FIXME: should this become a real interface, like in zope ? class ITaskListener: """ I am an interface for objects listening to tasks. """ # listener callbacks def progressed(self, task, value): """ Implement me to be informed about progress. :type value: float :param value: progress, from 0.0 to 1.0 """ def described(self, task, description): """ Implement me to be informed about description changes. :type description: str :param description: description """ def started(self, task): """ Implement me to be informed about the task starting. """ def stopped(self, task): """ Implement me to be informed about the task stopping. If the task had an error, task.exception will be set. """ # this is a Dummy task that can be used to test if this works at all class DummyTask(Task): def start(self, runner): Task.start(self, runner) self.schedule(1.0, self._wind) def _wind(self): self.setProgress(min(self.progress + 0.1, 1.0)) if self.progress >= 1.0: self.stop() return self.schedule(1.0, self._wind) class BaseMultiTask(Task, ITaskListener): """ I perform multiple tasks. :ivar tasks: the tasks to run :type tasks: list of :any:`Task` """ description = 'Doing various tasks' tasks = None def __init__(self): self.tasks = [] self._task = 0 def addTask(self, task): """ Add a task. :type task: Task """ if self.tasks is None: self.tasks = [] self.tasks.append(task) def start(self, runner): """ Start tasks. Tasks can still be added while running. For example, a first task can determine how many additional tasks to run. """ Task.start(self, runner) # initialize task tracking if not self.tasks: self.warning('no tasks') self._generic = self.description self.next() def next(self): """ Start the next task. """ try: # start next task task = self.tasks[self._task] self._task += 1 self.debug('BaseMultiTask.next(): starting task %d of %d: %r', self._task, len(self.tasks), task) self.setDescription("%s (%d of %d) ..." % ( task.description, self._task, len(self.tasks))) task.addListener(self) task.start(self.runner) self.debug('BaseMultiTask.next(): started task %d of %d: %r', self._task, len(self.tasks), task) # FIXME: catching too general exception (Exception) except Exception as e: self.setException(e) self.debug('Got exception during next: %r', self.exceptionMessage) self.stop() return # ITaskListener methods def started(self, task): pass def progressed(self, task, value): pass def stopped(self, task): """ Subclasses should chain up to me at the end of their implementation. They should fall through to chaining up if there is an exception. """ self.debug('BaseMultiTask.stopped: task %r (%d of %d)', task, self.tasks.index(task) + 1, len(self.tasks)) if task.exception: self.warning('BaseMultiTask.stopped: exception %r', task.exceptionMessage) self.exception = task.exception self.exceptionMessage = task.exceptionMessage self.stop() return if self._task == len(self.tasks): self.debug('BaseMultiTask.stopped: all tasks done') self.stop() return # pick another self.debug('BaseMultiTask.stopped: pick next task') self.schedule(0, self.next) class MultiSeparateTask(BaseMultiTask): """ I perform multiple tasks. I track progress of each individual task, going back to 0 for each task. """ description = 'Doing various tasks separately' def start(self, runner): self.debug('MultiSeparateTask.start()') BaseMultiTask.start(self, runner) def next(self): self.debug('MultiSeparateTask.next()') # start next task self.progress = 0.0 # reset progress for each task BaseMultiTask.next(self) # ITaskListener methods def progressed(self, task, value): self.setProgress(value) def described(self, description): self.setDescription("%s (%d of %d) ..." % ( description, self._task, len(self.tasks))) class MultiCombinedTask(BaseMultiTask): """ I perform multiple tasks. I track progress as a combined progress on all tasks on task granularity. """ description = 'Doing various tasks combined' _stopped = 0 # ITaskListener methods def progressed(self, task, value): self.setProgress(float(self._stopped + value) / len(self.tasks)) def stopped(self, task): self._stopped += 1 self.setProgress(float(self._stopped) / len(self.tasks)) BaseMultiTask.stopped(self, task) class TaskRunner(LogStub): """ I am a base class for task runners. Task runners should be reusable. """ logCategory = 'TaskRunner' def run(self, task): """ Run the given task. :type task: Task """ raise NotImplementedError # methods for tasks to call def schedule(self, delta, callable_task, *args, **kwargs): """ Schedule a single future call. Subclasses should implement this. :type delta: float :param delta: time in the future to schedule call for, in seconds. """ raise NotImplementedError class SyncRunner(TaskRunner, ITaskListener): """ I run the task synchronously in a GObject MainLoop. """ def __init__(self, verbose=True): self._verbose = verbose self._longest = 0 # longest string shown; for clearing def run(self, task, verbose=None, skip=False): self.debug('run task %r', task) self._task = task self._verboseRun = self._verbose if verbose is not None: self._verboseRun = verbose self._skip = skip self._loop = GLib.MainLoop() self._task.addListener(self) # only start the task after going into the mainloop, # otherwise the task might complete before we are in it GLib.timeout_add(0, self._startWrap, self._task) self.debug('run loop') self._loop.run() self.debug('done running task %r', task) if task.exception: # catch the exception message # FIXME: this gave a traceback in the logging module self.debug('raising TaskException for %r, %r' % ( task.exceptionMessage, task.exceptionTraceback)) msg = task.exceptionMessage if task.exceptionTraceback: msg += "\n" + task.exceptionTraceback raise TaskException(task.exception, message=msg) def _startWrap(self, task): # wrap task start such that we can report any exceptions and # never hang try: self.debug('start task %r' % task) task.start(self) # FIXME: catching too general exception (Exception) except Exception as e: # getExceptionMessage uses global exception state that doesn't # hang around, so store the message task.setException(e) self.debug('exception during start: %r', task.exceptionMessage) self.stopped(task) def schedule(self, task, delta, callable_task, *args, **kwargs): def c(): try: callable_task(*args, **kwargs) return False except Exception as e: self.debug('exception when calling scheduled callable %r', callable_task) task.setException(e) self.stopped(task) raise GLib.timeout_add(int(delta * 1000), c) # ITaskListener methods def progressed(self, task, value): if not self._verboseRun: return self._report() if value >= 1.0: if self._skip: self._output('%s %3d %%' % ( self._task.description, 100.0)) else: # clear with whitespace print(("%s\r" % (' ' * self._longest, )), end='') def _output(self, what, newline=False, ret=True): print(what, end='') print((' ' * (self._longest - len(what))), end='') if ret: print('\r', end='') if newline: print('') sys.stdout.flush() if len(what) > self._longest: self._longest = len(what) def described(self, task, description): if self._verboseRun: self._report() def stopped(self, task): self.debug('stopped task %r', task) self.progressed(task, 1.0) self._loop.quit() def _report(self): self._output('%s %3d %%' % ( self._task.description, self._task.progress * 100.0)) if __name__ == '__main__': task = DummyTask() runner = SyncRunner() runner.run(task) whipper-0.9.0/whipper/image/000077500000000000000000000000001357173224400157465ustar00rootroot00000000000000whipper-0.9.0/whipper/image/__init__.py000066400000000000000000000000001357173224400200450ustar00rootroot00000000000000whipper-0.9.0/whipper/image/cue.py000066400000000000000000000142221357173224400170750ustar00rootroot00000000000000# -*- Mode: Python; test-case-name: whipper.test.test_image_cue -*- # vi:si:et:sw=4:sts=4:ts=4 # 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 . """ Reading .cue files See http://digitalx.org/cuesheetsyntax.php """ import re from whipper.common import common from whipper.image import table import logging logger = logging.getLogger(__name__) _REM_RE = re.compile(r"^REM\s(\w+)\s(.*)$") _PERFORMER_RE = re.compile(r"^PERFORMER\s(.*)$") _TITLE_RE = re.compile(r"^TITLE\s(.*)$") _FILE_RE = re.compile(r""" ^FILE # FILE \s+"(?P.*)" # 'file name' in quotes \s+(?P\w+)$ # format (WAVE/MP3/AIFF/...) """, re.VERBOSE) _TRACK_RE = re.compile(r""" ^\s+TRACK # TRACK \s+(?P\d\d) # two-digit track number \s+(?P.+)$ # mode (AUDIO, MODEx/2xxx, ...) """, re.VERBOSE) _INDEX_RE = re.compile(r""" ^\s+INDEX # INDEX \s+(\d\d) # two-digit index number \s+(\d\d) # minutes :(\d\d) # seconds :(\d\d)$ # frames """, re.VERBOSE) class CueFile: """ I represent a .cue file as an object. :vartype table: table.Table :ivar table: the index table. """ logCategory = 'CueFile' def __init__(self, path): """ :type path: str """ assert isinstance(path, str), "%r is not str" % path self._path = path self._rems = {} self._messages = [] self.leadout = None self.table = table.Table() def parse(self): state = 'HEADER' currentFile = None currentTrack = None counter = 0 logger.info('parsing .cue file %r', self._path) with open(self._path) as f: content = f.readlines() for number, line in enumerate(content): line = line.rstrip() m = _REM_RE.search(line) if m: tag = m.expand('\\1') value = m.expand('\\2') if state != 'HEADER': self.message(number, 'REM %s outside of header' % tag) else: self._rems[tag] = value continue # look for FILE lines m = _FILE_RE.search(line) if m: counter += 1 filePath = m.group('name') fileFormat = m.group('format') currentFile = File(filePath, fileFormat) # look for TRACK lines m = _TRACK_RE.search(line) if m: if not currentFile: self.message(number, 'TRACK without preceding FILE') continue state = 'TRACK' trackNumber = int(m.group('track')) logger.debug('found track %d', trackNumber) currentTrack = table.Track(trackNumber) self.table.tracks.append(currentTrack) continue # look for INDEX lines m = _INDEX_RE.search(line) if m: if not currentTrack: self.message(number, 'INDEX without preceding TRACK') print('ouch') continue indexNumber = int(m.expand('\\1')) minutes = int(m.expand('\\2')) seconds = int(m.expand('\\3')) frames = int(m.expand('\\4')) frameOffset = int(frames + seconds * common.FRAMES_PER_SECOND + minutes * common.FRAMES_PER_SECOND * 60) logger.debug('found index %d of track %r in %r:%d', indexNumber, currentTrack, currentFile.path, frameOffset) # FIXME: what do we do about File's FORMAT ? currentTrack.index(indexNumber, path=currentFile.path, relative=frameOffset, counter=counter) continue def message(self, number, message): """ Add a message about a given line in the cue file. :param number: line number, counting from 0. """ self._messages.append((number + 1, message)) def getTrackLength(self, track): # returns track length in frames, or -1 if can't be determined and # complete file should be assumed # FIXME: this assumes a track can only be in one file; is this true ? i = self.table.tracks.index(track) if i == len(self.table.tracks) - 1: # last track, so no length known return -1 thisIndex = track.indexes[1] # FIXME: could be more nextIndex = self.table.tracks[i + 1].indexes[1] # FIXME: could be 0 c = thisIndex.counter if c is not None and c == nextIndex.counter: # they belong to the same source, so their relative delta is length return nextIndex.relative - thisIndex.relative # FIXME: more logic return -1 def getRealPath(self, path): """ Translate the .cue's FILE to an existing path. :type path: str """ return common.getRealPath(self._path, path) class File: """ I represent a FILE line in a cue file. """ def __init__(self, path, file_format): """ :type path: str """ assert isinstance(path, str), "%r is not str" % path self.path = path self.format = file_format def __repr__(self): return '' % (self.path, self.format) whipper-0.9.0/whipper/image/image.py000066400000000000000000000156211357173224400174070ustar00rootroot00000000000000# -*- Mode: Python; test-case-name: whipper.test.test_image_image -*- # vi:si:et:sw=4:sts=4:ts=4 # 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 . """ Wrap on-disk CD images based on the .cue file. """ import os from whipper.common import encode from whipper.common import common from whipper.image import cue, table from whipper.extern.task import task from whipper.program.soxi import AudioLengthTask import logging logger = logging.getLogger(__name__) class Image: """ :ivar table: The Table of Contents for this image. :vartype table: table.Table """ logCategory = 'Image' def __init__(self, path): """ :type path: str :param path: .cue path """ assert isinstance(path, str), "%r is not str" % path self._path = path self.cue = cue.CueFile(path) self.cue.parse() self._offsets = [] # 0 .. trackCount - 1 self._lengths = [] # 0 .. trackCount - 1 self.table = None def getRealPath(self, path): """ Translate the .cue's FILE to an existing path. :param path: .cue path """ assert isinstance(path, str), "%r is not str" % path return self.cue.getRealPath(path) def setup(self, runner): """ Do initial setup, like figuring out track lengths, and constructing the Table of Contents. """ logger.debug('setup image start') verify = ImageVerifyTask(self) logger.debug('verifying image') runner.run(verify) logger.debug('verified image') # calculate offset and length for each track # CD's have a standard lead-in time of 2 seconds; # checksums that use it should add it there if 0 in verify.lengths: offset = verify.lengths[0] else: offset = self.cue.table.tracks[0].getIndex(1).relative tracks = [] for i in range(len(self.cue.table.tracks)): length = self.cue.getTrackLength(self.cue.table.tracks[i]) if length == -1: length = verify.lengths[i + 1] t = table.Track(i + 1, audio=True) tracks.append(t) # FIXME: this probably only works for non-compliant .CUE files # where pregap is put at end of previous file t.index(1, absolute=offset, path=self.cue.table.tracks[i].getIndex(1).path, relative=0) offset += length self.table = table.Table(tracks) self.table.leadout = offset logger.debug('setup image done') class ImageVerifyTask(task.MultiSeparateTask): """ I verify a disk image and get the necessary track lengths. """ logCategory = 'ImageVerifyTask' description = "Checking tracks" lengths = None def __init__(self, image): task.MultiSeparateTask.__init__(self) self._image = image # XXX: Pylint, redefining name 'cue' from outer scope (import) cue = image.cue self._tasks = [] self.lengths = {} try: htoa = cue.table.tracks[0].indexes[0] track = cue.table.tracks[0] path = image.getRealPath(htoa.path) assert isinstance(path, str), "%r is not str" % path logger.debug('schedule scan of audio length of %r', path) taskk = AudioLengthTask(path) self.addTask(taskk) self._tasks.append((0, track, taskk)) except (KeyError, IndexError): logger.debug('no HTOA track') for trackIndex, track in enumerate(cue.table.tracks): logger.debug('verifying track %d', trackIndex + 1) index = track.indexes[1] length = cue.getTrackLength(track) if length == -1: path = image.getRealPath(index.path) assert isinstance(path, str), "%r is not str" % path logger.debug('schedule scan of audio length of %r', path) taskk = AudioLengthTask(path) self.addTask(taskk) self._tasks.append((trackIndex + 1, track, taskk)) else: logger.debug('track %d has length %d', trackIndex + 1, length) def stop(self): for trackIndex, track, taskk in self._tasks: if taskk.exception: logger.debug('subtask %r had exception %r, shutting down', taskk, taskk.exception) self.setException(taskk.exception) break if taskk.length is None: raise ValueError("Track length was not found; " "look for earlier errors " "in debug log (set RIP_DEBUG=4)") index = track.indexes[1] assert taskk.length % common.SAMPLES_PER_FRAME == 0 end = taskk.length // common.SAMPLES_PER_FRAME self.lengths[trackIndex] = end - index.relative task.MultiSeparateTask.stop(self) class ImageEncodeTask(task.MultiSeparateTask): """ I encode a disk image to a different format. """ description = "Encoding tracks" def __init__(self, image, outdir): task.MultiSeparateTask.__init__(self) self._image = image # XXX: Pylint, redefining name 'cue' from outer scope (import) cue = image.cue self._tasks = [] self.lengths = {} def add(index): path = image.getRealPath(index.path) assert isinstance(path, str), "%r is not str" % path logger.debug('schedule encode of %r', path) root, _ = os.path.splitext(os.path.basename(path)) outpath = os.path.join(outdir, root + '.' + 'flac') logger.debug('schedule encode to %r', outpath) taskk = encode.FlacEncodeTask( path, os.path.join(outdir, root + '.' + 'flac')) self.addTask(taskk) try: htoa = cue.table.tracks[0].indexes[0] logger.debug('encoding HTOA track') add(htoa) except (KeyError, IndexError): logger.debug('no HTOA track') for trackIndex, track in enumerate(cue.table.tracks): logger.debug('encoding track %d', trackIndex + 1) index = track.indexes[1] add(index) whipper-0.9.0/whipper/image/table.py000066400000000000000000000661401357173224400174160ustar00rootroot00000000000000# -*- Mode: Python; test-case-name: whipper.test.test_image_table -*- # vi:si:et:sw=4:sts=4:ts=4 # 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 . """ Wrap Table of Contents. """ import copy from urllib.parse import urlunparse, urlencode import whipper from whipper.common import common, config from whipper.extern.freedb import DiscID import logging logger = logging.getLogger(__name__) # FIXME: taken from libcdio, but no reference found for these CDTEXT_FIELDS = [ 'ARRANGER', 'COMPOSER', 'DISCID', 'GENRE', 'MESSAGE', 'ISRC', 'PERFORMER', 'SIZE_INFO', 'SONGWRITER', 'TITLE', 'TOC_INFO', 'TOC_INFO2', 'UPC_EAN', ] class Track: """ I represent a track entry in an Table. :cvar number: track number (1-based) :vartype number: int :cvar audio: whether the track is audio :vartype audio: bool :vartype indexes: dict of number -> :any:`Index` :cvar isrc: ISRC code (12 alphanumeric characters) :vartype isrc: str :cvar cdtext: dictionary of CD Text information; :any:`see CDTEXT_KEYS` :vartype cdtext: str :cvar pre_emphasis: whether track is pre-emphasised :vartype pre_emphasis: bool """ number = None audio = None indexes = None isrc = None cdtext = None session = None pre_emphasis = None def __repr__(self): return '' % self.number def __init__(self, number, audio=True, session=None): self.number = number self.audio = audio self.indexes = {} self.cdtext = {} def index(self, number, absolute=None, path=None, relative=None, counter=None): """ :type path: str or None """ if path is not None: assert isinstance(path, str), "%r is not str" % path i = Index(number, absolute, path, relative, counter) self.indexes[number] = i def getIndex(self, number): return self.indexes[number] def getFirstIndex(self): """ Get the first chronological index for this track. Typically this is INDEX 01; but it could be INDEX 00 if there's a pre-gap. """ indexes = sorted(self.indexes.keys()) return self.indexes[indexes[0]] def getLastIndex(self): indexes = sorted(self.indexes.keys()) return self.indexes[indexes[-1]] def getPregap(self): """ Returns the length of the pregap for this track. The pregap is 0 if there is no index 0, and the difference between index 1 and index 0 if there is. """ if 0 not in self.indexes: return 0 return self.indexes[1].absolute - self.indexes[0].absolute class Index: """ :cvar counter: counter for the index source; distinguishes between the matching FILE lines in .cue files for example :vartype path: str or None """ number = None absolute = None path = None relative = None counter = None def __init__(self, number, absolute=None, path=None, relative=None, counter=None): if path is not None: assert isinstance(path, str), "%r is not str" % path self.number = number self.absolute = absolute self.path = path self.relative = relative self.counter = counter def __repr__(self): return '' % ( self.number, self.absolute, self.path, self.relative, self.counter) class Table: """ I represent a table of indexes on a CD. :cvar tracks: tracks on this CD :vartype tracks: list of :any:`Track` :cvar catalog: catalog number :vartype catalog: str :vartype cdtext: dict of str -> str """ tracks = None # list of Track leadout = None # offset where the leadout starts catalog = None # catalog number; FIXME: is this UPC ? cdtext = None mbdiscid = None classVersion = 4 def __init__(self, tracks=None): if not tracks: tracks = [] self.tracks = tracks self.cdtext = {} # done this way because just having a class-defined instance var # gets overridden when unpickling self.instanceVersion = self.classVersion self.unpickled() def unpickled(self): self.logName = "Table 0x%08x v%d" % (id(self), self.instanceVersion) logger.debug('set logName') def getTrackStart(self, number): """ :param number: the track number, 1-based :type number: int :returns: the start of the given track number's index 1, in CD frames :rtype: int """ track = self.tracks[number - 1] return track.getIndex(1).absolute def getTrackEnd(self, number): """ :param number: the track number, 1-based :type number: int :returns: the end of the given track number (ie index 1 of next track) :rtype: int """ # default to end of disc end = self.leadout - 1 # if not last track, calculate it from the next track if number < len(self.tracks): end = self.tracks[number].getIndex(1).absolute - 1 # if on a session border, subtract the session leadin thisTrack = self.tracks[number - 1] nextTrack = self.tracks[number] # The session attribute of a track is None by default (session 1) # with value > 1 if the track is in another session. Py3 doesn't # allow NoneType comparisons so we compare against 1 in that case if int(nextTrack.session or 1) > int(thisTrack.session or 1): gap = self._getSessionGap(nextTrack.session) end -= gap return end def getTrackLength(self, number): """ :param number: the track number, 1-based :type number: int :returns: the length of the given track number, in CD frames :rtype: int """ return self.getTrackEnd(number) - self.getTrackStart(number) + 1 def getAudioTracks(self): """ :returns: the number of audio tracks on the CD :rtype: int """ return len([t for t in self.tracks if t.audio]) def hasDataTracks(self): """ :returns: whether this disc contains data tracks """ return len([t for t in self.tracks if not t.audio]) > 0 @staticmethod def _cddbSum(i): ret = 0 while i > 0: ret += (i % 10) i /= 10 return ret def getCDDBValues(self): """ Get all CDDB values needed to calculate disc id and lookup URL. This includes: - CDDB disc id - number of audio tracks - offset of index 1 of each track - length of disc in seconds (including data track) :rtype: list of int """ offsets = [] # cddb disc id takes into account data tracks # last byte is the number of tracks on the CD n = 0 # CD's have a standard lead-in time of 2 seconds # which gets added for CDDB disc id's delta = 2 * common.FRAMES_PER_SECOND debug = [str(len(self.tracks))] for track in self.tracks: offset = self.getTrackStart(track.number) + delta offsets.append(offset) debug.append(str(offset)) seconds = offset // common.FRAMES_PER_SECOND n += self._cddbSum(seconds) # the 'real' leadout, not offset by 150 frames last = self.tracks[-1] leadout = self.getTrackEnd(last.number) + 1 logger.debug('leadout LBA: %d', leadout) # FIXME: we can't replace these calculations with the getFrameLength # call because the start and leadout in the algorithm get rounded # before making the difference startSeconds = self.getTrackStart(1) // common.FRAMES_PER_SECOND leadoutSeconds = leadout // common.FRAMES_PER_SECOND t = leadoutSeconds - startSeconds # durationFrames = self.getFrameLength(data=True) # duration = durationFrames / common.FRAMES_PER_SECOND # assert t == duration, "%r != %r" % (t, duration) debug.append(str(leadoutSeconds + 2)) # 2 is the 150 frame cddb offset result = DiscID(offsets, t, len(self.tracks), leadoutSeconds) value = int(result) # compare this debug line to cd-discid output logger.debug('cddb values: %r', result) logger.debug('cddb disc id debug: %s', " ".join(["%08x" % value, ] + debug)) return result def getCDDBDiscId(self): """ Calculate the CDDB disc ID. :rtype: str :returns: the 8-character hexadecimal disc ID """ values = self.getCDDBValues() return "%08x" % int(values) def getMusicBrainzDiscId(self): """ Calculate the MusicBrainz disc ID. :rtype: str :returns: the 28-character base64-encoded disc ID """ if self.mbdiscid: logger.debug('getMusicBrainzDiscId: returning cached %r', self.mbdiscid) return self.mbdiscid values = self._getMusicBrainzValues() # MusicBrainz disc id does not take into account data tracks import base64 import hashlib sha1 = hashlib.sha1 sha = sha1() # number of first track sha.update(("%02X" % values[0]).encode()) # number of last track sha.update(("%02X" % values[1]).encode()) sha.update(("%08X" % values[2]).encode()) # offsets of tracks for i in range(1, 100): try: offset = values[2 + i] except IndexError: offset = 0 sha.update(("%08X" % offset).encode()) digest = sha.digest() assert len(digest) == 20, \ "digest should be 20 chars, not %d" % len(digest) # The RFC822 spec uses +, /, and = characters, all of which are special # HTTP/URL characters. To avoid the problems with dealing with that, I # (Rob) used ., _, and - # base64 altchars specify replacements for + and / result = base64.b64encode(digest, b'._').decode() # now replace = result = result.replace("=", "-") assert len(result) == 28, \ "Result should be 28 characters, not %d" % len(result) logger.debug('getMusicBrainzDiscId: returning %r', result) self.mbdiscid = result return result def getMusicBrainzSubmitURL(self): host = config.Config().get_musicbrainz_server() discid = self.getMusicBrainzDiscId() values = self._getMusicBrainzValues() query = urlencode([ ('toc', ' '.join([str(v) for v in values])), ('tracks', self.getAudioTracks()), ('id', discid), ]) return urlunparse(( 'https', host, '/cdtoc/attach', '', query, '')) def getFrameLength(self, data=False): """ Get the length in frames (excluding HTOA) :param data: whether to include the data tracks in the length """ # the 'real' leadout, not offset by 150 frames if data: last = self.tracks[-1] else: last = self.tracks[self.getAudioTracks() - 1] leadout = self.getTrackEnd(last.number) + 1 logger.debug('leadout LBA: %d', leadout) durationFrames = leadout - self.getTrackStart(1) return durationFrames def duration(self): """ Get the duration in ms for all audio tracks (excluding HTOA). """ return int(self.getFrameLength() * 1000.0 / common.FRAMES_PER_SECOND) def _getMusicBrainzValues(self): """ Get all MusicBrainz values needed to calculate disc id and submit URL. This includes: - track number of first track - number of audio tracks - leadout of disc - offset of index 1 of each track :rtype: list of int """ # MusicBrainz disc id does not take into account data tracks result = [] # number of first track result.append(1) # number of last audio track result.append(self.getAudioTracks()) leadout = self.leadout # if the disc is multi-session, last track is the data track, # and we should subtract 11250 + 150 from the last track's offset # for the leadout if self.hasDataTracks(): assert not self.tracks[-1].audio leadout = self.tracks[-1].getIndex(1).absolute - 11250 - 150 # treat leadout offset as track 0 offset result.append(150 + leadout) # offsets of tracks for i in range(1, 100): try: track = self.tracks[i - 1] if not track.audio: continue offset = track.getIndex(1).absolute + 150 result.append(offset) except IndexError: pass logger.debug('MusicBrainz values: %r', result) return result def cue(self, cuePath='', program='whipper'): """ :param cuePath: path to the cue file to be written. If empty, will treat paths as if in current directory. Dump our internal representation to a .cue file content. :rtype: str """ logger.debug('generating .cue for cuePath %r', cuePath) lines = [] def writeFile(path): targetPath = common.getRelativePath(path, cuePath) line = 'FILE "%s" WAVE' % targetPath lines.append(line) logger.debug('writeFile: %r', line) # header main = ['PERFORMER', 'TITLE'] for key in CDTEXT_FIELDS: if key not in main and key in self.cdtext: lines.append(" %s %s" % (key, self.cdtext[key])) assert self.hasTOC(), "Table does not represent a full CD TOC" lines.append('REM DISCID %s' % self.getCDDBDiscId().upper()) lines.append('REM COMMENT "%s %s"' % (program, whipper.__version__)) if self.catalog: lines.append("CATALOG %s" % self.catalog) for key in main: if key in self.cdtext: lines.append('%s "%s"' % (key, self.cdtext[key])) # FIXME: # - the first FILE statement goes before the first TRACK, even if # there is a non-file-using PREGAP # - the following FILE statements come after the last INDEX that # use that FILE; so before a next TRACK, PREGAP silence, ... # add the first FILE line; EAC always puts the first FILE # statement before TRACK 01 and any possible PRE-GAP firstTrack = self.tracks[0] index = firstTrack.getFirstIndex() indexOne = firstTrack.getIndex(1) counter = index.counter track = firstTrack while not index.path: t, i = self.getNextTrackIndex(track.number, index.number) track = self.tracks[t - 1] index = track.getIndex(i) counter = index.counter if index.path: logger.debug('counter %d, writeFile', counter) writeFile(index.path) for i, track in enumerate(self.tracks): logger.debug('track i %r, track %r', i, track) # FIXME: skip data tracks for now if not track.audio: continue indexes = sorted(track.indexes.keys()) wroteTrack = False for number in indexes: index = track.indexes[number] logger.debug('index %r, %r', number, index) # any time the source counter changes to a higher value, # write a FILE statement # it has to be higher, because we can run into the HTOA # at counter 0 here if index.counter > counter: if index.path: logger.debug('counter %d, writeFile', counter) writeFile(index.path) logger.debug('setting counter to index.counter %r', index.counter) counter = index.counter # any time we hit the first index, write a TRACK statement if not wroteTrack: wroteTrack = True line = " TRACK %02d %s" % (i + 1, 'AUDIO') lines.append(line) logger.debug('%r', line) for key in CDTEXT_FIELDS: if key in track.cdtext: lines.append(' %s "%s"' % ( key, track.cdtext[key])) if track.isrc is not None: lines.append(" ISRC %s" % track.isrc) if track.pre_emphasis is not None: lines.append(" FLAGS PRE") # handle TRACK 01 INDEX 00 specially if 0 in indexes: index00 = track.indexes[0] if i == 0: # if we have a silent pre-gap, output it if not index00.path: length = indexOne.absolute - index00.absolute lines.append(" PREGAP %s" % common.framesToMSF(length)) continue # handle any other INDEX 00 after its TRACK lines.append(" INDEX " "%02d %s" % (0, common.framesToMSF( index00.relative))) if number > 0: # index 00 is output after TRACK up above lines.append(" INDEX %02d %s" % (number, common.framesToMSF( index.relative))) lines.append("") return "\n".join(lines) # methods that modify the table def clearFiles(self): """ Clear all file backings. Resets indexes paths and relative offsets. """ # FIXME: do a loop over track indexes better, with a pythonic # construct that allows you to do for t, i in ... t = self.tracks[0].number index = self.tracks[0].getFirstIndex() i = index.number logger.debug('clearing path') while True: track = self.tracks[t - 1] index = track.getIndex(i) logger.debug('clearing path on track %d, index %d', t, i) index.path = None index.relative = None try: t, i = self.getNextTrackIndex(t, i) except IndexError: break def setFile(self, track, index, path, length, counter=None): """ Sets the given file as the source from the given index on. Will loop over all indexes that fall within the given length, to adjust the path. Assumes all indexes have an absolute offset and will raise if not. :type track: int :type index: int """ logger.debug('setFile: track %d, index %d, path %r, length %r, ' 'counter %r', track, index, path, length, counter) t = self.tracks[track - 1] i = t.indexes[index] start = i.absolute assert start is not None, "index %r is missing absolute offset" % i end = start + length - 1 # last sector that should come from this file # FIXME: check border conditions here, esp. wrt. toc's off-by-one bug while i.absolute <= end: i.path = path i.relative = i.absolute - start i.counter = counter logger.debug('setting path %r, relative %r on track %d, ' 'index %d, counter %r', path, i.relative, track, index, counter) try: track, index = self.getNextTrackIndex(track, index) t = self.tracks[track - 1] i = t.indexes[index] except IndexError: break def absolutize(self): """ Calculate absolute offsets on indexes as much as possible. Only possible for as long as tracks draw from the same file. """ t = self.tracks[0].number index = self.tracks[0].getFirstIndex() i = index.number # the first cut is the deepest counter = index.counter logger.debug('absolutizing') while True: track = self.tracks[t - 1] index = track.getIndex(i) assert track.number == t assert index.number == i if index.counter is None: logger.debug('track %d, index %d has no counter', t, i) break if index.counter != counter: logger.debug('track %d, index %d has a different counter', t, i) break logger.debug('setting absolute offset %d on track %d, index %d', index.relative, t, i) if index.absolute is not None: if index.absolute != index.relative: msg = 'Track %d, index %d had absolute %d,' \ ' overriding with %d' % ( t, i, index.absolute, index.relative) raise ValueError(msg) index.absolute = index.relative try: t, i = self.getNextTrackIndex(t, i) except IndexError: break def merge(self, other, session=2): """ Merges the given table at the end. The other table is assumed to be from an additional session, :type other: Table """ gap = self._getSessionGap(session) trackCount = len(self.tracks) sourceCounter = self.tracks[-1].getLastIndex().counter for track in other.tracks: t = copy.deepcopy(track) t.number = track.number + trackCount t.session = session for i in list(t.indexes.values()): if i.absolute is not None: i.absolute += self.leadout + gap logger.debug('fixing track %02d, index %02d, absolute %d', t.number, i.number, i.absolute) if i.counter is not None: i.counter += sourceCounter logger.debug('fixing track %02d, index %02d, counter %d', t.number, i.number, i.counter) self.tracks.append(t) self.leadout += other.leadout + gap # FIXME logger.debug('fixing leadout, now %d', self.leadout) @staticmethod def _getSessionGap(session): # From cdrecord multi-session info: # For the first additional session this is 11250 sectors # lead-out/lead-in overhead + 150 sectors for the pre-gap of the first # track after the lead-in = 11400 sectos. # For all further session this is 6750 sectors lead-out/lead-in # overhead + 150 sectors for the pre-gap of the first track after the # lead-in = 6900 sectors. gap = 11400 if session > 2: gap = 6900 return gap # lookups def getNextTrackIndex(self, track, index): """ Return the next track and index. :param track: track number, 1-based :raises IndexError: on last index :rtype: tuple of (int, int) """ t = self.tracks[track - 1] indexes = list(t.indexes) position = indexes.index(index) if position + 1 < len(indexes): return track, indexes[position + 1] track += 1 if track > len(self.tracks): raise IndexError("No index beyond track %d, index %d" % ( track - 1, index)) t = self.tracks[track - 1] indexes = list(t.indexes) return track, indexes[0] # various tests for types of Table def hasTOC(self): """ Check if the Table has a complete TOC. a TOC is a list of all tracks and their Index 01, with absolute offsets, as well as the leadout. """ if not self.leadout: logger.debug('no leadout, no TOC') return False for t in self.tracks: if 1 not in list(t.indexes): logger.debug('no index 1, no TOC') return False if t.indexes[1].absolute is None: logger.debug('no absolute index 1, no TOC') return False return True def accuraterip_ids(self): """ returns both AccurateRip disc ids as a tuple of 8-char hexadecimal strings (discid1, discid2) """ # AccurateRip does not take into account data tracks, # but does count the data track to determine the leadout offset discId1 = 0 discId2 = 0 for track in self.tracks: if not track.audio: continue offset = self.getTrackStart(track.number) discId1 += offset discId2 += (offset or 1) * track.number # also add end values, where leadout offset is one past the end # of the last track offset = self.getTrackEnd(self.tracks[-1].number) + 1 discId1 += offset discId2 += offset * (self.getAudioTracks() + 1) discId1 &= 0xffffffff discId2 &= 0xffffffff return "%08x" % discId1, "%08x" % discId2 def accuraterip_path(self): discId1, discId2 = self.accuraterip_ids() return "%s/%s/%s/dBAR-%.3d-%s-%s-%s.bin" % ( discId1[-1], discId1[-2], discId1[-3], self.getAudioTracks(), discId1, discId2, self.getCDDBDiscId() ) def canCue(self): """ Check if this table can be used to generate a .cue file """ if not self.hasTOC(): logger.debug('no TOC, cannot cue') return False for t in self.tracks: for i in list(t.indexes.values()): if i.relative is None: logger.debug('track %02d, Index %02d does not ' 'have relative', t.number, i.number) return False return True whipper-0.9.0/whipper/image/toc.py000066400000000000000000000370251357173224400171140ustar00rootroot00000000000000# -*- Mode: Python; test-case-name: whipper.test.test_image_toc -*- # vi:si:et:sw=4:sts=4:ts=4 # 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 . """ Reading .toc files The .toc file format is described in the man page of cdrdao """ import re from whipper.common import common from whipper.image import table import logging logger = logging.getLogger(__name__) # shared _CDTEXT_CANDIDATE_RE = re.compile(r'(?P\w+) "(?P.+)"') # header _CATALOG_RE = re.compile(r'^CATALOG "(?P\d+)"$') # pre emphasis _PRE_EMPHASIS_RE = re.compile(r'^PRE_EMPHASIS$') # records _TRACK_RE = re.compile(r""" ^TRACK # TRACK \s(?P.+)$ # mode (AUDIO, MODE2_FORM_MIX, MODEx/2xxx, ...) """, re.VERBOSE) _ISRC_RE = re.compile(r'^ISRC "(?P\w+)"$') # a HTOA is marked in the cdrdao's TOC as SILENCE _SILENCE_RE = re.compile(r""" ^SILENCE # SILENCE \s(?P.*)$ # pre-gap length """, re.VERBOSE) # ZERO is used as pre-gap source when switching mode _ZERO_RE = re.compile(r""" ^ZERO # ZERO \s(?P.+) # mode (AUDIO, MODEx/2xxx, ...) \s(?P.*)$ # zero length """, re.VERBOSE) _FILE_RE = re.compile(r""" ^FILE # FILE \s+"(?P.*)" # 'file name' in quotes \s+(?P.+) # start offset \s(?P.+)$ # length in frames of section """, re.VERBOSE) _DATAFILE_RE = re.compile(r""" ^DATAFILE # DATA FILE \s+"(?P.*)" # 'file name' in quotes \s+(?P\S+) # start offset \s*.* # possible // comment """, re.VERBOSE) # FIXME: start can be 0 _START_RE = re.compile(r""" ^START # START \s(?P.*)$ # pre-gap length """, re.VERBOSE) _INDEX_RE = re.compile(r""" ^INDEX # INDEX \s(?P.+)$ # start offset """, re.VERBOSE) class Sources: """ I represent the list of sources used in the .toc file. Each SILENCE and each FILE is a source. If the filename for FILE doesn't change, the counter is not increased. """ def __init__(self): self._sources = [] def append(self, counter, offset, source): """ :param counter: the source counter; updates for each different data source (silence or different file path) :type counter: int :param offset: the absolute disc offset where this source starts """ logger.debug('appending source, counter %d, abs offset %d, ' 'source %r', counter, offset, source) self._sources.append((counter, offset, source)) def get(self, offset): """ Retrieve the source used at the given offset. """ for i, (_, o, _) in enumerate(self._sources): if offset < o: return self._sources[i - 1] return self._sources[-1] def getCounterStart(self, counter): """ Retrieve the absolute offset of the first source for this counter """ for i, (c, _, _) in enumerate(self._sources): if c == counter: return self._sources[i][1] return self._sources[-1][1] class TocFile: def __init__(self, path): """ :type path: str """ assert isinstance(path, str), "%r is not str" % path self._path = path self._messages = [] self.table = table.Table() self.logName = '' % id(self) self._sources = Sources() def _index(self, currentTrack, i, absoluteOffset, trackOffset): absolute = absoluteOffset + trackOffset # this may be in a new source, so calculate relative c, _, s = self._sources.get(absolute) logger.debug('at abs offset %d, we are in source %r', absolute, s) counterStart = self._sources.getCounterStart(c) relative = absolute - counterStart currentTrack.index(i, path=s.path, absolute=absolute, relative=relative, counter=c) logger.debug('[track %02d index %02d] trackOffset %r, added %r', currentTrack.number, i, trackOffset, currentTrack.getIndex(i)) def parse(self): currentFile = None currentTrack = None state = 'HEADER' # counts sources for audio data; SILENCE/ZERO/FILE counter = 0 trackNumber = 0 indexNumber = 0 # running absolute offset: where each track starts absoluteOffset = 0 # running relative offset, relative to counter src relativeOffset = 0 # currentLength is accrued during TRACK record parsing length # of current track as parsed so far reset on each TRACK statement currentLength = 0 # accrued during TRACK record parsing, total disc totalLength = 0 # length of the pre-gap, current track in for loop pregapLength = 0 # the first track's INDEX 1 can only be gotten from the .toc # file once the first pregap is calculated; so we add INDEX 1 # at the end of each parsed TRACK record with open(self._path) as f: content = f.readlines() for number, line in enumerate(content): line = line.rstrip() # look for CDTEXT stuff in either header or tracks m = _CDTEXT_CANDIDATE_RE.search(line) if m: key = m.group('key') value = m.group('value') # usually, value is encoded with octal escapes and in latin-1 # FIXME: other encodings are possible, does cdrdao handle # them ? value = value.encode().decode('unicode_escape') if key in table.CDTEXT_FIELDS: # FIXME: consider ISRC separate for now, but this # is a limitation of our parser approach if state == 'HEADER': self.table.cdtext[key] = value logger.debug('found disc CD-Text %s: %r', key, value) elif state == 'TRACK': if key != 'ISRC' or not currentTrack \ or currentTrack.isrc is not None: logger.debug('found track CD-Text %s: %r', key, value) currentTrack.cdtext[key] = value # look for header elements m = _CATALOG_RE.search(line) if m: self.table.catalog = m.group('catalog') logger.debug("found catalog number %s", self.table.catalog) # look for TRACK lines m = _TRACK_RE.search(line) if m: state = 'TRACK' # set index 1 of previous track if there was one, using # pregapLength if applicable if currentTrack: self._index(currentTrack, 1, absoluteOffset, pregapLength) # create a new track to be filled by later lines trackNumber += 1 trackMode = m.group('mode') audio = trackMode == 'AUDIO' currentTrack = table.Track(trackNumber, audio=audio) self.table.tracks.append(currentTrack) # update running totals absoluteOffset += currentLength relativeOffset += currentLength totalLength += currentLength # FIXME: track mode logger.debug('found track %d, mode %s, at absoluteOffset %d', trackNumber, trackMode, absoluteOffset) # reset counters relative to a track currentLength = 0 indexNumber = 1 pregapLength = 0 continue # look for PRE_EMPHASIS lines m = _PRE_EMPHASIS_RE.search(line) if m: currentTrack.pre_emphasis = True logger.debug('track has PRE_EMPHASIS') # look for ISRC lines m = _ISRC_RE.search(line) if m: isrc = m.group('isrc') currentTrack.isrc = isrc logger.debug('found ISRC code %s', isrc) # look for SILENCE lines m = _SILENCE_RE.search(line) if m: length = m.group('length') logger.debug('silence of %r', length) self._sources.append(counter, absoluteOffset, None) if currentFile is not None: logger.debug('silence after file, increasing counter') counter += 1 relativeOffset = 0 currentFile = None currentLength += common.msfToFrames(length) # look for ZERO lines m = _ZERO_RE.search(line) if m: if currentFile is not None: logger.debug('zero after file, increasing counter') counter += 1 relativeOffset = 0 currentFile = None length = m.group('length') currentLength += common.msfToFrames(length) # look for FILE lines m = _FILE_RE.search(line) if m: filePath = m.group('name') start = m.group('start') length = m.group('length') logger.debug('file %s, start %r, length %r', filePath, common.msfToFrames(start), common.msfToFrames(length)) if not currentFile or filePath != currentFile.path: counter += 1 relativeOffset = 0 logger.debug('track %d, switched to new file, ' 'increased counter to %d', trackNumber, counter) currentFile = File(filePath, common.msfToFrames(start), common.msfToFrames(length)) self._sources.append(counter, absoluteOffset + currentLength, currentFile) currentLength += common.msfToFrames(length) # look for DATAFILE lines m = _DATAFILE_RE.search(line) if m: filePath = m.group('name') length = m.group('length') logger.debug('file %s, length %r', filePath, common.msfToFrames(length)) if not currentFile or filePath != currentFile.path: counter += 1 relativeOffset = 0 logger.debug('track %d, switched to new file, ' 'increased counter to %d', trackNumber, counter) # FIXME: assume that a MODE2_FORM_MIX track always starts at 0 currentFile = File(filePath, 0, common.msfToFrames(length)) self._sources.append(counter, absoluteOffset + currentLength, currentFile) currentLength += common.msfToFrames(length) # look for START lines m = _START_RE.search(line) if m: if not currentTrack: self.message(number, 'START without preceding TRACK') print('ouch') continue length = common.msfToFrames(m.group('length')) c, _, s = self._sources.get(absoluteOffset) logger.debug('at abs offset %d, we are in source %r', absoluteOffset, s) counterStart = self._sources.getCounterStart(c) relativeOffset = absoluteOffset - counterStart currentTrack.index(0, path=s and s.path or None, absolute=absoluteOffset, relative=relativeOffset, counter=c) logger.debug('[track %02d index 00] added %r', currentTrack.number, currentTrack.getIndex(0)) # store the pregapLength to add it when we index 1 for this # track on the next iteration pregapLength = length # look for INDEX lines m = _INDEX_RE.search(line) if m: if not currentTrack: self.message(number, 'INDEX without preceding TRACK') print('ouch') continue indexNumber += 1 offset = common.msfToFrames(m.group('offset')) self._index(currentTrack, indexNumber, absoluteOffset, offset) # handle index 1 of final track, if any if currentTrack: self._index(currentTrack, 1, absoluteOffset, pregapLength) # totalLength was added up to the penultimate track self.table.leadout = totalLength + currentLength logger.debug('parse: leadout: %r', self.table.leadout) def message(self, number, message): """ Add a message about a given line in the cue file. :param number: line number, counting from 0. """ self._messages.append((number + 1, message)) def getTrackLength(self, track): """ Returns the length of the given track, from its INDEX 01 to the next track's INDEX 01 """ # returns track length in frames, or -1 if can't be determined and # complete file should be assumed # FIXME: this assumes a track can only be in one file; is this true ? i = self.table.tracks.index(track) if i == len(self.table.tracks) - 1: # last track, so no length known return -1 thisIndex = track.indexes[1] # FIXME: could be more nextIndex = self.table.tracks[i + 1].indexes[1] # FIXME: could be 0 c = thisIndex.counter if c is not None and c == nextIndex.counter: # they belong to the same source, so their relative delta is length return nextIndex.relative - thisIndex.relative # FIXME: more logic return -1 def getRealPath(self, path): """ Translate the .toc's FILE to an existing path. :type path: str """ return common.getRealPath(self._path, path) class File: """ I represent a FILE line in a .toc file. """ def __init__(self, path, start, length): """ :type path: str :type start: int :param start: starting point for the track in this file, in frames :param length: length for the track in this file, in frames """ assert isinstance(path, str), "%r is not str" % path self.path = path self.start = start self.length = length def __repr__(self): return '' % (self.path, ) whipper-0.9.0/whipper/program/000077500000000000000000000000001357173224400163335ustar00rootroot00000000000000whipper-0.9.0/whipper/program/__init__.py000066400000000000000000000000001357173224400204320ustar00rootroot00000000000000whipper-0.9.0/whipper/program/arc.py000066400000000000000000000002141357173224400174470ustar00rootroot00000000000000import accuraterip def accuraterip_checksum(f, track_number, total_tracks): return accuraterip.compute(f, track_number, total_tracks) whipper-0.9.0/whipper/program/cdparanoia.py000066400000000000000000000515211357173224400210120ustar00rootroot00000000000000# -*- Mode: Python; test-case-name: whipper.test.test_program_cdparanoia -*- # vi:si:et:sw=4:sts=4:ts=4 # 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 errno import os import re import shutil import stat import subprocess import tempfile import time from whipper.common import common from whipper.common import task as ctask from whipper.extern import asyncsub from whipper.extern.task import task import logging logger = logging.getLogger(__name__) class FileSizeError(Exception): message = None """ The given path does not have the expected size. """ def __init__(self, path, message): self.args = (path, message) self.path = path self.message = message class ReturnCodeError(Exception): """ The program had a non-zero return code. """ def __init__(self, returncode): self.args = (returncode, ) self.returncode = returncode class ChecksumException(Exception): pass # example: # ##: 0 [read] @ 24696 _PROGRESS_RE = re.compile(r""" ^\#\#: (?P.+)\s # function code \[(?P.*)\]\s@\s # [function name] @ (?P\d+) # offset in words (2-byte one channel value) """, re.VERBOSE) _ERROR_RE = re.compile("^scsi_read error:") # from reading cdparanoia source code, it looks like offset is reported in # number of single-channel samples, ie. 2 bytes (word) per unit, and absolute class ProgressParser: read = 0 # last [read] frame wrote = 0 # last [wrote] frame errors = 0 # count of number of scsi errors _nframes = None # number of frames read on each [read] _firstFrames = None # number of frames read on first [read] reads = 0 # total number of reads def __init__(self, start, stop): """ :param start: first frame to rip :type start: int :param stop: last frame to rip (inclusive) :type stop: int """ self.start = start self.stop = stop # FIXME: privatize self.read = start self._reads = {} # read count for each sector def parse(self, line): """ Parse a line. """ m = _PROGRESS_RE.search(line) if m: # code = int(m.group('code')) function = m.group('function') wordOffset = int(m.group('offset')) if function == 'read': self._parse_read(wordOffset) elif function == 'wrote': self._parse_wrote(wordOffset) m = _ERROR_RE.search(line) if m: self.errors += 1 def _parse_read(self, wordOffset): if wordOffset % common.WORDS_PER_FRAME != 0: logger.debug('THOMAS: not a multiple of %d: %d', common.WORDS_PER_FRAME, wordOffset) return frameOffset = wordOffset / common.WORDS_PER_FRAME # set nframes if not yet set if self._nframes is None and self.read != 0: self._nframes = frameOffset - self.read logger.debug('set nframes to %r', self._nframes) # set firstFrames if not yet set if self._firstFrames is None: self._firstFrames = frameOffset - self.start logger.debug('set firstFrames to %r', self._firstFrames) markStart = None markEnd = None # the next unread frame (half-inclusive) # verify it either read nframes more or went back for verify if frameOffset > self.read: delta = frameOffset - self.read if self._nframes and delta != self._nframes: # my drive either reads 7 or 13 frames pass # update our read sectors hash markStart = self.read markEnd = frameOffset else: # went back to verify # we could use firstFrames as an estimate on how many frames this # read, but this lowers our track quality needlessly where # EAC still reports 100% track quality markStart = frameOffset # - self._firstFrames markEnd = frameOffset # FIXME: doing this is way too slow even for a testcase, so disable # for frame in range(markStart, markEnd): # if frame not in list(self._reads.keys()): # self._reads[frame] = 0 # self._reads[frame] += 1 # cdparanoia reads quite a bit beyond the current track before it # goes back to verify; don't count those # markStart, markEnd of 0, 21 with stop 0 should give 1 read if markEnd > self.stop + 1: markEnd = self.stop + 1 if markStart > self.stop + 1: markStart = self.stop + 1 self.reads += markEnd - markStart # update our read pointer self.read = frameOffset def _parse_wrote(self, wordOffset): # cdparanoia outputs most [wrote] calls with one word less than a frame frameOffset = (wordOffset + 1) / common.WORDS_PER_FRAME self.wrote = frameOffset def getTrackQuality(self): """ Each frame gets read twice. More than two reads for a frame reduce track quality. """ frames = self.stop - self.start + 1 # + 1 since stop is inclusive reads = self.reads logger.debug('getTrackQuality: frames %d, reads %d', frames, reads) try: # don't go over a 100% # we know that cdparanoia reads each frame at least twice return min(frames * 2.0 / reads, 1.0) except ZeroDivisionError: raise RuntimeError("cdparanoia couldn't read any frames " "for the current track") # FIXME: handle errors class ReadTrackTask(task.Task): """ I am a task that reads a track using cdparanoia. """ description = "Reading track" quality = None # set at end of reading speed = None duration = None # in seconds _MAXERROR = 100 # number of errors detected by parser def __init__(self, path, table, start, stop, overread, offset=0, device=None, action="Reading", what="track"): """ Read the given track. :param path: where to store the ripped track :type path: str :param table: table of contents of CD :type table: table.Table :param start: first frame to rip :type start: int :param stop: last frame to rip (inclusive); >= start :type stop: int :param offset: read offset, in samples :type offset: int :param device: the device to rip from :type device: str :param action: a string representing the action; e.g. Read/Verify :type action: str :param what: a string representing what's being read; e.g. Track :type what: str """ assert isinstance(path, str), "%r is not str" % path self.path = path self._table = table self._start = start self._stop = stop self._offset = offset self._parser = ProgressParser(start, stop) self._device = device self._start_time = None self._overread = overread self._buffer = "" # accumulate characters self._errors = [] self.description = "%s %s" % (action, what) def start(self, runner): task.Task.start(self, runner) # find on which track the range starts and stops startTrack = 0 startOffset = 0 stopTrack = 0 stopOffset = self._stop for i, _ in enumerate(self._table.tracks): if self._table.getTrackStart(i + 1) <= self._start: startTrack = i + 1 startOffset = self._start - self._table.getTrackStart(i + 1) if self._table.getTrackEnd(i + 1) <= self._stop: stopTrack = i + 1 stopOffset = self._stop - self._table.getTrackStart(i + 1) logger.debug('ripping from %d to %d (inclusive)', self._start, self._stop) logger.debug('starting at track %d, offset %d', startTrack, startOffset) logger.debug('stopping at track %d, offset %d', stopTrack, stopOffset) bufsize = 1024 if self._overread: argv = ["cd-paranoia", "--stderr-progress", "--sample-offset=%d" % self._offset, "--force-overread", ] else: argv = ["cd-paranoia", "--stderr-progress", "--sample-offset=%d" % self._offset, ] if self._device: argv.extend(["--force-cdrom-device", self._device, ]) argv.extend(["%d[%s]-%d[%s]" % ( startTrack, common.framesToHMSF(startOffset), stopTrack, common.framesToHMSF(stopOffset)), self.path]) logger.debug('running %s', (" ".join(argv), )) try: self._popen = asyncsub.Popen(argv, bufsize=bufsize, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) except OSError as e: if e.errno == errno.ENOENT: raise common.MissingDependencyException('cd-paranoia') raise self._start_time = time.time() self.schedule(1.0, self._read, runner) def _read(self, runner): ret = self._popen.recv_err() if not ret: if self._popen.poll() is not None: self._done() return self.schedule(0.01, self._read, runner) return self._buffer += ret.decode() # parse buffer into lines if possible, and parse them if "\n" in self._buffer: lines = self._buffer.split('\n') if lines[-1] != "\n": # last line didn't end yet self._buffer = lines[-1] del lines[-1] else: self._buffer = "" for line in lines: self._parser.parse(line) # fail if too many errors if self._parser.errors > self._MAXERROR: logger.debug('%d errors, terminating', self._parser.errors) self._popen.terminate() num = self._parser.wrote - self._start + 1 den = self._stop - self._start + 1 assert den != 0, "stop %d should be >= start %d" % ( self._stop, self._start) progress = float(num) / float(den) if progress < 1.0: self.setProgress(progress) # 0 does not give us output before we complete, 1.0 gives us output # too late self.schedule(0.01, self._read, runner) def _poll(self, runner): if self._popen.poll() is None: self.schedule(1.0, self._poll, runner) return self._done() def _done(self): end_time = time.time() self.setProgress(1.0) # check if the length matches size = os.stat(self.path)[stat.ST_SIZE] # wav header is 44 bytes offsetLength = self._stop - self._start + 1 expected = offsetLength * common.BYTES_PER_FRAME + 44 if size != expected: # FIXME: handle errors better logger.warning('file size %d did not match expected size %d', size, expected) if (size - expected) % common.BYTES_PER_FRAME == 0: logger.warning('%d frames difference', ( (size - expected) / common.BYTES_PER_FRAME)) else: logger.warning('non-integral amount of frames difference') self.setAndRaiseException(FileSizeError(self.path, "File size %d did not " "match expected " "size %d" % ( size, expected))) if not self.exception and self._popen.returncode != 0: if self._errors: print("\n".join(self._errors)) else: logger.warning('exit code %r', self._popen.returncode) self.exception = ReturnCodeError(self._popen.returncode) self.quality = self._parser.getTrackQuality() self.duration = end_time - self._start_time self.speed = (offsetLength / 75.0) / self.duration self.stop() return class ReadVerifyTrackTask(task.MultiSeparateTask): """ I am a task that reads and verifies a track using cdparanoia. I also encode the track. The path where the file is stored can be changed if necessary, for example if the file name is too long. :cvar checksum: the checksum of the track; set if they match. :cvar testchecksum: the test checksum of the track. :cvar copychecksum: the copy checksum of the track. :cvar testspeed: the test speed of the track, as a multiple of track duration. :cvar copyspeed: the copy speed of the track, as a multiple of track duration. :cvar testduration: the test duration of the track, in seconds. :cvar copyduration: the copy duration of the track, in seconds. :cvar peak: the peak level of the track """ checksum = None testchecksum = None copychecksum = None peak = None quality = None testspeed = None copyspeed = None testduration = None copyduration = None _tmpwavpath = None _tmppath = None def __init__(self, path, table, start, stop, overread, offset=0, device=None, taglist=None, what="track"): """ :param path: where to store the ripped track :type path: str :param table: table of contents of CD :type table: table.Table :param start: first frame to rip :type start: int :param stop: last frame to rip (inclusive) :type stop: int :param offset: read offset, in samples :type offset: int :param device: the device to rip from :type device: str :param taglist: a dict of tags :type taglist: dict """ task.MultiSeparateTask.__init__(self) logger.debug('creating read and verify task on %r', path) if taglist: logger.debug('read and verify with taglist %r', taglist) # FIXME: choose a dir on the same disk/dir as the final path fd, tmppath = tempfile.mkstemp(suffix='.whipper.wav') os.fchmod(fd, 0o644) os.close(fd) self._tmpwavpath = tmppath from whipper.common import checksum self.tasks = [] self.tasks.append( ReadTrackTask(tmppath, table, start, stop, overread, offset=offset, device=device, what=what)) self.tasks.append(checksum.CRC32Task(tmppath)) t = ReadTrackTask(tmppath, table, start, stop, overread, offset=offset, device=device, action="Verifying", what=what) self.tasks.append(t) self.tasks.append(checksum.CRC32Task(tmppath)) # encode to the final path + '.part' try: tmpoutpath = path + '.part' open(tmpoutpath, 'wb').close() except IOError as e: if errno.ENAMETOOLONG != e.errno: raise path = common.truncate_filename(common.shrinkPath(path)) tmpoutpath = common.truncate_filename(path + '.part') open(tmpoutpath, 'wb').close() self._tmppath = tmpoutpath self.path = path from whipper.common import encode self.tasks.append(encode.FlacEncodeTask(tmppath, tmpoutpath)) # MerlijnWajer: XXX: We run the CRC32Task on the wav file, because it's # in general stupid to run the CRC32 on the flac file since it already # has --verify. We should just get rid of this CRC32 step. # make sure our encoding is accurate self.tasks.append(checksum.CRC32Task(tmppath)) self.tasks.append(encode.SoxPeakTask(tmppath)) # TODO: Move tagging outside of cdparanoia self.tasks.append(encode.TaggingTask(tmpoutpath, taglist)) self.checksum = None def stop(self): # FIXME: maybe this kind of try-wrapping to make sure # we chain up should be handled by a parent class function ? try: if not self.exception: self.quality = max(self.tasks[0].quality, self.tasks[2].quality) self.peak = self.tasks[6].peak logger.debug('peak: %r', self.peak) self.testspeed = self.tasks[0].speed self.copyspeed = self.tasks[2].speed self.testduration = self.tasks[0].duration self.copyduration = self.tasks[2].duration self.testchecksum = c1 = self.tasks[1].checksum self.copychecksum = c2 = self.tasks[3].checksum if c1 == c2: logger.info('checksums match, %08x', c1) self.checksum = self.testchecksum else: # FIXME: detect this before encoding logger.info('checksums do not match, %08x %08x', c1, c2) self.exception = ChecksumException( 'read and verify failed: test checksum') if self.tasks[5].checksum != self.checksum: self.exception = ChecksumException( 'Encoding failed, checksum does not match') # delete the unencoded file os.unlink(self._tmpwavpath) if not self.exception: try: logger.debug('moving to final path %r', self.path) os.rename(self._tmppath, self.path) # FIXME: catching too general exception (Exception) except Exception as e: logger.debug('exception while moving to final ' 'path %r: %s', self.path, e) self.exception = e else: os.unlink(self._tmppath) else: logger.debug('stop: exception %r', self.exception) # FIXME: catching too general exception (Exception) except Exception as e: print('WARNING: unhandled exception %r' % (e, )) task.MultiSeparateTask.stop(self) _VERSION_RE = re.compile( "^cdparanoia (?P.+) release (?P.+)") def getCdParanoiaVersion(): getter = common.VersionGetter('cd-paranoia', ["cd-paranoia", "-V"], _VERSION_RE, "%(version)s %(release)s") return getter.get() _OK_RE = re.compile(r'Drive tests OK with Paranoia.') _WARNING_RE = re.compile(r'WARNING! PARANOIA MAY NOT BE') _ABORTING_RE = re.compile(r'aborting test\.') class AnalyzeTask(ctask.PopenTask): logCategory = 'AnalyzeTask' description = 'Analyzing drive caching behaviour' defeatsCache = None cwd = None _output = [] def __init__(self, device=None): # cdparanoia -A *always* writes cdparanoia.log self.cwd = tempfile.mkdtemp(suffix='.whipper.cache') self.command = ['cd-paranoia', '-A'] if device: self.command += ['-d', device] def commandMissing(self): raise common.MissingDependencyException('cd-paranoia') def readbyteserr(self, bytes_stderr): self._output.append(bytes_stderr) def done(self): if self.cwd: shutil.rmtree(self.cwd) output = "".join(o.decode() for o in self._output) m = _OK_RE.search(output) self.defeatsCache = bool(m) def failed(self): # cdparanoia exits with return code 1 if it can't determine # whether it can defeat the audio cache output = "".join(self._output) m = _WARNING_RE.search(output) if m or _ABORTING_RE.search(output): self.defeatsCache = False if self.cwd: shutil.rmtree(self.cwd) whipper-0.9.0/whipper/program/cdrdao.py000066400000000000000000000137701357173224400201510ustar00rootroot00000000000000import os import re import shutil import tempfile import subprocess from subprocess import Popen, PIPE from whipper.common.common import truncate_filename from whipper.image.toc import TocFile from whipper.extern.task import task from whipper.extern import asyncsub import logging logger = logging.getLogger(__name__) CDRDAO = 'cdrdao' _TRACK_RE = re.compile(r"^Analyzing track (?P[0-9]*) \(AUDIO\): start (?P[0-9]*:[0-9]*:[0-9]*), length (?P[0-9]*:[0-9]*:[0-9]*)") # noqa: E501 _CRC_RE = re.compile( r"Found (?P[0-9]*) Q sub-channels with CRC errors") _BEGIN_CDRDAO_RE = re.compile(r"-" * 60) _LAST_TRACK_RE = re.compile(r"^[ ]?(?P[0-9]*)") _LEADOUT_RE = re.compile( r"^Leadout AUDIO\s*[0-9]\s*[0-9]*:[0-9]*:[0-9]*\([0-9]*\)") class ProgressParser: tracks = 0 currentTrack = 0 oldline = '' # for leadout/final track number detection def parse(self, line): cdrdao_m = _BEGIN_CDRDAO_RE.match(line) if cdrdao_m: logger.debug("RE: Begin cdrdao toc-read") leadout_m = _LEADOUT_RE.match(line) if leadout_m: logger.debug("RE: Reached leadout") last_track_m = _LAST_TRACK_RE.match(self.oldline) if last_track_m: self.tracks = last_track_m.group('track') track_s = _TRACK_RE.search(line) if track_s: logger.debug("RE: Began reading track: %d", int(track_s.group('track'))) self.currentTrack = int(track_s.group('track')) crc_s = _CRC_RE.search(line) if crc_s: print("Track %d finished, " "found %d Q sub-channels with CRC errors" % (self.currentTrack, int(crc_s.group('channels')))) self.oldline = line class ReadTOCTask(task.Task): """ Task that reads the TOC of the disc using cdrdao """ description = "Reading TOC" toc = None def __init__(self, device, fast_toc=False, toc_path=None): """ Read the TOC for 'device'. :param device: block device to read TOC from :type device: str :param fast_toc: If to use fast-toc cdrdao mode :type fast_toc: bool :param toc_path: Where to save TOC if wanted. :type toc_path: str """ self.device = device self.fast_toc = fast_toc self.toc_path = toc_path self._buffer = "" # accumulate characters self._parser = ProgressParser() self.fd, self.tocfile = tempfile.mkstemp( suffix='.cdrdao.read-toc.whipper.task') def start(self, runner): task.Task.start(self, runner) os.close(self.fd) os.unlink(self.tocfile) cmd = ([CDRDAO, 'read-toc'] + (['--fast-toc'] if self.fast_toc else []) + ['--device', self.device, self.tocfile]) self._popen = asyncsub.Popen(cmd, bufsize=1024, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) self.schedule(0.01, self._read, runner) def _read(self, runner): ret = self._popen.recv_err() if not ret: if self._popen.poll() is not None: self._done() return self.schedule(0.01, self._read, runner) return self._buffer += ret.decode() # parse buffer into lines if possible, and parse them if "\n" in self._buffer: lines = self._buffer.split('\n') if lines[-1] != "\n": # last line didn't end yet self._buffer = lines[-1] del lines[-1] else: self._buffer = "" for line in lines: self._parser.parse(line) if (self._parser.currentTrack != 0 and self._parser.tracks != 0): progress = (float('%d' % self._parser.currentTrack) / float(self._parser.tracks)) if progress < 1.0: self.setProgress(progress) # 0 does not give us output before we complete, 1.0 gives us output # too late self.schedule(0.01, self._read, runner) def _poll(self, runner): if self._popen.poll() is None: self.schedule(1.0, self._poll, runner) return self._done() def _done(self): self.setProgress(1.0) self.toc = TocFile(self.tocfile) self.toc.parse() if self.toc_path is not None: t_comp = os.path.abspath(self.toc_path).split(os.sep) t_dirn = os.sep.join(t_comp[:-1]) # If the output path doesn't exist, make it recursively os.makedirs(t_dirn, exist_ok=True) t_dst = truncate_filename( os.path.join(t_dirn, t_comp[-1] + '.toc')) shutil.copy(self.tocfile, os.path.join(t_dirn, t_dst)) os.unlink(self.tocfile) self.stop() return def DetectCdr(device): """ Return whether cdrdao detects a CD-R for 'device'. """ cmd = [CDRDAO, 'disk-info', '-v1', '--device', device] logger.debug("executing %r", cmd) p = Popen(cmd, stdout=PIPE, stderr=PIPE) return 'CD-R medium : n/a' not in p.stdout.read().decode() def version(): """ Return cdrdao version as a string. """ cdrdao = Popen(CDRDAO, stderr=PIPE) _, err = cdrdao.communicate() if cdrdao.returncode != 1: logger.warning("cdrdao version detection failed: " "return code is %s", cdrdao.returncode) return None m = re.compile(r'^Cdrdao version (?P.*) - \(C\)').search( err.decode('utf-8')) if not m: logger.warning("cdrdao version detection failed: " "could not find version") return None return m.group('version') whipper-0.9.0/whipper/program/flac.py000066400000000000000000000010411357173224400176060ustar00rootroot00000000000000from subprocess import check_call, CalledProcessError import logging logger = logging.getLogger(__name__) def encode(infile, outfile): """ Encodes infile to outfile, with flac. Uses '-f' because whipper already creates the file. """ try: # TODO: Replace with Popen so that we can catch stderr and write it to # logging check_call(['flac', '--silent', '--verify', '-o', outfile, '-f', infile]) except CalledProcessError: logger.exception('flac failed') raise whipper-0.9.0/whipper/program/sox.py000066400000000000000000000016131357173224400175170ustar00rootroot00000000000000import os from subprocess import Popen, PIPE import logging logger = logging.getLogger(__name__) SOX = 'sox' def peak_level(track_path): """ Accepts a path to a sox-decodable audio file. Returns track peak level from sox ('maximum amplitude') as a float. Returns None on error. """ if not os.path.exists(track_path): logger.warning("SoX peak detection failed: file not found") return None sox = Popen([SOX, track_path, "-n", "stats", "-b", "16"], stderr=PIPE) _, err = sox.communicate() if sox.returncode: logger.warning("SoX peak detection failed: %s", sox.returncode) return None # relevant captured lines looks like this: # Min level -26215 # Max level 26215 min_level = int(err.splitlines()[2].split()[2]) max_level = int(err.splitlines()[3].split()[2]) return max(abs(min_level), abs(max_level)) whipper-0.9.0/whipper/program/soxi.py000066400000000000000000000023771357173224400177000ustar00rootroot00000000000000import os from whipper.common import common from whipper.common import task as ctask import logging logger = logging.getLogger(__name__) SOXI = 'soxi' class AudioLengthTask(ctask.PopenTask): """ I calculate the length of a track in audio samples. :cvar length: length of the decoded audio file, in audio samples. """ logCategory = 'AudioLengthTask' description = 'Getting length of audio track' length = None def __init__(self, path): """ :type path: str """ assert isinstance(path, str), "%r is not str" % path self.logName = os.path.basename(path) self.command = [SOXI, '-s', path] self._error = [] self._output = [] def commandMissing(self): raise common.MissingDependencyException('soxi') def readbytesout(self, bytes_stdout): self._output.append(bytes_stdout) def readbyteserr(self, bytes_stderr): self._error.append(bytes_stderr) def failed(self): self.setException(Exception("soxi failed: %s" % "".join(self._error))) def done(self): if self._error: logger.warning("soxi reported on stderr: %s", "".join(self._error)) self.length = int("".join(o.decode() for o in self._output)) whipper-0.9.0/whipper/program/utils.py000066400000000000000000000027201357173224400200460ustar00rootroot00000000000000import os import subprocess import logging logger = logging.getLogger(__name__) def eject_device(device): """ Eject the given device. """ logger.debug("ejecting device %s", device) try: # `eject device` prints nothing to stdout subprocess.check_output(['eject', device], stderr=subprocess.STDOUT) except subprocess.CalledProcessError as e: logger.warning("command '%s' returned with exit code '%d' (%s)", ' '.join(e.cmd), e.returncode, e.output.rstrip()) def load_device(device): """ Load the given device. """ logger.debug("loading (eject -t) device %s", device) try: # `eject -t device` prints nothing to stdout subprocess.check_output(['eject', '-t', device], stderr=subprocess.STDOUT) except subprocess.CalledProcessError as e: logger.warning("command '%s' returned with exit code '%d' (%s)", ' '.join(e.cmd), e.returncode, e.output.rstrip()) def unmount_device(device): """ Unmount the given device if it is mounted, as happens with automounted data tracks. If the given device is a symlink, the target will be checked. """ device = os.path.realpath(device) logger.debug('possibly unmount real path %r', device) proc = open('/proc/mounts').read() if device in proc: print('Device %s is mounted, unmounting' % device) os.system('umount %s' % device) whipper-0.9.0/whipper/result/000077500000000000000000000000001357173224400162025ustar00rootroot00000000000000whipper-0.9.0/whipper/result/__init__.py000066400000000000000000000000001357173224400203010ustar00rootroot00000000000000whipper-0.9.0/whipper/result/logger.py000066400000000000000000000214241357173224400200360ustar00rootroot00000000000000import time import hashlib import re import ruamel.yaml as yaml from ruamel.yaml.comments import CommentedMap as OrderedDict import whipper from whipper.common import common from whipper.result import result class WhipperLogger(result.Logger): _accuratelyRipped = 0 _inARDatabase = 0 _errors = False def log(self, ripResult, epoch=time.time()): """Returns big str: logfile joined text lines""" return self.logRip(ripResult, epoch) def logRip(self, ripResult, epoch): """Returns logfile lines list""" riplog = OrderedDict() # Ripper version riplog["Log created by"] = "whipper %s (internal logger)" % ( whipper.__version__) # Rip date date = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(epoch)).strip() riplog["Log creation date"] = date # Rip technical settings data = OrderedDict() data["Drive"] = "%s%s (revision %s)" % ( ripResult.vendor, ripResult.model, ripResult.release) data["Extraction engine"] = "cdparanoia %s" % ( ripResult.cdparanoiaVersion) data["Defeat audio cache"] = ripResult.cdparanoiaDefeatsCache data["Read offset correction"] = ripResult.offset # Currently unsupported by the official cdparanoia package # Only implemented in whipper (ripResult.overread) data["Overread into lead-out"] = True if ripResult.overread else False # Next one fully works only using the patched cdparanoia package # lines.append("Fill up missing offset samples with silence: true") data["Gap detection"] = "cdrdao %s" % ripResult.cdrdaoVersion data["CD-R detected"] = ripResult.isCdr riplog["Ripping phase information"] = data # CD metadata release = OrderedDict() release["Artist"] = ripResult.artist release["Title"] = ripResult.title data = OrderedDict() data["Release"] = release data["CDDB Disc ID"] = ripResult.table.getCDDBDiscId() data["MusicBrainz Disc ID"] = ripResult.table.getMusicBrainzDiscId() data["MusicBrainz lookup URL"] = ( ripResult.table.getMusicBrainzSubmitURL()) if ripResult.metadata: data["MusicBrainz Release URL"] = ripResult.metadata.url riplog["CD metadata"] = data # TOC section data = OrderedDict() table = ripResult.table # Test for HTOA presence htoa = None try: htoa = table.tracks[0].getIndex(0) except KeyError: pass # If True, include HTOA line into log's TOC if htoa and htoa.path: htoastart = htoa.absolute htoaend = table.getTrackEnd(0) htoalength = table.tracks[0].getIndex(1).absolute - htoastart track = OrderedDict() track["Start"] = common.framesToMSF(htoastart) track["Length"] = common.framesToMSF(htoalength) track["Start sector"] = htoastart track["End sector"] = htoaend data[0] = track # For every track include information in the TOC for t in table.tracks: start = t.getIndex(1).absolute length = table.getTrackLength(t.number) end = table.getTrackEnd(t.number) track = OrderedDict() track["Start"] = common.framesToMSF(start) track["Length"] = common.framesToMSF(length) track["Start sector"] = start track["End sector"] = end data[t.number] = track riplog["TOC"] = data # Tracks section data = OrderedDict() duration = 0.0 for t in ripResult.tracks: if not t.filename: continue track_dict, ARDB_entry, ARDB_match = self.trackLog(t) self._inARDatabase += int(ARDB_entry) self._accuratelyRipped += int(ARDB_match) data[t.number] = track_dict duration += t.testduration + t.copyduration riplog["Tracks"] = data # Status report data = OrderedDict() if self._inARDatabase == 0: message = ("None of the tracks are present in the " "AccurateRip database") else: nonHTOA = len(ripResult.tracks) if ripResult.tracks[0].number == 0: nonHTOA -= 1 if self._accuratelyRipped == 0: message = ("No tracks could be verified as accurate " "(you may have a different pressing from the " "one(s) in the database)") elif self._accuratelyRipped < nonHTOA: accurateTracks = nonHTOA - self._accuratelyRipped message = ("Some tracks could not be verified as " "accurate (%d/%d got no match)") % ( accurateTracks, nonHTOA) else: message = "All tracks accurately ripped" data["AccurateRip summary"] = message if self._errors: message = "There were errors" else: message = "No errors occurred" data["Health Status"] = message data["EOF"] = "End of status report" riplog["Conclusive status report"] = data riplog = yaml.dump( riplog, default_flow_style=False, width=4000, Dumper=yaml.RoundTripDumper ) # Add a newline after the "Log creation date" line riplog = re.sub( r'^(Log creation date: .*)$', "\\1\n", riplog, flags=re.MULTILINE ) # Add a newline after a dictionary ends and returns to top-level riplog = re.sub( r"^(\s{2})([^\n]*)\n([A-Z][^\n]+)", "\\1\\2\n\n\\3", riplog, flags=re.MULTILINE ) # Add a newline after a track closes riplog = re.sub( r"^(\s{4}[^\n]*)\n(\s{2}[0-9]+)", "\\1\n\n\\2", riplog, flags=re.MULTILINE ) # Remove single quotes around the "Log creation date" value riplog = re.sub( r"^(Log creation date: )'(.*)'", "\\1\\2", riplog, flags=re.MULTILINE ) # Log hash hasher = hashlib.sha256() hasher.update(riplog.encode("utf-8")) riplog += "\nSHA-256 hash: %s\n" % hasher.hexdigest().upper() return riplog def trackLog(self, trackResult): """Returns Tracks section lines: data picked from trackResult""" track = OrderedDict() # Filename (including path) of ripped track track["Filename"] = trackResult.filename # Pre-gap length pregap = trackResult.pregap if pregap: track["Pre-gap length"] = common.framesToMSF(pregap) # Peak level peak = trackResult.peak / 32768.0 track["Peak level"] = float("%.6f" % peak) # Pre-emphasis status # Only implemented in whipper (trackResult.pre_emphasis) track["Pre-emphasis"] = trackResult.pre_emphasis # Extraction speed if trackResult.copyspeed: track["Extraction speed"] = "%.1f X" % trackResult.copyspeed # Extraction quality if trackResult.quality and trackResult.quality > 0.001: track["Extraction quality"] = "%.2f %%" % ( trackResult.quality * 100.0, ) # Ripper Test CRC if trackResult.testcrc is not None: track["Test CRC"] = "%08X" % trackResult.testcrc # Ripper Copy CRC if trackResult.copycrc is not None: track["Copy CRC"] = "%08X" % trackResult.copycrc # AccurateRip track status ARDB_entry = 0 ARDB_match = 0 for v in ("v1", "v2"): data = OrderedDict() if trackResult.AR[v]["DBCRC"]: ARDB_entry += 1 if trackResult.AR[v]["CRC"] == trackResult.AR[v]["DBCRC"]: data["Result"] = "Found, exact match" ARDB_match += 1 else: data["Result"] = "Found, NO exact match" data["Confidence"] = trackResult.AR[v]["DBConfidence"] data["Local CRC"] = trackResult.AR[v]["CRC"].upper() data["Remote CRC"] = trackResult.AR[v]["DBCRC"].upper() elif trackResult.number != 0: data["Result"] = "Track not present in AccurateRip database" track["AccurateRip %s" % v] = data # Check if Test & Copy CRCs are equal if trackResult.testcrc == trackResult.copycrc: track["Status"] = "Copy OK" else: self._errors = True track["Status"] = "Error, CRC mismatch" return track, bool(ARDB_entry), bool(ARDB_match) whipper-0.9.0/whipper/result/result.py000066400000000000000000000100201357173224400200630ustar00rootroot00000000000000# -*- Mode: Python; test-case-name: whipper.test.test_result_result -*- # vi:si:et:sw=4:sts=4:ts=4 # 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 pkg_resources import time class TrackResult: number = None filename = None pregap = 0 # in frames pre_emphasis = None peak = 0 quality = 0.0 testspeed = 0.0 copyspeed = 0.0 testduration = 0.0 copyduration = 0.0 # 4 byte CRCs for the test and copy reads testcrc = None copycrc = None AR = None classVersion = 3 def __init__(self): """ CRC: calculated 4 byte AccurateRip CRC DBCRC: 4 byte AccurateRip CRC from the AR database DBConfidence: confidence for the matched AccurateRip DB CRC DBMaxConfidence: track's maximum confidence in the AccurateRip DB DBMaxConfidenceCRC: maximum confidence CRC """ self.AR = { 'v1': { 'CRC': None, 'DBCRC': None, 'DBConfidence': None, }, 'v2': { 'CRC': None, 'DBCRC': None, 'DBConfidence': None, }, 'DBMaxConfidence': None, 'DBMaxConfidenceCRC': None, } class RipResult: """ I hold information about the result for rips. I can be used to write log files. :cvar offset: sample read offset :cvar table: the full index table :vartype table: whipper.image.table.Table :cvar metadata: disc metadata from MusicBrainz (if available) :vartype metadata: whipper.common.mbngs.DiscMetadata :cvar vendor: vendor of the CD drive :cvar model: model of the CD drive :cvar release: release of the CD drive :cvar cdrdaoVersion: version of cdrdao used for the rip :cvar cdparanoiaVersion: version of cdparanoia used for the rip """ offset = 0 overread = None isCdr = None logger = None table = None artist = None title = None metadata = None vendor = None model = None release = None cdrdaoVersion = None cdparanoiaVersion = None cdparanoiaDefeatsCache = None classVersion = 3 def __init__(self): self.tracks = [] def getTrackResult(self, number): """ :param number: the track number (0 for HTOA) :type number: int :rtype: TrackResult """ for t in self.tracks: if t.number == number: return t return None class Logger: """ I log the result of a rip. """ def log(self, ripResult, epoch=time.time()): """ Create a log from the given ripresult. :param epoch: when the log file gets generated :type epoch: float :type ripResult: RipResult :rtype: str """ raise NotImplementedError # A setuptools-like entry point class EntryPoint: name = 'whipper' @staticmethod def load(): from whipper.result import logger return logger.WhipperLogger def getLoggers(): """ Get all logger plugins with entry point 'whipper.logger'. :rtype: dict of :class:`str` -> :any:`Logger` """ d = {} pluggables = list(pkg_resources.iter_entry_points("whipper.logger")) for entrypoint in [EntryPoint(), ] + pluggables: plugin_class = entrypoint.load() d[entrypoint.name] = plugin_class return d whipper-0.9.0/whipper/test/000077500000000000000000000000001357173224400156435ustar00rootroot00000000000000whipper-0.9.0/whipper/test/__init__.py000066400000000000000000000000001357173224400177420ustar00rootroot00000000000000whipper-0.9.0/whipper/test/bloc.cue000066400000000000000000000014041357173224400172570ustar00rootroot00000000000000REM DISCID AD0BE00D REM COMMENT "whipper 0.5.1" FILE "data.wav" WAVE TRACK 01 AUDIO PREGAP 03:22:70 INDEX 01 00:00:00 TRACK 02 AUDIO INDEX 01 04:21:74 TRACK 03 AUDIO INDEX 01 08:02:12 TRACK 04 AUDIO INDEX 01 11:57:45 TRACK 05 AUDIO INDEX 00 15:18:00 INDEX 01 15:18:72 TRACK 06 AUDIO INDEX 00 18:05:40 INDEX 01 18:06:06 TRACK 07 AUDIO INDEX 00 21:35:15 INDEX 01 21:35:32 TRACK 08 AUDIO INDEX 00 26:00:74 INDEX 01 26:01:03 TRACK 09 AUDIO INDEX 00 29:36:14 INDEX 01 29:36:25 TRACK 10 AUDIO INDEX 01 33:56:02 TRACK 11 AUDIO INDEX 00 37:48:26 INDEX 01 37:48:69 TRACK 12 AUDIO INDEX 00 41:44:45 INDEX 01 41:46:11 TRACK 13 AUDIO INDEX 00 45:56:11 INDEX 01 45:56:33 whipper-0.9.0/whipper/test/bloc.toc000066400000000000000000000026751357173224400173030ustar00rootroot00000000000000CD_DA // Track 1 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO SILENCE 03:22:70 FILE "data.wav" 0 04:21:74 START 03:22:70 // Track 2 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO FILE "data.wav" 04:21:74 03:40:13 // Track 3 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO FILE "data.wav" 08:02:12 03:55:33 // Track 4 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO FILE "data.wav" 11:57:45 03:20:30 // Track 5 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO FILE "data.wav" 15:18:00 02:47:40 START 00:00:72 // Track 6 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO FILE "data.wav" 18:05:40 03:29:50 START 00:00:41 // Track 7 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO FILE "data.wav" 21:35:15 04:25:59 START 00:00:17 // Track 8 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO FILE "data.wav" 26:00:74 03:35:15 START 00:00:04 // Track 9 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO FILE "data.wav" 29:36:14 04:19:63 START 00:00:11 // Track 10 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO FILE "data.wav" 33:56:02 03:52:24 // Track 11 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO FILE "data.wav" 37:48:26 03:56:19 START 00:00:43 // Track 12 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO FILE "data.wav" 41:44:45 04:11:41 START 00:01:41 // Track 13 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO FILE "data.wav" 45:56:11 04:43:60 START 00:00:22 whipper-0.9.0/whipper/test/breeders.cue000066400000000000000000000025501357173224400201360ustar00rootroot00000000000000REM DISCID BE08990D REM COMMENT "whipper 0.5.1" CATALOG 0652637280326 PERFORMER "THE BREEDERS" TITLE "MOUNTAIN BATTLES" FILE "data.wav" WAVE TRACK 01 AUDIO TITLE "OVERGLAZED" ISRC GBAFL0700213 INDEX 01 00:00:00 TRACK 02 AUDIO TITLE "BANG ON" ISRC GBAFL0700214 INDEX 00 02:14:51 INDEX 01 02:15:26 TRACK 03 AUDIO TITLE "NIGHT OF JOY" ISRC GBAFL0700215 INDEX 00 04:17:74 INDEX 01 04:18:34 TRACK 04 AUDIO TITLE "WE'RE GONNA RISE" ISRC GBAFL0700216 INDEX 01 07:44:22 TRACK 05 AUDIO TITLE "GERMAN STUDIES" ISRC GBAFL0700217 INDEX 01 11:37:39 TRACK 06 AUDIO TITLE "SPARK" ISRC GBAFL0700218 INDEX 00 13:51:54 INDEX 01 13:53:38 TRACK 07 AUDIO TITLE "INSTANBUL" ISRC GBAFL0700219 INDEX 00 16:31:20 INDEX 01 16:32:49 TRACK 08 AUDIO TITLE "WALK IT OFF" ISRC GBAFL0700220 INDEX 01 19:30:19 TRACK 09 AUDIO TITLE "REGLAME ESTA NOCHE" ISRC GBAFL0700221 INDEX 00 22:14:69 INDEX 01 22:16:27 TRACK 10 AUDIO TITLE "HERE NO MORE" ISRC GBAFL0700222 INDEX 00 25:06:18 INDEX 01 25:08:01 TRACK 11 AUDIO TITLE "NO WAY" ISRC GBAFL0700223 INDEX 01 27:46:64 TRACK 12 AUDIO TITLE "IT'S THE LOVE" ISRC GBAFL0700224 INDEX 01 30:19:39 TRACK 13 AUDIO TITLE "MOUNTAIN BATTLES" ISRC GBAFL0700225 INDEX 01 32:47:56 whipper-0.9.0/whipper/test/breeders.toc000066400000000000000000000056261357173224400201560ustar00rootroot00000000000000CD_DA CATALOG "0652637280326" CD_TEXT { LANGUAGE_MAP { 0: 9 } LANGUAGE 0 { TITLE "MOUNTAIN BATTLES" PERFORMER "THE BREEDERS" DISC_ID "CADD2803CD" SIZE_INFO { 1, 1, 20, 0, 16, 3, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 3, 22, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0} } } // Track 1 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAFL0700213" CD_TEXT { LANGUAGE 0 { TITLE "OVERGLAZED" PERFORMER "" } } FILE "data.wav" 0 02:14:51 // Track 2 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAFL0700214" CD_TEXT { LANGUAGE 0 { TITLE "BANG ON" PERFORMER "" } } FILE "data.wav" 02:14:51 02:03:23 START 00:00:50 // Track 3 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAFL0700215" CD_TEXT { LANGUAGE 0 { TITLE "NIGHT OF JOY" PERFORMER "" } } FILE "data.wav" 04:17:74 03:26:23 START 00:00:35 // Track 4 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAFL0700216" CD_TEXT { LANGUAGE 0 { TITLE "WE'RE GONNA RISE" PERFORMER "" } } FILE "data.wav" 07:44:22 03:53:17 // Track 5 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAFL0700217" CD_TEXT { LANGUAGE 0 { TITLE "GERMAN STUDIES" PERFORMER "" } } FILE "data.wav" 11:37:39 02:14:15 // Track 6 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAFL0700218" CD_TEXT { LANGUAGE 0 { TITLE "SPARK" PERFORMER "" } } FILE "data.wav" 13:51:54 02:39:41 START 00:01:59 // Track 7 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAFL0700219" CD_TEXT { LANGUAGE 0 { TITLE "INSTANBUL" PERFORMER "" } } FILE "data.wav" 16:31:20 02:58:74 START 00:01:29 // Track 8 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAFL0700220" CD_TEXT { LANGUAGE 0 { TITLE "WALK IT OFF" PERFORMER "" } } FILE "data.wav" 19:30:19 02:44:50 // Track 9 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAFL0700221" CD_TEXT { LANGUAGE 0 { TITLE "REGLAME ESTA NOCHE" PERFORMER "" } } FILE "data.wav" 22:14:69 02:51:24 START 00:01:33 // Track 10 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAFL0700222" CD_TEXT { LANGUAGE 0 { TITLE "HERE NO MORE" PERFORMER "" } } FILE "data.wav" 25:06:18 02:40:46 START 00:01:58 // Track 11 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAFL0700223" CD_TEXT { LANGUAGE 0 { TITLE "NO WAY" PERFORMER "" } } FILE "data.wav" 27:46:64 02:32:50 // Track 12 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAFL0700224" CD_TEXT { LANGUAGE 0 { TITLE "IT'S THE LOVE" PERFORMER "" } } FILE "data.wav" 30:19:39 02:28:17 // Track 13 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAFL0700225" CD_TEXT { LANGUAGE 0 { TITLE "MOUNTAIN BATTLES" PERFORMER "" } } FILE "data.wav" 32:47:56 03:53:66 whipper-0.9.0/whipper/test/cache/000077500000000000000000000000001357173224400167065ustar00rootroot00000000000000whipper-0.9.0/whipper/test/cache/result/000077500000000000000000000000001357173224400202245ustar00rootroot00000000000000whipper-0.9.0/whipper/test/cache/result/fe105a11.pickle000066400000000000000000000175251357173224400225520ustar00rootroot00000000000000(iwhipper.result.result RipResult p1 (dp2 S'vendor' p3 S'PLEXTOR ' p4 sS'artist' p5 S"Destiny's Child" p6 sS'title' p7 S"The Writing's on the Wall" p8 sS'tracks' p9 (lp10 (iwhipper.result.result TrackResult p11 (dp12 S'testcrc' p13 I938873735 sS'quality' p14 F1 sS'number' p15 I1 sS'filename' p16 Vwhipper/Destiny's Child - The Writing's on the Wall/01. Destiny's Child - Intro (The Writing's on the Wall).flac p17 sS'peak' p18 F0.988555908203125 sS'pregap' p19 I0 sS'copycrc' p20 I938873735 sba(iwhipper.result.result TrackResult p21 (dp22 g13 I764981533 sg14 F1 sg15 I2 sg16 Vwhipper/Destiny's Child - The Writing's on the Wall/02. Destiny's Child - So Good.flac p23 sg18 F0.988555908203125 sg19 I0 sg20 I764981533 sba(iwhipper.result.result TrackResult p24 (dp25 g13 I4263524715 sg14 F1 sg15 I3 sg16 Vwhipper/Destiny's Child - The Writing's on the Wall/03. Destiny's Child - Bills, Bills, Bills.flac p26 sg18 F0.988555908203125 sg19 I0 sg20 I4263524715 sba(iwhipper.result.result TrackResult p27 (dp28 g13 I30436888 sg14 F1 sg15 I4 sg16 Vwhipper/Destiny's Child - The Writing's on the Wall/04. Destiny's Child - Confessions (feat. Missy Elliott).flac p29 sg18 F0.9322509765625 sg19 I0 sg20 I30436888 sba(iwhipper.result.result TrackResult p30 (dp31 g13 I1780180644 sg14 F1 sg15 I5 sg16 Vwhipper/Destiny's Child - The Writing's on the Wall/05. Destiny's Child - Bug a Boo.flac p32 sg18 F0.988555908203125 sg19 I0 sg20 I1780180644 sba(iwhipper.result.result TrackResult p33 (dp34 g13 I2209417071 sg14 F1 sg15 I6 sg16 Vwhipper/Destiny's Child - The Writing's on the Wall/06. Destiny's Child - Temptation.flac p35 sg18 F0.988555908203125 sg19 I0 sg20 I2209417071 sba(iwhipper.result.result TrackResult p36 (dp37 g13 I3797794701 sg14 F1 sg15 I7 sg16 Vwhipper/Destiny's Child - The Writing's on the Wall/07. Destiny's Child - Now That She's Gone.flac p38 sg18 F0.98748779296875 sg19 I0 sg20 I3797794701 sba(iwhipper.result.result TrackResult p39 (dp40 g13 I3950896430 sg14 F1 sg15 I8 sg16 Vwhipper/Destiny's Child - The Writing's on the Wall/08. Destiny's Child - Where'd You Go.flac p41 sg18 F0.987518310546875 sg19 I0 sg20 I3950896430 sba(iwhipper.result.result TrackResult p42 (dp43 g13 I1794899732 sg14 F1 sg15 I9 sg16 Vwhipper/Destiny's Child - The Writing's on the Wall/09. Destiny's Child - Hey Ladies.flac p44 sg18 F0.98748779296875 sg19 I0 sg20 I1794899732 sba(iwhipper.result.result TrackResult p45 (dp46 g13 I1955082442 sg14 F1 sg15 I10 sg16 Vwhipper/Destiny's Child - The Writing's on the Wall/10. Destiny's Child - If You Leave (feat. Next).flac p47 sg18 F0.987518310546875 sg19 I0 sg20 I1955082442 sba(iwhipper.result.result TrackResult p48 (dp49 g13 I1068452097 sg14 F1 sg15 I11 sg16 Vwhipper/Destiny's Child - The Writing's on the Wall/11. Destiny's Child - Jumpin', Jumpin'.flac p50 sg18 F0.987518310546875 sg19 I0 sg20 I1068452097 sba(iwhipper.result.result TrackResult p51 (dp52 g13 I745616805 sg14 F1 sg15 I12 sg16 Vwhipper/Destiny's Child - The Writing's on the Wall/12. Destiny's Child - Say My Name.flac p53 sg18 F0.98748779296875 sg19 I0 sg20 I745616805 sba(iwhipper.result.result TrackResult p54 (dp55 g13 I4044254516 sg14 F1 sg15 I13 sg16 Vwhipper/Destiny's Child - The Writing's on the Wall/13. Destiny's Child - She Can't Love You.flac p56 sg18 F0.98748779296875 sg19 I0 sg20 I4044254516 sba(iwhipper.result.result TrackResult p57 (dp58 g13 I1397142025 sg14 F1 sg15 I14 sg16 Vwhipper/Destiny's Child - The Writing's on the Wall/14. Destiny's Child - Stay.flac p59 sg18 F0.98748779296875 sg19 I0 sg20 I1397142025 sba(iwhipper.result.result TrackResult p60 (dp61 g13 I379991554 sg14 F1 sg15 I15 sg16 Vwhipper/Destiny's Child - The Writing's on the Wall/15. Destiny's Child - Sweet Sixteen.flac p62 sg18 F0.98748779296875 sg19 I0 sg20 I379991554 sba(iwhipper.result.result TrackResult p63 (dp64 g13 I554404073 sg14 F1 sg15 I16 sg16 Vwhipper/Destiny's Child - The Writing's on the Wall/16. Destiny's Child - Outro (Amazing Grace... Dedicated to Andretta Tillman).flac p65 sg18 F0.987396240234375 sg19 I0 sg20 I554404073 sba(iwhipper.result.result TrackResult p66 (dp67 g13 I1166447811 sg14 F1 sg15 I17 sg16 Vwhipper/Destiny's Child - The Writing's on the Wall/17. Destiny's Child - Get on the Bus.flac p68 sg18 F0.988494873046875 sg19 I0 sg20 I1166447811 sbasS'offset' p69 I48 sS'table' p70 ccopy_reg _reconstructor p71 (cwhipper.image.table Table p72 c__builtin__ object p73 NtRp74 (dp75 S'catalog' p76 V5099749439429 p77 sg9 (lp78 (iwhipper.image.table Track p79 (dp80 S'isrc' p81 VUSSM19901053 p82 sS'audio' p83 I01 sg15 I1 sS'cdtext' p84 (dp85 sS'indexes' p86 (dp87 I1 (iwhipper.image.table Index p88 (dp89 S'relative' p90 I0 sS'path' p91 g17 sS'counter' p92 I1 sg15 I1 sS'absolute' p93 I0 sbssba(iwhipper.image.table Track p94 (dp95 g81 VUSSM19901043 p96 sg83 I01 sg15 I2 sg84 (dp97 sg86 (dp98 I1 (iwhipper.image.table Index p99 (dp100 g90 I0 sg91 g23 sg92 I2 sg15 I1 sg93 I9435 sbssba(iwhipper.image.table Track p101 (dp102 g81 VUSSM19900561 p103 sg83 I01 sg15 I3 sg84 (dp104 sg86 (dp105 I1 (iwhipper.image.table Index p106 (dp107 g90 I0 sg91 g26 sg92 I3 sg15 I1 sg93 I23980 sbssba(iwhipper.image.table Track p108 (dp109 g81 VUSSM19901050 p110 sg83 I01 sg15 I4 sg84 (dp111 sg86 (dp112 I1 (iwhipper.image.table Index p113 (dp114 g90 I0 sg91 g29 sg92 I4 sg15 I1 sg93 I43182 sbssba(iwhipper.image.table Track p115 (dp116 g81 VUSSM19901045 p117 sg83 I01 sg15 I5 sg84 (dp118 sg86 (dp119 I1 (iwhipper.image.table Index p120 (dp121 g90 I0 sg91 g32 sg92 I5 sg15 I1 sg93 I65482 sbssba(iwhipper.image.table Track p122 (dp123 g81 VUSSM19901046 p124 sg83 I01 sg15 I6 sg84 (dp125 sg86 (dp126 I1 (iwhipper.image.table Index p127 (dp128 g90 I0 sg91 g35 sg92 I6 sg15 I1 sg93 I81415 sbssba(iwhipper.image.table Track p129 (dp130 g81 VUSSM19901049 p131 sg83 I01 sg15 I7 sg84 (dp132 sg86 (dp133 I1 (iwhipper.image.table Index p134 (dp135 g90 I0 sg91 g38 sg92 I7 sg15 I1 sg93 I99835 sbssba(iwhipper.image.table Track p136 (dp137 g81 VUSSM19901048 p138 sg83 I01 sg15 I8 sg84 (dp139 sg86 (dp140 I1 (iwhipper.image.table Index p141 (dp142 g90 I0 sg91 g41 sg92 I8 sg15 I1 sg93 I124995 sbssba(iwhipper.image.table Track p143 (dp144 g81 VUSSM19901044 p145 sg83 I01 sg15 I9 sg84 (dp146 sg86 (dp147 I1 (iwhipper.image.table Index p148 (dp149 g90 I0 sg91 g44 sg92 I9 sg15 I1 sg93 I144140 sbssba(iwhipper.image.table Track p150 (dp151 g81 VUSSM19901055 p152 sg83 I01 sg15 I10 sg84 (dp153 sg86 (dp154 I1 (iwhipper.image.table Index p155 (dp156 g90 I0 sg91 g47 sg92 I10 sg15 I1 sg93 I163335 sbssba(iwhipper.image.table Track p157 (dp158 g81 VUSSM19901054 p159 sg83 I01 sg15 I11 sg84 (dp160 sg86 (dp161 I1 (iwhipper.image.table Index p162 (dp163 g90 I0 sg91 g50 sg92 I11 sg15 I1 sg93 I183972 sbssba(iwhipper.image.table Track p164 (dp165 g81 VUSSM19901056 p166 sg83 I01 sg15 I12 sg84 (dp167 sg86 (dp168 I1 (iwhipper.image.table Index p169 (dp170 g90 I0 sg91 g53 sg92 I12 sg15 I1 sg93 I201237 sbssba(iwhipper.image.table Track p171 (dp172 g81 VUSSM19901056 p173 sg83 I01 sg15 I13 sg84 (dp174 sg86 (dp175 I1 (iwhipper.image.table Index p176 (dp177 g90 I0 sg91 g56 sg92 I13 sg15 I1 sg93 I221587 sbssba(iwhipper.image.table Track p178 (dp179 g81 VUSSM19901051 p180 sg83 I01 sg15 I14 sg84 (dp181 sg86 (dp182 I1 (iwhipper.image.table Index p183 (dp184 g90 I0 sg91 g59 sg92 I14 sg15 I1 sg93 I239952 sbssba(iwhipper.image.table Track p185 (dp186 g81 VUSSM19901047 p187 sg83 I01 sg15 I15 sg84 (dp188 sg86 (dp189 I1 (iwhipper.image.table Index p190 (dp191 g90 I0 sg91 g62 sg92 I15 sg15 I1 sg93 I261795 sbssba(iwhipper.image.table Track p192 (dp193 g81 VUSSM19901052 p194 sg83 I01 sg15 I16 sg84 (dp195 sg86 (dp196 I1 (iwhipper.image.table Index p197 (dp198 g90 I0 sg91 g65 sg92 I16 sg15 I1 sg93 I280750 sbssba(iwhipper.image.table Track p199 (dp200 g83 I01 sg15 I17 sg84 (dp201 sg86 (dp202 I1 (iwhipper.image.table Index p203 (dp204 g90 I0 sg91 g68 sg92 I17 sg15 I1 sg93 I292627 sbssbasS'logName' p205 S'Table 0x023B9D90' p206 sg84 (dp207 sS'instanceVersion' p208 I2 sS'leadout' p209 I313955 sbsS'model' p210 S'DVDR PX-810SA ' p211 sb. whipper-0.9.0/whipper/test/capital.1.toc000066400000000000000000000027531357173224400201350ustar00rootroot00000000000000CD_DA // Track 1 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAAA0300350" SILENCE 05:22:20 FILE "data.wav" 0 04:32:55 START 05:22:20 // Track 2 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAAA0300351" FILE "data.wav" 04:32:55 04:16:02 START 00:01:05 // Track 3 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAAA0300352" FILE "data.wav" 08:48:57 03:03:65 START 00:01:38 // Track 4 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAAA0300353" FILE "data.wav" 11:52:47 02:16:03 START 00:01:43 // Track 5 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAAA0300355" FILE "data.wav" 14:08:50 03:32:55 START 00:01:50 // Track 6 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAAA0300356" FILE "data.wav" 17:41:30 03:09:70 START 00:01:20 // Track 7 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAAA0300357" FILE "data.wav" 20:51:25 02:27:25 START 00:01:00 // Track 8 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAAA0300358" FILE "data.wav" 23:18:50 02:46:35 START 00:00:35 // Track 9 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAAA0300359" FILE "data.wav" 26:05:10 05:02:72 START 00:00:60 // Track 10 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAAA0300360" FILE "data.wav" 31:08:07 03:50:38 START 00:00:60 // Track 11 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAAA0300361" FILE "data.wav" 34:58:45 03:35:10 START 00:00:65 whipper-0.9.0/whipper/test/capital.2.toc000066400000000000000000000001421357173224400201240ustar00rootroot00000000000000CD_ROM // Track 1 TRACK MODE1 NO COPY DATAFILE "data_1" 27:30:00 // length in bytes: 253440000 whipper-0.9.0/whipper/test/capital.fast.toc000066400000000000000000000026651357173224400207340ustar00rootroot00000000000000CD_ROM // Track 1 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAAA0300350" FILE "data.wav" 0 04:33:60 // Track 2 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAAA0300351" FILE "data.wav" 04:33:60 04:16:35 // Track 3 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAAA0300352" FILE "data.wav" 08:50:20 03:03:70 // Track 4 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAAA0300353" FILE "data.wav" 11:54:15 02:16:10 // Track 5 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAAA0300355" FILE "data.wav" 14:10:25 03:32:25 // Track 6 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAAA0300356" FILE "data.wav" 17:42:50 03:09:50 // Track 7 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAAA0300357" FILE "data.wav" 20:52:25 02:26:60 // Track 8 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAAA0300358" FILE "data.wav" 23:19:10 02:46:60 // Track 9 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAAA0300359" FILE "data.wav" 26:05:70 05:02:72 // Track 10 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAAA0300360" FILE "data.wav" 31:08:67 03:50:43 // Track 11 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBAAA0300361" FILE "data.wav" 34:59:35 06:04:20 // Track 12 TRACK MODE1 NO COPY ZERO MODE1 00:02:00 DATAFILE "data_12" 27:30:00 // length in bytes: 253440000 START 00:02:00 whipper-0.9.0/whipper/test/cdparanoia.progress000066400000000000000000001411441357173224400215370ustar00rootroot00000000000000Sending all callbacks to stderr for wrapper script cdparanoia III release 10.2 (September 11, 2008) Ripping from sector 45990 (track 4 [0:00.00]) to sector 47719 (track 4 [0:23.04]) outputting to cdda.wav ##: 0 [read] @ 54101880 ##: 0 [read] @ 54133632 ##: 0 [read] @ 54165384 ##: 0 [read] @ 54197136 ##: 0 [read] @ 54228888 ##: 0 [read] @ 54260640 ##: 0 [read] @ 54292392 ##: 0 [read] @ 54324144 ##: 0 [read] @ 54355896 ##: 0 [read] @ 54387648 ##: 0 [read] @ 54419400 ##: 0 [read] @ 54451152 ##: 0 [read] @ 54482904 ##: 0 [read] @ 54514656 ##: 0 [read] @ 54546408 ##: 0 [read] @ 54578160 ##: 0 [read] @ 54609912 ##: 0 [read] @ 54641664 ##: 0 [read] @ 54673416 ##: 0 [read] @ 54705168 ##: 0 [read] @ 54736920 ##: 0 [read] @ 54768672 ##: 0 [read] @ 54800424 ##: 0 [read] @ 54832176 ##: 0 [read] @ 54863928 ##: 0 [read] @ 54895680 ##: 0 [read] @ 54927432 ##: 0 [read] @ 54959184 ##: 0 [read] @ 54990936 ##: 0 [read] @ 55022688 ##: 0 [read] @ 55054440 ##: 0 [read] @ 55086192 ##: 0 [read] @ 55117944 ##: 0 [read] @ 55149696 ##: 0 [read] @ 55181448 ##: 0 [read] @ 55213200 ##: 0 [read] @ 55244952 ##: 0 [read] @ 55276704 ##: 0 [read] @ 55308456 ##: 0 [read] @ 55340208 ##: 0 [read] @ 55371960 ##: 0 [read] @ 55403712 ##: 0 [read] @ 55435464 ##: 0 [read] @ 55467216 ##: 0 [read] @ 55494264 ##: 0 [read] @ 54100704 ##: 0 [read] @ 54132456 ##: 0 [read] @ 54164208 ##: 0 [read] @ 54195960 ##: 0 [read] @ 54227712 ##: 0 [read] @ 54259464 ##: 0 [read] @ 54291216 ##: 0 [read] @ 54322968 ##: 0 [read] @ 54354720 ##: 0 [read] @ 54386472 ##: 0 [read] @ 54418224 ##: 0 [read] @ 54449976 ##: 0 [read] @ 54481728 ##: 0 [read] @ 54513480 ##: 0 [read] @ 54545232 ##: 0 [read] @ 54576984 ##: 0 [read] @ 54608736 ##: 0 [read] @ 54640488 ##: 0 [read] @ 54672240 ##: 0 [read] @ 54703992 ##: 0 [read] @ 54735744 ##: 0 [read] @ 54767496 ##: 0 [read] @ 54799248 ##: 0 [read] @ 54831000 ##: 0 [read] @ 54862752 ##: 0 [read] @ 54894504 ##: 0 [read] @ 54926256 ##: 0 [read] @ 54958008 ##: 0 [read] @ 54989760 ##: 0 [read] @ 55021512 ##: 0 [read] @ 55053264 ##: 0 [read] @ 55085016 ##: 0 [read] @ 55116768 ##: 0 [read] @ 55148520 ##: 0 [read] @ 55180272 ##: 0 [read] @ 55212024 ##: 0 [read] @ 55243776 ##: 0 [read] @ 55275528 ##: 0 [read] @ 55307280 ##: 0 [read] @ 55339032 ##: 0 [read] @ 55370784 ##: 0 [read] @ 55402536 ##: 0 [read] @ 55434288 ##: 0 [read] @ 55466040 ##: 0 [read] @ 55494264 ##: 1 [verify] @ 54084240 ##: -2 [wrote] @ 54085415 ##: -2 [wrote] @ 54086591 ##: -2 [wrote] @ 54087767 ##: -2 [wrote] @ 54088943 ##: -2 [wrote] @ 54090119 ##: -2 [wrote] @ 54091295 ##: -2 [wrote] @ 54092471 ##: -2 [wrote] @ 54093647 ##: -2 [wrote] @ 54094823 ##: -2 [wrote] @ 54095999 ##: -2 [wrote] @ 54097175 ##: -2 [wrote] @ 54098351 ##: -2 [wrote] @ 54099527 ##: -2 [wrote] @ 54100703 ##: -2 [wrote] @ 54101879 ##: -2 [wrote] @ 54103055 ##: -2 [wrote] @ 54104231 ##: -2 [wrote] @ 54105407 ##: -2 [wrote] @ 54106583 ##: -2 [wrote] @ 54107759 ##: -2 [wrote] @ 54108935 ##: -2 [wrote] @ 54110111 ##: -2 [wrote] @ 54111287 ##: -2 [wrote] @ 54112463 ##: -2 [wrote] @ 54113639 ##: -2 [wrote] @ 54114815 ##: -2 [wrote] @ 54115991 ##: -2 [wrote] @ 54117167 ##: -2 [wrote] @ 54118343 ##: -2 [wrote] @ 54119519 ##: -2 [wrote] @ 54120695 ##: -2 [wrote] @ 54121871 ##: -2 [wrote] @ 54123047 ##: -2 [wrote] @ 54124223 ##: -2 [wrote] @ 54125399 ##: -2 [wrote] @ 54126575 ##: -2 [wrote] @ 54127751 ##: -2 [wrote] @ 54128927 ##: -2 [wrote] @ 54130103 ##: -2 [wrote] @ 54131279 ##: -2 [wrote] @ 54132455 ##: -2 [wrote] @ 54133631 ##: -2 [wrote] @ 54134807 ##: -2 [wrote] @ 54135983 ##: -2 [wrote] @ 54137159 ##: -2 [wrote] @ 54138335 ##: -2 [wrote] @ 54139511 ##: -2 [wrote] @ 54140687 ##: -2 [wrote] @ 54141863 ##: -2 [wrote] @ 54143039 ##: -2 [wrote] @ 54144215 ##: -2 [wrote] @ 54145391 ##: -2 [wrote] @ 54146567 ##: -2 [wrote] @ 54147743 ##: -2 [wrote] @ 54148919 ##: -2 [wrote] @ 54150095 ##: -2 [wrote] @ 54151271 ##: -2 [wrote] @ 54152447 ##: -2 [wrote] @ 54153623 ##: -2 [wrote] @ 54154799 ##: -2 [wrote] @ 54155975 ##: -2 [wrote] @ 54157151 ##: -2 [wrote] @ 54158327 ##: -2 [wrote] @ 54159503 ##: -2 [wrote] @ 54160679 ##: -2 [wrote] @ 54161855 ##: -2 [wrote] @ 54163031 ##: -2 [wrote] @ 54164207 ##: -2 [wrote] @ 54165383 ##: -2 [wrote] @ 54166559 ##: -2 [wrote] @ 54167735 ##: -2 [wrote] @ 54168911 ##: -2 [wrote] @ 54170087 ##: -2 [wrote] @ 54171263 ##: -2 [wrote] @ 54172439 ##: -2 [wrote] @ 54173615 ##: -2 [wrote] @ 54174791 ##: -2 [wrote] @ 54175967 ##: -2 [wrote] @ 54177143 ##: -2 [wrote] @ 54178319 ##: -2 [wrote] @ 54179495 ##: -2 [wrote] @ 54180671 ##: -2 [wrote] @ 54181847 ##: -2 [wrote] @ 54183023 ##: -2 [wrote] @ 54184199 ##: -2 [wrote] @ 54185375 ##: -2 [wrote] @ 54186551 ##: -2 [wrote] @ 54187727 ##: -2 [wrote] @ 54188903 ##: -2 [wrote] @ 54190079 ##: -2 [wrote] @ 54191255 ##: -2 [wrote] @ 54192431 ##: -2 [wrote] @ 54193607 ##: -2 [wrote] @ 54194783 ##: -2 [wrote] @ 54195959 ##: -2 [wrote] @ 54197135 ##: -2 [wrote] @ 54198311 ##: -2 [wrote] @ 54199487 ##: -2 [wrote] @ 54200663 ##: -2 [wrote] @ 54201839 ##: -2 [wrote] @ 54203015 ##: -2 [wrote] @ 54204191 ##: -2 [wrote] @ 54205367 ##: -2 [wrote] @ 54206543 ##: -2 [wrote] @ 54207719 ##: -2 [wrote] @ 54208895 ##: -2 [wrote] @ 54210071 ##: -2 [wrote] @ 54211247 ##: -2 [wrote] @ 54212423 ##: -2 [wrote] @ 54213599 ##: -2 [wrote] @ 54214775 ##: -2 [wrote] @ 54215951 ##: -2 [wrote] @ 54217127 ##: -2 [wrote] @ 54218303 ##: -2 [wrote] @ 54219479 ##: -2 [wrote] @ 54220655 ##: -2 [wrote] @ 54221831 ##: -2 [wrote] @ 54223007 ##: -2 [wrote] @ 54224183 ##: -2 [wrote] @ 54225359 ##: -2 [wrote] @ 54226535 ##: -2 [wrote] @ 54227711 ##: -2 [wrote] @ 54228887 ##: -2 [wrote] @ 54230063 ##: -2 [wrote] @ 54231239 ##: -2 [wrote] @ 54232415 ##: -2 [wrote] @ 54233591 ##: -2 [wrote] @ 54234767 ##: -2 [wrote] @ 54235943 ##: -2 [wrote] @ 54237119 ##: -2 [wrote] @ 54238295 ##: -2 [wrote] @ 54239471 ##: -2 [wrote] @ 54240647 ##: -2 [wrote] @ 54241823 ##: -2 [wrote] @ 54242999 ##: -2 [wrote] @ 54244175 ##: -2 [wrote] @ 54245351 ##: -2 [wrote] @ 54246527 ##: -2 [wrote] @ 54247703 ##: -2 [wrote] @ 54248879 ##: -2 [wrote] @ 54250055 ##: -2 [wrote] @ 54251231 ##: -2 [wrote] @ 54252407 ##: -2 [wrote] @ 54253583 ##: -2 [wrote] @ 54254759 ##: -2 [wrote] @ 54255935 ##: -2 [wrote] @ 54257111 ##: -2 [wrote] @ 54258287 ##: -2 [wrote] @ 54259463 ##: -2 [wrote] @ 54260639 ##: -2 [wrote] @ 54261815 ##: -2 [wrote] @ 54262991 ##: -2 [wrote] @ 54264167 ##: -2 [wrote] @ 54265343 ##: -2 [wrote] @ 54266519 ##: -2 [wrote] @ 54267695 ##: -2 [wrote] @ 54268871 ##: -2 [wrote] @ 54270047 ##: -2 [wrote] @ 54271223 ##: -2 [wrote] @ 54272399 ##: -2 [wrote] @ 54273575 ##: -2 [wrote] @ 54274751 ##: -2 [wrote] @ 54275927 ##: -2 [wrote] @ 54277103 ##: -2 [wrote] @ 54278279 ##: -2 [wrote] @ 54279455 ##: -2 [wrote] @ 54280631 ##: -2 [wrote] @ 54281807 ##: -2 [wrote] @ 54282983 ##: -2 [wrote] @ 54284159 ##: -2 [wrote] @ 54285335 ##: -2 [wrote] @ 54286511 ##: -2 [wrote] @ 54287687 ##: -2 [wrote] @ 54288863 ##: -2 [wrote] @ 54290039 ##: -2 [wrote] @ 54291215 ##: -2 [wrote] @ 54292391 ##: -2 [wrote] @ 54293567 ##: -2 [wrote] @ 54294743 ##: -2 [wrote] @ 54295919 ##: -2 [wrote] @ 54297095 ##: -2 [wrote] @ 54298271 ##: -2 [wrote] @ 54299447 ##: -2 [wrote] @ 54300623 ##: -2 [wrote] @ 54301799 ##: -2 [wrote] @ 54302975 ##: -2 [wrote] @ 54304151 ##: -2 [wrote] @ 54305327 ##: -2 [wrote] @ 54306503 ##: -2 [wrote] @ 54307679 ##: -2 [wrote] @ 54308855 ##: -2 [wrote] @ 54310031 ##: -2 [wrote] @ 54311207 ##: -2 [wrote] @ 54312383 ##: -2 [wrote] @ 54313559 ##: -2 [wrote] @ 54314735 ##: -2 [wrote] @ 54315911 ##: -2 [wrote] @ 54317087 ##: -2 [wrote] @ 54318263 ##: -2 [wrote] @ 54319439 ##: -2 [wrote] @ 54320615 ##: -2 [wrote] @ 54321791 ##: -2 [wrote] @ 54322967 ##: -2 [wrote] @ 54324143 ##: -2 [wrote] @ 54325319 ##: -2 [wrote] @ 54326495 ##: -2 [wrote] @ 54327671 ##: -2 [wrote] @ 54328847 ##: -2 [wrote] @ 54330023 ##: -2 [wrote] @ 54331199 ##: -2 [wrote] @ 54332375 ##: -2 [wrote] @ 54333551 ##: -2 [wrote] @ 54334727 ##: -2 [wrote] @ 54335903 ##: -2 [wrote] @ 54337079 ##: -2 [wrote] @ 54338255 ##: -2 [wrote] @ 54339431 ##: -2 [wrote] @ 54340607 ##: -2 [wrote] @ 54341783 ##: -2 [wrote] @ 54342959 ##: -2 [wrote] @ 54344135 ##: -2 [wrote] @ 54345311 ##: -2 [wrote] @ 54346487 ##: -2 [wrote] @ 54347663 ##: -2 [wrote] @ 54348839 ##: -2 [wrote] @ 54350015 ##: -2 [wrote] @ 54351191 ##: -2 [wrote] @ 54352367 ##: -2 [wrote] @ 54353543 ##: -2 [wrote] @ 54354719 ##: -2 [wrote] @ 54355895 ##: -2 [wrote] @ 54357071 ##: -2 [wrote] @ 54358247 ##: -2 [wrote] @ 54359423 ##: -2 [wrote] @ 54360599 ##: -2 [wrote] @ 54361775 ##: -2 [wrote] @ 54362951 ##: -2 [wrote] @ 54364127 ##: -2 [wrote] @ 54365303 ##: -2 [wrote] @ 54366479 ##: -2 [wrote] @ 54367655 ##: -2 [wrote] @ 54368831 ##: -2 [wrote] @ 54370007 ##: -2 [wrote] @ 54371183 ##: -2 [wrote] @ 54372359 ##: -2 [wrote] @ 54373535 ##: -2 [wrote] @ 54374711 ##: -2 [wrote] @ 54375887 ##: -2 [wrote] @ 54377063 ##: -2 [wrote] @ 54378239 ##: -2 [wrote] @ 54379415 ##: -2 [wrote] @ 54380591 ##: -2 [wrote] @ 54381767 ##: -2 [wrote] @ 54382943 ##: -2 [wrote] @ 54384119 ##: -2 [wrote] @ 54385295 ##: -2 [wrote] @ 54386471 ##: -2 [wrote] @ 54387647 ##: -2 [wrote] @ 54388823 ##: -2 [wrote] @ 54389999 ##: -2 [wrote] @ 54391175 ##: -2 [wrote] @ 54392351 ##: -2 [wrote] @ 54393527 ##: -2 [wrote] @ 54394703 ##: -2 [wrote] @ 54395879 ##: -2 [wrote] @ 54397055 ##: -2 [wrote] @ 54398231 ##: -2 [wrote] @ 54399407 ##: -2 [wrote] @ 54400583 ##: -2 [wrote] @ 54401759 ##: -2 [wrote] @ 54402935 ##: -2 [wrote] @ 54404111 ##: -2 [wrote] @ 54405287 ##: -2 [wrote] @ 54406463 ##: -2 [wrote] @ 54407639 ##: -2 [wrote] @ 54408815 ##: -2 [wrote] @ 54409991 ##: -2 [wrote] @ 54411167 ##: -2 [wrote] @ 54412343 ##: -2 [wrote] @ 54413519 ##: -2 [wrote] @ 54414695 ##: -2 [wrote] @ 54415871 ##: -2 [wrote] @ 54417047 ##: -2 [wrote] @ 54418223 ##: -2 [wrote] @ 54419399 ##: -2 [wrote] @ 54420575 ##: -2 [wrote] @ 54421751 ##: -2 [wrote] @ 54422927 ##: -2 [wrote] @ 54424103 ##: -2 [wrote] @ 54425279 ##: -2 [wrote] @ 54426455 ##: -2 [wrote] @ 54427631 ##: -2 [wrote] @ 54428807 ##: -2 [wrote] @ 54429983 ##: -2 [wrote] @ 54431159 ##: -2 [wrote] @ 54432335 ##: -2 [wrote] @ 54433511 ##: -2 [wrote] @ 54434687 ##: -2 [wrote] @ 54435863 ##: -2 [wrote] @ 54437039 ##: -2 [wrote] @ 54438215 ##: -2 [wrote] @ 54439391 ##: -2 [wrote] @ 54440567 ##: -2 [wrote] @ 54441743 ##: -2 [wrote] @ 54442919 ##: -2 [wrote] @ 54444095 ##: -2 [wrote] @ 54445271 ##: -2 [wrote] @ 54446447 ##: -2 [wrote] @ 54447623 ##: -2 [wrote] @ 54448799 ##: -2 [wrote] @ 54449975 ##: -2 [wrote] @ 54451151 ##: -2 [wrote] @ 54452327 ##: -2 [wrote] @ 54453503 ##: -2 [wrote] @ 54454679 ##: -2 [wrote] @ 54455855 ##: -2 [wrote] @ 54457031 ##: -2 [wrote] @ 54458207 ##: -2 [wrote] @ 54459383 ##: -2 [wrote] @ 54460559 ##: -2 [wrote] @ 54461735 ##: -2 [wrote] @ 54462911 ##: -2 [wrote] @ 54464087 ##: -2 [wrote] @ 54465263 ##: -2 [wrote] @ 54466439 ##: -2 [wrote] @ 54467615 ##: -2 [wrote] @ 54468791 ##: -2 [wrote] @ 54469967 ##: -2 [wrote] @ 54471143 ##: -2 [wrote] @ 54472319 ##: -2 [wrote] @ 54473495 ##: -2 [wrote] @ 54474671 ##: -2 [wrote] @ 54475847 ##: -2 [wrote] @ 54477023 ##: -2 [wrote] @ 54478199 ##: -2 [wrote] @ 54479375 ##: -2 [wrote] @ 54480551 ##: -2 [wrote] @ 54481727 ##: -2 [wrote] @ 54482903 ##: -2 [wrote] @ 54484079 ##: -2 [wrote] @ 54485255 ##: -2 [wrote] @ 54486431 ##: -2 [wrote] @ 54487607 ##: -2 [wrote] @ 54488783 ##: -2 [wrote] @ 54489959 ##: -2 [wrote] @ 54491135 ##: -2 [wrote] @ 54492311 ##: -2 [wrote] @ 54493487 ##: -2 [wrote] @ 54494663 ##: -2 [wrote] @ 54495839 ##: -2 [wrote] @ 54497015 ##: -2 [wrote] @ 54498191 ##: -2 [wrote] @ 54499367 ##: -2 [wrote] @ 54500543 ##: -2 [wrote] @ 54501719 ##: -2 [wrote] @ 54502895 ##: -2 [wrote] @ 54504071 ##: -2 [wrote] @ 54505247 ##: -2 [wrote] @ 54506423 ##: -2 [wrote] @ 54507599 ##: -2 [wrote] @ 54508775 ##: -2 [wrote] @ 54509951 ##: -2 [wrote] @ 54511127 ##: -2 [wrote] @ 54512303 ##: -2 [wrote] @ 54513479 ##: -2 [wrote] @ 54514655 ##: -2 [wrote] @ 54515831 ##: -2 [wrote] @ 54517007 ##: -2 [wrote] @ 54518183 ##: -2 [wrote] @ 54519359 ##: -2 [wrote] @ 54520535 ##: -2 [wrote] @ 54521711 ##: -2 [wrote] @ 54522887 ##: -2 [wrote] @ 54524063 ##: -2 [wrote] @ 54525239 ##: -2 [wrote] @ 54526415 ##: -2 [wrote] @ 54527591 ##: -2 [wrote] @ 54528767 ##: -2 [wrote] @ 54529943 ##: -2 [wrote] @ 54531119 ##: -2 [wrote] @ 54532295 ##: -2 [wrote] @ 54533471 ##: -2 [wrote] @ 54534647 ##: -2 [wrote] @ 54535823 ##: -2 [wrote] @ 54536999 ##: -2 [wrote] @ 54538175 ##: -2 [wrote] @ 54539351 ##: -2 [wrote] @ 54540527 ##: -2 [wrote] @ 54541703 ##: -2 [wrote] @ 54542879 ##: -2 [wrote] @ 54544055 ##: -2 [wrote] @ 54545231 ##: -2 [wrote] @ 54546407 ##: -2 [wrote] @ 54547583 ##: -2 [wrote] @ 54548759 ##: -2 [wrote] @ 54549935 ##: -2 [wrote] @ 54551111 ##: -2 [wrote] @ 54552287 ##: -2 [wrote] @ 54553463 ##: -2 [wrote] @ 54554639 ##: -2 [wrote] @ 54555815 ##: -2 [wrote] @ 54556991 ##: -2 [wrote] @ 54558167 ##: -2 [wrote] @ 54559343 ##: -2 [wrote] @ 54560519 ##: -2 [wrote] @ 54561695 ##: -2 [wrote] @ 54562871 ##: -2 [wrote] @ 54564047 ##: -2 [wrote] @ 54565223 ##: -2 [wrote] @ 54566399 ##: -2 [wrote] @ 54567575 ##: -2 [wrote] @ 54568751 ##: -2 [wrote] @ 54569927 ##: -2 [wrote] @ 54571103 ##: -2 [wrote] @ 54572279 ##: -2 [wrote] @ 54573455 ##: -2 [wrote] @ 54574631 ##: -2 [wrote] @ 54575807 ##: -2 [wrote] @ 54576983 ##: -2 [wrote] @ 54578159 ##: -2 [wrote] @ 54579335 ##: -2 [wrote] @ 54580511 ##: -2 [wrote] @ 54581687 ##: -2 [wrote] @ 54582863 ##: -2 [wrote] @ 54584039 ##: -2 [wrote] @ 54585215 ##: -2 [wrote] @ 54586391 ##: -2 [wrote] @ 54587567 ##: -2 [wrote] @ 54588743 ##: -2 [wrote] @ 54589919 ##: -2 [wrote] @ 54591095 ##: -2 [wrote] @ 54592271 ##: -2 [wrote] @ 54593447 ##: -2 [wrote] @ 54594623 ##: -2 [wrote] @ 54595799 ##: -2 [wrote] @ 54596975 ##: -2 [wrote] @ 54598151 ##: -2 [wrote] @ 54599327 ##: -2 [wrote] @ 54600503 ##: -2 [wrote] @ 54601679 ##: -2 [wrote] @ 54602855 ##: -2 [wrote] @ 54604031 ##: -2 [wrote] @ 54605207 ##: -2 [wrote] @ 54606383 ##: -2 [wrote] @ 54607559 ##: -2 [wrote] @ 54608735 ##: -2 [wrote] @ 54609911 ##: -2 [wrote] @ 54611087 ##: -2 [wrote] @ 54612263 ##: -2 [wrote] @ 54613439 ##: -2 [wrote] @ 54614615 ##: -2 [wrote] @ 54615791 ##: -2 [wrote] @ 54616967 ##: -2 [wrote] @ 54618143 ##: -2 [wrote] @ 54619319 ##: -2 [wrote] @ 54620495 ##: -2 [wrote] @ 54621671 ##: -2 [wrote] @ 54622847 ##: -2 [wrote] @ 54624023 ##: -2 [wrote] @ 54625199 ##: -2 [wrote] @ 54626375 ##: -2 [wrote] @ 54627551 ##: -2 [wrote] @ 54628727 ##: -2 [wrote] @ 54629903 ##: -2 [wrote] @ 54631079 ##: -2 [wrote] @ 54632255 ##: -2 [wrote] @ 54633431 ##: -2 [wrote] @ 54634607 ##: -2 [wrote] @ 54635783 ##: -2 [wrote] @ 54636959 ##: -2 [wrote] @ 54638135 ##: -2 [wrote] @ 54639311 ##: -2 [wrote] @ 54640487 ##: -2 [wrote] @ 54641663 ##: -2 [wrote] @ 54642839 ##: -2 [wrote] @ 54644015 ##: -2 [wrote] @ 54645191 ##: -2 [wrote] @ 54646367 ##: -2 [wrote] @ 54647543 ##: -2 [wrote] @ 54648719 ##: -2 [wrote] @ 54649895 ##: -2 [wrote] @ 54651071 ##: -2 [wrote] @ 54652247 ##: -2 [wrote] @ 54653423 ##: -2 [wrote] @ 54654599 ##: -2 [wrote] @ 54655775 ##: -2 [wrote] @ 54656951 ##: -2 [wrote] @ 54658127 ##: -2 [wrote] @ 54659303 ##: -2 [wrote] @ 54660479 ##: -2 [wrote] @ 54661655 ##: -2 [wrote] @ 54662831 ##: -2 [wrote] @ 54664007 ##: -2 [wrote] @ 54665183 ##: -2 [wrote] @ 54666359 ##: -2 [wrote] @ 54667535 ##: -2 [wrote] @ 54668711 ##: -2 [wrote] @ 54669887 ##: -2 [wrote] @ 54671063 ##: -2 [wrote] @ 54672239 ##: -2 [wrote] @ 54673415 ##: -2 [wrote] @ 54674591 ##: -2 [wrote] @ 54675767 ##: -2 [wrote] @ 54676943 ##: -2 [wrote] @ 54678119 ##: -2 [wrote] @ 54679295 ##: -2 [wrote] @ 54680471 ##: -2 [wrote] @ 54681647 ##: -2 [wrote] @ 54682823 ##: -2 [wrote] @ 54683999 ##: -2 [wrote] @ 54685175 ##: -2 [wrote] @ 54686351 ##: -2 [wrote] @ 54687527 ##: -2 [wrote] @ 54688703 ##: -2 [wrote] @ 54689879 ##: -2 [wrote] @ 54691055 ##: -2 [wrote] @ 54692231 ##: -2 [wrote] @ 54693407 ##: -2 [wrote] @ 54694583 ##: -2 [wrote] @ 54695759 ##: -2 [wrote] @ 54696935 ##: -2 [wrote] @ 54698111 ##: -2 [wrote] @ 54699287 ##: -2 [wrote] @ 54700463 ##: -2 [wrote] @ 54701639 ##: -2 [wrote] @ 54702815 ##: -2 [wrote] @ 54703991 ##: -2 [wrote] @ 54705167 ##: -2 [wrote] @ 54706343 ##: -2 [wrote] @ 54707519 ##: -2 [wrote] @ 54708695 ##: -2 [wrote] @ 54709871 ##: -2 [wrote] @ 54711047 ##: -2 [wrote] @ 54712223 ##: -2 [wrote] @ 54713399 ##: -2 [wrote] @ 54714575 ##: -2 [wrote] @ 54715751 ##: -2 [wrote] @ 54716927 ##: -2 [wrote] @ 54718103 ##: -2 [wrote] @ 54719279 ##: -2 [wrote] @ 54720455 ##: -2 [wrote] @ 54721631 ##: -2 [wrote] @ 54722807 ##: -2 [wrote] @ 54723983 ##: -2 [wrote] @ 54725159 ##: -2 [wrote] @ 54726335 ##: -2 [wrote] @ 54727511 ##: -2 [wrote] @ 54728687 ##: -2 [wrote] @ 54729863 ##: -2 [wrote] @ 54731039 ##: -2 [wrote] @ 54732215 ##: -2 [wrote] @ 54733391 ##: -2 [wrote] @ 54734567 ##: -2 [wrote] @ 54735743 ##: -2 [wrote] @ 54736919 ##: -2 [wrote] @ 54738095 ##: -2 [wrote] @ 54739271 ##: -2 [wrote] @ 54740447 ##: -2 [wrote] @ 54741623 ##: -2 [wrote] @ 54742799 ##: -2 [wrote] @ 54743975 ##: -2 [wrote] @ 54745151 ##: -2 [wrote] @ 54746327 ##: -2 [wrote] @ 54747503 ##: -2 [wrote] @ 54748679 ##: -2 [wrote] @ 54749855 ##: -2 [wrote] @ 54751031 ##: -2 [wrote] @ 54752207 ##: -2 [wrote] @ 54753383 ##: -2 [wrote] @ 54754559 ##: -2 [wrote] @ 54755735 ##: -2 [wrote] @ 54756911 ##: -2 [wrote] @ 54758087 ##: -2 [wrote] @ 54759263 ##: -2 [wrote] @ 54760439 ##: -2 [wrote] @ 54761615 ##: -2 [wrote] @ 54762791 ##: -2 [wrote] @ 54763967 ##: -2 [wrote] @ 54765143 ##: -2 [wrote] @ 54766319 ##: -2 [wrote] @ 54767495 ##: -2 [wrote] @ 54768671 ##: -2 [wrote] @ 54769847 ##: -2 [wrote] @ 54771023 ##: -2 [wrote] @ 54772199 ##: -2 [wrote] @ 54773375 ##: -2 [wrote] @ 54774551 ##: -2 [wrote] @ 54775727 ##: -2 [wrote] @ 54776903 ##: -2 [wrote] @ 54778079 ##: -2 [wrote] @ 54779255 ##: -2 [wrote] @ 54780431 ##: -2 [wrote] @ 54781607 ##: -2 [wrote] @ 54782783 ##: -2 [wrote] @ 54783959 ##: -2 [wrote] @ 54785135 ##: -2 [wrote] @ 54786311 ##: -2 [wrote] @ 54787487 ##: -2 [wrote] @ 54788663 ##: -2 [wrote] @ 54789839 ##: -2 [wrote] @ 54791015 ##: -2 [wrote] @ 54792191 ##: -2 [wrote] @ 54793367 ##: -2 [wrote] @ 54794543 ##: -2 [wrote] @ 54795719 ##: -2 [wrote] @ 54796895 ##: -2 [wrote] @ 54798071 ##: -2 [wrote] @ 54799247 ##: -2 [wrote] @ 54800423 ##: -2 [wrote] @ 54801599 ##: -2 [wrote] @ 54802775 ##: -2 [wrote] @ 54803951 ##: -2 [wrote] @ 54805127 ##: -2 [wrote] @ 54806303 ##: -2 [wrote] @ 54807479 ##: -2 [wrote] @ 54808655 ##: -2 [wrote] @ 54809831 ##: -2 [wrote] @ 54811007 ##: -2 [wrote] @ 54812183 ##: -2 [wrote] @ 54813359 ##: -2 [wrote] @ 54814535 ##: -2 [wrote] @ 54815711 ##: -2 [wrote] @ 54816887 ##: -2 [wrote] @ 54818063 ##: -2 [wrote] @ 54819239 ##: -2 [wrote] @ 54820415 ##: -2 [wrote] @ 54821591 ##: -2 [wrote] @ 54822767 ##: -2 [wrote] @ 54823943 ##: -2 [wrote] @ 54825119 ##: -2 [wrote] @ 54826295 ##: -2 [wrote] @ 54827471 ##: -2 [wrote] @ 54828647 ##: -2 [wrote] @ 54829823 ##: -2 [wrote] @ 54830999 ##: -2 [wrote] @ 54832175 ##: -2 [wrote] @ 54833351 ##: -2 [wrote] @ 54834527 ##: -2 [wrote] @ 54835703 ##: -2 [wrote] @ 54836879 ##: -2 [wrote] @ 54838055 ##: -2 [wrote] @ 54839231 ##: -2 [wrote] @ 54840407 ##: -2 [wrote] @ 54841583 ##: -2 [wrote] @ 54842759 ##: -2 [wrote] @ 54843935 ##: -2 [wrote] @ 54845111 ##: -2 [wrote] @ 54846287 ##: -2 [wrote] @ 54847463 ##: -2 [wrote] @ 54848639 ##: -2 [wrote] @ 54849815 ##: -2 [wrote] @ 54850991 ##: -2 [wrote] @ 54852167 ##: -2 [wrote] @ 54853343 ##: -2 [wrote] @ 54854519 ##: -2 [wrote] @ 54855695 ##: -2 [wrote] @ 54856871 ##: -2 [wrote] @ 54858047 ##: -2 [wrote] @ 54859223 ##: -2 [wrote] @ 54860399 ##: -2 [wrote] @ 54861575 ##: -2 [wrote] @ 54862751 ##: -2 [wrote] @ 54863927 ##: -2 [wrote] @ 54865103 ##: -2 [wrote] @ 54866279 ##: -2 [wrote] @ 54867455 ##: -2 [wrote] @ 54868631 ##: -2 [wrote] @ 54869807 ##: -2 [wrote] @ 54870983 ##: -2 [wrote] @ 54872159 ##: -2 [wrote] @ 54873335 ##: -2 [wrote] @ 54874511 ##: -2 [wrote] @ 54875687 ##: -2 [wrote] @ 54876863 ##: -2 [wrote] @ 54878039 ##: -2 [wrote] @ 54879215 ##: -2 [wrote] @ 54880391 ##: -2 [wrote] @ 54881567 ##: -2 [wrote] @ 54882743 ##: -2 [wrote] @ 54883919 ##: -2 [wrote] @ 54885095 ##: -2 [wrote] @ 54886271 ##: -2 [wrote] @ 54887447 ##: -2 [wrote] @ 54888623 ##: -2 [wrote] @ 54889799 ##: -2 [wrote] @ 54890975 ##: -2 [wrote] @ 54892151 ##: -2 [wrote] @ 54893327 ##: -2 [wrote] @ 54894503 ##: -2 [wrote] @ 54895679 ##: -2 [wrote] @ 54896855 ##: -2 [wrote] @ 54898031 ##: -2 [wrote] @ 54899207 ##: -2 [wrote] @ 54900383 ##: -2 [wrote] @ 54901559 ##: -2 [wrote] @ 54902735 ##: -2 [wrote] @ 54903911 ##: -2 [wrote] @ 54905087 ##: -2 [wrote] @ 54906263 ##: -2 [wrote] @ 54907439 ##: -2 [wrote] @ 54908615 ##: -2 [wrote] @ 54909791 ##: -2 [wrote] @ 54910967 ##: -2 [wrote] @ 54912143 ##: -2 [wrote] @ 54913319 ##: -2 [wrote] @ 54914495 ##: -2 [wrote] @ 54915671 ##: -2 [wrote] @ 54916847 ##: -2 [wrote] @ 54918023 ##: -2 [wrote] @ 54919199 ##: -2 [wrote] @ 54920375 ##: -2 [wrote] @ 54921551 ##: -2 [wrote] @ 54922727 ##: -2 [wrote] @ 54923903 ##: -2 [wrote] @ 54925079 ##: -2 [wrote] @ 54926255 ##: -2 [wrote] @ 54927431 ##: -2 [wrote] @ 54928607 ##: -2 [wrote] @ 54929783 ##: -2 [wrote] @ 54930959 ##: -2 [wrote] @ 54932135 ##: -2 [wrote] @ 54933311 ##: -2 [wrote] @ 54934487 ##: -2 [wrote] @ 54935663 ##: -2 [wrote] @ 54936839 ##: -2 [wrote] @ 54938015 ##: -2 [wrote] @ 54939191 ##: -2 [wrote] @ 54940367 ##: -2 [wrote] @ 54941543 ##: -2 [wrote] @ 54942719 ##: -2 [wrote] @ 54943895 ##: -2 [wrote] @ 54945071 ##: -2 [wrote] @ 54946247 ##: -2 [wrote] @ 54947423 ##: -2 [wrote] @ 54948599 ##: -2 [wrote] @ 54949775 ##: -2 [wrote] @ 54950951 ##: -2 [wrote] @ 54952127 ##: -2 [wrote] @ 54953303 ##: -2 [wrote] @ 54954479 ##: -2 [wrote] @ 54955655 ##: -2 [wrote] @ 54956831 ##: -2 [wrote] @ 54958007 ##: -2 [wrote] @ 54959183 ##: -2 [wrote] @ 54960359 ##: -2 [wrote] @ 54961535 ##: -2 [wrote] @ 54962711 ##: -2 [wrote] @ 54963887 ##: -2 [wrote] @ 54965063 ##: -2 [wrote] @ 54966239 ##: -2 [wrote] @ 54967415 ##: -2 [wrote] @ 54968591 ##: -2 [wrote] @ 54969767 ##: -2 [wrote] @ 54970943 ##: -2 [wrote] @ 54972119 ##: -2 [wrote] @ 54973295 ##: -2 [wrote] @ 54974471 ##: -2 [wrote] @ 54975647 ##: -2 [wrote] @ 54976823 ##: -2 [wrote] @ 54977999 ##: -2 [wrote] @ 54979175 ##: -2 [wrote] @ 54980351 ##: -2 [wrote] @ 54981527 ##: -2 [wrote] @ 54982703 ##: -2 [wrote] @ 54983879 ##: -2 [wrote] @ 54985055 ##: -2 [wrote] @ 54986231 ##: -2 [wrote] @ 54987407 ##: -2 [wrote] @ 54988583 ##: -2 [wrote] @ 54989759 ##: -2 [wrote] @ 54990935 ##: -2 [wrote] @ 54992111 ##: -2 [wrote] @ 54993287 ##: -2 [wrote] @ 54994463 ##: -2 [wrote] @ 54995639 ##: -2 [wrote] @ 54996815 ##: -2 [wrote] @ 54997991 ##: -2 [wrote] @ 54999167 ##: -2 [wrote] @ 55000343 ##: -2 [wrote] @ 55001519 ##: -2 [wrote] @ 55002695 ##: -2 [wrote] @ 55003871 ##: -2 [wrote] @ 55005047 ##: -2 [wrote] @ 55006223 ##: -2 [wrote] @ 55007399 ##: -2 [wrote] @ 55008575 ##: -2 [wrote] @ 55009751 ##: -2 [wrote] @ 55010927 ##: -2 [wrote] @ 55012103 ##: -2 [wrote] @ 55013279 ##: -2 [wrote] @ 55014455 ##: -2 [wrote] @ 55015631 ##: -2 [wrote] @ 55016807 ##: -2 [wrote] @ 55017983 ##: -2 [wrote] @ 55019159 ##: -2 [wrote] @ 55020335 ##: -2 [wrote] @ 55021511 ##: -2 [wrote] @ 55022687 ##: -2 [wrote] @ 55023863 ##: -2 [wrote] @ 55025039 ##: -2 [wrote] @ 55026215 ##: -2 [wrote] @ 55027391 ##: -2 [wrote] @ 55028567 ##: -2 [wrote] @ 55029743 ##: -2 [wrote] @ 55030919 ##: -2 [wrote] @ 55032095 ##: -2 [wrote] @ 55033271 ##: -2 [wrote] @ 55034447 ##: -2 [wrote] @ 55035623 ##: -2 [wrote] @ 55036799 ##: -2 [wrote] @ 55037975 ##: -2 [wrote] @ 55039151 ##: -2 [wrote] @ 55040327 ##: -2 [wrote] @ 55041503 ##: -2 [wrote] @ 55042679 ##: -2 [wrote] @ 55043855 ##: -2 [wrote] @ 55045031 ##: -2 [wrote] @ 55046207 ##: -2 [wrote] @ 55047383 ##: -2 [wrote] @ 55048559 ##: -2 [wrote] @ 55049735 ##: -2 [wrote] @ 55050911 ##: -2 [wrote] @ 55052087 ##: -2 [wrote] @ 55053263 ##: -2 [wrote] @ 55054439 ##: -2 [wrote] @ 55055615 ##: -2 [wrote] @ 55056791 ##: -2 [wrote] @ 55057967 ##: -2 [wrote] @ 55059143 ##: -2 [wrote] @ 55060319 ##: -2 [wrote] @ 55061495 ##: -2 [wrote] @ 55062671 ##: -2 [wrote] @ 55063847 ##: -2 [wrote] @ 55065023 ##: -2 [wrote] @ 55066199 ##: -2 [wrote] @ 55067375 ##: -2 [wrote] @ 55068551 ##: -2 [wrote] @ 55069727 ##: -2 [wrote] @ 55070903 ##: -2 [wrote] @ 55072079 ##: -2 [wrote] @ 55073255 ##: -2 [wrote] @ 55074431 ##: -2 [wrote] @ 55075607 ##: -2 [wrote] @ 55076783 ##: -2 [wrote] @ 55077959 ##: -2 [wrote] @ 55079135 ##: -2 [wrote] @ 55080311 ##: -2 [wrote] @ 55081487 ##: -2 [wrote] @ 55082663 ##: -2 [wrote] @ 55083839 ##: -2 [wrote] @ 55085015 ##: -2 [wrote] @ 55086191 ##: -2 [wrote] @ 55087367 ##: -2 [wrote] @ 55088543 ##: -2 [wrote] @ 55089719 ##: -2 [wrote] @ 55090895 ##: -2 [wrote] @ 55092071 ##: -2 [wrote] @ 55093247 ##: -2 [wrote] @ 55094423 ##: -2 [wrote] @ 55095599 ##: -2 [wrote] @ 55096775 ##: -2 [wrote] @ 55097951 ##: -2 [wrote] @ 55099127 ##: -2 [wrote] @ 55100303 ##: -2 [wrote] @ 55101479 ##: -2 [wrote] @ 55102655 ##: -2 [wrote] @ 55103831 ##: -2 [wrote] @ 55105007 ##: -2 [wrote] @ 55106183 ##: -2 [wrote] @ 55107359 ##: -2 [wrote] @ 55108535 ##: -2 [wrote] @ 55109711 ##: -2 [wrote] @ 55110887 ##: -2 [wrote] @ 55112063 ##: -2 [wrote] @ 55113239 ##: -2 [wrote] @ 55114415 ##: -2 [wrote] @ 55115591 ##: -2 [wrote] @ 55116767 ##: -2 [wrote] @ 55117943 ##: -2 [wrote] @ 55119119 ##: -2 [wrote] @ 55120295 ##: -2 [wrote] @ 55121471 ##: -2 [wrote] @ 55122647 ##: -2 [wrote] @ 55123823 ##: -2 [wrote] @ 55124999 ##: -2 [wrote] @ 55126175 ##: -2 [wrote] @ 55127351 ##: -2 [wrote] @ 55128527 ##: -2 [wrote] @ 55129703 ##: -2 [wrote] @ 55130879 ##: -2 [wrote] @ 55132055 ##: -2 [wrote] @ 55133231 ##: -2 [wrote] @ 55134407 ##: -2 [wrote] @ 55135583 ##: -2 [wrote] @ 55136759 ##: -2 [wrote] @ 55137935 ##: -2 [wrote] @ 55139111 ##: -2 [wrote] @ 55140287 ##: -2 [wrote] @ 55141463 ##: -2 [wrote] @ 55142639 ##: -2 [wrote] @ 55143815 ##: -2 [wrote] @ 55144991 ##: -2 [wrote] @ 55146167 ##: -2 [wrote] @ 55147343 ##: -2 [wrote] @ 55148519 ##: -2 [wrote] @ 55149695 ##: -2 [wrote] @ 55150871 ##: -2 [wrote] @ 55152047 ##: -2 [wrote] @ 55153223 ##: -2 [wrote] @ 55154399 ##: -2 [wrote] @ 55155575 ##: -2 [wrote] @ 55156751 ##: -2 [wrote] @ 55157927 ##: -2 [wrote] @ 55159103 ##: -2 [wrote] @ 55160279 ##: -2 [wrote] @ 55161455 ##: -2 [wrote] @ 55162631 ##: -2 [wrote] @ 55163807 ##: -2 [wrote] @ 55164983 ##: -2 [wrote] @ 55166159 ##: -2 [wrote] @ 55167335 ##: -2 [wrote] @ 55168511 ##: -2 [wrote] @ 55169687 ##: -2 [wrote] @ 55170863 ##: -2 [wrote] @ 55172039 ##: -2 [wrote] @ 55173215 ##: -2 [wrote] @ 55174391 ##: -2 [wrote] @ 55175567 ##: -2 [wrote] @ 55176743 ##: -2 [wrote] @ 55177919 ##: -2 [wrote] @ 55179095 ##: -2 [wrote] @ 55180271 ##: -2 [wrote] @ 55181447 ##: -2 [wrote] @ 55182623 ##: -2 [wrote] @ 55183799 ##: -2 [wrote] @ 55184975 ##: -2 [wrote] @ 55186151 ##: -2 [wrote] @ 55187327 ##: -2 [wrote] @ 55188503 ##: -2 [wrote] @ 55189679 ##: -2 [wrote] @ 55190855 ##: -2 [wrote] @ 55192031 ##: -2 [wrote] @ 55193207 ##: -2 [wrote] @ 55194383 ##: -2 [wrote] @ 55195559 ##: -2 [wrote] @ 55196735 ##: -2 [wrote] @ 55197911 ##: -2 [wrote] @ 55199087 ##: -2 [wrote] @ 55200263 ##: -2 [wrote] @ 55201439 ##: -2 [wrote] @ 55202615 ##: -2 [wrote] @ 55203791 ##: -2 [wrote] @ 55204967 ##: -2 [wrote] @ 55206143 ##: -2 [wrote] @ 55207319 ##: -2 [wrote] @ 55208495 ##: -2 [wrote] @ 55209671 ##: -2 [wrote] @ 55210847 ##: -2 [wrote] @ 55212023 ##: -2 [wrote] @ 55213199 ##: -2 [wrote] @ 55214375 ##: -2 [wrote] @ 55215551 ##: -2 [wrote] @ 55216727 ##: -2 [wrote] @ 55217903 ##: -2 [wrote] @ 55219079 ##: -2 [wrote] @ 55220255 ##: -2 [wrote] @ 55221431 ##: -2 [wrote] @ 55222607 ##: -2 [wrote] @ 55223783 ##: -2 [wrote] @ 55224959 ##: -2 [wrote] @ 55226135 ##: -2 [wrote] @ 55227311 ##: -2 [wrote] @ 55228487 ##: -2 [wrote] @ 55229663 ##: -2 [wrote] @ 55230839 ##: -2 [wrote] @ 55232015 ##: -2 [wrote] @ 55233191 ##: -2 [wrote] @ 55234367 ##: -2 [wrote] @ 55235543 ##: -2 [wrote] @ 55236719 ##: -2 [wrote] @ 55237895 ##: -2 [wrote] @ 55239071 ##: -2 [wrote] @ 55240247 ##: -2 [wrote] @ 55241423 ##: -2 [wrote] @ 55242599 ##: -2 [wrote] @ 55243775 ##: -2 [wrote] @ 55244951 ##: -2 [wrote] @ 55246127 ##: -2 [wrote] @ 55247303 ##: -2 [wrote] @ 55248479 ##: -2 [wrote] @ 55249655 ##: -2 [wrote] @ 55250831 ##: -2 [wrote] @ 55252007 ##: -2 [wrote] @ 55253183 ##: -2 [wrote] @ 55254359 ##: -2 [wrote] @ 55255535 ##: -2 [wrote] @ 55256711 ##: -2 [wrote] @ 55257887 ##: -2 [wrote] @ 55259063 ##: -2 [wrote] @ 55260239 ##: -2 [wrote] @ 55261415 ##: -2 [wrote] @ 55262591 ##: -2 [wrote] @ 55263767 ##: -2 [wrote] @ 55264943 ##: -2 [wrote] @ 55266119 ##: -2 [wrote] @ 55267295 ##: -2 [wrote] @ 55268471 ##: -2 [wrote] @ 55269647 ##: -2 [wrote] @ 55270823 ##: -2 [wrote] @ 55271999 ##: -2 [wrote] @ 55273175 ##: -2 [wrote] @ 55274351 ##: -2 [wrote] @ 55275527 ##: -2 [wrote] @ 55276703 ##: -2 [wrote] @ 55277879 ##: -2 [wrote] @ 55279055 ##: -2 [wrote] @ 55280231 ##: -2 [wrote] @ 55281407 ##: -2 [wrote] @ 55282583 ##: -2 [wrote] @ 55283759 ##: -2 [wrote] @ 55284935 ##: -2 [wrote] @ 55286111 ##: -2 [wrote] @ 55287287 ##: -2 [wrote] @ 55288463 ##: -2 [wrote] @ 55289639 ##: -2 [wrote] @ 55290815 ##: -2 [wrote] @ 55291991 ##: -2 [wrote] @ 55293167 ##: -2 [wrote] @ 55294343 ##: -2 [wrote] @ 55295519 ##: -2 [wrote] @ 55296695 ##: -2 [wrote] @ 55297871 ##: -2 [wrote] @ 55299047 ##: -2 [wrote] @ 55300223 ##: -2 [wrote] @ 55301399 ##: -2 [wrote] @ 55302575 ##: -2 [wrote] @ 55303751 ##: -2 [wrote] @ 55304927 ##: -2 [wrote] @ 55306103 ##: -2 [wrote] @ 55307279 ##: -2 [wrote] @ 55308455 ##: -2 [wrote] @ 55309631 ##: -2 [wrote] @ 55310807 ##: -2 [wrote] @ 55311983 ##: -2 [wrote] @ 55313159 ##: -2 [wrote] @ 55314335 ##: -2 [wrote] @ 55315511 ##: -2 [wrote] @ 55316687 ##: -2 [wrote] @ 55317863 ##: -2 [wrote] @ 55319039 ##: -2 [wrote] @ 55320215 ##: -2 [wrote] @ 55321391 ##: -2 [wrote] @ 55322567 ##: -2 [wrote] @ 55323743 ##: -2 [wrote] @ 55324919 ##: -2 [wrote] @ 55326095 ##: -2 [wrote] @ 55327271 ##: -2 [wrote] @ 55328447 ##: -2 [wrote] @ 55329623 ##: -2 [wrote] @ 55330799 ##: -2 [wrote] @ 55331975 ##: -2 [wrote] @ 55333151 ##: -2 [wrote] @ 55334327 ##: -2 [wrote] @ 55335503 ##: -2 [wrote] @ 55336679 ##: -2 [wrote] @ 55337855 ##: -2 [wrote] @ 55339031 ##: -2 [wrote] @ 55340207 ##: -2 [wrote] @ 55341383 ##: -2 [wrote] @ 55342559 ##: -2 [wrote] @ 55343735 ##: -2 [wrote] @ 55344911 ##: -2 [wrote] @ 55346087 ##: -2 [wrote] @ 55347263 ##: -2 [wrote] @ 55348439 ##: -2 [wrote] @ 55349615 ##: -2 [wrote] @ 55350791 ##: -2 [wrote] @ 55351967 ##: -2 [wrote] @ 55353143 ##: -2 [wrote] @ 55354319 ##: -2 [wrote] @ 55355495 ##: -2 [wrote] @ 55356671 ##: -2 [wrote] @ 55357847 ##: -2 [wrote] @ 55359023 ##: -2 [wrote] @ 55360199 ##: -2 [wrote] @ 55361375 ##: -2 [wrote] @ 55362551 ##: -2 [wrote] @ 55363727 ##: -2 [wrote] @ 55364903 ##: -2 [wrote] @ 55366079 ##: -2 [wrote] @ 55367255 ##: -2 [wrote] @ 55368431 ##: -2 [wrote] @ 55369607 ##: -2 [wrote] @ 55370783 ##: -2 [wrote] @ 55371959 ##: -2 [wrote] @ 55373135 ##: -2 [wrote] @ 55374311 ##: -2 [wrote] @ 55375487 ##: -2 [wrote] @ 55376663 ##: -2 [wrote] @ 55377839 ##: -2 [wrote] @ 55379015 ##: -2 [wrote] @ 55380191 ##: -2 [wrote] @ 55381367 ##: -2 [wrote] @ 55382543 ##: -2 [wrote] @ 55383719 ##: -2 [wrote] @ 55384895 ##: -2 [wrote] @ 55386071 ##: -2 [wrote] @ 55387247 ##: -2 [wrote] @ 55388423 ##: -2 [wrote] @ 55389599 ##: -2 [wrote] @ 55390775 ##: -2 [wrote] @ 55391951 ##: -2 [wrote] @ 55393127 ##: -2 [wrote] @ 55394303 ##: -2 [wrote] @ 55395479 ##: -2 [wrote] @ 55396655 ##: -2 [wrote] @ 55397831 ##: -2 [wrote] @ 55399007 ##: -2 [wrote] @ 55400183 ##: -2 [wrote] @ 55401359 ##: -2 [wrote] @ 55402535 ##: -2 [wrote] @ 55403711 ##: -2 [wrote] @ 55404887 ##: -2 [wrote] @ 55406063 ##: -2 [wrote] @ 55407239 ##: -2 [wrote] @ 55408415 ##: -2 [wrote] @ 55409591 ##: -2 [wrote] @ 55410767 ##: -2 [wrote] @ 55411943 ##: -2 [wrote] @ 55413119 ##: -2 [wrote] @ 55414295 ##: -2 [wrote] @ 55415471 ##: -2 [wrote] @ 55416647 ##: -2 [wrote] @ 55417823 ##: -2 [wrote] @ 55418999 ##: -2 [wrote] @ 55420175 ##: -2 [wrote] @ 55421351 ##: -2 [wrote] @ 55422527 ##: -2 [wrote] @ 55423703 ##: -2 [wrote] @ 55424879 ##: -2 [wrote] @ 55426055 ##: -2 [wrote] @ 55427231 ##: -2 [wrote] @ 55428407 ##: -2 [wrote] @ 55429583 ##: -2 [wrote] @ 55430759 ##: -2 [wrote] @ 55431935 ##: -2 [wrote] @ 55433111 ##: -2 [wrote] @ 55434287 ##: -2 [wrote] @ 55435463 ##: -2 [wrote] @ 55436639 ##: -2 [wrote] @ 55437815 ##: -2 [wrote] @ 55438991 ##: -2 [wrote] @ 55440167 ##: -2 [wrote] @ 55441343 ##: -2 [wrote] @ 55442519 ##: -2 [wrote] @ 55443695 ##: -2 [wrote] @ 55444871 ##: -2 [wrote] @ 55446047 ##: -2 [wrote] @ 55447223 ##: -2 [wrote] @ 55448399 ##: -2 [wrote] @ 55449575 ##: -2 [wrote] @ 55450751 ##: -2 [wrote] @ 55451927 ##: -2 [wrote] @ 55453103 ##: -2 [wrote] @ 55454279 ##: -2 [wrote] @ 55455455 ##: -2 [wrote] @ 55456631 ##: -2 [wrote] @ 55457807 ##: 0 [read] @ 55478976 ##: 0 [read] @ 55510728 ##: 0 [read] @ 55542480 ##: 0 [read] @ 55574232 ##: 0 [read] @ 55605984 ##: 0 [read] @ 55637736 ##: 0 [read] @ 55669488 ##: 0 [read] @ 55701240 ##: 0 [read] @ 55732992 ##: 0 [read] @ 55764744 ##: 0 [read] @ 55796496 ##: 0 [read] @ 55828248 ##: 0 [read] @ 55860000 ##: 0 [read] @ 55891752 ##: 0 [read] @ 55923504 ##: 0 [read] @ 55955256 ##: 0 [read] @ 55987008 ##: 0 [read] @ 56018760 ##: 0 [read] @ 56050512 ##: 0 [read] @ 56082264 ##: 0 [read] @ 56114016 ##: 0 [read] @ 56145768 ##: 0 [read] @ 56177520 ##: 0 [read] @ 56209272 ##: 0 [read] @ 56241024 ##: 0 [read] @ 56272776 ##: 0 [read] @ 56304528 ##: 0 [read] @ 56336280 ##: 0 [read] @ 56368032 ##: 0 [read] @ 56399784 ##: 0 [read] @ 56431536 ##: 0 [read] @ 56463288 ##: 0 [read] @ 56495040 ##: 0 [read] @ 56526792 ##: 0 [read] @ 56558544 ##: 0 [read] @ 56590296 ##: 0 [read] @ 56622048 ##: 0 [read] @ 56653800 ##: 0 [read] @ 56685552 ##: 0 [read] @ 56717304 ##: 0 [read] @ 56749056 ##: 0 [read] @ 56780808 ##: 0 [read] @ 56812560 ##: 0 [read] @ 56844312 ##: 0 [read] @ 56858424 ##: 1 [verify] @ 55448400 ##: 1 [verify] @ 55448400 ##: 1 [verify] @ 55448400 ##: 0 [read] @ 55477800 ##: 0 [read] @ 55509552 ##: 0 [read] @ 55541304 ##: 0 [read] @ 55573056 ##: 0 [read] @ 55604808 ##: 0 [read] @ 55636560 ##: 0 [read] @ 55668312 ##: 0 [read] @ 55700064 ##: 0 [read] @ 55731816 ##: 0 [read] @ 55763568 ##: 0 [read] @ 55795320 ##: 0 [read] @ 55827072 ##: 0 [read] @ 55858824 ##: 0 [read] @ 55890576 ##: 0 [read] @ 55922328 ##: 0 [read] @ 55954080 ##: 0 [read] @ 55985832 ##: 0 [read] @ 56017584 ##: 0 [read] @ 56049336 ##: 0 [read] @ 56081088 ##: 0 [read] @ 56112840 ##: 0 [read] @ 56144592 ##: 0 [read] @ 56176344 ##: 0 [read] @ 56208096 ##: 0 [read] @ 56239848 ##: 0 [read] @ 56271600 ##: 0 [read] @ 56303352 ##: 0 [read] @ 56335104 ##: 0 [read] @ 56366856 ##: 0 [read] @ 56398608 ##: 0 [read] @ 56430360 ##: 0 [read] @ 56462112 ##: 0 [read] @ 56493864 ##: 0 [read] @ 56525616 ##: 0 [read] @ 56557368 ##: 0 [read] @ 56589120 ##: 0 [read] @ 56620872 ##: 0 [read] @ 56652624 ##: 0 [read] @ 56684376 ##: 0 [read] @ 56716128 ##: 0 [read] @ 56747880 ##: 0 [read] @ 56779632 ##: 0 [read] @ 56811384 ##: 0 [read] @ 56843136 ##: 0 [read] @ 56857248 ##: 1 [verify] @ 55447224 ##: 1 [verify] @ 55447224 ##: 1 [verify] @ 55447224 ##: 1 [verify] @ 55447224 ##: -2 [wrote] @ 55458983 ##: -2 [wrote] @ 55460159 ##: -2 [wrote] @ 55461335 ##: -2 [wrote] @ 55462511 ##: -2 [wrote] @ 55463687 ##: -2 [wrote] @ 55464863 ##: -2 [wrote] @ 55466039 ##: -2 [wrote] @ 55467215 ##: -2 [wrote] @ 55468391 ##: -2 [wrote] @ 55469567 ##: -2 [wrote] @ 55470743 ##: -2 [wrote] @ 55471919 ##: -2 [wrote] @ 55473095 ##: -2 [wrote] @ 55474271 ##: -2 [wrote] @ 55475447 ##: -2 [wrote] @ 55476623 ##: -2 [wrote] @ 55477799 ##: -2 [wrote] @ 55478975 ##: -2 [wrote] @ 55480151 ##: -2 [wrote] @ 55481327 ##: -2 [wrote] @ 55482503 ##: -2 [wrote] @ 55483679 ##: -2 [wrote] @ 55484855 ##: -2 [wrote] @ 55486031 ##: -2 [wrote] @ 55487207 ##: -2 [wrote] @ 55488383 ##: -2 [wrote] @ 55489559 ##: -2 [wrote] @ 55490735 ##: -2 [wrote] @ 55491911 ##: -2 [wrote] @ 55493087 ##: -2 [wrote] @ 55494263 ##: -2 [wrote] @ 55495439 ##: -2 [wrote] @ 55496615 ##: -2 [wrote] @ 55497791 ##: -2 [wrote] @ 55498967 ##: -2 [wrote] @ 55500143 ##: -2 [wrote] @ 55501319 ##: -2 [wrote] @ 55502495 ##: -2 [wrote] @ 55503671 ##: -2 [wrote] @ 55504847 ##: -2 [wrote] @ 55506023 ##: -2 [wrote] @ 55507199 ##: -2 [wrote] @ 55508375 ##: -2 [wrote] @ 55509551 ##: -2 [wrote] @ 55510727 ##: -2 [wrote] @ 55511903 ##: -2 [wrote] @ 55513079 ##: -2 [wrote] @ 55514255 ##: -2 [wrote] @ 55515431 ##: -2 [wrote] @ 55516607 ##: -2 [wrote] @ 55517783 ##: -2 [wrote] @ 55518959 ##: -2 [wrote] @ 55520135 ##: -2 [wrote] @ 55521311 ##: -2 [wrote] @ 55522487 ##: -2 [wrote] @ 55523663 ##: -2 [wrote] @ 55524839 ##: -2 [wrote] @ 55526015 ##: -2 [wrote] @ 55527191 ##: -2 [wrote] @ 55528367 ##: -2 [wrote] @ 55529543 ##: -2 [wrote] @ 55530719 ##: -2 [wrote] @ 55531895 ##: -2 [wrote] @ 55533071 ##: -2 [wrote] @ 55534247 ##: -2 [wrote] @ 55535423 ##: -2 [wrote] @ 55536599 ##: -2 [wrote] @ 55537775 ##: -2 [wrote] @ 55538951 ##: -2 [wrote] @ 55540127 ##: -2 [wrote] @ 55541303 ##: -2 [wrote] @ 55542479 ##: -2 [wrote] @ 55543655 ##: -2 [wrote] @ 55544831 ##: -2 [wrote] @ 55546007 ##: -2 [wrote] @ 55547183 ##: -2 [wrote] @ 55548359 ##: -2 [wrote] @ 55549535 ##: -2 [wrote] @ 55550711 ##: -2 [wrote] @ 55551887 ##: -2 [wrote] @ 55553063 ##: -2 [wrote] @ 55554239 ##: -2 [wrote] @ 55555415 ##: -2 [wrote] @ 55556591 ##: -2 [wrote] @ 55557767 ##: -2 [wrote] @ 55558943 ##: -2 [wrote] @ 55560119 ##: -2 [wrote] @ 55561295 ##: -2 [wrote] @ 55562471 ##: -2 [wrote] @ 55563647 ##: -2 [wrote] @ 55564823 ##: -2 [wrote] @ 55565999 ##: -2 [wrote] @ 55567175 ##: -2 [wrote] @ 55568351 ##: -2 [wrote] @ 55569527 ##: -2 [wrote] @ 55570703 ##: -2 [wrote] @ 55571879 ##: -2 [wrote] @ 55573055 ##: -2 [wrote] @ 55574231 ##: -2 [wrote] @ 55575407 ##: -2 [wrote] @ 55576583 ##: -2 [wrote] @ 55577759 ##: -2 [wrote] @ 55578935 ##: -2 [wrote] @ 55580111 ##: -2 [wrote] @ 55581287 ##: -2 [wrote] @ 55582463 ##: -2 [wrote] @ 55583639 ##: -2 [wrote] @ 55584815 ##: -2 [wrote] @ 55585991 ##: -2 [wrote] @ 55587167 ##: -2 [wrote] @ 55588343 ##: -2 [wrote] @ 55589519 ##: -2 [wrote] @ 55590695 ##: -2 [wrote] @ 55591871 ##: -2 [wrote] @ 55593047 ##: -2 [wrote] @ 55594223 ##: -2 [wrote] @ 55595399 ##: -2 [wrote] @ 55596575 ##: -2 [wrote] @ 55597751 ##: -2 [wrote] @ 55598927 ##: -2 [wrote] @ 55600103 ##: -2 [wrote] @ 55601279 ##: -2 [wrote] @ 55602455 ##: -2 [wrote] @ 55603631 ##: -2 [wrote] @ 55604807 ##: -2 [wrote] @ 55605983 ##: -2 [wrote] @ 55607159 ##: -2 [wrote] @ 55608335 ##: -2 [wrote] @ 55609511 ##: -2 [wrote] @ 55610687 ##: -2 [wrote] @ 55611863 ##: -2 [wrote] @ 55613039 ##: -2 [wrote] @ 55614215 ##: -2 [wrote] @ 55615391 ##: -2 [wrote] @ 55616567 ##: -2 [wrote] @ 55617743 ##: -2 [wrote] @ 55618919 ##: -2 [wrote] @ 55620095 ##: -2 [wrote] @ 55621271 ##: -2 [wrote] @ 55622447 ##: -2 [wrote] @ 55623623 ##: -2 [wrote] @ 55624799 ##: -2 [wrote] @ 55625975 ##: -2 [wrote] @ 55627151 ##: -2 [wrote] @ 55628327 ##: -2 [wrote] @ 55629503 ##: -2 [wrote] @ 55630679 ##: -2 [wrote] @ 55631855 ##: -2 [wrote] @ 55633031 ##: -2 [wrote] @ 55634207 ##: -2 [wrote] @ 55635383 ##: -2 [wrote] @ 55636559 ##: -2 [wrote] @ 55637735 ##: -2 [wrote] @ 55638911 ##: -2 [wrote] @ 55640087 ##: -2 [wrote] @ 55641263 ##: -2 [wrote] @ 55642439 ##: -2 [wrote] @ 55643615 ##: -2 [wrote] @ 55644791 ##: -2 [wrote] @ 55645967 ##: -2 [wrote] @ 55647143 ##: -2 [wrote] @ 55648319 ##: -2 [wrote] @ 55649495 ##: -2 [wrote] @ 55650671 ##: -2 [wrote] @ 55651847 ##: -2 [wrote] @ 55653023 ##: -2 [wrote] @ 55654199 ##: -2 [wrote] @ 55655375 ##: -2 [wrote] @ 55656551 ##: -2 [wrote] @ 55657727 ##: -2 [wrote] @ 55658903 ##: -2 [wrote] @ 55660079 ##: -2 [wrote] @ 55661255 ##: -2 [wrote] @ 55662431 ##: -2 [wrote] @ 55663607 ##: -2 [wrote] @ 55664783 ##: -2 [wrote] @ 55665959 ##: -2 [wrote] @ 55667135 ##: -2 [wrote] @ 55668311 ##: -2 [wrote] @ 55669487 ##: -2 [wrote] @ 55670663 ##: -2 [wrote] @ 55671839 ##: -2 [wrote] @ 55673015 ##: -2 [wrote] @ 55674191 ##: -2 [wrote] @ 55675367 ##: -2 [wrote] @ 55676543 ##: -2 [wrote] @ 55677719 ##: -2 [wrote] @ 55678895 ##: -2 [wrote] @ 55680071 ##: -2 [wrote] @ 55681247 ##: -2 [wrote] @ 55682423 ##: -2 [wrote] @ 55683599 ##: -2 [wrote] @ 55684775 ##: -2 [wrote] @ 55685951 ##: -2 [wrote] @ 55687127 ##: -2 [wrote] @ 55688303 ##: -2 [wrote] @ 55689479 ##: -2 [wrote] @ 55690655 ##: -2 [wrote] @ 55691831 ##: -2 [wrote] @ 55693007 ##: -2 [wrote] @ 55694183 ##: -2 [wrote] @ 55695359 ##: -2 [wrote] @ 55696535 ##: -2 [wrote] @ 55697711 ##: -2 [wrote] @ 55698887 ##: -2 [wrote] @ 55700063 ##: -2 [wrote] @ 55701239 ##: -2 [wrote] @ 55702415 ##: -2 [wrote] @ 55703591 ##: -2 [wrote] @ 55704767 ##: -2 [wrote] @ 55705943 ##: -2 [wrote] @ 55707119 ##: -2 [wrote] @ 55708295 ##: -2 [wrote] @ 55709471 ##: -2 [wrote] @ 55710647 ##: -2 [wrote] @ 55711823 ##: -2 [wrote] @ 55712999 ##: -2 [wrote] @ 55714175 ##: -2 [wrote] @ 55715351 ##: -2 [wrote] @ 55716527 ##: -2 [wrote] @ 55717703 ##: -2 [wrote] @ 55718879 ##: -2 [wrote] @ 55720055 ##: -2 [wrote] @ 55721231 ##: -2 [wrote] @ 55722407 ##: -2 [wrote] @ 55723583 ##: -2 [wrote] @ 55724759 ##: -2 [wrote] @ 55725935 ##: -2 [wrote] @ 55727111 ##: -2 [wrote] @ 55728287 ##: -2 [wrote] @ 55729463 ##: -2 [wrote] @ 55730639 ##: -2 [wrote] @ 55731815 ##: -2 [wrote] @ 55732991 ##: -2 [wrote] @ 55734167 ##: -2 [wrote] @ 55735343 ##: -2 [wrote] @ 55736519 ##: -2 [wrote] @ 55737695 ##: -2 [wrote] @ 55738871 ##: -2 [wrote] @ 55740047 ##: -2 [wrote] @ 55741223 ##: -2 [wrote] @ 55742399 ##: -2 [wrote] @ 55743575 ##: -2 [wrote] @ 55744751 ##: -2 [wrote] @ 55745927 ##: -2 [wrote] @ 55747103 ##: -2 [wrote] @ 55748279 ##: -2 [wrote] @ 55749455 ##: -2 [wrote] @ 55750631 ##: -2 [wrote] @ 55751807 ##: -2 [wrote] @ 55752983 ##: -2 [wrote] @ 55754159 ##: -2 [wrote] @ 55755335 ##: -2 [wrote] @ 55756511 ##: -2 [wrote] @ 55757687 ##: -2 [wrote] @ 55758863 ##: -2 [wrote] @ 55760039 ##: -2 [wrote] @ 55761215 ##: -2 [wrote] @ 55762391 ##: -2 [wrote] @ 55763567 ##: -2 [wrote] @ 55764743 ##: -2 [wrote] @ 55765919 ##: -2 [wrote] @ 55767095 ##: -2 [wrote] @ 55768271 ##: -2 [wrote] @ 55769447 ##: -2 [wrote] @ 55770623 ##: -2 [wrote] @ 55771799 ##: -2 [wrote] @ 55772975 ##: -2 [wrote] @ 55774151 ##: -2 [wrote] @ 55775327 ##: -2 [wrote] @ 55776503 ##: -2 [wrote] @ 55777679 ##: -2 [wrote] @ 55778855 ##: -2 [wrote] @ 55780031 ##: -2 [wrote] @ 55781207 ##: -2 [wrote] @ 55782383 ##: -2 [wrote] @ 55783559 ##: -2 [wrote] @ 55784735 ##: -2 [wrote] @ 55785911 ##: -2 [wrote] @ 55787087 ##: -2 [wrote] @ 55788263 ##: -2 [wrote] @ 55789439 ##: -2 [wrote] @ 55790615 ##: -2 [wrote] @ 55791791 ##: -2 [wrote] @ 55792967 ##: -2 [wrote] @ 55794143 ##: -2 [wrote] @ 55795319 ##: -2 [wrote] @ 55796495 ##: -2 [wrote] @ 55797671 ##: -2 [wrote] @ 55798847 ##: -2 [wrote] @ 55800023 ##: -2 [wrote] @ 55801199 ##: -2 [wrote] @ 55802375 ##: -2 [wrote] @ 55803551 ##: -2 [wrote] @ 55804727 ##: -2 [wrote] @ 55805903 ##: -2 [wrote] @ 55807079 ##: -2 [wrote] @ 55808255 ##: -2 [wrote] @ 55809431 ##: -2 [wrote] @ 55810607 ##: -2 [wrote] @ 55811783 ##: -2 [wrote] @ 55812959 ##: -2 [wrote] @ 55814135 ##: -2 [wrote] @ 55815311 ##: -2 [wrote] @ 55816487 ##: -2 [wrote] @ 55817663 ##: -2 [wrote] @ 55818839 ##: -2 [wrote] @ 55820015 ##: -2 [wrote] @ 55821191 ##: -2 [wrote] @ 55822367 ##: -2 [wrote] @ 55823543 ##: -2 [wrote] @ 55824719 ##: -2 [wrote] @ 55825895 ##: -2 [wrote] @ 55827071 ##: -2 [wrote] @ 55828247 ##: -2 [wrote] @ 55829423 ##: -2 [wrote] @ 55830599 ##: -2 [wrote] @ 55831775 ##: -2 [wrote] @ 55832951 ##: -2 [wrote] @ 55834127 ##: -2 [wrote] @ 55835303 ##: -2 [wrote] @ 55836479 ##: -2 [wrote] @ 55837655 ##: -2 [wrote] @ 55838831 ##: -2 [wrote] @ 55840007 ##: -2 [wrote] @ 55841183 ##: -2 [wrote] @ 55842359 ##: -2 [wrote] @ 55843535 ##: -2 [wrote] @ 55844711 ##: -2 [wrote] @ 55845887 ##: -2 [wrote] @ 55847063 ##: -2 [wrote] @ 55848239 ##: -2 [wrote] @ 55849415 ##: -2 [wrote] @ 55850591 ##: -2 [wrote] @ 55851767 ##: -2 [wrote] @ 55852943 ##: -2 [wrote] @ 55854119 ##: -2 [wrote] @ 55855295 ##: -2 [wrote] @ 55856471 ##: -2 [wrote] @ 55857647 ##: -2 [wrote] @ 55858823 ##: -2 [wrote] @ 55859999 ##: -2 [wrote] @ 55861175 ##: -2 [wrote] @ 55862351 ##: -2 [wrote] @ 55863527 ##: -2 [wrote] @ 55864703 ##: -2 [wrote] @ 55865879 ##: -2 [wrote] @ 55867055 ##: -2 [wrote] @ 55868231 ##: -2 [wrote] @ 55869407 ##: -2 [wrote] @ 55870583 ##: -2 [wrote] @ 55871759 ##: -2 [wrote] @ 55872935 ##: -2 [wrote] @ 55874111 ##: -2 [wrote] @ 55875287 ##: -2 [wrote] @ 55876463 ##: -2 [wrote] @ 55877639 ##: -2 [wrote] @ 55878815 ##: -2 [wrote] @ 55879991 ##: -2 [wrote] @ 55881167 ##: -2 [wrote] @ 55882343 ##: -2 [wrote] @ 55883519 ##: -2 [wrote] @ 55884695 ##: -2 [wrote] @ 55885871 ##: -2 [wrote] @ 55887047 ##: -2 [wrote] @ 55888223 ##: -2 [wrote] @ 55889399 ##: -2 [wrote] @ 55890575 ##: -2 [wrote] @ 55891751 ##: -2 [wrote] @ 55892927 ##: -2 [wrote] @ 55894103 ##: -2 [wrote] @ 55895279 ##: -2 [wrote] @ 55896455 ##: -2 [wrote] @ 55897631 ##: -2 [wrote] @ 55898807 ##: -2 [wrote] @ 55899983 ##: -2 [wrote] @ 55901159 ##: -2 [wrote] @ 55902335 ##: -2 [wrote] @ 55903511 ##: -2 [wrote] @ 55904687 ##: -2 [wrote] @ 55905863 ##: -2 [wrote] @ 55907039 ##: -2 [wrote] @ 55908215 ##: -2 [wrote] @ 55909391 ##: -2 [wrote] @ 55910567 ##: -2 [wrote] @ 55911743 ##: -2 [wrote] @ 55912919 ##: -2 [wrote] @ 55914095 ##: -2 [wrote] @ 55915271 ##: -2 [wrote] @ 55916447 ##: -2 [wrote] @ 55917623 ##: -2 [wrote] @ 55918799 ##: -2 [wrote] @ 55919975 ##: -2 [wrote] @ 55921151 ##: -2 [wrote] @ 55922327 ##: -2 [wrote] @ 55923503 ##: -2 [wrote] @ 55924679 ##: -2 [wrote] @ 55925855 ##: -2 [wrote] @ 55927031 ##: -2 [wrote] @ 55928207 ##: -2 [wrote] @ 55929383 ##: -2 [wrote] @ 55930559 ##: -2 [wrote] @ 55931735 ##: -2 [wrote] @ 55932911 ##: -2 [wrote] @ 55934087 ##: -2 [wrote] @ 55935263 ##: -2 [wrote] @ 55936439 ##: -2 [wrote] @ 55937615 ##: -2 [wrote] @ 55938791 ##: -2 [wrote] @ 55939967 ##: -2 [wrote] @ 55941143 ##: -2 [wrote] @ 55942319 ##: -2 [wrote] @ 55943495 ##: -2 [wrote] @ 55944671 ##: -2 [wrote] @ 55945847 ##: -2 [wrote] @ 55947023 ##: -2 [wrote] @ 55948199 ##: -2 [wrote] @ 55949375 ##: -2 [wrote] @ 55950551 ##: -2 [wrote] @ 55951727 ##: -2 [wrote] @ 55952903 ##: -2 [wrote] @ 55954079 ##: -2 [wrote] @ 55955255 ##: -2 [wrote] @ 55956431 ##: -2 [wrote] @ 55957607 ##: -2 [wrote] @ 55958783 ##: -2 [wrote] @ 55959959 ##: -2 [wrote] @ 55961135 ##: -2 [wrote] @ 55962311 ##: -2 [wrote] @ 55963487 ##: -2 [wrote] @ 55964663 ##: -2 [wrote] @ 55965839 ##: -2 [wrote] @ 55967015 ##: -2 [wrote] @ 55968191 ##: -2 [wrote] @ 55969367 ##: -2 [wrote] @ 55970543 ##: -2 [wrote] @ 55971719 ##: -2 [wrote] @ 55972895 ##: -2 [wrote] @ 55974071 ##: -2 [wrote] @ 55975247 ##: -2 [wrote] @ 55976423 ##: -2 [wrote] @ 55977599 ##: -2 [wrote] @ 55978775 ##: -2 [wrote] @ 55979951 ##: -2 [wrote] @ 55981127 ##: -2 [wrote] @ 55982303 ##: -2 [wrote] @ 55983479 ##: -2 [wrote] @ 55984655 ##: -2 [wrote] @ 55985831 ##: -2 [wrote] @ 55987007 ##: -2 [wrote] @ 55988183 ##: -2 [wrote] @ 55989359 ##: -2 [wrote] @ 55990535 ##: -2 [wrote] @ 55991711 ##: -2 [wrote] @ 55992887 ##: -2 [wrote] @ 55994063 ##: -2 [wrote] @ 55995239 ##: -2 [wrote] @ 55996415 ##: -2 [wrote] @ 55997591 ##: -2 [wrote] @ 55998767 ##: -2 [wrote] @ 55999943 ##: -2 [wrote] @ 56001119 ##: -2 [wrote] @ 56002295 ##: -2 [wrote] @ 56003471 ##: -2 [wrote] @ 56004647 ##: -2 [wrote] @ 56005823 ##: -2 [wrote] @ 56006999 ##: -2 [wrote] @ 56008175 ##: -2 [wrote] @ 56009351 ##: -2 [wrote] @ 56010527 ##: -2 [wrote] @ 56011703 ##: -2 [wrote] @ 56012879 ##: -2 [wrote] @ 56014055 ##: -2 [wrote] @ 56015231 ##: -2 [wrote] @ 56016407 ##: -2 [wrote] @ 56017583 ##: -2 [wrote] @ 56018759 ##: -2 [wrote] @ 56019935 ##: -2 [wrote] @ 56021111 ##: -2 [wrote] @ 56022287 ##: -2 [wrote] @ 56023463 ##: -2 [wrote] @ 56024639 ##: -2 [wrote] @ 56025815 ##: -2 [wrote] @ 56026991 ##: -2 [wrote] @ 56028167 ##: -2 [wrote] @ 56029343 ##: -2 [wrote] @ 56030519 ##: -2 [wrote] @ 56031695 ##: -2 [wrote] @ 56032871 ##: -2 [wrote] @ 56034047 ##: -2 [wrote] @ 56035223 ##: -2 [wrote] @ 56036399 ##: -2 [wrote] @ 56037575 ##: -2 [wrote] @ 56038751 ##: -2 [wrote] @ 56039927 ##: -2 [wrote] @ 56041103 ##: -2 [wrote] @ 56042279 ##: -2 [wrote] @ 56043455 ##: -2 [wrote] @ 56044631 ##: -2 [wrote] @ 56045807 ##: -2 [wrote] @ 56046983 ##: -2 [wrote] @ 56048159 ##: -2 [wrote] @ 56049335 ##: -2 [wrote] @ 56050511 ##: -2 [wrote] @ 56051687 ##: -2 [wrote] @ 56052863 ##: -2 [wrote] @ 56054039 ##: -2 [wrote] @ 56055215 ##: -2 [wrote] @ 56056391 ##: -2 [wrote] @ 56057567 ##: -2 [wrote] @ 56058743 ##: -2 [wrote] @ 56059919 ##: -2 [wrote] @ 56061095 ##: -2 [wrote] @ 56062271 ##: -2 [wrote] @ 56063447 ##: -2 [wrote] @ 56064623 ##: -2 [wrote] @ 56065799 ##: -2 [wrote] @ 56066975 ##: -2 [wrote] @ 56068151 ##: -2 [wrote] @ 56069327 ##: -2 [wrote] @ 56070503 ##: -2 [wrote] @ 56071679 ##: -2 [wrote] @ 56072855 ##: -2 [wrote] @ 56074031 ##: -2 [wrote] @ 56075207 ##: -2 [wrote] @ 56076383 ##: -2 [wrote] @ 56077559 ##: -2 [wrote] @ 56078735 ##: -2 [wrote] @ 56079911 ##: -2 [wrote] @ 56081087 ##: -2 [wrote] @ 56082263 ##: -2 [wrote] @ 56083439 ##: -2 [wrote] @ 56084615 ##: -2 [wrote] @ 56085791 ##: -2 [wrote] @ 56086967 ##: -2 [wrote] @ 56088143 ##: -2 [wrote] @ 56089319 ##: -2 [wrote] @ 56090495 ##: -2 [wrote] @ 56091671 ##: -2 [wrote] @ 56092847 ##: -2 [wrote] @ 56094023 ##: -2 [wrote] @ 56095199 ##: -2 [wrote] @ 56096375 ##: -2 [wrote] @ 56097551 ##: -2 [wrote] @ 56098727 ##: -2 [wrote] @ 56099903 ##: -2 [wrote] @ 56101079 ##: -2 [wrote] @ 56102255 ##: -2 [wrote] @ 56103431 ##: -2 [wrote] @ 56104607 ##: -2 [wrote] @ 56105783 ##: -2 [wrote] @ 56106959 ##: -2 [wrote] @ 56108135 ##: -2 [wrote] @ 56109311 ##: -2 [wrote] @ 56110487 ##: -2 [wrote] @ 56111663 ##: -2 [wrote] @ 56112839 ##: -2 [wrote] @ 56114015 ##: -2 [wrote] @ 56115191 ##: -2 [wrote] @ 56116367 ##: -2 [wrote] @ 56117543 ##: -2 [wrote] @ 56118719 ##: -1 [finished] @ 56118719 Done. whipper-0.9.0/whipper/test/cdparanoia.progress.error000066400000000000000000002433531357173224400226740ustar00rootroot00000000000000Sending all callbacks to stderr for wrapper script cdparanoia III release 10.2 (September 11, 2008) Ripping from sector -2 (track 1 [0:00.00]) to sector 10797 (track 1 [2:23.74]) outputting to cdda.wav scsi_read error: sector=-2 length=8 retry=0 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=4 retry=1 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=2 retry=2 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=3 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=4 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=5 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=6 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=7 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=8 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument ##: 12 [transport error] @ -2352 ##: 0 [read] @ 5880 ##: 0 [read] @ 37632 ##: 0 [read] @ 69384 ##: 0 [read] @ 101136 ##: 0 [read] @ 132888 ##: 0 [read] @ 164640 ##: 0 [read] @ 196392 ##: 0 [read] @ 228144 ##: 0 [read] @ 259896 ##: 0 [read] @ 291648 ##: 0 [read] @ 323400 ##: 0 [read] @ 355152 ##: 0 [read] @ 386904 ##: 0 [read] @ 418656 ##: 0 [read] @ 450408 ##: 0 [read] @ 482160 ##: 0 [read] @ 513912 ##: 0 [read] @ 545664 ##: 0 [read] @ 577416 ##: 0 [read] @ 609168 ##: 0 [read] @ 640920 ##: 0 [read] @ 672672 ##: 0 [read] @ 704424 ##: 0 [read] @ 736176 ##: 0 [read] @ 767928 ##: 0 [read] @ 799680 ##: 0 [read] @ 831432 ##: 0 [read] @ 863184 ##: 0 [read] @ 894936 ##: 0 [read] @ 926688 ##: 0 [read] @ 958440 ##: 0 [read] @ 990192 ##: 0 [read] @ 1021944 ##: 0 [read] @ 1053696 ##: 0 [read] @ 1085448 ##: 0 [read] @ 1117200 ##: 0 [read] @ 1148952 ##: 0 [read] @ 1180704 ##: 0 [read] @ 1212456 ##: 0 [read] @ 1244208 ##: 0 [read] @ 1275960 ##: 0 [read] @ 1307712 ##: 0 [read] @ 1339464 ##: 0 [read] @ 1371216 ##: 0 [read] @ 1402968 ##: 0 [read] @ 1407672 scsi_read error: sector=-2 length=22 retry=0 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=11 retry=1 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=5 retry=2 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=2 retry=3 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=4 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=5 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=6 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=7 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=8 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument ##: 12 [transport error] @ -2352 ##: 0 [read] @ 22344 ##: 0 [read] @ 54096 ##: 0 [read] @ 85848 ##: 0 [read] @ 117600 ##: 0 [read] @ 149352 ##: 0 [read] @ 181104 ##: 0 [read] @ 212856 ##: 0 [read] @ 244608 ##: 0 [read] @ 276360 ##: 0 [read] @ 308112 ##: 0 [read] @ 339864 ##: 0 [read] @ 371616 ##: 0 [read] @ 403368 ##: 0 [read] @ 435120 ##: 0 [read] @ 466872 ##: 0 [read] @ 498624 ##: 0 [read] @ 530376 ##: 0 [read] @ 562128 ##: 0 [read] @ 593880 ##: 0 [read] @ 625632 ##: 0 [read] @ 657384 ##: 0 [read] @ 689136 ##: 0 [read] @ 720888 ##: 0 [read] @ 752640 ##: 0 [read] @ 784392 ##: 0 [read] @ 816144 ##: 0 [read] @ 847896 ##: 0 [read] @ 879648 ##: 0 [read] @ 911400 ##: 0 [read] @ 943152 ##: 0 [read] @ 974904 ##: 0 [read] @ 1006656 ##: 0 [read] @ 1038408 ##: 0 [read] @ 1070160 ##: 0 [read] @ 1101912 ##: 0 [read] @ 1133664 ##: 0 [read] @ 1165416 ##: 0 [read] @ 1197168 ##: 0 [read] @ 1228920 ##: 0 [read] @ 1260672 ##: 0 [read] @ 1292424 ##: 0 [read] @ 1324176 ##: 0 [read] @ 1355928 ##: 0 [read] @ 1387680 ##: 0 [read] @ 1407672 ##: 1 [verify] @ 23520 ##: 2 [jitter] @ 32928 ##: 2 [jitter] @ 1418256 scsi_read error: sector=-2 length=21 retry=0 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=10 retry=1 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=5 retry=2 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=2 retry=3 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=4 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=5 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=6 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=7 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=8 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument ##: 12 [transport error] @ -2352 ##: 0 [read] @ 21168 ##: 0 [read] @ 52920 ##: 0 [read] @ 84672 ##: 0 [read] @ 116424 ##: 0 [read] @ 148176 ##: 0 [read] @ 179928 ##: 0 [read] @ 211680 ##: 0 [read] @ 243432 ##: 0 [read] @ 275184 ##: 0 [read] @ 306936 ##: 0 [read] @ 338688 ##: 0 [read] @ 370440 ##: 0 [read] @ 402192 ##: 0 [read] @ 433944 ##: 0 [read] @ 465696 ##: 0 [read] @ 497448 ##: 0 [read] @ 529200 ##: 0 [read] @ 560952 ##: 0 [read] @ 592704 ##: 0 [read] @ 624456 ##: 0 [read] @ 656208 ##: 0 [read] @ 687960 ##: 0 [read] @ 719712 ##: 0 [read] @ 751464 ##: 0 [read] @ 783216 ##: 0 [read] @ 814968 ##: 0 [read] @ 846720 ##: 0 [read] @ 878472 ##: 0 [read] @ 910224 ##: 0 [read] @ 941976 ##: 0 [read] @ 973728 ##: 0 [read] @ 1005480 ##: 0 [read] @ 1037232 ##: 0 [read] @ 1068984 ##: 0 [read] @ 1100736 ##: 0 [read] @ 1132488 ##: 0 [read] @ 1164240 ##: 0 [read] @ 1195992 ##: 0 [read] @ 1227744 ##: 0 [read] @ 1259496 ##: 0 [read] @ 1291248 ##: 0 [read] @ 1323000 ##: 0 [read] @ 1354752 ##: 0 [read] @ 1386504 ##: 0 [read] @ 1407672 ##: 1 [verify] @ 22344 ##: 2 [jitter] @ 31752 ##: 2 [jitter] @ 1418256 ##: 1 [verify] @ 22344 ##: 2 [jitter] @ 49392 ##: 2 [jitter] @ 1434720 scsi_read error: sector=-2 length=20 retry=0 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=10 retry=1 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=5 retry=2 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=2 retry=3 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=4 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=5 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=6 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=7 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=8 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument ##: 12 [transport error] @ -2352 ##: 0 [read] @ 19992 ##: 0 [read] @ 51744 ##: 0 [read] @ 83496 ##: 0 [read] @ 115248 ##: 0 [read] @ 147000 ##: 0 [read] @ 178752 ##: 0 [read] @ 210504 ##: 0 [read] @ 242256 ##: 0 [read] @ 274008 ##: 0 [read] @ 305760 ##: 0 [read] @ 337512 ##: 0 [read] @ 369264 ##: 0 [read] @ 401016 ##: 0 [read] @ 432768 ##: 0 [read] @ 464520 ##: 0 [read] @ 496272 ##: 0 [read] @ 528024 ##: 0 [read] @ 559776 ##: 0 [read] @ 591528 ##: 0 [read] @ 623280 ##: 0 [read] @ 655032 ##: 0 [read] @ 686784 ##: 0 [read] @ 718536 ##: 0 [read] @ 750288 ##: 0 [read] @ 782040 ##: 0 [read] @ 813792 ##: 0 [read] @ 845544 ##: 0 [read] @ 877296 ##: 0 [read] @ 909048 ##: 0 [read] @ 940800 ##: 0 [read] @ 972552 ##: 0 [read] @ 1004304 ##: 0 [read] @ 1036056 ##: 0 [read] @ 1067808 ##: 0 [read] @ 1099560 ##: 0 [read] @ 1131312 ##: 0 [read] @ 1163064 ##: 0 [read] @ 1194816 ##: 0 [read] @ 1226568 ##: 0 [read] @ 1258320 ##: 0 [read] @ 1290072 ##: 0 [read] @ 1321824 ##: 0 [read] @ 1353576 ##: 0 [read] @ 1385328 ##: 0 [read] @ 1407672 ##: 1 [verify] @ 21168 ##: 2 [jitter] @ 30576 ##: 2 [jitter] @ 1418256 ##: 1 [verify] @ 21168 ##: 2 [jitter] @ 49392 ##: 2 [jitter] @ 1434720 ##: 1 [verify] @ 21168 ##: 2 [jitter] @ 47040 ##: 2 [jitter] @ 1433544 scsi_read error: sector=-2 length=19 retry=0 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=9 retry=1 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=4 retry=2 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=2 retry=3 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=4 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=5 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=6 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=7 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=8 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument ##: 12 [transport error] @ -2352 ##: 0 [read] @ 18816 ##: 0 [read] @ 50568 ##: 0 [read] @ 82320 ##: 0 [read] @ 114072 ##: 0 [read] @ 145824 ##: 0 [read] @ 177576 ##: 0 [read] @ 209328 ##: 0 [read] @ 241080 ##: 0 [read] @ 272832 ##: 0 [read] @ 304584 ##: 0 [read] @ 336336 ##: 0 [read] @ 368088 ##: 0 [read] @ 399840 ##: 0 [read] @ 431592 ##: 0 [read] @ 463344 ##: 0 [read] @ 495096 ##: 0 [read] @ 526848 ##: 0 [read] @ 558600 ##: 0 [read] @ 590352 ##: 0 [read] @ 622104 ##: 0 [read] @ 653856 ##: 0 [read] @ 685608 ##: 0 [read] @ 717360 ##: 0 [read] @ 749112 ##: 0 [read] @ 780864 ##: 0 [read] @ 812616 ##: 0 [read] @ 844368 ##: 0 [read] @ 876120 ##: 0 [read] @ 907872 ##: 0 [read] @ 939624 ##: 0 [read] @ 971376 ##: 0 [read] @ 1003128 ##: 0 [read] @ 1034880 ##: 0 [read] @ 1066632 ##: 0 [read] @ 1098384 ##: 0 [read] @ 1130136 ##: 0 [read] @ 1161888 ##: 0 [read] @ 1193640 ##: 0 [read] @ 1225392 ##: 0 [read] @ 1257144 ##: 0 [read] @ 1288896 ##: 0 [read] @ 1320648 ##: 0 [read] @ 1352400 ##: 0 [read] @ 1384152 ##: 0 [read] @ 1407672 ##: 1 [verify] @ 19992 ##: 2 [jitter] @ 29400 ##: 2 [jitter] @ 1418256 ##: 1 [verify] @ 19992 ##: 2 [jitter] @ 49392 ##: 2 [jitter] @ 1434720 ##: 1 [verify] @ 19992 ##: 2 [jitter] @ 47040 ##: 2 [jitter] @ 1433544 ##: 1 [verify] @ 19992 ##: 9 [overlap] @ 24696 ##: 2 [jitter] @ 44688 ##: 2 [jitter] @ 1432368 ##: 9 [overlap] @ 37044 scsi_read error: sector=-2 length=18 retry=0 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=9 retry=1 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=4 retry=2 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=2 retry=3 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=4 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=5 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=6 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=7 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=8 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument ##: 12 [transport error] @ -2352 ##: 0 [read] @ 17640 ##: 0 [read] @ 49392 ##: 0 [read] @ 81144 ##: 0 [read] @ 112896 ##: 0 [read] @ 144648 ##: 0 [read] @ 176400 ##: 0 [read] @ 208152 ##: 0 [read] @ 239904 ##: 0 [read] @ 271656 ##: 0 [read] @ 303408 ##: 0 [read] @ 335160 ##: 0 [read] @ 366912 ##: 0 [read] @ 398664 ##: 0 [read] @ 430416 ##: 0 [read] @ 462168 ##: 0 [read] @ 493920 ##: 0 [read] @ 525672 ##: 0 [read] @ 557424 ##: 0 [read] @ 589176 ##: 0 [read] @ 620928 ##: 0 [read] @ 652680 ##: 0 [read] @ 684432 ##: 0 [read] @ 716184 ##: 0 [read] @ 747936 ##: 0 [read] @ 779688 ##: 0 [read] @ 811440 ##: 0 [read] @ 843192 ##: 0 [read] @ 874944 ##: 0 [read] @ 906696 ##: 0 [read] @ 938448 ##: 0 [read] @ 970200 ##: 0 [read] @ 1001952 ##: 0 [read] @ 1033704 ##: 0 [read] @ 1065456 ##: 0 [read] @ 1097208 ##: 0 [read] @ 1128960 ##: 0 [read] @ 1160712 ##: 0 [read] @ 1192464 ##: 0 [read] @ 1224216 ##: 0 [read] @ 1255968 ##: 0 [read] @ 1287720 ##: 0 [read] @ 1319472 ##: 0 [read] @ 1351224 ##: 0 [read] @ 1382976 ##: 0 [read] @ 1407672 ##: 1 [verify] @ 18816 ##: 2 [jitter] @ 28224 ##: 2 [jitter] @ 1418256 ##: 1 [verify] @ 18816 ##: 2 [jitter] @ 49392 ##: 2 [jitter] @ 1434720 ##: 1 [verify] @ 18816 ##: 2 [jitter] @ 47040 ##: 2 [jitter] @ 1433544 ##: 1 [verify] @ 18816 ##: 2 [jitter] @ 44688 ##: 2 [jitter] @ 1432368 ##: 1 [verify] @ 18816 ##: 2 [jitter] @ 42336 ##: 2 [jitter] @ 1431192 scsi_read error: sector=-2 length=17 retry=0 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=8 retry=1 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=4 retry=2 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=2 retry=3 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=4 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=5 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=6 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=7 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=8 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument ##: 12 [transport error] @ -2352 ##: 0 [read] @ 16464 ##: 0 [read] @ 48216 ##: 0 [read] @ 79968 ##: 0 [read] @ 111720 ##: 0 [read] @ 143472 ##: 0 [read] @ 175224 ##: 0 [read] @ 206976 ##: 0 [read] @ 238728 ##: 0 [read] @ 270480 ##: 0 [read] @ 302232 ##: 0 [read] @ 333984 ##: 0 [read] @ 365736 ##: 0 [read] @ 397488 ##: 0 [read] @ 429240 ##: 0 [read] @ 460992 ##: 0 [read] @ 492744 ##: 0 [read] @ 524496 ##: 0 [read] @ 556248 ##: 0 [read] @ 588000 ##: 0 [read] @ 619752 ##: 0 [read] @ 651504 ##: 0 [read] @ 683256 ##: 0 [read] @ 715008 ##: 0 [read] @ 746760 ##: 0 [read] @ 778512 ##: 0 [read] @ 810264 ##: 0 [read] @ 842016 ##: 0 [read] @ 873768 ##: 0 [read] @ 905520 ##: 0 [read] @ 937272 ##: 0 [read] @ 969024 ##: 0 [read] @ 1000776 ##: 0 [read] @ 1032528 ##: 0 [read] @ 1064280 ##: 0 [read] @ 1096032 ##: 0 [read] @ 1127784 ##: 0 [read] @ 1159536 ##: 0 [read] @ 1191288 ##: 0 [read] @ 1223040 ##: 0 [read] @ 1254792 ##: 0 [read] @ 1286544 ##: 0 [read] @ 1318296 ##: 0 [read] @ 1350048 ##: 0 [read] @ 1381800 ##: 0 [read] @ 1407672 ##: 1 [verify] @ 17640 ##: 2 [jitter] @ 27048 ##: 2 [jitter] @ 1418256 ##: 1 [verify] @ 17640 ##: 2 [jitter] @ 49392 ##: 2 [jitter] @ 1434720 ##: 1 [verify] @ 17640 ##: 9 [overlap] @ 17640 ##: 2 [jitter] @ 47040 ##: 2 [jitter] @ 1433544 ##: 1 [verify] @ 17640 ##: 2 [jitter] @ 44688 ##: 2 [jitter] @ 1432368 ##: 1 [verify] @ 17640 ##: 2 [jitter] @ 42336 ##: 2 [jitter] @ 1431192 ##: 1 [verify] @ 17640 ##: 2 [jitter] @ 39984 ##: 2 [jitter] @ 1430016 scsi_read error: sector=-2 length=6 retry=0 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=3 retry=1 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=2 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=3 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=4 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=5 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=6 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=7 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=8 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument ##: 12 [transport error] @ -2352 ##: 0 [read] @ 3528 ##: 0 [read] @ 35280 ##: 0 [read] @ 67032 ##: 0 [read] @ 98784 ##: 0 [read] @ 130536 ##: 0 [read] @ 162288 ##: 0 [read] @ 194040 ##: 0 [read] @ 225792 ##: 0 [read] @ 257544 ##: 0 [read] @ 289296 ##: 0 [read] @ 321048 ##: 0 [read] @ 352800 ##: 0 [read] @ 384552 ##: 0 [read] @ 416304 ##: 0 [read] @ 448056 ##: 0 [read] @ 479808 ##: 0 [read] @ 511560 ##: 0 [read] @ 543312 ##: 0 [read] @ 575064 ##: 0 [read] @ 606816 ##: 0 [read] @ 638568 ##: 0 [read] @ 670320 ##: 0 [read] @ 702072 ##: 0 [read] @ 733824 ##: 0 [read] @ 765576 ##: 0 [read] @ 797328 ##: 0 [read] @ 829080 ##: 0 [read] @ 860832 ##: 0 [read] @ 892584 ##: 0 [read] @ 924336 ##: 0 [read] @ 956088 ##: 0 [read] @ 987840 ##: 0 [read] @ 1019592 ##: 0 [read] @ 1051344 ##: 0 [read] @ 1083096 ##: 0 [read] @ 1114848 ##: 0 [read] @ 1146600 ##: 0 [read] @ 1178352 ##: 0 [read] @ 1210104 ##: 0 [read] @ 1241856 ##: 0 [read] @ 1273608 ##: 0 [read] @ 1305360 ##: 0 [read] @ 1337112 ##: 0 [read] @ 1368864 ##: 0 [read] @ 1400616 ##: 0 [read] @ 1407672 ##: 1 [verify] @ 4704 ##: 2 [jitter] @ 16464 ##: 2 [jitter] @ 1418256 ##: 1 [verify] @ 4704 ##: 1 [verify] @ 4704 ##: 2 [jitter] @ 47040 ##: 2 [jitter] @ 1433544 ##: 1 [verify] @ 4704 ##: 2 [jitter] @ 44688 ##: 2 [jitter] @ 1432368 ##: 1 [verify] @ 4704 ##: 2 [jitter] @ 42336 ##: 2 [jitter] @ 1431192 ##: 1 [verify] @ 4704 ##: 9 [overlap] @ 26460 ##: 2 [jitter] @ 39984 ##: 2 [jitter] @ 1430016 ##: 1 [verify] @ 4704 ##: 2 [jitter] @ 37632 ##: 2 [jitter] @ 1428840 scsi_read error: sector=-2 length=17 retry=0 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=8 retry=1 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=4 retry=2 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=2 retry=3 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=4 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=5 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=6 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=7 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=8 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument ##: 12 [transport error] @ -2352 ##: 0 [read] @ 16464 ##: 0 [read] @ 48216 ##: 0 [read] @ 79968 ##: 0 [read] @ 111720 ##: 0 [read] @ 143472 ##: 0 [read] @ 175224 ##: 0 [read] @ 206976 ##: 0 [read] @ 238728 ##: 0 [read] @ 270480 ##: 0 [read] @ 302232 ##: 0 [read] @ 333984 ##: 0 [read] @ 365736 ##: 0 [read] @ 397488 ##: 0 [read] @ 429240 ##: 0 [read] @ 460992 ##: 0 [read] @ 492744 ##: 0 [read] @ 524496 ##: 0 [read] @ 556248 ##: 0 [read] @ 588000 ##: 0 [read] @ 619752 ##: 0 [read] @ 651504 ##: 0 [read] @ 683256 ##: 0 [read] @ 715008 ##: 0 [read] @ 746760 ##: 0 [read] @ 778512 ##: 0 [read] @ 810264 ##: 0 [read] @ 842016 ##: 0 [read] @ 873768 ##: 0 [read] @ 905520 ##: 0 [read] @ 937272 ##: 0 [read] @ 969024 ##: 0 [read] @ 1000776 ##: 0 [read] @ 1032528 ##: 0 [read] @ 1064280 ##: 0 [read] @ 1096032 ##: 0 [read] @ 1127784 ##: 0 [read] @ 1159536 ##: 0 [read] @ 1191288 ##: 0 [read] @ 1223040 ##: 0 [read] @ 1254792 ##: 0 [read] @ 1286544 ##: 0 [read] @ 1318296 ##: 0 [read] @ 1350048 ##: 0 [read] @ 1381800 ##: 0 [read] @ 1407672 ##: 1 [verify] @ 17640 ##: 2 [jitter] @ 27048 ##: 2 [jitter] @ 1418256 ##: 1 [verify] @ 17640 ##: 2 [jitter] @ 49392 ##: 2 [jitter] @ 1434720 ##: 1 [verify] @ 17640 ##: 2 [jitter] @ 47040 ##: 2 [jitter] @ 1433544 ##: 1 [verify] @ 17640 ##: 2 [jitter] @ 44688 ##: 2 [jitter] @ 1432368 ##: 1 [verify] @ 17640 ##: 2 [jitter] @ 42336 ##: 2 [jitter] @ 1431192 ##: 1 [verify] @ 17640 ##: 2 [jitter] @ 39984 ##: 2 [jitter] @ 1430016 ##: 1 [verify] @ 17640 ##: 9 [overlap] @ 19404 ##: 1 [verify] @ 17640 scsi_read error: sector=-2 length=4 retry=0 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=2 retry=1 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=2 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=3 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=4 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=5 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=6 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=7 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=8 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument ##: 12 [transport error] @ -2352 ##: 0 [read] @ 1176 ##: 0 [read] @ 32928 ##: 0 [read] @ 64680 ##: 0 [read] @ 96432 ##: 0 [read] @ 128184 ##: 0 [read] @ 159936 ##: 0 [read] @ 191688 ##: 0 [read] @ 223440 ##: 0 [read] @ 255192 ##: 0 [read] @ 286944 ##: 0 [read] @ 318696 ##: 0 [read] @ 350448 ##: 0 [read] @ 382200 ##: 0 [read] @ 413952 ##: 0 [read] @ 445704 ##: 0 [read] @ 477456 ##: 0 [read] @ 509208 ##: 0 [read] @ 540960 ##: 0 [read] @ 572712 ##: 0 [read] @ 604464 ##: 0 [read] @ 636216 ##: 0 [read] @ 667968 ##: 0 [read] @ 699720 ##: 0 [read] @ 731472 ##: 0 [read] @ 763224 ##: 0 [read] @ 794976 ##: 0 [read] @ 826728 ##: 0 [read] @ 858480 ##: 0 [read] @ 890232 ##: 0 [read] @ 921984 ##: 0 [read] @ 953736 ##: 0 [read] @ 985488 ##: 0 [read] @ 1017240 ##: 0 [read] @ 1048992 ##: 0 [read] @ 1080744 ##: 0 [read] @ 1112496 ##: 0 [read] @ 1144248 ##: 0 [read] @ 1176000 ##: 0 [read] @ 1207752 ##: 0 [read] @ 1239504 ##: 0 [read] @ 1271256 ##: 0 [read] @ 1303008 ##: 0 [read] @ 1334760 ##: 0 [read] @ 1366512 ##: 0 [read] @ 1398264 ##: 0 [read] @ 1407672 ##: 1 [verify] @ 2352 ##: 2 [jitter] @ 16464 ##: 2 [jitter] @ 1418256 ##: 1 [verify] @ 2352 ##: 1 [verify] @ 2352 ##: 1 [verify] @ 2352 ##: 2 [jitter] @ 44688 ##: 2 [jitter] @ 1432368 ##: 1 [verify] @ 2352 ##: 2 [jitter] @ 42336 ##: 2 [jitter] @ 1431192 ##: 1 [verify] @ 2352 ##: 2 [jitter] @ 39984 ##: 2 [jitter] @ 1430016 ##: 1 [verify] @ 2352 ##: 2 [jitter] @ 37632 ##: 2 [jitter] @ 1428840 ##: 1 [verify] @ 2352 ##: 2 [jitter] @ 11760 ##: 2 [jitter] @ 1415904 ##: 1 [verify] @ 2352 ##: 9 [overlap] @ 29106 ##: 2 [jitter] @ 37632 ##: 2 [jitter] @ 1428840 ##: 9 [overlap] @ 37632 scsi_read error: sector=-2 length=13 retry=0 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=6 retry=1 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=3 retry=2 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=3 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=4 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=5 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=6 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=7 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=8 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument ##: 12 [transport error] @ -2352 ##: 0 [read] @ 11760 ##: 0 [read] @ 43512 ##: 0 [read] @ 75264 ##: 0 [read] @ 107016 ##: 0 [read] @ 138768 ##: 0 [read] @ 170520 ##: 0 [read] @ 202272 ##: 0 [read] @ 234024 ##: 0 [read] @ 265776 ##: 0 [read] @ 297528 ##: 0 [read] @ 329280 ##: 0 [read] @ 361032 ##: 0 [read] @ 392784 ##: 0 [read] @ 424536 ##: 0 [read] @ 456288 ##: 0 [read] @ 488040 ##: 0 [read] @ 519792 ##: 0 [read] @ 551544 ##: 0 [read] @ 583296 ##: 0 [read] @ 615048 ##: 0 [read] @ 646800 ##: 0 [read] @ 678552 ##: 0 [read] @ 710304 ##: 0 [read] @ 742056 ##: 0 [read] @ 773808 ##: 0 [read] @ 805560 ##: 0 [read] @ 837312 ##: 0 [read] @ 869064 ##: 0 [read] @ 900816 ##: 0 [read] @ 932568 ##: 0 [read] @ 964320 ##: 0 [read] @ 996072 ##: 0 [read] @ 1027824 ##: 0 [read] @ 1059576 ##: 0 [read] @ 1091328 ##: 0 [read] @ 1123080 ##: 0 [read] @ 1154832 ##: 0 [read] @ 1186584 ##: 0 [read] @ 1218336 ##: 0 [read] @ 1250088 ##: 0 [read] @ 1281840 ##: 0 [read] @ 1313592 ##: 0 [read] @ 1345344 ##: 0 [read] @ 1377096 ##: 0 [read] @ 1407672 ##: 1 [verify] @ 12936 ##: 2 [jitter] @ 22344 ##: 2 [jitter] @ 1418256 ##: 1 [verify] @ 12936 ##: 2 [jitter] @ 49392 ##: 2 [jitter] @ 1434720 ##: 1 [verify] @ 12936 ##: 2 [jitter] @ 47040 ##: 2 [jitter] @ 1433544 ##: 1 [verify] @ 12936 ##: 2 [jitter] @ 44688 ##: 2 [jitter] @ 1432368 ##: 1 [verify] @ 12936 ##: 2 [jitter] @ 42336 ##: 2 [jitter] @ 1431192 ##: 1 [verify] @ 12936 ##: 2 [jitter] @ 39984 ##: 2 [jitter] @ 1430016 ##: 1 [verify] @ 12936 ##: 2 [jitter] @ 37632 ##: 2 [jitter] @ 1428840 ##: 1 [verify] @ 12936 ##: 1 [verify] @ 12936 ##: 9 [overlap] @ 20667 ##: 2 [jitter] @ 37632 ##: 2 [jitter] @ 1428840 ##: 1 [verify] @ 12936 scsi_read error: sector=-2 length=1 retry=0 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=1 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=2 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=3 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=4 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=5 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=6 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=7 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=8 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument ##: 12 [transport error] @ -2352 ##: 0 [read] @ -2352 scsi_read error: sector=-1 length=27 retry=0 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-1 length=13 retry=1 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-1 length=6 retry=2 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-1 length=3 retry=3 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-1 length=1 retry=4 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-1 length=1 retry=5 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-1 length=1 retry=6 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-1 length=1 retry=7 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-1 length=1 retry=8 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument ##: 12 [transport error] @ -1176 ##: 0 [read] @ 29400 ##: 0 [read] @ 61152 ##: 0 [read] @ 92904 ##: 0 [read] @ 124656 ##: 0 [read] @ 156408 ##: 0 [read] @ 188160 ##: 0 [read] @ 219912 ##: 0 [read] @ 251664 ##: 0 [read] @ 283416 ##: 0 [read] @ 315168 ##: 0 [read] @ 346920 ##: 0 [read] @ 378672 ##: 0 [read] @ 410424 ##: 0 [read] @ 442176 ##: 0 [read] @ 473928 ##: 0 [read] @ 505680 ##: 0 [read] @ 537432 ##: 0 [read] @ 569184 ##: 0 [read] @ 600936 ##: 0 [read] @ 632688 ##: 0 [read] @ 664440 ##: 0 [read] @ 696192 ##: 0 [read] @ 727944 ##: 0 [read] @ 759696 ##: 0 [read] @ 791448 ##: 0 [read] @ 823200 ##: 0 [read] @ 854952 ##: 0 [read] @ 886704 ##: 0 [read] @ 918456 ##: 0 [read] @ 950208 ##: 0 [read] @ 981960 ##: 0 [read] @ 1013712 ##: 0 [read] @ 1045464 ##: 0 [read] @ 1077216 ##: 0 [read] @ 1108968 ##: 0 [read] @ 1140720 ##: 0 [read] @ 1172472 ##: 0 [read] @ 1204224 ##: 0 [read] @ 1235976 ##: 0 [read] @ 1267728 ##: 0 [read] @ 1299480 ##: 0 [read] @ 1331232 ##: 0 [read] @ 1362984 ##: 0 [read] @ 1394736 ##: 0 [read] @ 1407672 ##: 1 [verify] @ 30576 ##: 1 [verify] @ 30576 ##: 2 [jitter] @ 56448 ##: 2 [jitter] @ 1434720 ##: 1 [verify] @ 30576 ##: 2 [jitter] @ 55272 ##: 2 [jitter] @ 1433544 ##: 1 [verify] @ 30576 ##: 2 [jitter] @ 54096 ##: 2 [jitter] @ 1432368 ##: 1 [verify] @ 30576 ##: 2 [jitter] @ 52920 ##: 2 [jitter] @ 1431192 ##: 1 [verify] @ 30576 ##: 2 [jitter] @ 51744 ##: 2 [jitter] @ 1430016 ##: 1 [verify] @ 30576 ##: 2 [jitter] @ 50568 ##: 2 [jitter] @ 1428840 ##: 1 [verify] @ 30576 ##: 1 [verify] @ 30576 ##: 2 [jitter] @ 50568 ##: 2 [jitter] @ 1428840 ##: 1 [verify] @ 30576 ##: 1 [verify] @ 30576 ##: 2 [jitter] @ 45864 ##: 2 [jitter] @ 1424136 scsi_read error: sector=-2 length=27 retry=0 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=13 retry=1 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=6 retry=2 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=3 retry=3 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=4 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=5 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=6 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=7 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=8 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument ##: 12 [transport error] @ -2352 ##: 0 [read] @ 28224 ##: 0 [read] @ 59976 ##: 0 [read] @ 91728 ##: 0 [read] @ 123480 ##: 0 [read] @ 155232 ##: 0 [read] @ 186984 ##: 0 [read] @ 218736 ##: 0 [read] @ 250488 ##: 0 [read] @ 282240 ##: 0 [read] @ 313992 ##: 0 [read] @ 345744 ##: 0 [read] @ 377496 ##: 0 [read] @ 409248 ##: 0 [read] @ 441000 ##: 0 [read] @ 472752 ##: 0 [read] @ 504504 ##: 0 [read] @ 536256 ##: 0 [read] @ 568008 ##: 0 [read] @ 599760 ##: 0 [read] @ 631512 ##: 0 [read] @ 663264 ##: 0 [read] @ 695016 ##: 0 [read] @ 726768 ##: 0 [read] @ 758520 ##: 0 [read] @ 790272 ##: 0 [read] @ 822024 ##: 0 [read] @ 853776 ##: 0 [read] @ 885528 ##: 0 [read] @ 917280 ##: 0 [read] @ 949032 ##: 0 [read] @ 980784 ##: 0 [read] @ 1012536 ##: 0 [read] @ 1044288 ##: 0 [read] @ 1076040 ##: 0 [read] @ 1107792 ##: 0 [read] @ 1139544 ##: 0 [read] @ 1171296 ##: 0 [read] @ 1203048 ##: 0 [read] @ 1234800 ##: 0 [read] @ 1266552 ##: 0 [read] @ 1298304 ##: 0 [read] @ 1330056 ##: 0 [read] @ 1361808 ##: 0 [read] @ 1393560 ##: 0 [read] @ 1407672 ##: 1 [verify] @ 29400 ##: 1 [verify] @ 29400 ##: 9 [overlap] @ 26460 ##: 2 [jitter] @ 55272 ##: 2 [jitter] @ 1434720 ##: 1 [verify] @ 29400 ##: 2 [jitter] @ 54096 ##: 2 [jitter] @ 1433544 ##: 1 [verify] @ 29400 ##: 2 [jitter] @ 52920 ##: 2 [jitter] @ 1432368 ##: 1 [verify] @ 29400 ##: 2 [jitter] @ 51744 ##: 2 [jitter] @ 1431192 ##: 1 [verify] @ 29400 ##: 2 [jitter] @ 50568 ##: 2 [jitter] @ 1430016 ##: 1 [verify] @ 29400 ##: 2 [jitter] @ 49392 ##: 2 [jitter] @ 1428840 ##: 1 [verify] @ 29400 ##: 2 [jitter] @ 36456 ##: 2 [jitter] @ 1415904 ##: 1 [verify] @ 29400 ##: 2 [jitter] @ 49392 ##: 2 [jitter] @ 1428840 ##: 1 [verify] @ 29400 ##: 1 [verify] @ 29400 ##: 2 [jitter] @ 44688 ##: 2 [jitter] @ 1424136 ##: 1 [verify] @ 29400 ##: 2 [jitter] @ 63504 ##: 2 [jitter] @ 1441776 scsi_read error: sector=-2 length=27 retry=0 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=13 retry=1 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=6 retry=2 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=3 retry=3 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=4 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=5 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=6 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=7 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=8 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument ##: 12 [transport error] @ -2352 ##: 0 [read] @ 28224 ##: 0 [read] @ 59976 ##: 0 [read] @ 91728 ##: 0 [read] @ 123480 ##: 0 [read] @ 155232 ##: 0 [read] @ 186984 ##: 0 [read] @ 218736 ##: 0 [read] @ 250488 ##: 0 [read] @ 282240 ##: 0 [read] @ 313992 ##: 0 [read] @ 345744 ##: 0 [read] @ 377496 ##: 0 [read] @ 409248 ##: 0 [read] @ 441000 ##: 0 [read] @ 472752 ##: 0 [read] @ 504504 ##: 0 [read] @ 536256 ##: 0 [read] @ 568008 ##: 0 [read] @ 599760 ##: 0 [read] @ 631512 ##: 0 [read] @ 663264 ##: 0 [read] @ 695016 ##: 0 [read] @ 726768 ##: 0 [read] @ 758520 ##: 0 [read] @ 790272 ##: 0 [read] @ 822024 ##: 0 [read] @ 853776 ##: 0 [read] @ 885528 ##: 0 [read] @ 917280 ##: 0 [read] @ 949032 ##: 0 [read] @ 980784 ##: 0 [read] @ 1012536 ##: 0 [read] @ 1044288 ##: 0 [read] @ 1076040 ##: 0 [read] @ 1107792 ##: 0 [read] @ 1139544 ##: 0 [read] @ 1171296 ##: 0 [read] @ 1203048 ##: 0 [read] @ 1234800 ##: 0 [read] @ 1266552 ##: 0 [read] @ 1298304 ##: 0 [read] @ 1330056 ##: 0 [read] @ 1361808 ##: 0 [read] @ 1393560 ##: 0 [read] @ 1407672 ##: 1 [verify] @ 29400 ##: 9 [overlap] @ 37044 ##: 2 [jitter] @ 38808 ##: 2 [jitter] @ 1418256 ##: 1 [verify] @ 29400 ##: 2 [jitter] @ 55272 ##: 2 [jitter] @ 1434720 ##: 1 [verify] @ 29400 ##: 2 [jitter] @ 54096 ##: 2 [jitter] @ 1433544 ##: 1 [verify] @ 29400 ##: 2 [jitter] @ 52920 ##: 2 [jitter] @ 1432368 ##: 1 [verify] @ 29400 ##: 2 [jitter] @ 51744 ##: 2 [jitter] @ 1431192 ##: 1 [verify] @ 29400 ##: 2 [jitter] @ 50568 ##: 2 [jitter] @ 1430016 ##: 1 [verify] @ 29400 ##: 2 [jitter] @ 49392 ##: 2 [jitter] @ 1428840 ##: 1 [verify] @ 29400 ##: 2 [jitter] @ 36456 ##: 2 [jitter] @ 1415904 ##: 1 [verify] @ 29400 ##: 2 [jitter] @ 49392 ##: 2 [jitter] @ 1428840 ##: 1 [verify] @ 29400 ##: 2 [jitter] @ 34104 ##: 2 [jitter] @ 1413552 ##: 1 [verify] @ 29400 ##: 9 [overlap] @ 37632 ##: 2 [jitter] @ 44688 ##: 2 [jitter] @ 1424136 ##: 1 [verify] @ 29400 ##: 2 [jitter] @ 63504 ##: 2 [jitter] @ 1441776 ##: 1 [verify] @ 29400 scsi_read error: sector=-2 length=9 retry=0 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=4 retry=1 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=2 retry=2 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=3 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=4 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=5 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=6 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=7 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=8 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument ##: 12 [transport error] @ -2352 ##: 0 [read] @ 7056 ##: 0 [read] @ 38808 ##: 0 [read] @ 70560 ##: 0 [read] @ 102312 ##: 0 [read] @ 134064 ##: 0 [read] @ 165816 ##: 0 [read] @ 197568 ##: 0 [read] @ 229320 ##: 0 [read] @ 261072 ##: 0 [read] @ 292824 ##: 0 [read] @ 324576 ##: 0 [read] @ 356328 ##: 0 [read] @ 388080 ##: 0 [read] @ 419832 ##: 0 [read] @ 451584 ##: 0 [read] @ 483336 ##: 0 [read] @ 515088 ##: 0 [read] @ 546840 ##: 0 [read] @ 578592 ##: 0 [read] @ 610344 ##: 0 [read] @ 642096 ##: 0 [read] @ 673848 ##: 0 [read] @ 705600 ##: 0 [read] @ 737352 ##: 0 [read] @ 769104 ##: 0 [read] @ 800856 ##: 0 [read] @ 832608 ##: 0 [read] @ 864360 ##: 0 [read] @ 896112 ##: 0 [read] @ 927864 ##: 0 [read] @ 959616 ##: 0 [read] @ 991368 ##: 0 [read] @ 1023120 ##: 0 [read] @ 1054872 ##: 0 [read] @ 1086624 ##: 0 [read] @ 1118376 ##: 0 [read] @ 1150128 ##: 0 [read] @ 1181880 ##: 0 [read] @ 1213632 ##: 0 [read] @ 1245384 ##: 0 [read] @ 1277136 ##: 0 [read] @ 1308888 ##: 0 [read] @ 1340640 ##: 0 [read] @ 1372392 ##: 0 [read] @ 1404144 ##: 0 [read] @ 1407672 ##: 1 [verify] @ 8232 ##: 2 [jitter] @ 17640 ##: 2 [jitter] @ 1418256 ##: 1 [verify] @ 8232 ##: 2 [jitter] @ 49392 ##: 2 [jitter] @ 1434720 ##: 1 [verify] @ 8232 ##: 2 [jitter] @ 47040 ##: 2 [jitter] @ 1433544 ##: 1 [verify] @ 8232 ##: 2 [jitter] @ 44688 ##: 2 [jitter] @ 1432368 ##: 1 [verify] @ 8232 ##: 2 [jitter] @ 42336 ##: 2 [jitter] @ 1431192 ##: 1 [verify] @ 8232 ##: 9 [overlap] @ 24996 ##: 2 [jitter] @ 39984 ##: 2 [jitter] @ 1430016 ##: 1 [verify] @ 8232 ##: 2 [jitter] @ 37632 ##: 2 [jitter] @ 1428840 ##: 1 [verify] @ 8232 ##: 1 [verify] @ 8232 ##: 2 [jitter] @ 37632 ##: 2 [jitter] @ 1428840 ##: 1 [verify] @ 8232 ##: 1 [verify] @ 8232 ##: 2 [jitter] @ 28224 ##: 2 [jitter] @ 1424136 ##: 1 [verify] @ 8232 ##: 2 [jitter] @ 63504 ##: 2 [jitter] @ 1441776 ##: 1 [verify] @ 8232 ##: 2 [jitter] @ 61152 ##: 2 [jitter] @ 1440600 ##: 1 [verify] @ 8232 ##: 2 [jitter] @ 61152 ##: 2 [jitter] @ 1440600 ##: 9 [overlap] @ 37494 scsi_read error: sector=-2 length=8 retry=0 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=4 retry=1 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=2 retry=2 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=3 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=4 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=5 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=6 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=7 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=8 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument ##: 12 [transport error] @ -2352 ##: 0 [read] @ 5880 ##: 0 [read] @ 37632 ##: 0 [read] @ 69384 ##: 0 [read] @ 101136 ##: 0 [read] @ 132888 ##: 0 [read] @ 164640 ##: 0 [read] @ 196392 ##: 0 [read] @ 228144 ##: 0 [read] @ 259896 ##: 0 [read] @ 291648 ##: 0 [read] @ 323400 ##: 0 [read] @ 355152 ##: 0 [read] @ 386904 ##: 0 [read] @ 418656 ##: 0 [read] @ 450408 ##: 0 [read] @ 482160 ##: 0 [read] @ 513912 ##: 0 [read] @ 545664 ##: 0 [read] @ 577416 ##: 0 [read] @ 609168 ##: 0 [read] @ 640920 ##: 0 [read] @ 672672 ##: 0 [read] @ 704424 ##: 0 [read] @ 736176 ##: 0 [read] @ 767928 ##: 0 [read] @ 799680 ##: 0 [read] @ 831432 ##: 0 [read] @ 863184 ##: 0 [read] @ 894936 ##: 0 [read] @ 926688 ##: 0 [read] @ 958440 ##: 0 [read] @ 990192 ##: 0 [read] @ 1021944 ##: 0 [read] @ 1053696 ##: 0 [read] @ 1085448 ##: 0 [read] @ 1117200 ##: 0 [read] @ 1148952 ##: 0 [read] @ 1180704 ##: 0 [read] @ 1212456 ##: 0 [read] @ 1244208 ##: 0 [read] @ 1275960 ##: 0 [read] @ 1307712 ##: 0 [read] @ 1339464 ##: 0 [read] @ 1371216 ##: 0 [read] @ 1402968 ##: 0 [read] @ 1407672 ##: 1 [verify] @ 7056 ##: 9 [overlap] @ 37632 ##: 2 [jitter] @ 49392 ##: 2 [jitter] @ 1434720 ##: 1 [verify] @ 7056 ##: 2 [jitter] @ 47040 ##: 2 [jitter] @ 1433544 ##: 1 [verify] @ 7056 ##: 2 [jitter] @ 44688 ##: 2 [jitter] @ 1432368 ##: 1 [verify] @ 7056 ##: 2 [jitter] @ 42336 ##: 2 [jitter] @ 1431192 ##: 1 [verify] @ 7056 ##: 2 [jitter] @ 39984 ##: 2 [jitter] @ 1430016 ##: 1 [verify] @ 7056 ##: 2 [jitter] @ 37632 ##: 2 [jitter] @ 1428840 ##: 1 [verify] @ 7056 ##: 1 [verify] @ 7056 ##: 2 [jitter] @ 37632 ##: 2 [jitter] @ 1428840 ##: 1 [verify] @ 7056 ##: 1 [verify] @ 7056 ##: 2 [jitter] @ 28224 ##: 2 [jitter] @ 1424136 ##: 1 [verify] @ 7056 ##: 9 [overlap] @ 35280 ##: 2 [jitter] @ 63504 ##: 2 [jitter] @ 1441776 ##: 1 [verify] @ 7056 ##: 2 [jitter] @ 61152 ##: 2 [jitter] @ 1440600 ##: 1 [verify] @ 7056 ##: 2 [jitter] @ 61152 ##: 2 [jitter] @ 1440600 ##: 1 [verify] @ 7056 ##: 2 [jitter] @ 18816 ##: 2 [jitter] @ 1419432 scsi_read error: sector=-2 length=23 retry=0 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=11 retry=1 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=5 retry=2 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=2 retry=3 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=4 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=5 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=6 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=7 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=8 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument ##: 12 [transport error] @ -2352 ##: 0 [read] @ 23520 ##: 0 [read] @ 55272 ##: 0 [read] @ 87024 ##: 0 [read] @ 118776 ##: 0 [read] @ 150528 ##: 0 [read] @ 182280 ##: 0 [read] @ 214032 ##: 0 [read] @ 245784 ##: 0 [read] @ 277536 ##: 0 [read] @ 309288 ##: 0 [read] @ 341040 ##: 0 [read] @ 372792 ##: 0 [read] @ 404544 ##: 0 [read] @ 436296 ##: 0 [read] @ 468048 ##: 0 [read] @ 499800 ##: 0 [read] @ 531552 ##: 0 [read] @ 563304 ##: 0 [read] @ 595056 ##: 0 [read] @ 626808 ##: 0 [read] @ 658560 ##: 0 [read] @ 690312 ##: 0 [read] @ 722064 ##: 0 [read] @ 753816 ##: 0 [read] @ 785568 ##: 0 [read] @ 817320 ##: 0 [read] @ 849072 ##: 0 [read] @ 880824 ##: 0 [read] @ 912576 ##: 0 [read] @ 944328 ##: 0 [read] @ 976080 ##: 0 [read] @ 1007832 ##: 0 [read] @ 1039584 ##: 0 [read] @ 1071336 ##: 0 [read] @ 1103088 ##: 0 [read] @ 1134840 ##: 0 [read] @ 1166592 ##: 0 [read] @ 1198344 ##: 0 [read] @ 1230096 ##: 0 [read] @ 1261848 ##: 0 [read] @ 1293600 ##: 0 [read] @ 1325352 ##: 0 [read] @ 1357104 ##: 0 [read] @ 1388856 ##: 0 [read] @ 1407672 ##: 1 [verify] @ 24696 ##: 2 [jitter] @ 49392 ##: 2 [jitter] @ 1433544 ##: 1 [verify] @ 24696 ##: 1 [verify] @ 24696 ##: 1 [verify] @ 24696 ##: 1 [verify] @ 24696 ##: 2 [jitter] @ 44688 ##: 2 [jitter] @ 1428840 ##: 1 [verify] @ 24696 ##: 9 [overlap] @ 33516 ##: 2 [jitter] @ 31752 ##: 2 [jitter] @ 1415904 ##: 1 [verify] @ 24696 ##: 2 [jitter] @ 44688 ##: 2 [jitter] @ 1428840 ##: 1 [verify] @ 24696 ##: 2 [jitter] @ 29400 ##: 2 [jitter] @ 1413552 ##: 1 [verify] @ 24696 ##: 2 [jitter] @ 39984 ##: 2 [jitter] @ 1424136 ##: 1 [verify] @ 24696 ##: 2 [jitter] @ 63504 ##: 2 [jitter] @ 1441776 ##: 1 [verify] @ 24696 ##: 2 [jitter] @ 61152 ##: 2 [jitter] @ 1440600 ##: 1 [verify] @ 24696 ##: 2 [jitter] @ 61152 ##: 2 [jitter] @ 1440600 ##: 1 [verify] @ 24696 ##: 2 [jitter] @ 35280 ##: 2 [jitter] @ 1419432 ##: 1 [verify] @ 24696 ##: 2 [jitter] @ 34104 ##: 2 [jitter] @ 1418256 scsi_read error: sector=-2 length=23 retry=0 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=11 retry=1 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=5 retry=2 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=2 retry=3 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=4 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=5 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=6 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=7 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=8 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument ##: 12 [transport error] @ -2352 ##: 0 [read] @ 23520 ##: 0 [read] @ 55272 ##: 0 [read] @ 87024 ##: 0 [read] @ 118776 ##: 0 [read] @ 150528 ##: 0 [read] @ 182280 ##: 0 [read] @ 214032 ##: 0 [read] @ 245784 ##: 0 [read] @ 277536 ##: 0 [read] @ 309288 ##: 0 [read] @ 341040 ##: 0 [read] @ 372792 ##: 0 [read] @ 404544 ##: 0 [read] @ 436296 ##: 0 [read] @ 468048 ##: 0 [read] @ 499800 ##: 0 [read] @ 531552 ##: 0 [read] @ 563304 ##: 0 [read] @ 595056 ##: 0 [read] @ 626808 ##: 0 [read] @ 658560 ##: 0 [read] @ 690312 ##: 0 [read] @ 722064 ##: 0 [read] @ 753816 ##: 0 [read] @ 785568 ##: 0 [read] @ 817320 ##: 0 [read] @ 849072 ##: 0 [read] @ 880824 ##: 0 [read] @ 912576 ##: 0 [read] @ 944328 ##: 0 [read] @ 976080 ##: 0 [read] @ 1007832 ##: 0 [read] @ 1039584 ##: 0 [read] @ 1071336 ##: 0 [read] @ 1103088 ##: 0 [read] @ 1134840 ##: 0 [read] @ 1166592 ##: 0 [read] @ 1198344 ##: 0 [read] @ 1230096 ##: 0 [read] @ 1261848 ##: 0 [read] @ 1293600 ##: 0 [read] @ 1325352 ##: 0 [read] @ 1357104 ##: 0 [read] @ 1388856 ##: 0 [read] @ 1407672 ##: 1 [verify] @ 24696 ##: 9 [overlap] @ 33516 ##: 2 [jitter] @ 48216 ##: 2 [jitter] @ 1432368 ##: 1 [verify] @ 24696 ##: 1 [verify] @ 24696 ##: 1 [verify] @ 24696 ##: 2 [jitter] @ 44688 ##: 2 [jitter] @ 1428840 ##: 1 [verify] @ 24696 ##: 2 [jitter] @ 31752 ##: 2 [jitter] @ 1415904 ##: 1 [verify] @ 24696 ##: 2 [jitter] @ 44688 ##: 2 [jitter] @ 1428840 ##: 1 [verify] @ 24696 ##: 2 [jitter] @ 29400 ##: 2 [jitter] @ 1413552 ##: 1 [verify] @ 24696 ##: 2 [jitter] @ 39984 ##: 2 [jitter] @ 1424136 ##: 1 [verify] @ 24696 ##: 2 [jitter] @ 63504 ##: 2 [jitter] @ 1441776 ##: 1 [verify] @ 24696 ##: 2 [jitter] @ 61152 ##: 2 [jitter] @ 1440600 ##: 1 [verify] @ 24696 ##: 9 [overlap] @ 33516 ##: 2 [jitter] @ 61152 ##: 2 [jitter] @ 1440600 ##: 1 [verify] @ 24696 ##: 2 [jitter] @ 35280 ##: 2 [jitter] @ 1419432 ##: 1 [verify] @ 24696 ##: 2 [jitter] @ 34104 ##: 2 [jitter] @ 1418256 ##: 1 [verify] @ 24696 scsi_read error: sector=-2 length=22 retry=0 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=11 retry=1 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=5 retry=2 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=2 retry=3 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=4 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=5 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=6 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=7 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=8 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument ##: 12 [transport error] @ -2352 ##: 0 [read] @ 22344 ##: 0 [read] @ 54096 ##: 0 [read] @ 85848 ##: 0 [read] @ 117600 ##: 0 [read] @ 149352 ##: 0 [read] @ 181104 ##: 0 [read] @ 212856 ##: 0 [read] @ 244608 ##: 0 [read] @ 276360 ##: 0 [read] @ 308112 ##: 0 [read] @ 339864 ##: 0 [read] @ 371616 ##: 0 [read] @ 403368 ##: 0 [read] @ 435120 ##: 0 [read] @ 466872 ##: 0 [read] @ 498624 ##: 0 [read] @ 530376 ##: 0 [read] @ 562128 ##: 0 [read] @ 593880 ##: 0 [read] @ 625632 ##: 0 [read] @ 657384 ##: 0 [read] @ 689136 ##: 0 [read] @ 720888 ##: 0 [read] @ 752640 ##: 0 [read] @ 784392 ##: 0 [read] @ 816144 ##: 0 [read] @ 847896 ##: 0 [read] @ 879648 ##: 0 [read] @ 911400 ##: 0 [read] @ 943152 ##: 0 [read] @ 974904 ##: 0 [read] @ 1006656 ##: 0 [read] @ 1038408 ##: 0 [read] @ 1070160 ##: 0 [read] @ 1101912 ##: 0 [read] @ 1133664 ##: 0 [read] @ 1165416 ##: 0 [read] @ 1197168 ##: 0 [read] @ 1228920 ##: 0 [read] @ 1260672 ##: 0 [read] @ 1292424 ##: 0 [read] @ 1324176 ##: 0 [read] @ 1355928 ##: 0 [read] @ 1387680 ##: 0 [read] @ 1407672 ##: 1 [verify] @ 23520 ##: 2 [jitter] @ 45864 ##: 2 [jitter] @ 1431192 ##: 1 [verify] @ 23520 ##: 1 [verify] @ 23520 ##: 1 [verify] @ 23520 ##: 2 [jitter] @ 30576 ##: 2 [jitter] @ 1415904 ##: 1 [verify] @ 23520 ##: 9 [overlap] @ 28224 ##: 1 [verify] @ 23520 ##: 2 [jitter] @ 28224 ##: 2 [jitter] @ 1413552 ##: 1 [verify] @ 23520 ##: 2 [jitter] @ 38808 ##: 2 [jitter] @ 1424136 ##: 1 [verify] @ 23520 ##: 2 [jitter] @ 63504 ##: 2 [jitter] @ 1441776 ##: 1 [verify] @ 23520 ##: 2 [jitter] @ 61152 ##: 2 [jitter] @ 1440600 ##: 1 [verify] @ 23520 ##: 2 [jitter] @ 61152 ##: 2 [jitter] @ 1440600 ##: 1 [verify] @ 23520 ##: 2 [jitter] @ 34104 ##: 2 [jitter] @ 1419432 ##: 1 [verify] @ 23520 ##: 2 [jitter] @ 32928 ##: 2 [jitter] @ 1418256 ##: 1 [verify] @ 23520 ##: 2 [jitter] @ 51744 ##: 2 [jitter] @ 1435896 ##: 1 [verify] @ 23520 ##: 2 [jitter] @ 51744 ##: 2 [jitter] @ 1435896 scsi_read error: sector=-2 length=20 retry=0 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=10 retry=1 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=5 retry=2 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=2 retry=3 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=4 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=5 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=6 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=7 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=8 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument ##: 12 [transport error] @ -2352 ##: 0 [read] @ 19992 ##: 0 [read] @ 51744 ##: 0 [read] @ 83496 ##: 0 [read] @ 115248 ##: 0 [read] @ 147000 ##: 0 [read] @ 178752 ##: 0 [read] @ 210504 ##: 0 [read] @ 242256 ##: 0 [read] @ 274008 ##: 0 [read] @ 305760 ##: 0 [read] @ 337512 ##: 0 [read] @ 369264 ##: 0 [read] @ 401016 ##: 0 [read] @ 432768 ##: 0 [read] @ 464520 ##: 0 [read] @ 496272 ##: 0 [read] @ 528024 ##: 0 [read] @ 559776 ##: 0 [read] @ 591528 ##: 0 [read] @ 623280 ##: 0 [read] @ 655032 ##: 0 [read] @ 686784 ##: 0 [read] @ 718536 ##: 0 [read] @ 750288 ##: 0 [read] @ 782040 ##: 0 [read] @ 813792 ##: 0 [read] @ 845544 ##: 0 [read] @ 877296 ##: 0 [read] @ 909048 ##: 0 [read] @ 940800 ##: 0 [read] @ 972552 ##: 0 [read] @ 1004304 ##: 0 [read] @ 1036056 ##: 0 [read] @ 1067808 ##: 0 [read] @ 1099560 ##: 0 [read] @ 1131312 ##: 0 [read] @ 1163064 ##: 0 [read] @ 1194816 ##: 0 [read] @ 1226568 ##: 0 [read] @ 1258320 ##: 0 [read] @ 1290072 ##: 0 [read] @ 1321824 ##: 0 [read] @ 1353576 ##: 0 [read] @ 1385328 ##: 0 [read] @ 1407672 ##: 1 [verify] @ 21168 ##: 9 [overlap] @ 31752 ##: 2 [jitter] @ 42336 ##: 2 [jitter] @ 1430016 ##: 1 [verify] @ 21168 ##: 1 [verify] @ 21168 ##: 2 [jitter] @ 28224 ##: 2 [jitter] @ 1415904 ##: 1 [verify] @ 21168 ##: 1 [verify] @ 21168 ##: 2 [jitter] @ 25872 ##: 2 [jitter] @ 1413552 ##: 1 [verify] @ 21168 ##: 1 [verify] @ 21168 ##: 2 [jitter] @ 63504 ##: 2 [jitter] @ 1441776 ##: 1 [verify] @ 21168 ##: 2 [jitter] @ 61152 ##: 2 [jitter] @ 1440600 ##: 1 [verify] @ 21168 ##: 2 [jitter] @ 61152 ##: 2 [jitter] @ 1440600 ##: 1 [verify] @ 21168 ##: 9 [overlap] @ 28224 ##: 2 [jitter] @ 31752 ##: 2 [jitter] @ 1419432 ##: 1 [verify] @ 21168 ##: 2 [jitter] @ 30576 ##: 2 [jitter] @ 1418256 ##: 1 [verify] @ 21168 ##: 2 [jitter] @ 51744 ##: 2 [jitter] @ 1435896 ##: 1 [verify] @ 21168 ##: 2 [jitter] @ 51744 ##: 2 [jitter] @ 1435896 ##: 1 [verify] @ 21168 ##: 2 [jitter] @ 49392 ##: 2 [jitter] @ 1434720 ##: 6 [skip] @ 0 scsi_read error: sector=-2 length=19 retry=0 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=9 retry=1 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=4 retry=2 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=2 retry=3 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=4 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=5 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=6 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=7 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=8 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument ##: 12 [transport error] @ -2352 ##: 0 [read] @ 18816 ##: 0 [read] @ 50568 ##: 0 [read] @ 82320 ##: 0 [read] @ 114072 ##: 0 [read] @ 145824 ##: 0 [read] @ 177576 ##: 0 [read] @ 209328 ##: 0 [read] @ 241080 ##: 0 [read] @ 272832 ##: 0 [read] @ 304584 ##: 0 [read] @ 336336 ##: 0 [read] @ 368088 ##: 0 [read] @ 399840 ##: 0 [read] @ 431592 ##: 0 [read] @ 463344 ##: 0 [read] @ 495096 ##: 0 [read] @ 526848 ##: 0 [read] @ 558600 ##: 0 [read] @ 590352 ##: 0 [read] @ 622104 ##: 0 [read] @ 653856 ##: 0 [read] @ 685608 ##: 0 [read] @ 717360 ##: 0 [read] @ 749112 ##: 0 [read] @ 780864 ##: 0 [read] @ 812616 ##: 0 [read] @ 844368 ##: 0 [read] @ 876120 ##: 0 [read] @ 907872 ##: 0 [read] @ 939624 ##: 0 [read] @ 971376 ##: 0 [read] @ 1003128 ##: 0 [read] @ 1034880 ##: 0 [read] @ 1066632 ##: 0 [read] @ 1098384 ##: 0 [read] @ 1130136 ##: 0 [read] @ 1161888 ##: 0 [read] @ 1193640 ##: 0 [read] @ 1225392 ##: 0 [read] @ 1257144 ##: 0 [read] @ 1288896 ##: 0 [read] @ 1320648 ##: 0 [read] @ 1352400 ##: 0 [read] @ 1384152 ##: 0 [read] @ 1407672 ##: 1 [verify] @ 19992 ##: 2 [jitter] @ 39984 ##: 2 [jitter] @ 1428840 ##: 1 [verify] @ 19992 ##: 2 [jitter] @ 27048 ##: 2 [jitter] @ 1415904 ##: 1 [verify] @ 19992 ##: 1 [verify] @ 19992 ##: 2 [jitter] @ 24696 ##: 2 [jitter] @ 1413552 ##: 1 [verify] @ 19992 ##: 9 [overlap] @ 26460 ##: 1 [verify] @ 19992 ##: 2 [jitter] @ 63504 ##: 2 [jitter] @ 1441776 ##: 1 [verify] @ 19992 ##: 2 [jitter] @ 61152 ##: 2 [jitter] @ 1440600 ##: 1 [verify] @ 19992 ##: 2 [jitter] @ 61152 ##: 2 [jitter] @ 1440600 ##: 1 [verify] @ 19992 ##: 2 [jitter] @ 30576 ##: 2 [jitter] @ 1419432 ##: 1 [verify] @ 19992 ##: 2 [jitter] @ 29400 ##: 2 [jitter] @ 1418256 ##: 1 [verify] @ 19992 ##: 2 [jitter] @ 51744 ##: 2 [jitter] @ 1435896 ##: 1 [verify] @ 19992 ##: 2 [jitter] @ 51744 ##: 2 [jitter] @ 1435896 ##: 1 [verify] @ 19992 ##: 2 [jitter] @ 49392 ##: 2 [jitter] @ 1434720 ##: 1 [verify] @ 19992 ##: 2 [jitter] @ 44688 ##: 2 [jitter] @ 1432368 scsi_read error: sector=-2 length=19 retry=0 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=9 retry=1 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=4 retry=2 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=2 retry=3 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=4 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=5 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=6 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=7 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=8 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument ##: 12 [transport error] @ -2352 ##: 0 [read] @ 18816 ##: 0 [read] @ 50568 ##: 0 [read] @ 82320 ##: 0 [read] @ 114072 ##: 0 [read] @ 145824 ##: 0 [read] @ 177576 ##: 0 [read] @ 209328 ##: 0 [read] @ 241080 ##: 0 [read] @ 272832 ##: 0 [read] @ 304584 ##: 0 [read] @ 336336 ##: 0 [read] @ 368088 ##: 0 [read] @ 399840 ##: 0 [read] @ 431592 ##: 0 [read] @ 463344 ##: 0 [read] @ 495096 ##: 0 [read] @ 526848 ##: 0 [read] @ 558600 ##: 0 [read] @ 590352 ##: 0 [read] @ 622104 ##: 0 [read] @ 653856 ##: 0 [read] @ 685608 ##: 0 [read] @ 717360 ##: 0 [read] @ 749112 ##: 0 [read] @ 780864 ##: 0 [read] @ 812616 ##: 0 [read] @ 844368 ##: 0 [read] @ 876120 ##: 0 [read] @ 907872 ##: 0 [read] @ 939624 ##: 0 [read] @ 971376 ##: 0 [read] @ 1003128 ##: 0 [read] @ 1034880 ##: 0 [read] @ 1066632 ##: 0 [read] @ 1098384 ##: 0 [read] @ 1130136 ##: 0 [read] @ 1161888 ##: 0 [read] @ 1193640 ##: 0 [read] @ 1225392 ##: 0 [read] @ 1257144 ##: 0 [read] @ 1288896 ##: 0 [read] @ 1320648 ##: 0 [read] @ 1352400 ##: 0 [read] @ 1384152 ##: 0 [read] @ 1407672 ##: 1 [verify] @ 19992 ##: 9 [overlap] @ 25320 ##: 2 [jitter] @ 27048 ##: 2 [jitter] @ 1415904 ##: 1 [verify] @ 19992 ##: 1 [verify] @ 19992 ##: 2 [jitter] @ 24696 ##: 2 [jitter] @ 1413552 ##: 1 [verify] @ 19992 ##: 1 [verify] @ 19992 ##: 2 [jitter] @ 63504 ##: 2 [jitter] @ 1441776 ##: 1 [verify] @ 19992 ##: 2 [jitter] @ 61152 ##: 2 [jitter] @ 1440600 ##: 1 [verify] @ 19992 ##: 2 [jitter] @ 61152 ##: 2 [jitter] @ 1440600 ##: 1 [verify] @ 19992 ##: 2 [jitter] @ 30576 ##: 2 [jitter] @ 1419432 ##: 1 [verify] @ 19992 ##: 2 [jitter] @ 29400 ##: 2 [jitter] @ 1418256 ##: 1 [verify] @ 19992 ##: 2 [jitter] @ 51744 ##: 2 [jitter] @ 1435896 ##: 1 [verify] @ 19992 ##: 9 [overlap] @ 26460 ##: 2 [jitter] @ 51744 ##: 2 [jitter] @ 1435896 ##: 1 [verify] @ 19992 ##: 2 [jitter] @ 49392 ##: 2 [jitter] @ 1434720 ##: 1 [verify] @ 19992 ##: 2 [jitter] @ 44688 ##: 2 [jitter] @ 1432368 ##: 1 [verify] @ 19992 scsi_read error: sector=-2 length=18 retry=0 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=9 retry=1 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=4 retry=2 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=2 retry=3 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=4 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=5 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=6 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=7 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument scsi_read error: sector=-2 length=1 retry=8 Sense key: 5 ASC: 21 ASCQ: 0 Transport error: Illegal SCSI request (rejected by target) System error: Invalid argument ##: 12 [transport error] @ -2352 ##: 0 [read] @ 17640 ##: 0 [read] @ 49392 ##: 0 [read] @ 81144 ##: 0 [read] @ 112896 ##: 0 [read] @ 144648 ##: 0 [read] @ 176400 ##: 0 [read] @ 208152 ##: 0 [read] @ 239904 ##: 0 [read] @ 271656 ##: 0 [read] @ 303408 ##: 0 [read] @ 335160 ##: 0 [read] @ 366912 ##: 0 [read] @ 398664 ##: 0 [read] @ 430416 ##: 0 [read] @ 462168 ##: 0 [read] @ 493920 ##: 0 [read] @ 525672 ##: 0 [read] @ 557424 ##: 0 [read] @ 589176 ##: 0 [read] @ 620928 ##: 0 [read] @ 652680 ##: 0 [read] @ 684432 ##: 0 [read] @ 716184 ##: 0 [read] @ 747936 ##: 0 [read] @ 779688 ##: 0 [read] @ 811440 ##: 0 [read] @ 843192 ##: 0 [read] @ 874944 ##: 0 [read] @ 906696 ##: 0 [read] @ 938448 ##: 0 [read] @ 970200 ##: 0 [read] @ 1001952 ##: 0 [read] @ 1033704 ##: 0 [read] @ 1065456 ##: 0 [read] @ 1097208 ##: 0 [read] @ 1128960 ##: 0 [read] @ 1160712 ##: 0 [read] @ 1192464 ##: 0 [read] @ 1224216 ##: 0 [read] @ 1255968 ##: 0 [read] @ 1287720 ##: 0 [read] @ 1319472 ##: 0 [read] @ 1351224 whipper-0.9.0/whipper/test/cdparanoia.progress.strokes000066400000000000000000000045711357173224400232320ustar00rootroot00000000000000Sending all callbacks to stderr for wrapper script cdparanoia III release 10.2 (September 11, 2008) Ripping from sector 0 (track 0 [0:00.00]) to sector 0 (track 0 [0:00.00]) outputting to cdda.wav ##: 0 [read] @ 24696 ##: 0 [read] @ 56448 ##: 0 [read] @ 88200 ##: 0 [read] @ 119952 ##: 0 [read] @ 151704 ##: 0 [read] @ 183456 ##: 0 [read] @ 215208 ##: 0 [read] @ 246960 ##: 0 [read] @ 278712 ##: 0 [read] @ 310464 ##: 0 [read] @ 342216 ##: 0 [read] @ 373968 ##: 0 [read] @ 405720 ##: 0 [read] @ 437472 ##: 0 [read] @ 469224 ##: 0 [read] @ 500976 ##: 0 [read] @ 532728 ##: 0 [read] @ 564480 ##: 0 [read] @ 596232 ##: 0 [read] @ 627984 ##: 0 [read] @ 659736 ##: 0 [read] @ 691488 ##: 0 [read] @ 723240 ##: 0 [read] @ 754992 ##: 0 [read] @ 786744 ##: 0 [read] @ 818496 ##: 0 [read] @ 850248 ##: 0 [read] @ 882000 ##: 0 [read] @ 913752 ##: 0 [read] @ 945504 ##: 0 [read] @ 977256 ##: 0 [read] @ 1009008 ##: 0 [read] @ 1040760 ##: 0 [read] @ 1072512 ##: 0 [read] @ 1104264 ##: 0 [read] @ 1136016 ##: 0 [read] @ 1167768 ##: 0 [read] @ 1199520 ##: 0 [read] @ 1231272 ##: 0 [read] @ 1263024 ##: 0 [read] @ 1294776 ##: 0 [read] @ 1326528 ##: 0 [read] @ 1358280 ##: 0 [read] @ 1390032 ##: 0 [read] @ 1410024 ##: 0 [read] @ 23520 ##: 0 [read] @ 55272 ##: 0 [read] @ 87024 ##: 0 [read] @ 118776 ##: 0 [read] @ 150528 ##: 0 [read] @ 182280 ##: 0 [read] @ 214032 ##: 0 [read] @ 245784 ##: 0 [read] @ 277536 ##: 0 [read] @ 309288 ##: 0 [read] @ 341040 ##: 0 [read] @ 372792 ##: 0 [read] @ 404544 ##: 0 [read] @ 436296 ##: 0 [read] @ 468048 ##: 0 [read] @ 499800 ##: 0 [read] @ 531552 ##: 0 [read] @ 563304 ##: 0 [read] @ 595056 ##: 0 [read] @ 626808 ##: 0 [read] @ 658560 ##: 0 [read] @ 690312 ##: 0 [read] @ 722064 ##: 0 [read] @ 753816 ##: 0 [read] @ 785568 ##: 0 [read] @ 817320 ##: 0 [read] @ 849072 ##: 0 [read] @ 880824 ##: 0 [read] @ 912576 ##: 0 [read] @ 944328 ##: 0 [read] @ 976080 ##: 0 [read] @ 1007832 ##: 0 [read] @ 1039584 ##: 0 [read] @ 1071336 ##: 0 [read] @ 1103088 ##: 0 [read] @ 1134840 ##: 0 [read] @ 1166592 ##: 0 [read] @ 1198344 ##: 0 [read] @ 1230096 ##: 0 [read] @ 1261848 ##: 0 [read] @ 1293600 ##: 0 [read] @ 1325352 ##: 0 [read] @ 1357104 ##: 0 [read] @ 1388856 ##: 0 [read] @ 1410024 ##: 1 [verify] @ 0 ##: 3 [correction] @ 1005459 ##: 3 [correction] @ 1005480 ##: 1 [verify] @ 1005480 ##: 1 [verify] @ 1005480 ##: -2 [wrote] @ 1175 ##: -2 [wrote] @ 1176 ##: -1 [finished] @ 1175 Done. whipper-0.9.0/whipper/test/cdparanoia/000077500000000000000000000000001357173224400177445ustar00rootroot00000000000000whipper-0.9.0/whipper/test/cdparanoia/MATSHITA.cdparanoia-A.log000066400000000000000000000554521357173224400241520ustar00rootroot00000000000000cdparanoia -A cdparanoia III release 10.2 (September 11, 2008) Using cdda library version: 10.2 Using paranoia library version: 10.2 Attempting to set cdrom to full speed... drive returned OK. =================== Checking drive cache/timing behavior =================== Seek/read timing: [45:24.28]: 204328:1:46 204329:27:33 204356:27:35 204383:27:33 204410:27:35 204437:27:33 204464:27:35 204491:27:33 204518:27:35 204545:27:33 204572:27:35 204599:27:32 204626:27:35 204653:27:33 204680:27:35 204707:27:33 204734:27:35 204761:27:33 204788:27:35 204815:27:33 204842:27:35 204869:27:33 204896:27:35 204923:27:32 204950:27:35 204977:27:33 205004:27:35 205031:27:33 205058:27:35 205085:27:33 205112:27:35 205139:27:33 205166:27:35 205193:27:33 205220:27:35 205247:27:33 205274:27:35 205301:27:33 Initial seek latency (1000 sectors): 46ms Average read latency: 1.26ms/sector (raw speed: 10.6x) Read latency standard deviation: 0.04ms/sector [45:24.27]: 204327:1:45 204328:27:33 204355:27:35 204382:27:33 204409:27:35 204436:27:33 204463:27:35 204490:27:33 204517:27:35 204544:27:32 204571:27:35 204598:27:32 204625:27:35 204652:27:33 204679:27:35 204706:27:33 204733:27:35 204760:27:33 204787:27:35 204814:27:33 204841:27:35 204868:27:32 204895:27:35 204922:27:32 204949:27:35 204976:27:33 205003:27:35 205030:27:33 205057:27:35 205084:27:33 205111:27:35 205138:27:33 205165:27:35 205192:27:33 205219:27:35 205246:27:33 205273:27:35 205300:27:33 Initial seek latency (1000 sectors): 45ms Average read latency: 1.25ms/sector (raw speed: 10.6x) Read latency standard deviation: 0.04ms/sector [45:24.26]: 204326:1:45 204327:27:33 204354:27:35 204381:27:33 204408:27:35 204435:27:33 204462:27:35 204489:27:33 204516:27:35 204543:27:33 204570:27:35 204597:27:33 204624:27:35 204651:27:33 204678:27:35 204705:27:33 204732:27:35 204759:27:33 204786:27:35 204813:27:33 204840:27:35 204867:27:33 204894:27:35 204921:27:33 204948:27:35 204975:27:33 205002:27:35 205029:27:33 205056:27:35 205083:27:32 205110:27:35 205137:27:33 205164:27:35 205191:27:33 205218:27:35 205245:27:33 205272:27:35 205299:27:32 Initial seek latency (1000 sectors): 45ms Average read latency: 1.26ms/sector (raw speed: 10.6x) Read latency standard deviation: 0.04ms/sector [45:24.25]: 204325:1:44 204326:27:33 204353:27:35 204380:27:33 204407:27:35 204434:27:32 204461:27:35 204488:27:33 204515:27:35 204542:27:33 204569:27:35 204596:27:33 204623:27:35 204650:27:33 204677:27:35 204704:27:33 204731:27:35 204758:27:32 204785:27:35 204812:27:33 204839:27:35 204866:27:33 204893:27:35 204920:27:33 204947:27:35 204974:27:33 205001:27:35 205028:27:33 205055:27:35 205082:27:33 205109:27:35 205136:27:33 205163:27:35 205190:27:32 205217:27:35 205244:27:33 205271:27:35 205298:27:33 Initial seek latency (1000 sectors): 44ms Average read latency: 1.26ms/sector (raw speed: 10.6x) Read latency standard deviation: 0.04ms/sector [45:24.24]: 204324:1:45 204325:27:33 204352:27:35 204379:27:32 204406:27:35 204433:27:33 204460:27:35 204487:27:33 204514:27:35 204541:27:33 204568:27:35 204595:27:32 204622:27:35 204649:27:33 204676:27:35 204703:27:33 204730:27:35 204757:27:33 204784:27:35 204811:27:32 204838:27:35 204865:27:33 204892:27:35 204919:27:33 204946:27:35 204973:27:33 205000:27:35 205027:27:33 205054:27:35 205081:27:33 205108:27:35 205135:27:33 205162:27:35 205189:27:33 205216:27:35 205243:27:33 205270:27:35 205297:27:33 Initial seek latency (1000 sectors): 45ms Average read latency: 1.26ms/sector (raw speed: 10.6x) Read latency standard deviation: 0.04ms/sector [40:00.00]: 180000:1:50 180001:27:34 180028:27:37 180055:27:34 180082:27:37 180109:27:34 180136:27:37 180163:27:34 180190:27:37 180217:27:34 180244:27:37 180271:27:34 180298:27:37 180325:27:34 180352:27:37 180379:27:34 180406:27:37 180433:27:34 180460:27:37 180487:27:34 180514:27:37 180541:27:34 180568:27:37 180595:27:34 180622:27:37 180649:27:34 180676:27:37 180703:27:34 180730:27:37 180757:27:34 180784:27:37 180811:27:34 180838:27:37 180865:27:34 180892:27:37 180919:27:34 180946:27:37 180973:27:34 Initial seek latency (1000 sectors): 50ms Average read latency: 1.31ms/sector (raw speed: 10.2x) Read latency standard deviation: 0.06ms/sector [30:00.00]: 135000:1:64 135001:27:38 135028:27:41 135055:27:38 135082:27:41 135109:27:38 135136:27:41 135163:27:38 135190:27:41 135217:27:38 135244:27:41 135271:27:38 135298:27:41 135325:27:38 135352:27:41 135379:27:38 135406:27:41 135433:27:38 135460:27:41 135487:27:38 135514:27:41 135541:27:38 135568:27:41 135595:27:38 135622:27:41 135649:27:38 135676:27:41 135703:27:38 135730:27:41 135757:27:38 135784:27:41 135811:27:38 135838:27:41 135865:27:38 135892:27:41 135919:27:38 135946:27:41 135973:27:38 Initial seek latency (1000 sectors): 64ms Average read latency: 1.46ms/sector (raw speed: 9.1x) Read latency standard deviation: 0.06ms/sector [20:00.00]: 90000:1:63 90001:27:43 90028:27:47 90055:27:43 90082:27:47 90109:27:43 90136:27:46 90163:27:43 90190:27:47 90217:27:43 90244:27:46 90271:27:43 90298:27:47 90325:27:43 90352:27:46 90379:27:43 90406:27:46 90433:27:43 90460:27:46 90487:27:43 90514:27:46 90541:27:43 90568:27:46 90595:27:43 90622:27:46 90649:27:43 90676:27:46 90703:27:43 90730:27:46 90757:27:43 90784:27:46 90811:27:43 90838:27:46 90865:27:43 90892:27:46 90919:27:43 90946:27:46 90973:27:43 Initial seek latency (1000 sectors): 63ms Average read latency: 1.65ms/sector (raw speed: 8.1x) Read latency standard deviation: 0.06ms/sector [10:00.00]: 45000:1:61 45001:27:51 45028:27:55 45055:27:51 45082:27:55 45109:27:52 45136:27:55 45163:27:51 45190:27:55 45217:27:51 45244:27:55 45271:27:51 45298:27:55 45325:27:51 45352:27:55 45379:27:51 45406:27:55 45433:27:51 45460:27:55 45487:27:51 45514:27:55 45541:27:51 45568:27:55 45595:27:51 45622:27:55 45649:27:51 45676:27:55 45703:27:51 45730:27:55 45757:27:51 45784:27:55 45811:27:51 45838:27:55 45865:27:51 45892:27:55 45919:27:51 45946:27:55 45973:27:51 Initial seek latency (1000 sectors): 61ms Average read latency: 1.96ms/sector (raw speed: 6.8x) Read latency standard deviation: 0.07ms/sector [00:00.00]: 0:1:84 1:27:67 28:27:72 55:27:67 82:27:72 109:27:67 136:27:72 163:27:67 190:27:72 217:27:67 244:27:72 271:27:67 298:27:72 325:27:67 352:27:72 379:27:67 406:27:72 433:27:67 460:27:72 487:27:67 514:27:72 541:27:67 568:27:72 595:27:67 622:27:72 649:27:67 676:27:72 703:27:67 730:27:72 757:27:67 784:27:72 811:27:67 838:27:72 865:27:67 892:27:72 919:27:67 946:27:72 973:27:67 Initial seek latency (1000 sectors): 84ms Average read latency: 2.57ms/sector (raw speed: 5.2x) Read latency standard deviation: 0.09ms/sector Analyzing cache behavior... Fast search for approximate cache size... 0 sectors >>> fast_read=10:1:71 seek_read=10:1:0 Fast search for approximate cache size... 1 sectors >>> fast_read=11:1:0 seek_read=10:1:65 >>> fast_read=11:1:0 Fast search for approximate cache size... 2 sectors >>> fast_read=12:1:4 seek_read=10:1:61 >>> fast_read=12:1:5 Fast search for approximate cache size... 3 sectors >>> fast_read=13:1:0 seek_read=10:1:61 >>> fast_read=13:1:5 Fast search for approximate cache size... 4 sectors >>> fast_read=14:1:5 seek_read=10:1:56 >>> fast_read=14:1:10 seek_read=10:1:56 >>> fast_read=14:1:10 seek_read=10:1:56 >>> fast_read=14:1:10 seek_read=10:1:56 >>> fast_read=14:1:10 seek_read=10:1:56 >>> fast_read=14:1:10 seek_read=10:1:56 >>> fast_read=14:1:10 seek_read=10:1:56 >>> fast_read=14:1:10 seek_read=10:1:56 >>> fast_read=14:1:10 seek_read=10:1:56 >>> fast_read=14:1:10 seek_read=10:1:56 >>> fast_read=14:1:10 seek_read=10:1:56 >>> fast_read=14:1:10 seek_read=10:1:56 >>> fast_read=14:1:10 seek_read=10:1:56 >>> fast_read=14:1:10 seek_read=10:1:56 >>> fast_read=14:1:10 seek_read=10:1:56 Slow verify for approximate cache size... 4 sectors Attempting to reduce read speed to 1x... drive said OK >>> slow_read=10:5:10 seek_read=10:1:0 Attempting to reset read speed to full... drive said OK Fast search for approximate cache size... 5 sectors >>> fast_read=15:1:0 seek_read=10:1:54 >>> fast_read=15:1:10 seek_read=10:1:56 >>> fast_read=15:1:10 seek_read=10:1:56 >>> fast_read=15:1:10 seek_read=10:1:56 >>> fast_read=15:1:10 seek_read=10:1:56 >>> fast_read=15:1:10 seek_read=10:1:56 >>> fast_read=15:1:10 seek_read=10:1:56 >>> fast_read=15:1:10 seek_read=10:1:56 >>> fast_read=15:1:10 seek_read=10:1:56 >>> fast_read=15:1:10 seek_read=10:1:56 >>> fast_read=15:1:10 seek_read=10:1:56 >>> fast_read=15:1:10 seek_read=10:1:56 >>> fast_read=15:1:10 seek_read=10:1:56 >>> fast_read=15:1:10 seek_read=10:1:56 >>> fast_read=15:1:10 seek_read=10:1:56 Slow verify for approximate cache size... 5 sectors Attempting to reduce read speed to 1x... drive said OK >>> slow_read=10:6:9 seek_read=10:1:0 Attempting to reset read speed to full... drive said OK Fast search for approximate cache size... 6 sectors >>> fast_read=16:1:4 seek_read=10:1:51 >>> fast_read=16:1:15 seek_read=10:1:51 >>> fast_read=16:1:15 seek_read=10:1:51 >>> fast_read=16:1:15 seek_read=10:1:51 >>> fast_read=16:1:15 seek_read=10:1:51 >>> fast_read=16:1:15 seek_read=10:1:51 >>> fast_read=16:1:15 seek_read=10:1:51 >>> fast_read=16:1:15 seek_read=10:1:51 >>> fast_read=16:1:15 seek_read=10:1:51 >>> fast_read=16:1:15 seek_read=10:1:51 >>> fast_read=16:1:15 seek_read=10:1:51 >>> fast_read=16:1:15 seek_read=10:1:51 >>> fast_read=16:1:15 seek_read=10:1:51 >>> fast_read=16:1:15 seek_read=10:1:51 >>> fast_read=16:1:15 seek_read=10:1:51 Slow verify for approximate cache size... 6 sectors Attempting to reduce read speed to 1x... drive said OK >>> slow_read=10:7:15 seek_read=10:1:0 Attempting to reset read speed to full... drive said OK Fast search for approximate cache size... 7 sectors >>> fast_read=17:1:0 seek_read=10:1:49 >>> fast_read=17:1:15 seek_read=10:1:51 >>> fast_read=17:1:15 seek_read=10:1:51 >>> fast_read=17:1:15 seek_read=10:1:51 >>> fast_read=17:1:15 seek_read=10:1:51 >>> fast_read=17:1:15 seek_read=10:1:51 >>> fast_read=17:1:15 seek_read=10:1:51 >>> fast_read=17:1:15 seek_read=10:1:51 >>> fast_read=17:1:15 seek_read=10:1:51 >>> fast_read=17:1:15 seek_read=10:1:51 >>> fast_read=17:1:15 seek_read=10:1:51 >>> fast_read=17:1:15 seek_read=10:1:51 >>> fast_read=17:1:15 seek_read=10:1:51 >>> fast_read=17:1:15 seek_read=10:1:51 >>> fast_read=17:1:15 seek_read=10:1:51 Slow verify for approximate cache size... 7 sectors Attempting to reduce read speed to 1x... drive said OK >>> slow_read=10:8:15 seek_read=10:1:0 Attempting to reset read speed to full... drive said OK Fast search for approximate cache size... 8 sectors >>> fast_read=18:1:3 seek_read=10:1:46 >>> fast_read=18:1:20 seek_read=10:1:46 >>> fast_read=18:1:20 seek_read=10:1:46 >>> fast_read=18:1:21 seek_read=10:1:46 >>> fast_read=18:1:20 seek_read=10:1:46 >>> fast_read=18:1:20 seek_read=10:1:46 >>> fast_read=18:1:20 seek_read=10:1:46 >>> fast_read=18:1:21 seek_read=10:1:45 >>> fast_read=18:1:21 seek_read=10:1:45 >>> fast_read=18:1:20 seek_read=10:1:46 >>> fast_read=18:1:20 seek_read=10:1:46 >>> fast_read=18:1:20 seek_read=10:1:46 >>> fast_read=18:1:20 seek_read=10:1:46 >>> fast_read=18:1:20 seek_read=10:1:46 >>> fast_read=18:1:20 seek_read=10:1:46 Slow verify for approximate cache size... 8 sectors Attempting to reduce read speed to 1x... drive said OK >>> slow_read=10:9:18 seek_read=10:1:46 >>> slow_read=10:9:20 seek_read=10:1:46 >>> slow_read=10:9:20 seek_read=10:1:45 >>> slow_read=10:9:20 seek_read=10:1:46 >>> slow_read=10:9:20 seek_read=10:1:46 >>> slow_read=10:9:20 seek_read=10:1:46 >>> slow_read=10:9:20 seek_read=10:1:46 >>> slow_read=10:9:20 seek_read=10:1:46 >>> slow_read=10:9:20 seek_read=10:1:46 >>> slow_read=10:9:20 seek_read=10:1:46 Approximate random access cache size: 8 sector(s) Attempting to reset read speed to full... drive said OK Verifying that cache is contiguous... >>> 34:1:61 seek_read:10:1:49 >>> 34:1:62 seek_read:10:1:49 >>> 34:1:62 seek_read:10:1:49 >>> 34:1:62 seek_read:10:1:49 >>> 34:1:62 seek_read:10:1:49 >>> 34:1:62 seek_read:10:1:49 >>> 34:1:62 seek_read:10:1:49 >>> 34:1:62 seek_read:10:1:49 >>> 34:1:62 seek_read:10:1:49 >>> 34:1:62 seek_read:10:1:49 >>> 34:1:62 seek_read:10:1:49 >>> 34:1:62 seek_read:10:1:49 >>> 34:1:62 seek_read:10:1:49 >>> 34:1:62 seek_read:10:1:49 >>> 34:1:62 seek_read:10:1:49 >>> 34:1:62 seek_read:10:1:49 >>> 34:1:62 seek_read:10:1:49 >>> 34:1:62 seek_read:10:1:49 >>> 34:1:62 seek_read:10:1:49 >>> 34:1:62 seek_read:10:1:49 Drive cache tests as contiguous Testing background readahead past read cursor... 64 0 >>> 10:8:15 sleep=197299us seek=81:1:0 Testing background readahead past read cursor... 128 0 >>> 10:8:69 sleep=394598us seek=145:1:0 Testing background readahead past read cursor... 192 0 >>> 10:8:73 sleep=591897us seek=209:1:0 Testing background readahead past read cursor... 256 0 >>> 10:8:77 sleep=789196us seek=273:1:0 Testing background readahead past read cursor... 320 0 >>> 10:8:81 sleep=986496us seek=337:1:50 1 >>> 10:8:81 sleep=1150912us seek=337:1:64 Retiming drive... 10:1:65 11:27:67 38:27:72 65:27:67 92:27:72 119:27:67 146:27:72 173:27:67 200:27:72 227:27:67 254:27:72 281:27:67 308:27:72 335:27:67 362:27:72 389:27:67 416:27:72 443:27:67 470:27:72 497:27:67 524:27:72 551:27:67 578:27:72 605:27:67 632:27:72 659:27:67 686:27:72 713:27:67 740:27:72 767:27:67 794:27:72 821:27:67 848:27:72 875:27:67 902:27:72 929:27:67 956:27:72 983:27:67 1010:27:72 1037:27:67 1064:27:72 1091:27:67 1118:27:72 1145:27:67 1172:27:72 1199:27:67 1226:27:72 1253:27:67 1280:27:72 1307:27:67 1334:27:72 1361:27:67 1388:27:72 1415:27:66 1442:27:72 1469:27:67 1496:27:72 1523:27:66 1550:27:72 1577:27:66 1604:27:71 1631:27:66 1658:27:72 1685:27:66 1712:27:71 1739:27:66 1766:27:71 1793:27:66 1820:27:71 1847:27:66 1874:27:71 1901:27:66 1928:27:71 1955:1:0 Initial seek latency (1946 sectors): 65ms Average read latency: 2.57ms/sector (raw speed: 5.2x) Read latency standard deviation: 0.11ms/sector Old mean=2.57ms/sec, New mean=2.56ms/sec 2 >>> 10:8:106 sleep=1315328us seek=337:1:56 Testing background readahead past read cursor... 264 0 >>> 10:8:81 sleep=813859us seek=281:1:0 Testing background readahead past read cursor... 272 0 >>> 10:8:79 sleep=838521us seek=289:1:50 1 >>> 10:8:72 sleep=978275us seek=289:1:45 Retiming drive... 10:1:56 11:27:67 38:27:72 65:27:67 92:27:72 119:27:67 146:27:72 173:27:67 200:27:72 227:27:67 254:27:72 281:27:67 308:27:72 335:27:67 362:27:72 389:27:67 416:27:72 443:27:67 470:27:72 497:27:67 524:27:72 551:27:67 578:27:72 605:27:67 632:27:72 659:27:67 686:27:72 713:27:67 740:27:72 767:27:67 794:27:72 821:27:67 848:27:72 875:27:67 902:27:72 929:27:67 956:27:72 983:27:67 1010:27:72 1037:27:67 1064:27:72 1091:27:67 1118:27:72 1145:27:67 1172:27:72 1199:27:67 1226:27:72 1253:27:67 1280:27:72 1307:27:67 1334:27:72 1361:27:67 1388:27:72 1415:27:67 1442:27:72 1469:27:66 1496:27:72 1523:27:66 1550:27:72 1577:27:66 1604:27:72 1631:27:66 1658:27:72 1685:27:66 1712:27:71 1739:27:66 1766:27:71 1793:27:66 1820:27:71 1847:27:66 1874:27:71 1901:27:66 1928:27:71 1955:1:0 Initial seek latency (1946 sectors): 56ms Average read latency: 2.57ms/sector (raw speed: 5.2x) Read latency standard deviation: 0.11ms/sector Old mean=2.57ms/sec, New mean=2.56ms/sec 2 >>> 10:8:105 sleep=1118028us seek=289:1:39 Testing background readahead past read cursor... 265 0 >>> 10:8:71 sleep=816942us seek=282:1:54 1 >>> 10:8:67 sleep=953098us seek=282:1:52 Retiming drive... 10:1:51 11:27:67 38:27:72 65:27:67 92:27:72 119:27:67 146:27:72 173:27:67 200:27:72 227:27:67 254:27:72 281:27:67 308:27:72 335:27:67 362:27:72 389:27:67 416:27:72 443:27:67 470:27:72 497:27:67 524:27:72 551:27:67 578:27:72 605:27:67 632:27:72 659:27:67 686:27:72 713:27:67 740:27:72 767:27:67 794:27:72 821:27:67 848:27:72 875:27:67 902:27:72 929:27:67 956:27:72 983:27:67 1010:27:72 1037:27:67 1064:27:72 1091:27:67 1118:27:72 1145:27:67 1172:27:72 1199:27:67 1226:27:72 1253:27:67 1280:27:72 1307:27:67 1334:27:72 1361:27:67 1388:27:72 1415:27:67 1442:27:71 1469:27:67 1496:27:72 1523:27:66 1550:27:72 1577:27:67 1604:27:71 1631:27:66 1658:27:72 1685:27:66 1712:27:72 1739:27:67 1766:27:71 1793:27:66 1820:27:71 1847:27:66 1874:27:71 1901:27:67 1928:27:71 1955:1:0 Initial seek latency (1946 sectors): 51ms Average read latency: 2.57ms/sector (raw speed: 5.2x) Read latency standard deviation: 0.11ms/sector Old mean=2.57ms/sec, New mean=2.57ms/sec 2 >>> 10:8:106 sleep=1089256us seek=282:1:50 3 >>> 10:8:67 sleep=1225413us seek=282:1:48 4 >>> 10:8:67 sleep=1361569us seek=282:1:46 Retiming drive... 10:1:52 11:27:67 38:27:72 65:27:67 92:27:72 119:27:67 146:27:72 173:27:67 200:27:72 227:27:67 254:27:72 281:27:67 308:27:72 335:27:67 362:27:72 389:27:67 416:27:72 443:27:67 470:27:72 497:27:67 524:27:72 551:27:67 578:27:72 605:27:67 632:27:72 659:27:67 686:27:72 713:27:67 740:27:72 767:27:67 794:27:72 821:27:67 848:27:72 875:27:67 902:27:72 929:27:67 956:27:72 983:27:67 1010:27:72 1037:27:67 1064:27:72 1091:27:67 1118:27:72 1145:27:67 1172:27:72 1199:27:67 1226:27:72 1253:27:67 1280:27:72 1307:27:67 1334:27:72 1361:27:67 1388:27:72 1415:27:66 1442:27:72 1469:27:66 1496:27:72 1523:27:67 1550:27:72 1577:27:66 1604:27:72 1631:27:66 1658:27:72 1685:27:66 1712:27:71 1739:27:67 1766:27:71 1793:27:66 1820:27:71 1847:27:66 1874:27:71 1901:27:66 1928:27:71 1955:1:0 Initial seek latency (1946 sectors): 52ms Average read latency: 2.57ms/sector (raw speed: 5.2x) Read latency standard deviation: 0.11ms/sector Old mean=2.57ms/sec, New mean=2.56ms/sec 5 >>> 10:8:106 sleep=1497727us seek=282:1:44 6 >>> 10:8:90 sleep=1633884us seek=282:1:42 7 >>> 10:8:67 sleep=1770041us seek=282:1:40 Retiming drive... 10:1:52 11:27:67 38:27:72 65:27:67 92:27:72 119:27:67 146:27:72 173:27:67 200:27:72 227:27:67 254:27:72 281:27:67 308:27:72 335:27:67 362:27:72 389:27:67 416:27:72 443:27:67 470:27:72 497:27:67 524:27:72 551:27:67 578:27:72 605:27:67 632:27:72 659:27:67 686:27:72 713:27:67 740:27:72 767:27:67 794:27:72 821:27:67 848:27:72 875:27:67 902:27:72 929:27:67 956:27:72 983:27:67 1010:27:72 1037:27:67 1064:27:72 1091:27:67 1118:27:72 1145:27:67 1172:27:72 1199:27:67 1226:27:72 1253:27:67 1280:27:72 1307:27:66 1334:27:72 1361:27:67 1388:27:72 1415:27:67 1442:27:72 1469:27:66 1496:27:72 1523:27:66 1550:27:72 1577:27:67 1604:27:72 1631:27:66 1658:27:72 1685:27:66 1712:27:71 1739:27:66 1766:27:72 1793:27:66 1820:27:71 1847:27:66 1874:27:71 1901:27:66 1928:27:71 1955:1:0 Initial seek latency (1946 sectors): 52ms Average read latency: 2.57ms/sector (raw speed: 5.2x) Read latency standard deviation: 0.11ms/sector Old mean=2.57ms/sec, New mean=2.56ms/sec 8 >>> 10:8:106 sleep=1906197us seek=282:1:60 9 >>> 10:8:67 sleep=2042354us seek=282:1:58 Drive readahead past read cursor: 264 sector(s) Testing cache tail cursor... >>> 10:8:67 sleeping 1017324 microseconds <<< 7:1:0 6:1:55 >>> 10:8:66 sleeping 1017324 microseconds <<< 6:1:0 5:1:52 >>> 10:8:69 sleeping 1017324 microseconds <<< 5:1:0 4:1:49 >>> 10:8:72 sleeping 1017324 microseconds <<< 4:1:0 3:1:69 >>> 10:8:74 sleeping 1017324 microseconds <<< 3:1:0 2:1:67 >>> 10:8:77 sleeping 1017324 microseconds <<< 2:1:0 1:1:64 >>> 10:8:79 sleeping 1017324 microseconds <<< 1:1:0 0:1:61 >>> 10:8:15 sleeping 1017324 microseconds <<< 0:1:0 Retiming drive... 10:1:0 11:27:1 38:27:1 65:27:1 92:27:1 119:27:1 146:27:3 173:27:3 200:27:3 227:27:3 254:27:3 281:27:91 308:27:72 335:27:67 362:27:72 389:27:67 416:27:72 443:27:67 470:27:72 497:27:67 524:27:72 551:27:67 578:27:72 605:27:67 632:27:72 659:27:67 686:27:72 713:27:67 740:27:72 767:27:67 794:27:72 821:27:67 848:27:72 875:27:67 902:27:72 929:27:67 956:27:72 983:27:67 1010:27:72 1037:27:67 1064:27:72 1091:27:67 1118:27:72 1145:27:67 1172:27:72 1199:27:67 1226:27:72 1253:27:67 1280:27:72 1307:27:67 1334:27:72 1361:27:67 1388:27:72 1415:27:66 1442:27:71 1469:27:67 1496:27:72 1523:27:66 1550:27:71 1577:27:67 1604:27:71 1631:27:66 1658:27:72 1685:27:66 1712:27:71 1739:27:66 1766:27:71 1793:27:66 1820:27:71 1847:27:66 1874:27:71 1901:27:66 1928:27:71 1955:1:0 Initial seek latency (1946 sectors): 0ms Average read latency: 2.23ms/sector (raw speed: 6.0x) Read latency standard deviation: 0.88ms/sector Old mean=2.57ms/sec, New mean=2.23ms/sec Cache tail cursor tied to read cursor Testing granularity of cache tail >>> 10:9:112 sleeping 1017324 microseconds <<< 18:1:0 17:1:52 >>> 10:9:69 sleeping 1017324 microseconds <<< 17:1:0 16:1:72 >>> 10:9:80 sleeping 1017324 microseconds <<< 16:1:0 15:1:69 >>> 10:9:74 sleeping 1017324 microseconds <<< 15:1:0 14:1:67 >>> 10:9:77 sleeping 1017324 microseconds <<< 14:1:0 13:1:64 >>> 10:9:79 sleeping 1017324 microseconds <<< 13:1:0 12:1:61 >>> 10:9:82 sleeping 1017324 microseconds <<< 12:1:0 11:1:59 >>> 10:9:85 sleeping 1017324 microseconds <<< 11:1:0 10:1:56 >>> 10:9:20 sleeping 1017324 microseconds <<< 10:1:57 >>> 10:9:20 sleeping 1017324 microseconds <<< 10:1:57 >>> 10:9:20 sleeping 1017324 microseconds <<< 10:1:57 >>> 10:9:20 sleeping 1017324 microseconds <<< 10:1:57 >>> 10:9:20 sleeping 1017324 microseconds <<< 10:1:57 >>> 10:9:20 sleeping 1017324 microseconds <<< 10:1:57 >>> 10:9:20 sleeping 1017324 microseconds <<< 10:1:57 >>> 10:9:20 sleeping 1017324 microseconds <<< 10:1:57 >>> 10:9:20 sleeping 1017324 microseconds <<< 10:1:57 >>> 10:9:20 sleeping 1017324 microseconds <<< 10:1:57 Retiming drive... 10:1:0 11:27:66 38:27:72 65:27:67 92:27:72 119:27:67 146:27:72 173:27:67 200:27:72 227:27:67 254:27:72 281:27:67 308:27:72 335:27:67 362:27:72 389:27:67 416:27:72 443:27:67 470:27:72 497:27:67 524:27:72 551:27:67 578:27:72 605:27:67 632:27:72 659:27:67 686:27:72 713:27:67 740:27:72 767:27:67 794:27:72 821:27:67 848:27:72 875:27:67 902:27:72 929:27:67 956:27:72 983:27:67 1010:27:72 1037:27:67 1064:27:72 1091:27:67 1118:27:72 1145:27:67 1172:27:72 1199:27:67 1226:27:72 1253:27:67 1280:27:72 1307:27:67 1334:27:71 1361:27:67 1388:27:72 1415:27:67 1442:27:72 1469:27:66 1496:27:72 1523:27:66 1550:27:72 1577:27:66 1604:27:71 1631:27:66 1658:27:72 1685:27:66 1712:27:71 1739:27:66 1766:27:71 1793:27:67 1820:27:71 1847:27:66 1874:27:71 1901:27:66 1928:27:71 1955:1:0 Initial seek latency (1946 sectors): 0ms Average read latency: 2.56ms/sector (raw speed: 5.2x) Read latency standard deviation: 0.11ms/sector Old mean=2.57ms/sec, New mean=2.56ms/sec Cache tail granularity: 1 sector(s) Cache size (considering rollbehind) too small to test cache speed. Drive tests OK with Paranoia. whipper-0.9.0/whipper/test/cdparanoia/MATSHITA.cdparanoia-A.stderr000066400000000000000000000131221357173224400246600ustar00rootroot00000000000000cdparanoia III release 10.2 (September 11, 2008) Using cdda library version: 10.2 Using paranoia library version: 10.2 Checking /dev/cdrom for cdrom... Could not stat /dev/cdrom: No such file or directory Checking /dev/cdroms/cdrom0 for cdrom... Could not stat /dev/cdroms/cdrom0: No such file or directory Checking /dev/cdroms/cdroma for cdrom... Could not stat /dev/cdroms/cdroma: No such file or directory Checking /dev/cdroms/cdrom1 for cdrom... Could not stat /dev/cdroms/cdrom1: No such file or directory Checking /dev/cdroms/cdromb for cdrom... Could not stat /dev/cdroms/cdromb: No such file or directory Checking /dev/cdroms/cdrom2 for cdrom... Could not stat /dev/cdroms/cdrom2: No such file or directory Checking /dev/cdroms/cdromc for cdrom... Could not stat /dev/cdroms/cdromc: No such file or directory Checking /dev/cdroms/cdrom3 for cdrom... Could not stat /dev/cdroms/cdrom3: No such file or directory Checking /dev/cdroms/cdromd for cdrom... Could not stat /dev/cdroms/cdromd: No such file or directory Checking /dev/hd0 for cdrom... Could not stat /dev/hd0: No such file or directory Checking /dev/hda for cdrom... Could not stat /dev/hda: No such file or directory Checking /dev/hd1 for cdrom... Could not stat /dev/hd1: No such file or directory Checking /dev/hdb for cdrom... Could not stat /dev/hdb: No such file or directory Checking /dev/hd2 for cdrom... Could not stat /dev/hd2: No such file or directory Checking /dev/hdc for cdrom... Could not stat /dev/hdc: No such file or directory Checking /dev/hd3 for cdrom... Could not stat /dev/hd3: No such file or directory Checking /dev/hdd for cdrom... Could not stat /dev/hdd: No such file or directory Checking /dev/sg0 for cdrom... Testing /dev/sg0 for SCSI/MMC interface Could not access device /dev/sg0 to test for SG_IO support: Permission denied no SG_IO support for device: /dev/sg0 Could not access device /dev/sg0: Permission denied generic device: /dev/sg0 ioctl device: not found Could not open generic SCSI device /dev/sg0: Permission denied Testing /dev/sg0 for cooked ioctl() interface /dev/sg0 is not a cooked ioctl CDROM. Checking /dev/sga for cdrom... Could not stat /dev/sga: No such file or directory Checking /dev/sg1 for cdrom... Testing /dev/sg1 for SCSI/MMC interface SG_IO device: /dev/sg1 CDROM model sensed sensed: MATSHITA DVD-RAM UJ8A0A SB02 Checking for SCSI emulation... Drive is ATAPI (using SG_IO host adaptor emulation) Checking for MMC style command set... Drive is MMC style DMA scatter/gather table entries: 167 table entry size: 524288 bytes maximum theoretical transfer: 37074 sectors Setting default read size to 27 sectors (63504 bytes). Verifying CDDA command set... Expected command set reads OK. Attempting to set cdrom to full speed... drive returned OK. =================== Checking drive cache/timing behavior =================== Seek/read timing: [45:24.28]: 46ms seek, 1.26ms/sec read [10.6x] spinning up... [45:24.27]: 45ms seek, 1.25ms/sec read [10.6x] spinning up... [45:24.26]: 45ms seek, 1.26ms/sec read [10.6x] spinning up... [45:24.25]: 44ms seek, 1.26ms/sec read [10.6x] spinning up... [45:24.24]: 45ms seek, 1.26ms/sec read [10.6x] [40:00.00]: 50ms seek, 1.31ms/sec read [10.2x] [30:00.00]: 64ms seek, 1.46ms/sec read [9.1x] [20:00.00]: 63ms seek, 1.65ms/sec read [8.1x] [10:00.00]: 61ms seek, 1.96ms/sec read [6.8x] [00:00.00]: 84ms seek, 2.57ms/sec read [5.2x] Analyzing cache behavior... Fast search for approximate cache size... 0 sectors Fast search for approximate cache size... 1 sectors Fast search for approximate cache size... 2 sectors Fast search for approximate cache size... 3 sectors Fast search for approximate cache size... 4 sectors Slow verify for approximate cache size... 4 sectors. Fast search for approximate cache size... 5 sectors Slow verify for approximate cache size... 5 sectors. Fast search for approximate cache size... 6 sectors Slow verify for approximate cache size... 6 sectors. Fast search for approximate cache size... 7 sectors Slow verify for approximate cache size... 7 sectors. Fast search for approximate cache size... 8 sectors Slow verify for approximate cache size... 8 sectors.......... Approximate random access cache size: 8 sector(s) Verifying that cache is contiguous....................... Drive cache tests as contiguous Testing background readahead past read cursor... 64 . Testing background readahead past read cursor... 128 . Testing background readahead past read cursor... 192 . Testing background readahead past read cursor... 256 . Testing background readahead past read cursor... 320 ..o. Testing background readahead past read cursor... 264 . Testing background readahead past read cursor... 272 ..o. Testing background readahead past read cursor... 265 ..o...o...o.. Drive readahead past read cursor: 264 sector(s) Testing cache tail cursor...........o Cache tail cursor tied to read cursor Testing granularity of cache tail..................o Cache tail granularity: 1 sector(s) Cache size (considering rollbehind) too small to test cache speed. Drive tests OK with Paranoia. whipper-0.9.0/whipper/test/cdparanoia/PX-L890SA.cdparanoia-A.log000066400000000000000000000367361357173224400241510ustar00rootroot00000000000000cdparanoia -A cdparanoia III release 10.2 (September 11, 2008) Using cdda library version: 10.2 Using paranoia library version: 10.2 Attempting to set cdrom to full speed... drive returned OK. =================== Checking drive cache/timing behavior =================== Seek/read timing: [39:43.53]: 178778:1:19 178779:27:19 178806:27:19 178833:27:19 178860:27:19 178887:27:19 178914:27:19 178941:27:19 178968:27:19 178995:27:19 179022:27:19 179049:27:19 179076:27:19 179103:27:19 179130:27:19 179157:27:19 179184:27:19 179211:27:19 179238:27:19 179265:27:19 179292:27:19 179319:27:19 179346:27:19 179373:27:19 179400:27:19 179427:27:19 179454:27:20 179481:27:19 179508:27:19 179535:27:19 179562:27:19 179589:27:19 179616:27:19 179643:27:19 179670:27:19 179697:27:19 179724:27:19 179751:27:19 Initial seek latency (1000 sectors): 19ms Average read latency: 0.70ms/sector (raw speed: 18.9x) Read latency standard deviation: 0.01ms/sector [39:43.52]: 178777:1:19 178778:27:20 178805:27:20 178832:27:20 178859:27:20 178886:27:20 178913:27:20 178940:27:20 178967:27:732 178994:27:14 179021:27:14 179048:27:14 179075:27:14 179102:27:14 179129:27:14 179156:27:14 179183:27:14 179210:27:14 179237:27:14 179264:27:14 179291:27:14 179318:27:14 179345:27:14 179372:27:14 179399:27:14 179426:27:14 179453:27:14 179480:27:14 179507:27:14 179534:27:14 179561:27:14 179588:27:14 179615:27:14 179642:27:14 179669:27:14 179696:27:14 179723:27:14 179750:27:14 Initial seek latency (1000 sectors): 19ms Average read latency: 1.28ms/sector (raw speed: 10.4x) Read latency standard deviation: 4.31ms/sector [39:43.51]: 178776:1:23 178777:27:14 178804:27:14 178831:27:14 178858:27:14 178885:27:14 178912:27:14 178939:27:14 178966:27:14 178993:27:14 179020:27:14 179047:27:14 179074:27:14 179101:27:14 179128:27:14 179155:27:14 179182:27:14 179209:27:14 179236:27:14 179263:27:14 179290:27:14 179317:27:14 179344:27:14 179371:27:14 179398:27:14 179425:27:14 179452:27:14 179479:27:14 179506:27:14 179533:27:14 179560:27:14 179587:27:14 179614:27:14 179641:27:14 179668:27:14 179695:27:14 179722:27:14 179749:27:14 Initial seek latency (1000 sectors): 23ms Average read latency: 0.52ms/sector (raw speed: 25.7x) Read latency standard deviation: -nanms/sector [39:43.50]: 178775:1:231 178776:27:13 178803:27:13 178830:27:13 178857:27:13 178884:27:13 178911:27:13 178938:27:13 178965:27:13 178992:27:13 179019:27:13 179046:27:13 179073:27:13 179100:27:13 179127:27:13 179154:27:12 179181:27:12 179208:27:12 179235:27:13 179262:27:12 179289:27:12 179316:27:12 179343:27:12 179370:27:12 179397:27:12 179424:27:12 179451:27:12 179478:27:12 179505:27:12 179532:27:12 179559:27:12 179586:27:12 179613:27:12 179640:27:12 179667:27:12 179694:27:12 179721:27:12 179748:27:12 Initial seek latency (1000 sectors): 231ms Average read latency: 0.46ms/sector (raw speed: 29.0x) Read latency standard deviation: 0.02ms/sector [39:43.49]: 178774:1:18 178775:27:11 178802:27:11 178829:27:11 178856:27:11 178883:27:11 178910:27:11 178937:27:11 178964:27:11 178991:27:11 179018:27:11 179045:27:11 179072:27:11 179099:27:11 179126:27:11 179153:27:11 179180:27:11 179207:27:11 179234:27:11 179261:27:11 179288:27:11 179315:27:11 179342:27:11 179369:27:11 179396:27:11 179423:27:11 179450:27:11 179477:27:11 179504:27:11 179531:27:11 179558:27:11 179585:27:11 179612:27:11 179639:27:11 179666:27:11 179693:27:11 179720:27:11 179747:27:11 Initial seek latency (1000 sectors): 18ms Average read latency: 0.41ms/sector (raw speed: 32.7x) Read latency standard deviation: 0.00ms/sector [39:43.48]: 178773:1:18 178774:27:11 178801:27:11 178828:27:11 178855:27:11 178882:27:11 178909:27:941 178936:27:9 178963:27:9 178990:27:9 179017:27:9 179044:27:9 179071:27:9 179098:27:9 179125:27:9 179152:27:9 179179:27:9 179206:27:9 179233:27:9 179260:27:9 179287:27:9 179314:27:10 179341:27:9 179368:27:10 179395:27:9 179422:27:9 179449:27:9 179476:27:9 179503:27:10 179530:27:9 179557:27:9 179584:27:9 179611:27:10 179638:27:9 179665:27:9 179692:27:9 179719:27:10 179746:27:9 Initial seek latency (1000 sectors): 18ms Average read latency: 1.28ms/sector (raw speed: 10.4x) Read latency standard deviation: 5.60ms/sector [39:43.47]: 178772:1:21 178773:27:10 178800:27:10 178827:27:10 178854:27:10 178881:27:9 178908:27:10 178935:27:10 178962:27:10 178989:27:10 179016:27:10 179043:27:10 179070:27:10 179097:27:10 179124:27:10 179151:27:10 179178:27:10 179205:27:10 179232:27:9 179259:27:10 179286:27:10 179313:27:10 179340:27:10 179367:27:10 179394:27:10 179421:27:10 179448:27:10 179475:27:10 179502:27:10 179529:27:10 179556:27:10 179583:27:10 179610:27:10 179637:27:10 179664:27:10 179691:27:10 179718:27:10 179745:27:9 Initial seek latency (1000 sectors): 21ms Average read latency: 0.37ms/sector (raw speed: 36.3x) Read latency standard deviation: 0.01ms/sector [39:43.46]: 178771:1:21 178772:27:10 178799:27:9 178826:27:10 178853:27:10 178880:27:10 178907:27:10 178934:27:10 178961:27:10 178988:27:10 179015:27:10 179042:27:10 179069:27:10 179096:27:10 179123:27:10 179150:27:10 179177:27:10 179204:27:10 179231:27:10 179258:27:10 179285:27:10 179312:27:10 179339:27:10 179366:27:10 179393:27:10 179420:27:10 179447:27:10 179474:27:10 179501:27:10 179528:27:10 179555:27:10 179582:27:10 179609:27:10 179636:27:10 179663:27:10 179690:27:10 179717:27:10 179744:27:10 Initial seek latency (1000 sectors): 21ms Average read latency: 0.37ms/sector (raw speed: 36.1x) Read latency standard deviation: 0.01ms/sector [39:43.45]: 178770:1:15 178771:27:10 178798:27:10 178825:27:10 178852:27:10 178879:27:10 178906:27:10 178933:27:10 178960:27:10 178987:27:10 179014:27:10 179041:27:10 179068:27:10 179095:27:10 179122:27:10 179149:27:10 179176:27:10 179203:27:10 179230:27:10 179257:27:10 179284:27:10 179311:27:10 179338:27:10 179365:27:10 179392:27:10 179419:27:10 179446:27:10 179473:27:10 179500:27:10 179527:27:10 179554:27:10 179581:27:10 179608:27:10 179635:27:10 179662:27:10 179689:27:10 179716:27:10 179743:27:10 Initial seek latency (1000 sectors): 15ms Average read latency: 0.37ms/sector (raw speed: 36.0x) Read latency standard deviation: -nanms/sector [39:43.44]: 178769:1:21 178770:27:10 178797:27:10 178824:27:10 178851:27:10 178878:27:10 178905:27:10 178932:27:10 178959:27:10 178986:27:10 179013:27:10 179040:27:10 179067:27:10 179094:27:10 179121:27:10 179148:27:10 179175:27:10 179202:27:10 179229:27:10 179256:27:10 179283:27:10 179310:27:10 179337:27:10 179364:27:10 179391:27:10 179418:27:10 179445:27:10 179472:27:10 179499:27:10 179526:27:10 179553:27:10 179580:27:10 179607:27:10 179634:27:10 179661:27:10 179688:27:10 179715:27:10 179742:27:10 Initial seek latency (1000 sectors): 21ms Average read latency: 0.37ms/sector (raw speed: 36.0x) Read latency standard deviation: -nanms/sector [39:43.43]: 178768:1:15 178769:27:10 178796:27:10 178823:27:10 178850:27:10 178877:27:10 178904:27:10 178931:27:10 178958:27:10 178985:27:10 179012:27:10 179039:27:10 179066:27:10 179093:27:10 179120:27:10 179147:27:10 179174:27:10 179201:27:10 179228:27:10 179255:27:10 179282:27:10 179309:27:10 179336:27:10 179363:27:10 179390:27:10 179417:27:10 179444:27:10 179471:27:10 179498:27:10 179525:27:10 179552:27:10 179579:27:10 179606:27:10 179633:27:10 179660:27:10 179687:27:10 179714:27:10 179741:27:10 Initial seek latency (1000 sectors): 15ms Average read latency: 0.37ms/sector (raw speed: 36.0x) Read latency standard deviation: -nanms/sector [39:43.42]: 178767:1:21 178768:27:10 178795:27:10 178822:27:10 178849:27:10 178876:27:10 178903:27:10 178930:27:10 178957:27:10 178984:27:10 179011:27:10 179038:27:10 179065:27:10 179092:27:10 179119:27:10 179146:27:10 179173:27:10 179200:27:10 179227:27:10 179254:27:10 179281:27:10 179308:27:10 179335:27:10 179362:27:10 179389:27:10 179416:27:10 179443:27:10 179470:27:10 179497:27:10 179524:27:10 179551:27:10 179578:27:10 179605:27:10 179632:27:10 179659:27:10 179686:27:10 179713:27:10 179740:27:10 Initial seek latency (1000 sectors): 21ms Average read latency: 0.37ms/sector (raw speed: 36.0x) Read latency standard deviation: -nanms/sector [39:43.41]: 178766:1:15 178767:27:10 178794:27:10 178821:27:10 178848:27:10 178875:27:10 178902:27:10 178929:27:10 178956:27:10 178983:27:10 179010:27:10 179037:27:10 179064:27:10 179091:27:10 179118:27:10 179145:27:10 179172:27:10 179199:27:10 179226:27:10 179253:27:10 179280:27:10 179307:27:10 179334:27:10 179361:27:10 179388:27:10 179415:27:10 179442:27:10 179469:27:10 179496:27:10 179523:27:10 179550:27:10 179577:27:10 179604:27:10 179631:27:10 179658:27:10 179685:27:10 179712:27:10 179739:27:10 Initial seek latency (1000 sectors): 15ms Average read latency: 0.37ms/sector (raw speed: 36.0x) Read latency standard deviation: -nanms/sector [39:43.40]: 178765:1:21 178766:27:10 178793:27:10 178820:27:10 178847:27:10 178874:27:10 178901:27:10 178928:27:10 178955:27:10 178982:27:10 179009:27:10 179036:27:10 179063:27:10 179090:27:10 179117:27:10 179144:27:10 179171:27:10 179198:27:10 179225:27:10 179252:27:10 179279:27:10 179306:27:10 179333:27:10 179360:27:10 179387:27:10 179414:27:10 179441:27:10 179468:27:10 179495:27:10 179522:27:10 179549:27:10 179576:27:10 179603:27:10 179630:27:10 179657:27:10 179684:27:10 179711:27:10 179738:27:10 Initial seek latency (1000 sectors): 21ms Average read latency: 0.37ms/sector (raw speed: 36.0x) Read latency standard deviation: -nanms/sector [39:43.39]: 178764:1:21 178765:27:10 178792:27:10 178819:27:10 178846:27:10 178873:27:10 178900:27:10 178927:27:10 178954:27:10 178981:27:10 179008:27:10 179035:27:10 179062:27:10 179089:27:10 179116:27:10 179143:27:10 179170:27:10 179197:27:10 179224:27:10 179251:27:10 179278:27:10 179305:27:10 179332:27:10 179359:27:10 179386:27:10 179413:27:10 179440:27:10 179467:27:10 179494:27:10 179521:27:10 179548:27:10 179575:27:10 179602:27:10 179629:27:10 179656:27:10 179683:27:10 179710:27:10 179737:27:10 Initial seek latency (1000 sectors): 21ms Average read latency: 0.37ms/sector (raw speed: 36.0x) Read latency standard deviation: -nanms/sector [39:43.38]: 178763:1:15 178764:27:10 178791:27:10 178818:27:10 178845:27:10 178872:27:10 178899:27:10 178926:27:10 178953:27:10 178980:27:10 179007:27:10 179034:27:10 179061:27:10 179088:27:10 179115:27:10 179142:27:10 179169:27:10 179196:27:10 179223:27:10 179250:27:10 179277:27:10 179304:27:10 179331:27:10 179358:27:10 179385:27:10 179412:27:10 179439:27:10 179466:27:10 179493:27:10 179520:27:10 179547:27:10 179574:27:10 179601:27:10 179628:27:10 179655:27:10 179682:27:10 179709:27:10 179736:27:10 Initial seek latency (1000 sectors): 15ms Average read latency: 0.37ms/sector (raw speed: 36.0x) Read latency standard deviation: -nanms/sector [39:43.37]: 178762:1:21 178763:27:10 178790:27:10 178817:27:10 178844:27:10 178871:27:10 178898:27:10 178925:27:10 178952:27:10 178979:27:10 179006:27:10 179033:27:10 179060:27:10 179087:27:10 179114:27:10 179141:27:10 179168:27:10 179195:27:10 179222:27:10 179249:27:10 179276:27:10 179303:27:10 179330:27:10 179357:27:10 179384:27:10 179411:27:10 179438:27:10 179465:27:10 179492:27:10 179519:27:10 179546:27:10 179573:27:10 179600:27:10 179627:27:10 179654:27:10 179681:27:10 179708:27:10 179735:27:10 Initial seek latency (1000 sectors): 21ms Average read latency: 0.37ms/sector (raw speed: 36.0x) Read latency standard deviation: -nanms/sector [39:43.36]: 178761:1:15 178762:27:10 178789:27:10 178816:27:10 178843:27:10 178870:27:10 178897:27:10 178924:27:10 178951:27:10 178978:27:10 179005:27:10 179032:27:10 179059:27:10 179086:27:10 179113:27:10 179140:27:10 179167:27:10 179194:27:10 179221:27:10 179248:27:10 179275:27:10 179302:27:10 179329:27:10 179356:27:10 179383:27:10 179410:27:10 179437:27:10 179464:27:10 179491:27:10 179518:27:10 179545:27:10 179572:27:10 179599:27:10 179626:27:10 179653:27:10 179680:27:10 179707:27:10 179734:27:10 Initial seek latency (1000 sectors): 15ms Average read latency: 0.37ms/sector (raw speed: 36.0x) Read latency standard deviation: -nanms/sector [30:00.00]: 135000:1:21 135001:27:11 135028:27:11 135055:27:11 135082:27:11 135109:27:11 135136:27:11 135163:27:11 135190:27:11 135217:27:11 135244:27:11 135271:27:11 135298:27:11 135325:27:11 135352:27:11 135379:27:11 135406:27:11 135433:27:11 135460:27:11 135487:27:11 135514:27:11 135541:27:11 135568:27:11 135595:27:11 135622:27:11 135649:27:11 135676:27:11 135703:27:11 135730:27:11 135757:27:11 135784:27:11 135811:27:11 135838:27:11 135865:27:11 135892:27:11 135919:27:11 135946:27:11 135973:27:11 Initial seek latency (1000 sectors): 21ms Average read latency: 0.41ms/sector (raw speed: 32.7x) Read latency standard deviation: 0.00ms/sector [20:00.00]: 90000:1:22 90001:27:12 90028:27:12 90055:27:12 90082:27:12 90109:27:12 90136:27:12 90163:27:12 90190:27:12 90217:27:12 90244:27:12 90271:27:12 90298:27:12 90325:27:12 90352:27:12 90379:27:12 90406:27:12 90433:27:12 90460:27:12 90487:27:12 90514:27:12 90541:27:12 90568:27:12 90595:27:12 90622:27:12 90649:27:12 90676:27:12 90703:27:12 90730:27:12 90757:27:12 90784:27:12 90811:27:12 90838:27:12 90865:27:12 90892:27:12 90919:27:12 90946:27:12 90973:27:12 Initial seek latency (1000 sectors): 22ms Average read latency: 0.44ms/sector (raw speed: 30.0x) Read latency standard deviation: 0.00ms/sector [10:00.00]: 45000:1:30 45001:27:14 45028:27:14 45055:27:14 45082:27:14 45109:27:14 45136:27:14 45163:27:14 45190:27:14 45217:27:14 45244:27:14 45271:27:14 45298:27:14 45325:27:14 45352:27:14 45379:27:14 45406:27:14 45433:27:14 45460:27:14 45487:27:14 45514:27:14 45541:27:14 45568:27:14 45595:27:14 45622:27:14 45649:27:14 45676:27:14 45703:27:14 45730:27:14 45757:27:14 45784:27:14 45811:27:14 45838:27:14 45865:27:14 45892:27:14 45919:27:14 45946:27:14 45973:27:14 Initial seek latency (1000 sectors): 30ms Average read latency: 0.52ms/sector (raw speed: 25.7x) Read latency standard deviation: -nanms/sector [00:00.00]: 0:1:33 1:27:19 28:27:19 55:27:19 82:27:19 109:27:19 136:27:19 163:27:19 190:27:19 217:27:19 244:27:19 271:27:19 298:27:19 325:27:19 352:27:19 379:27:19 406:27:19 433:27:19 460:27:19 487:27:19 514:27:19 541:27:19 568:27:19 595:27:19 622:27:19 649:27:19 676:27:19 703:27:19 730:27:19 757:27:19 784:27:19 811:27:19 838:27:19 865:27:19 892:27:19 919:27:19 946:27:19 973:27:19 Initial seek latency (1000 sectors): 33ms Average read latency: 0.70ms/sector (raw speed: 18.9x) Read latency standard deviation: -nanms/sector Analyzing cache behavior... Fast search for approximate cache size... 0 sectors >>> fast_read=10:1:35 seek_read=10:1:18 >>> fast_read=10:1:18 seek_read=10:1:18 >>> fast_read=10:1:18 seek_read=10:1:18 >>> fast_read=10:1:18 seek_read=10:1:18 >>> fast_read=10:1:18 seek_read=10:1:18 >>> fast_read=10:1:18 seek_read=10:1:18 >>> fast_read=10:1:18 seek_read=10:1:18 >>> fast_read=10:1:18 seek_read=10:1:18 >>> fast_read=10:1:18 seek_read=10:1:364 >>> fast_read=10:1:21 seek_read=10:1:21 >>> fast_read=10:1:22 seek_read=10:1:22 >>> fast_read=10:1:22 seek_read=10:1:22 >>> fast_read=10:1:22 seek_read=10:1:22 >>> fast_read=10:1:22 seek_read=10:1:22 >>> fast_read=10:1:22 seek_read=10:1:22 Slow verify for approximate cache size... 0 sectors Attempting to reduce read speed to 1x... drive said OK >>> slow_read=10:1:21 seek_read=10:1:22 >>> slow_read=10:1:22 seek_read=10:1:22 >>> slow_read=10:1:22 seek_read=10:1:22 >>> slow_read=10:1:22 seek_read=10:1:22 >>> slow_read=10:1:22 seek_read=10:1:22 >>> slow_read=10:1:22 seek_read=10:1:22 >>> slow_read=10:1:22 seek_read=10:1:22 >>> slow_read=10:1:22 seek_read=10:1:22 >>> slow_read=10:1:22 seek_read=10:1:22 >>> slow_read=10:1:22 seek_read=10:1:22 Drive does not cache nonlinear access Drive tests OK with Paranoia. whipper-0.9.0/whipper/test/cdparanoia/PX-L890SA.cdparanoia-A.stderr000066400000000000000000000047651357173224400246700ustar00rootroot00000000000000cdparanoia III release 10.2 (September 11, 2008) Using cdda library version: 10.2 Using paranoia library version: 10.2 Checking /dev/cdrom for cdrom... Testing /dev/cdrom for SCSI/MMC interface SG_IO device: /dev/sr0 CDROM model sensed sensed: PLEXTOR DVDR PX-L890SA 1.05 Checking for SCSI emulation... Drive is ATAPI (using SG_IO host adaptor emulation) Checking for MMC style command set... Drive is MMC style DMA scatter/gather table entries: 1 table entry size: 524288 bytes maximum theoretical transfer: 222 sectors Setting default read size to 27 sectors (63504 bytes). Verifying CDDA command set... Expected command set reads OK. Attempting to set cdrom to full speed... drive returned OK. =================== Checking drive cache/timing behavior =================== Seek/read timing: [39:43.53]: 19ms seek, 0.70ms/sec read [18.9x] spinning up... [39:43.52]: 19ms seek, 1.28ms/sec read [10.4x] spinning up... [39:43.51]: 23ms seek, 0.52ms/sec read [25.7x] spinning up... [39:43.50]: 231ms seek, 0.46ms/sec read [29.0x] spinning up... [39:43.49]: 18ms seek, 0.41ms/sec read [32.7x] spinning up... [39:43.48]: 18ms seek, 1.28ms/sec read [10.4x] spinning up... [39:43.47]: 21ms seek, 0.37ms/sec read [36.3x] spinning up... [39:43.46]: 21ms seek, 0.37ms/sec read [36.1x] spinning up... [39:43.45]: 15ms seek, 0.37ms/sec read [36.0x] spinning up... [39:43.44]: 21ms seek, 0.37ms/sec read [36.0x] spinning up... [39:43.43]: 15ms seek, 0.37ms/sec read [36.0x] spinning up... [39:43.42]: 21ms seek, 0.37ms/sec read [36.0x] spinning up... [39:43.41]: 15ms seek, 0.37ms/sec read [36.0x] spinning up... [39:43.40]: 21ms seek, 0.37ms/sec read [36.0x] spinning up... [39:43.39]: 21ms seek, 0.37ms/sec read [36.0x] spinning up... [39:43.38]: 15ms seek, 0.37ms/sec read [36.0x] spinning up... [39:43.37]: 21ms seek, 0.37ms/sec read [36.0x] spinning up... [39:43.36]: 15ms seek, 0.37ms/sec read [36.0x] [30:00.00]: 21ms seek, 0.41ms/sec read [32.7x] [20:00.00]: 22ms seek, 0.44ms/sec read [30.0x] [10:00.00]: 30ms seek, 0.52ms/sec read [25.7x] [00:00.00]: 33ms seek, 0.70ms/sec read [18.9x] Analyzing cache behavior... Fast search for approximate cache size... 0 sectors Slow verify for approximate cache size... 0 sectors.......... Drive does not cache nonlinear access Drive tests OK with Paranoia. whipper-0.9.0/whipper/test/cdrdao.readtoc.progress000066400000000000000000000655201357173224400223150ustar00rootroot00000000000000Cdrdao version 1.2.2 - (C) Andreas Mueller SCSI interface library - (C) Joerg Schilling Paranoia DAE library - (C) Monty Check http://cdrdao.sourceforge.net/drives.html#dt for current driver tables. Using libscg version 'schily-0.8' /dev/cdrecorder: PLEXTOR DVDR PX-810SA Rev: 1.00 Using driver: Generic SCSI-3/MMC - Version 2.0 (options 0x0000) Reading toc data... Track Mode Flags Start Length ------------------------------------------------------------ 1 AUDIO 0 00:00:00( 0) 03:04:64( 13864) 2 AUDIO 0 03:04:64( 13864) 04:00:57( 18057) 3 AUDIO 0 07:05:46( 31921) 03:38:61( 16411) 4 AUDIO 0 10:44:32( 48332) 02:58:51( 13401) 5 AUDIO 0 13:43:08( 61733) 04:16:28( 19228) 6 AUDIO 0 17:59:36( 80961) 04:16:58( 19258) 7 AUDIO 0 22:16:19(100219) 03:34:22( 16072) 8 AUDIO 0 25:50:41(116291) 04:25:22( 19897) 9 AUDIO 0 30:15:63(136188) 04:44:16( 21316) 10 AUDIO 0 35:00:04(157504) 03:56:71( 17771) 11 AUDIO 0 38:57:00(175275) 06:24:30( 28830) Leadout AUDIO 0 45:21:30(204105) PQ sub-channel reading (audio track) is supported, data format is BCD. Raw P-W sub-channel reading (audio track) is supported. Analyzing track 01 (AUDIO): start 00:00:00, length 03:04:64... 00:01:00 00:02:00 00:03:00 00:04:00 00:05:00 00:06:00 00:07:00 00:08:00 00:09:00 00:10:00 00:11:00 00:12:00 00:13:00 00:14:00 00:15:00 00:16:00 00:17:00 00:18:00 00:19:00 00:20:00 00:21:00 00:22:00 00:23:00 00:24:00 00:25:00 00:26:00 00:27:00 00:28:00 00:29:00 00:30:00 00:31:00 00:32:00 00:33:00 00:34:00 00:35:00 00:36:00 00:37:00 00:38:00 00:39:00 00:40:00 00:41:00 00:42:00 00:43:00 00:44:00 00:45:00 00:46:00 00:47:00 00:48:00 00:49:00 00:50:00 00:51:00 00:52:00 00:53:00 00:54:00 00:55:00 00:56:00 00:57:00 00:58:00 00:59:00 01:00:00 01:01:00 01:02:00 01:03:00 01:04:00 01:05:00 01:06:00 01:07:00 01:08:00 01:09:00 01:10:00 01:11:00 01:12:00 01:13:00 01:14:00 01:15:00 01:16:00 01:17:00 01:18:00 01:19:00 01:20:00 01:21:00 01:22:00 01:23:00 01:24:00 01:25:00 01:26:00 01:27:00 01:28:00 01:29:00 01:30:00 01:31:00 01:32:00 01:33:00 01:34:00 01:35:00 01:36:00 01:37:00 01:38:00 01:39:00 01:40:00 01:41:00 01:42:00 01:43:00 01:44:00 01:45:00 01:46:00 01:47:00 01:48:00 01:49:00 01:50:00 01:51:00 01:52:00 01:53:00 01:54:00 01:55:00 01:56:00 01:57:00 01:58:00 01:59:00 02:00:00 02:01:00 02:02:00 02:03:00 02:04:00 02:05:00 02:06:00 02:07:00 02:08:00 02:09:00 02:10:00 02:11:00 02:12:00 02:13:00 02:14:00 02:15:00 02:16:00 02:17:00 02:18:00 02:19:00 02:20:00 02:21:00 02:22:00 02:23:00 02:24:00 02:25:00 02:26:00 02:27:00 02:28:00 02:29:00 02:30:00 02:31:00 02:32:00 02:33:00 02:34:00 02:35:00 02:36:00 02:37:00 02:38:00 02:39:00 02:40:00 02:41:00 02:42:00 02:43:00 02:44:00 02:45:00 02:46:00 02:47:00 02:48:00 02:49:00 02:50:00 02:51:00 02:52:00 02:53:00 02:54:00 02:55:00 02:56:00 02:57:00 02:58:00 02:59:00 03:00:00 03:01:00 03:02:00 03:03:00 03:04:00 Found ISRC code. Control nibbles of track match CD-TOC settings. Analyzing track 02 (AUDIO): start 03:04:64, length 04:00:57... 00:01:00 00:02:00 00:03:00 00:04:00 00:05:00 00:06:00 00:07:00 00:08:00 00:09:00 00:10:00 00:11:00 00:12:00 00:13:00 00:14:00 00:15:00 00:16:00 00:17:00 00:18:00 00:19:00 00:20:00 00:21:00 00:22:00 00:23:00 00:24:00 00:25:00 00:26:00 00:27:00 00:28:00 00:29:00 00:30:00 00:31:00 00:32:00 00:33:00 00:34:00 00:35:00 00:36:00 00:37:00 00:38:00 00:39:00 00:40:00 00:41:00 00:42:00 00:43:00 00:44:00 00:45:00 00:46:00 00:47:00 00:48:00 00:49:00 00:50:00 00:51:00 00:52:00 00:53:00 00:54:00 00:55:00 00:56:00 00:57:00 00:58:00 00:59:00 01:00:00 01:01:00 01:02:00 01:03:00 01:04:00 01:05:00 01:06:00 01:07:00 01:08:00 01:09:00 01:10:00 01:11:00 01:12:00 01:13:00 01:14:00 01:15:00 01:16:00 01:17:00 01:18:00 01:19:00 01:20:00 01:21:00 01:22:00 01:23:00 01:24:00 01:25:00 01:26:00 01:27:00 01:28:00 01:29:00 01:30:00 01:31:00 01:32:00 01:33:00 01:34:00 01:35:00 01:36:00 01:37:00 01:38:00 01:39:00 01:40:00 01:41:00 01:42:00 01:43:00 01:44:00 01:45:00 01:46:00 01:47:00 01:48:00 01:49:00 01:50:00 01:51:00 01:52:00 01:53:00 01:54:00 01:55:00 01:56:00 01:57:00 01:58:00 01:59:00 02:00:00 02:01:00 02:02:00 02:03:00 02:04:00 02:05:00 02:06:00 02:07:00 02:08:00 02:09:00 02:10:00 02:11:00 02:12:00 02:13:00 02:14:00 02:15:00 02:16:00 02:17:00 02:18:00 02:19:00 02:20:00 02:21:00 02:22:00 02:23:00 02:24:00 02:25:00 02:26:00 02:27:00 02:28:00 02:29:00 02:30:00 02:31:00 02:32:00 02:33:00 02:34:00 02:35:00 02:36:00 02:37:00 02:38:00 02:39:00 02:40:00 02:41:00 02:42:00 02:43:00 02:44:00 02:45:00 02:46:00 02:47:00 02:48:00 02:49:00 02:50:00 02:51:00 02:52:00 02:53:00 02:54:00 02:55:00 02:56:00 02:57:00 02:58:00 02:59:00 03:00:00 03:01:00 03:02:00 03:03:00 03:04:00 03:05:00 03:06:00 03:07:00 03:08:00 03:09:00 03:10:00 03:11:00 03:12:00 03:13:00 03:14:00 03:15:00 03:16:00 03:17:00 03:18:00 03:19:00 03:20:00 03:21:00 03:22:00 03:23:00 03:24:00 03:25:00 03:26:00 03:27:00 03:28:00 03:29:00 03:30:00 03:31:00 03:32:00 03:33:00 03:34:00 03:35:00 03:36:00 03:37:00 03:38:00 03:39:00 03:40:00 03:41:00 03:42:00 03:43:00 03:44:00 03:45:00 03:46:00 03:47:00 03:48:00 03:49:00 03:50:00 03:51:00 03:52:00 03:53:00 03:54:00 03:55:00 03:56:00 03:57:00 03:58:00 03:59:00 04:00:00 Found ISRC code. Control nibbles of track match CD-TOC settings. Analyzing track 03 (AUDIO): start 07:05:46, length 03:38:61... 00:01:00 00:02:00 00:03:00 00:04:00 00:05:00 00:06:00 00:07:00 00:08:00 00:09:00 00:10:00 00:11:00 00:12:00 00:13:00 00:14:00 00:15:00 00:16:00 00:17:00 00:18:00 00:19:00 00:20:00 00:21:00 00:22:00 00:23:00 00:24:00 00:25:00 00:26:00 00:27:00 00:28:00 00:29:00 00:30:00 00:31:00 00:32:00 00:33:00 00:34:00 00:35:00 00:36:00 00:37:00 00:38:00 00:39:00 00:40:00 00:41:00 00:42:00 00:43:00 00:44:00 00:45:00 00:46:00 00:47:00 00:48:00 00:49:00 00:50:00 00:51:00 00:52:00 00:53:00 00:54:00 00:55:00 00:56:00 00:57:00 00:58:00 00:59:00 01:00:00 01:01:00 01:02:00 01:03:00 01:04:00 01:05:00 01:06:00 01:07:00 01:08:00 01:09:00 01:10:00 01:11:00 01:12:00 01:13:00 01:14:00 01:15:00 01:16:00 01:17:00 01:18:00 01:19:00 01:20:00 01:21:00 01:22:00 01:23:00 01:24:00 01:25:00 01:26:00 01:27:00 01:28:00 01:29:00 01:30:00 01:31:00 01:32:00 01:33:00 01:34:00 01:35:00 01:36:00 01:37:00 01:38:00 01:39:00 01:40:00 01:41:00 01:42:00 01:43:00 01:44:00 01:45:00 01:46:00 01:47:00 01:48:00 01:49:00 01:50:00 01:51:00 01:52:00 01:53:00 01:54:00 01:55:00 01:56:00 01:57:00 01:58:00 01:59:00 02:00:00 02:01:00 02:02:00 02:03:00 02:04:00 02:05:00 02:06:00 02:07:00 02:08:00 02:09:00 02:10:00 02:11:00 02:12:00 02:13:00 02:14:00 02:15:00 02:16:00 02:17:00 02:18:00 02:19:00 02:20:00 02:21:00 02:22:00 02:23:00 02:24:00 02:25:00 02:26:00 02:27:00 02:28:00 02:29:00 02:30:00 02:31:00 02:32:00 02:33:00 02:34:00 02:35:00 02:36:00 02:37:00 02:38:00 02:39:00 02:40:00 02:41:00 02:42:00 02:43:00 02:44:00 02:45:00 02:46:00 02:47:00 02:48:00 02:49:00 02:50:00 02:51:00 02:52:00 02:53:00 02:54:00 02:55:00 02:56:00 02:57:00 02:58:00 02:59:00 03:00:00 03:01:00 03:02:00 03:03:00 03:04:00 03:05:00 03:06:00 03:07:00 03:08:00 03:09:00 03:10:00 03:11:00 03:12:00 03:13:00 03:14:00 03:15:00 03:16:00 03:17:00 03:18:00 03:19:00 03:20:00 03:21:00 03:22:00 03:23:00 03:24:00 03:25:00 03:26:00 03:27:00 03:28:00 03:29:00 03:30:00 03:31:00 03:32:00 03:33:00 03:34:00 03:35:00 03:36:00 03:37:00 03:38:00 Found 1 Q sub-channels with CRC errors. Found ISRC code. Control nibbles of track match CD-TOC settings. Analyzing track 04 (AUDIO): start 10:44:32, length 02:58:51... 00:01:00 00:02:00 00:03:00 00:04:00 00:05:00 00:06:00 00:07:00 00:08:00 00:09:00 00:10:00 00:11:00 00:12:00 00:13:00 00:14:00 00:15:00 00:16:00 00:17:00 00:18:00 00:19:00 00:20:00 00:21:00 00:22:00 00:23:00 00:24:00 00:25:00 00:26:00 00:27:00 00:28:00 00:29:00 00:30:00 00:31:00 00:32:00 00:33:00 00:34:00 00:35:00 00:36:00 00:37:00 00:38:00 00:39:00 00:40:00 00:41:00 00:42:00 00:43:00 00:44:00 00:45:00 00:46:00 00:47:00 00:48:00 00:49:00 00:50:00 00:51:00 00:52:00 00:53:00 00:54:00 00:55:00 00:56:00 00:57:00 00:58:00 00:59:00 01:00:00 01:01:00 01:02:00 01:03:00 01:04:00 01:05:00 01:06:00 01:07:00 01:08:00 01:09:00 01:10:00 01:11:00 01:12:00 01:13:00 01:14:00 01:15:00 01:16:00 01:17:00 01:18:00 01:19:00 01:20:00 01:21:00 01:22:00 01:23:00 01:24:00 01:25:00 01:26:00 01:27:00 01:28:00 01:29:00 01:30:00 01:31:00 01:32:00 01:33:00 01:34:00 01:35:00 01:36:00 01:37:00 01:38:00 01:39:00 01:40:00 01:41:00 01:42:00 01:43:00 01:44:00 01:45:00 01:46:00 01:47:00 01:48:00 01:49:00 01:50:00 01:51:00 01:52:00 01:53:00 01:54:00 01:55:00 01:56:00 01:57:00 01:58:00 01:59:00 02:00:00 02:01:00 02:02:00 02:03:00 02:04:00 02:05:00 02:06:00 02:07:00 02:08:00 02:09:00 02:10:00 02:11:00 02:12:00 02:13:00 02:14:00 02:15:00 02:16:00 02:17:00 02:18:00 02:19:00 02:20:00 02:21:00 02:22:00 02:23:00 02:24:00 02:25:00 02:26:00 02:27:00 02:28:00 02:29:00 02:30:00 02:31:00 02:32:00 02:33:00 02:34:00 02:35:00 02:36:00 02:37:00 02:38:00 02:39:00 02:40:00 02:41:00 02:42:00 02:43:00 02:44:00 02:45:00 02:46:00 02:47:00 02:48:00 02:49:00 02:50:00 02:51:00 02:52:00 02:53:00 02:54:00 02:55:00 02:56:00 02:57:00 02:58:00 Found ISRC code. Control nibbles of track match CD-TOC settings. Analyzing track 05 (AUDIO): start 13:43:08, length 04:16:28... 00:01:00 00:02:00 00:03:00 00:04:00 00:05:00 00:06:00 00:07:00 00:08:00 00:09:00 00:10:00 00:11:00 00:12:00 00:13:00 00:14:00 00:15:00 00:16:00 00:17:00 00:18:00 00:19:00 00:20:00 00:21:00 00:22:00 00:23:00 00:24:00 00:25:00 00:26:00 00:27:00 00:28:00 00:29:00 00:30:00 00:31:00 00:32:00 00:33:00 00:34:00 00:35:00 00:36:00 00:37:00 00:38:00 00:39:00 00:40:00 00:41:00 00:42:00 00:43:00 00:44:00 00:45:00 00:46:00 00:47:00 00:48:00 00:49:00 00:50:00 00:51:00 00:52:00 00:53:00 00:54:00 00:55:00 00:56:00 00:57:00 00:58:00 00:59:00 01:00:00 01:01:00 01:02:00 01:03:00 01:04:00 01:05:00 01:06:00 01:07:00 01:08:00 01:09:00 01:10:00 01:11:00 01:12:00 01:13:00 01:14:00 01:15:00 01:16:00 01:17:00 01:18:00 01:19:00 01:20:00 01:21:00 01:22:00 01:23:00 01:24:00 01:25:00 01:26:00 01:27:00 01:28:00 01:29:00 01:30:00 01:31:00 01:32:00 01:33:00 01:34:00 01:35:00 01:36:00 01:37:00 01:38:00 01:39:00 01:40:00 01:41:00 01:42:00 01:43:00 01:44:00 01:45:00 01:46:00 01:47:00 01:48:00 01:49:00 01:50:00 01:51:00 01:52:00 01:53:00 01:54:00 01:55:00 01:56:00 01:57:00 01:58:00 01:59:00 02:00:00 02:01:00 02:02:00 02:03:00 02:04:00 02:05:00 02:06:00 02:07:00 02:08:00 02:09:00 02:10:00 02:11:00 02:12:00 02:13:00 02:14:00 02:15:00 02:16:00 02:17:00 02:18:00 02:19:00 02:20:00 02:21:00 02:22:00 02:23:00 02:24:00 02:25:00 02:26:00 02:27:00 02:28:00 02:29:00 02:30:00 02:31:00 02:32:00 02:33:00 02:34:00 02:35:00 02:36:00 02:37:00 02:38:00 02:39:00 02:40:00 02:41:00 02:42:00 02:43:00 02:44:00 02:45:00 02:46:00 02:47:00 02:48:00 02:49:00 02:50:00 02:51:00 02:52:00 02:53:00 02:54:00 02:55:00 02:56:00 02:57:00 02:58:00 02:59:00 03:00:00 03:01:00 03:02:00 03:03:00 03:04:00 03:05:00 03:06:00 03:07:00 03:08:00 03:09:00 03:10:00 03:11:00 03:12:00 03:13:00 03:14:00 03:15:00 03:16:00 03:17:00 03:18:00 03:19:00 03:20:00 03:21:00 03:22:00 03:23:00 03:24:00 03:25:00 03:26:00 03:27:00 03:28:00 03:29:00 03:30:00 03:31:00 03:32:00 03:33:00 03:34:00 03:35:00 03:36:00 03:37:00 03:38:00 03:39:00 03:40:00 03:41:00 03:42:00 03:43:00 03:44:00 03:45:00 03:46:00 03:47:00 03:48:00 03:49:00 03:50:00 03:51:00 03:52:00 03:53:00 03:54:00 03:55:00 03:56:00 03:57:00 03:58:00 03:59:00 04:00:00 04:01:00 04:02:00 04:03:00 04:04:00 04:05:00 04:06:00 04:07:00 04:08:00 04:09:00 04:10:00 04:11:00 04:12:00 04:13:00 04:14:00 04:15:00 04:16:00 Found 1 Q sub-channels with CRC errors. Found ISRC code. Control nibbles of track match CD-TOC settings. Analyzing track 06 (AUDIO): start 17:59:36, length 04:16:58... 00:01:00 00:02:00 00:03:00 00:04:00 00:05:00 00:06:00 00:07:00 00:08:00 00:09:00 00:10:00 00:11:00 00:12:00 00:13:00 00:14:00 00:15:00 00:16:00 00:17:00 00:18:00 00:19:00 00:20:00 00:21:00 00:22:00 00:23:00 00:24:00 00:25:00 00:26:00 00:27:00 00:28:00 00:29:00 00:30:00 00:31:00 00:32:00 00:33:00 00:34:00 00:35:00 00:36:00 00:37:00 00:38:00 00:39:00 00:40:00 00:41:00 00:42:00 00:43:00 00:44:00 00:45:00 00:46:00 00:47:00 00:48:00 00:49:00 00:50:00 00:51:00 00:52:00 00:53:00 00:54:00 00:55:00 00:56:00 00:57:00 00:58:00 00:59:00 01:00:00 01:01:00 01:02:00 01:03:00 01:04:00 01:05:00 01:06:00 01:07:00 01:08:00 01:09:00 01:10:00 01:11:00 01:12:00 01:13:00 01:14:00 01:15:00 01:16:00 01:17:00 01:18:00 01:19:00 01:20:00 01:21:00 01:22:00 01:23:00 01:24:00 01:25:00 01:26:00 01:27:00 01:28:00 01:29:00 01:30:00 01:31:00 01:32:00 01:33:00 01:34:00 01:35:00 01:36:00 01:37:00 01:38:00 01:39:00 01:40:00 01:41:00 01:42:00 01:43:00 01:44:00 01:45:00 01:46:00 01:47:00 01:48:00 01:49:00 01:50:00 01:51:00 01:52:00 01:53:00 01:54:00 01:55:00 01:56:00 01:57:00 01:58:00 01:59:00 02:00:00 02:01:00 02:02:00 02:03:00 02:04:00 02:05:00 02:06:00 02:07:00 02:08:00 02:09:00 02:10:00 02:11:00 02:12:00 02:13:00 02:14:00 02:15:00 02:16:00 02:17:00 02:18:00 02:19:00 02:20:00 02:21:00 02:22:00 02:23:00 02:24:00 02:25:00 02:26:00 02:27:00 02:28:00 02:29:00 02:30:00 02:31:00 02:32:00 02:33:00 02:34:00 02:35:00 02:36:00 02:37:00 02:38:00 02:39:00 02:40:00 02:41:00 02:42:00 02:43:00 02:44:00 02:45:00 02:46:00 02:47:00 02:48:00 02:49:00 02:50:00 02:51:00 02:52:00 02:53:00 02:54:00 02:55:00 02:56:00 02:57:00 02:58:00 02:59:00 03:00:00 03:01:00 03:02:00 03:03:00 03:04:00 03:05:00 03:06:00 03:07:00 03:08:00 03:09:00 03:10:00 03:11:00 03:12:00 03:13:00 03:14:00 03:15:00 03:16:00 03:17:00 03:18:00 03:19:00 03:20:00 03:21:00 03:22:00 03:23:00 03:24:00 03:25:00 03:26:00 03:27:00 03:28:00 03:29:00 03:30:00 03:31:00 03:32:00 03:33:00 03:34:00 03:35:00 03:36:00 03:37:00 03:38:00 03:39:00 03:40:00 03:41:00 03:42:00 03:43:00 03:44:00 03:45:00 03:46:00 03:47:00 03:48:00 03:49:00 03:50:00 03:51:00 03:52:00 03:53:00 03:54:00 03:55:00 03:56:00 03:57:00 03:58:00 03:59:00 04:00:00 04:01:00 04:02:00 04:03:00 04:04:00 04:05:00 04:06:00 04:07:00 04:08:00 04:09:00 04:10:00 04:11:00 04:12:00 04:13:00 04:14:00 04:15:00 04:16:00 Found 2 Q sub-channels with CRC errors. Found ISRC code. Control nibbles of track match CD-TOC settings. Analyzing track 07 (AUDIO): start 22:16:19, length 03:34:22... 00:01:00 00:02:00 00:03:00 00:04:00 00:05:00 00:06:00 00:07:00 00:08:00 00:09:00 00:10:00 00:11:00 00:12:00 00:13:00 00:14:00 00:15:00 00:16:00 00:17:00 00:18:00 00:19:00 00:20:00 00:21:00 00:22:00 00:23:00 00:24:00 00:25:00 00:26:00 00:27:00 00:28:00 00:29:00 00:30:00 00:31:00 00:32:00 00:33:00 00:34:00 00:35:00 00:36:00 00:37:00 00:38:00 00:39:00 00:40:00 00:41:00 00:42:00 00:43:00 00:44:00 00:45:00 00:46:00 00:47:00 00:48:00 00:49:00 00:50:00 00:51:00 00:52:00 00:53:00 00:54:00 00:55:00 00:56:00 00:57:00 00:58:00 00:59:00 01:00:00 01:01:00 01:02:00 01:03:00 01:04:00 01:05:00 01:06:00 01:07:00 01:08:00 01:09:00 01:10:00 01:11:00 01:12:00 01:13:00 01:14:00 01:15:00 01:16:00 01:17:00 01:18:00 01:19:00 01:20:00 01:21:00 01:22:00 01:23:00 01:24:00 01:25:00 01:26:00 01:27:00 01:28:00 01:29:00 01:30:00 01:31:00 01:32:00 01:33:00 01:34:00 01:35:00 01:36:00 01:37:00 01:38:00 01:39:00 01:40:00 01:41:00 01:42:00 01:43:00 01:44:00 01:45:00 01:46:00 01:47:00 01:48:00 01:49:00 01:50:00 01:51:00 01:52:00 01:53:00 01:54:00 01:55:00 01:56:00 01:57:00 01:58:00 01:59:00 02:00:00 02:01:00 02:02:00 02:03:00 02:04:00 02:05:00 02:06:00 02:07:00 02:08:00 02:09:00 02:10:00 02:11:00 02:12:00 02:13:00 02:14:00 02:15:00 02:16:00 02:17:00 02:18:00 02:19:00 02:20:00 02:21:00 02:22:00 02:23:00 02:24:00 02:25:00 02:26:00 02:27:00 02:28:00 02:29:00 02:30:00 02:31:00 02:32:00 02:33:00 02:34:00 02:35:00 02:36:00 02:37:00 02:38:00 02:39:00 02:40:00 02:41:00 02:42:00 02:43:00 02:44:00 02:45:00 02:46:00 02:47:00 02:48:00 02:49:00 02:50:00 02:51:00 02:52:00 02:53:00 02:54:00 02:55:00 02:56:00 02:57:00 02:58:00 02:59:00 03:00:00 03:01:00 03:02:00 03:03:00 03:04:00 03:05:00 03:06:00 03:07:00 03:08:00 03:09:00 03:10:00 03:11:00 03:12:00 03:13:00 03:14:00 03:15:00 03:16:00 03:17:00 03:18:00 03:19:00 03:20:00 03:21:00 03:22:00 03:23:00 03:24:00 03:25:00 03:26:00 03:27:00 03:28:00 03:29:00 03:30:00 03:31:00 03:32:00 03:33:00 03:34:00 Found ISRC code. Control nibbles of track match CD-TOC settings. Analyzing track 08 (AUDIO): start 25:50:41, length 04:25:22... 00:01:00 00:02:00 00:03:00 00:04:00 00:05:00 00:06:00 00:07:00 00:08:00 00:09:00 00:10:00 00:11:00 00:12:00 00:13:00 00:14:00 00:15:00 00:16:00 00:17:00 00:18:00 00:19:00 00:20:00 00:21:00 00:22:00 00:23:00 00:24:00 00:25:00 00:26:00 00:27:00 00:28:00 00:29:00 00:30:00 00:31:00 00:32:00 00:33:00 00:34:00 00:35:00 00:36:00 00:37:00 00:38:00 00:39:00 00:40:00 00:41:00 00:42:00 00:43:00 00:44:00 00:45:00 00:46:00 00:47:00 00:48:00 00:49:00 00:50:00 00:51:00 00:52:00 00:53:00 00:54:00 00:55:00 00:56:00 00:57:00 00:58:00 00:59:00 01:00:00 01:01:00 01:02:00 01:03:00 01:04:00 01:05:00 01:06:00 01:07:00 01:08:00 01:09:00 01:10:00 01:11:00 01:12:00 01:13:00 01:14:00 01:15:00 01:16:00 01:17:00 01:18:00 01:19:00 01:20:00 01:21:00 01:22:00 01:23:00 01:24:00 01:25:00 01:26:00 01:27:00 01:28:00 01:29:00 01:30:00 01:31:00 01:32:00 01:33:00 01:34:00 01:35:00 01:36:00 01:37:00 01:38:00 01:39:00 01:40:00 01:41:00 01:42:00 01:43:00 01:44:00 01:45:00 01:46:00 01:47:00 01:48:00 01:49:00 01:50:00 01:51:00 01:52:00 01:53:00 01:54:00 01:55:00 01:56:00 01:57:00 01:58:00 01:59:00 02:00:00 02:01:00 02:02:00 02:03:00 02:04:00 02:05:00 02:06:00 02:07:00 02:08:00 02:09:00 02:10:00 02:11:00 02:12:00 02:13:00 02:14:00 02:15:00 02:16:00 02:17:00 02:18:00 02:19:00 02:20:00 02:21:00 02:22:00 02:23:00 02:24:00 02:25:00 02:26:00 02:27:00 02:28:00 02:29:00 02:30:00 02:31:00 02:32:00 02:33:00 02:34:00 02:35:00 02:36:00 02:37:00 02:38:00 02:39:00 02:40:00 02:41:00 02:42:00 02:43:00 02:44:00 02:45:00 02:46:00 02:47:00 02:48:00 02:49:00 02:50:00 02:51:00 02:52:00 02:53:00 02:54:00 02:55:00 02:56:00 02:57:00 02:58:00 02:59:00 03:00:00 03:01:00 03:02:00 03:03:00 03:04:00 03:05:00 03:06:00 03:07:00 03:08:00 03:09:00 03:10:00 03:11:00 03:12:00 03:13:00 03:14:00 03:15:00 03:16:00 03:17:00 03:18:00 03:19:00 03:20:00 03:21:00 03:22:00 03:23:00 03:24:00 03:25:00 03:26:00 03:27:00 03:28:00 03:29:00 03:30:00 03:31:00 03:32:00 03:33:00 03:34:00 03:35:00 03:36:00 03:37:00 03:38:00 03:39:00 03:40:00 03:41:00 03:42:00 03:43:00 03:44:00 03:45:00 03:46:00 03:47:00 03:48:00 03:49:00 03:50:00 03:51:00 03:52:00 03:53:00 03:54:00 03:55:00 03:56:00 03:57:00 03:58:00 03:59:00 04:00:00 04:01:00 04:02:00 04:03:00 04:04:00 04:05:00 04:06:00 04:07:00 04:08:00 04:09:00 04:10:00 04:11:00 04:12:00 04:13:00 04:14:00 04:15:00 04:16:00 04:17:00 04:18:00 04:19:00 04:20:00 04:21:00 04:22:00 04:23:00 04:24:00 04:25:00 Found 8 Q sub-channels with CRC errors. Found ISRC code. Control nibbles of track match CD-TOC settings. Analyzing track 09 (AUDIO): start 30:15:63, length 04:44:16... 00:01:00 00:02:00 00:03:00 00:04:00 00:05:00 00:06:00 00:07:00 00:08:00 00:09:00 00:10:00 00:11:00 00:12:00 00:13:00 00:14:00 00:15:00 00:16:00 00:17:00 00:18:00 00:19:00 00:20:00 00:21:00 00:22:00 00:23:00 00:24:00 00:25:00 00:26:00 00:27:00 00:28:00 00:29:00 00:30:00 00:31:00 00:32:00 00:33:00 00:34:00 00:35:00 00:36:00 00:37:00 00:38:00 00:39:00 00:40:00 00:41:00 00:42:00 00:43:00 00:44:00 00:45:00 00:46:00 00:47:00 00:48:00 00:49:00 00:50:00 00:51:00 00:52:00 00:53:00 00:54:00 00:55:00 00:56:00 00:57:00 00:58:00 00:59:00 01:00:00 01:01:00 01:02:00 01:03:00 01:04:00 01:05:00 01:06:00 01:07:00 01:08:00 01:09:00 01:10:00 01:11:00 01:12:00 01:13:00 01:14:00 01:15:00 01:16:00 01:17:00 01:18:00 01:19:00 01:20:00 01:21:00 01:22:00 01:23:00 01:24:00 01:25:00 01:26:00 01:27:00 01:28:00 01:29:00 01:30:00 01:31:00 01:32:00 01:33:00 01:34:00 01:35:00 01:36:00 01:37:00 01:38:00 01:39:00 01:40:00 01:41:00 01:42:00 01:43:00 01:44:00 01:45:00 01:46:00 01:47:00 01:48:00 01:49:00 01:50:00 01:51:00 01:52:00 01:53:00 01:54:00 01:55:00 01:56:00 01:57:00 01:58:00 01:59:00 02:00:00 02:01:00 02:02:00 02:03:00 02:04:00 02:05:00 02:06:00 02:07:00 02:08:00 02:09:00 02:10:00 02:11:00 02:12:00 02:13:00 02:14:00 02:15:00 02:16:00 02:17:00 02:18:00 02:19:00 02:20:00 02:21:00 02:22:00 02:23:00 02:24:00 02:25:00 02:26:00 02:27:00 02:28:00 02:29:00 02:30:00 02:31:00 02:32:00 02:33:00 02:34:00 02:35:00 02:36:00 02:37:00 02:38:00 02:39:00 02:40:00 02:41:00 02:42:00 02:43:00 02:44:00 02:45:00 02:46:00 02:47:00 02:48:00 02:49:00 02:50:00 02:51:00 02:52:00 02:53:00 02:54:00 02:55:00 02:56:00 02:57:00 02:58:00 02:59:00 03:00:00 03:01:00 03:02:00 03:03:00 03:04:00 03:05:00 03:06:00 03:07:00 03:08:00 03:09:00 03:10:00 03:11:00 03:12:00 03:13:00 03:14:00 03:15:00 03:16:00 03:17:00 03:18:00 03:19:00 03:20:00 03:21:00 03:22:00 03:23:00 03:24:00 03:25:00 03:26:00 03:27:00 03:28:00 03:29:00 03:30:00 03:31:00 03:32:00 03:33:00 03:34:00 03:35:00 03:36:00 03:37:00 03:38:00 03:39:00 03:40:00 03:41:00 03:42:00 03:43:00 03:44:00 03:45:00 03:46:00 03:47:00 03:48:00 03:49:00 03:50:00 03:51:00 03:52:00 03:53:00 03:54:00 03:55:00 03:56:00 03:57:00 03:58:00 03:59:00 04:00:00 04:01:00 04:02:00 04:03:00 04:04:00 04:05:00 04:06:00 04:07:00 04:08:00 04:09:00 04:10:00 04:11:00 04:12:00 04:13:00 04:14:00 04:15:00 04:16:00 04:17:00 04:18:00 04:19:00 04:20:00 04:21:00 04:22:00 04:23:00 04:24:00 04:25:00 04:26:00 04:27:00 04:28:00 04:29:00 04:30:00 04:31:00 04:32:00 04:33:00 04:34:00 04:35:00 04:36:00 04:37:00 04:38:00 04:39:00 04:40:00 04:41:00 04:42:00 04:43:00 04:44:00 Found ISRC code. Control nibbles of track match CD-TOC settings. Analyzing track 10 (AUDIO): start 35:00:04, length 03:56:71... 00:01:00 00:02:00 00:03:00 00:04:00 00:05:00 00:06:00 00:07:00 00:08:00 00:09:00 00:10:00 00:11:00 00:12:00 00:13:00 00:14:00 00:15:00 00:16:00 00:17:00 00:18:00 00:19:00 00:20:00 00:21:00 00:22:00 00:23:00 00:24:00 00:25:00 00:26:00 00:27:00 00:28:00 00:29:00 00:30:00 00:31:00 00:32:00 00:33:00 00:34:00 00:35:00 00:36:00 00:37:00 00:38:00 00:39:00 00:40:00 00:41:00 00:42:00 00:43:00 00:44:00 00:45:00 00:46:00 00:47:00 00:48:00 00:49:00 00:50:00 00:51:00 00:52:00 00:53:00 00:54:00 00:55:00 00:56:00 00:57:00 00:58:00 00:59:00 01:00:00 01:01:00 01:02:00 01:03:00 01:04:00 01:05:00 01:06:00 01:07:00 01:08:00 01:09:00 01:10:00 01:11:00 01:12:00 01:13:00 01:14:00 01:15:00 01:16:00 01:17:00 01:18:00 01:19:00 01:20:00 01:21:00 01:22:00 01:23:00 01:24:00 01:25:00 01:26:00 01:27:00 01:28:00 01:29:00 01:30:00 01:31:00 01:32:00 01:33:00 01:34:00 01:35:00 01:36:00 01:37:00 01:38:00 01:39:00 01:40:00 01:41:00 01:42:00 01:43:00 01:44:00 01:45:00 01:46:00 01:47:00 01:48:00 01:49:00 01:50:00 01:51:00 01:52:00 01:53:00 01:54:00 01:55:00 01:56:00 01:57:00 01:58:00 01:59:00 02:00:00 02:01:00 02:02:00 02:03:00 02:04:00 02:05:00 02:06:00 02:07:00 02:08:00 02:09:00 02:10:00 02:11:00 02:12:00 02:13:00 02:14:00 02:15:00 02:16:00 02:17:00 02:18:00 02:19:00 02:20:00 02:21:00 02:22:00 02:23:00 02:24:00 02:25:00 02:26:00 02:27:00 02:28:00 02:29:00 02:30:00 02:31:00 02:32:00 02:33:00 02:34:00 02:35:00 02:36:00 02:37:00 02:38:00 02:39:00 02:40:00 02:41:00 02:42:00 02:43:00 02:44:00 02:45:00 02:46:00 02:47:00 02:48:00 02:49:00 02:50:00 02:51:00 02:52:00 02:53:00 02:54:00 02:55:00 02:56:00 02:57:00 02:58:00 02:59:00 03:00:00 03:01:00 03:02:00 03:03:00 03:04:00 03:05:00 03:06:00 03:07:00 03:08:00 03:09:00 03:10:00 03:11:00 03:12:00 03:13:00 03:14:00 03:15:00 03:16:00 03:17:00 03:18:00 03:19:00 03:20:00 03:21:00 03:22:00 03:23:00 03:24:00 03:25:00 03:26:00 03:27:00 03:28:00 03:29:00 03:30:00 03:31:00 03:32:00 03:33:00 03:34:00 03:35:00 03:36:00 03:37:00 03:38:00 03:39:00 03:40:00 03:41:00 03:42:00 03:43:00 03:44:00 03:45:00 03:46:00 03:47:00 03:48:00 03:49:00 03:50:00 03:51:00 03:52:00 03:53:00 03:54:00 03:55:00 03:56:00 Found ISRC code. Control nibbles of track match CD-TOC settings. Analyzing track 11 (AUDIO): start 38:57:00, length 06:24:30... 00:01:00 00:02:00 00:03:00 00:04:00 00:05:00 00:06:00 00:07:00 00:08:00 00:09:00 00:10:00 00:11:00 00:12:00 00:13:00 00:14:00 00:15:00 00:16:00 00:17:00 00:18:00 00:19:00 00:20:00 00:21:00 00:22:00 00:23:00 00:24:00 00:25:00 00:26:00 00:27:00 00:28:00 00:29:00 00:30:00 00:31:00 00:32:00 00:33:00 00:34:00 00:35:00 00:36:00 00:37:00 00:38:00 00:39:00 00:40:00 00:41:00 00:42:00 00:43:00 00:44:00 00:45:00 00:46:00 00:47:00 00:48:00 00:49:00 00:50:00 00:51:00 00:52:00 00:53:00 00:54:00 00:55:00 00:56:00 00:57:00 00:58:00 00:59:00 01:00:00 01:01:00 01:02:00 01:03:00 01:04:00 01:05:00 01:06:00 01:07:00 01:08:00 01:09:00 01:10:00 01:11:00 01:12:00 01:13:00 01:14:00 01:15:00 01:16:00 01:17:00 01:18:00 01:19:00 01:20:00 01:21:00 01:22:00 01:23:00 01:24:00 01:25:00 01:26:00 01:27:00 01:28:00 01:29:00 01:30:00 01:31:00 01:32:00 01:33:00 01:34:00 01:35:00 01:36:00 01:37:00 01:38:00 01:39:00 01:40:00 01:41:00 01:42:00 01:43:00 01:44:00 01:45:00 01:46:00 01:47:00 01:48:00 01:49:00 01:50:00 01:51:00 01:52:00 01:53:00 01:54:00 01:55:00 01:56:00 01:57:00 01:58:00 01:59:00 02:00:00 02:01:00 02:02:00 02:03:00 02:04:00 02:05:00 02:06:00 02:07:00 02:08:00 02:09:00 02:10:00 02:11:00 02:12:00 02:13:00 02:14:00 02:15:00 02:16:00 02:17:00 02:18:00 02:19:00 02:20:00 02:21:00 02:22:00 02:23:00 02:24:00 02:25:00 02:26:00 02:27:00 02:28:00 02:29:00 02:30:00 02:31:00 02:32:00 02:33:00 02:34:00 02:35:00 02:36:00 02:37:00 02:38:00 02:39:00 02:40:00 02:41:00 02:42:00 02:43:00 02:44:00 02:45:00 02:46:00 02:47:00 02:48:00 02:49:00 02:50:00 02:51:00 02:52:00 02:53:00 02:54:00 02:55:00 02:56:00 02:57:00 02:58:00 02:59:00 03:00:00 03:01:00 03:02:00 03:03:00 03:04:00 03:05:00 03:06:00 03:07:00 03:08:00 03:09:00 03:10:00 03:11:00 03:12:00 03:13:00 03:14:00 03:15:00 03:16:00 03:17:00 03:18:00 03:19:00 03:20:00 03:21:00 03:22:00 03:23:00 03:24:00 03:25:00 03:26:00 03:27:00 03:28:00 03:29:00 03:30:00 03:31:00 03:32:00 03:33:00 03:34:00 03:35:00 03:36:00 03:37:00 03:38:00 03:39:00 03:40:00 03:41:00 03:42:00 03:43:00 03:44:00 03:45:00 03:46:00 03:47:00 03:48:00 03:49:00 03:50:00 03:51:00 03:52:00 03:53:00 03:54:00 03:55:00 03:56:00 03:57:00 03:58:00 03:59:00 04:00:00 04:01:00 04:02:00 04:03:00 04:04:00 04:05:00 04:06:00 04:07:00 04:08:00 04:09:00 04:10:00 04:11:00 04:12:00 04:13:00 04:14:00 04:15:00 04:16:00 04:17:00 04:18:00 04:19:00 04:20:00 04:21:00 04:22:00 04:23:00 04:24:00 04:25:00 04:26:00 04:27:00 04:28:00 04:29:00 04:30:00 04:31:00 04:32:00 04:33:00 04:34:00 04:35:00 04:36:00 04:37:00 04:38:00 04:39:00 04:40:00 04:41:00 04:42:00 04:43:00 04:44:00 04:45:00 04:46:00 04:47:00 04:48:00 04:49:00 04:50:00 04:51:00 04:52:00 04:53:00 04:54:00 04:55:00 04:56:00 04:57:00 04:58:00 04:59:00 05:00:00 05:01:00 05:02:00 05:03:00 05:04:00 05:05:00 05:06:00 05:07:00 05:08:00 05:09:00 05:10:00 05:11:00 05:12:00 05:13:00 05:14:00 05:15:00 05:16:00 05:17:00 05:18:00 05:19:00 05:20:00 05:21:00 05:22:00 05:23:00 05:24:00 05:25:00 05:26:00 05:27:00 05:28:00 05:29:00 05:30:00 05:31:00 05:32:00 05:33:00 05:34:00 05:35:00 05:36:00 05:37:00 05:38:00 05:39:00 05:40:00 05:41:00 05:42:00 05:43:00 05:44:00 05:45:00 05:46:00 05:47:00 05:48:00 05:49:00 05:50:00 05:51:00 05:52:00 05:53:00 05:54:00 05:55:00 05:56:00 05:57:00 05:58:00 05:59:00 06:00:00 06:01:00 06:02:00 06:03:00 06:04:00 06:05:00 06:06:00 06:07:00 06:08:00 06:09:00 06:10:00 06:11:00 06:12:00 06:13:00 06:14:00 06:15:00 06:16:00 06:17:00 06:18:00 06:19:00 06:20:00 06:21:00 06:22:00 06:23:00 06:24:00 Found 2 Q sub-channels with CRC errors. Found ISRC code. Control nibbles of track match CD-TOC settings. Found disk catalogue number. Reading of toc data finished successfully. whipper-0.9.0/whipper/test/common.py000066400000000000000000000047351357173224400175160ustar00rootroot00000000000000# -*- Mode: Python -*- # vi:si:et:sw=4:sts=4:ts=4 import re import os import sys import whipper # twisted's unittests have skip support, standard unittest don't from twisted.trial import unittest # lifted from flumotion def _diff(old, new, desc): import difflib lines = difflib.unified_diff(old, new) lines = list(lines) if not lines: return output = '' for line in lines: output += '%s: %s\n' % (desc, line[:-1]) raise AssertionError( ("\nError while comparing strings:\n" "%s") % (output.encode('utf-8'), )) def diffStrings(orig, new, desc='input'): assert isinstance(orig, type(new)), 'type %s and %s are different' % ( type(orig), type(new)) def _tolines(s): return [line + '\n' for line in s.split('\n')] return _diff(_tolines(orig), _tolines(new), desc=desc) class TestCase(unittest.TestCase): # unittest.TestCase.failUnlessRaises does not return the exception, # and we'd like to check for the actual exception under TaskException, # so override the way twisted.trial.unittest does, without failure # XXX: Pylint, method could be a function (no-self-use) def failUnlessRaises(self, exception, f, *args, **kwargs): try: result = f(*args, **kwargs) except exception as inst: return inst except exception as e: raise Exception('%s raised instead of %s:\n %s' % (sys.exc_info()[0], exception.__name__, str(e)) ) else: raise Exception('%s not raised (%r returned)' % (exception.__name__, result) ) assertRaises = failUnlessRaises @staticmethod def readCue(name): """ Read a .cue file, and replace the version comment with the current version so we can use it in comparisons. """ cuefile = os.path.join(os.path.dirname(__file__), name) with open(cuefile) as f: ret = f.read() ret = re.sub( 'REM COMMENT "whipper.*', 'REM COMMENT "whipper %s"' % whipper.__version__, ret, re.MULTILINE) return ret class UnicodeTestMixin: # A helper mixin to skip tests if we're not in a UTF-8 locale try: os.stat('whipper.test.B\xeate Noire.empty') except UnicodeEncodeError: skip = 'No UTF-8 locale' except OSError: pass whipper-0.9.0/whipper/test/cure.cue000066400000000000000000000021741357173224400173030ustar00rootroot00000000000000REM DISCID B90C650D REM COMMENT "whipper 0.5.1" CATALOG 0602517642256 FILE "data.wav" WAVE TRACK 01 AUDIO ISRC USUM70839873 INDEX 01 00:00:00 TRACK 02 AUDIO ISRC USUM70839874 INDEX 00 06:16:45 INDEX 01 06:17:49 TRACK 03 AUDIO ISRC USUM70839875 INDEX 00 10:13:02 INDEX 01 10:14:60 TRACK 04 AUDIO ISRC USUM70839876 INDEX 00 14:50:07 INDEX 01 14:50:17 TRACK 05 AUDIO ISRC USUM70839877 INDEX 00 17:18:42 INDEX 01 17:20:47 TRACK 06 AUDIO ISRC USUM70839878 INDEX 00 19:43:06 INDEX 01 19:43:10 TRACK 07 AUDIO ISRC USUM70839879 INDEX 00 24:25:07 INDEX 01 24:26:41 TRACK 08 AUDIO ISRC USUM70839880 INDEX 00 28:56:00 INDEX 01 28:56:09 TRACK 09 AUDIO ISRC USUM70839881 INDEX 00 32:38:11 INDEX 01 32:40:45 TRACK 10 AUDIO ISRC USUM70839882 INDEX 00 36:01:58 INDEX 01 36:02:04 TRACK 11 AUDIO ISRC USUM70839883 INDEX 00 40:08:30 INDEX 01 40:08:53 TRACK 12 AUDIO ISRC USUM70839884 INDEX 00 43:59:51 INDEX 01 44:00:27 TRACK 13 AUDIO ISRC USUM70839885 INDEX 00 48:35:63 INDEX 01 48:36:71 whipper-0.9.0/whipper/test/cure.toc000066400000000000000000000033651357173224400173170ustar00rootroot00000000000000CD_DA CATALOG "0602517642256" // Track 1 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "USUM70839873" FILE "data.wav" 0 06:16:45 // Track 2 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "USUM70839874" FILE "data.wav" 06:16:45 03:56:32 START 00:01:04 // Track 3 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "USUM70839875" FILE "data.wav" 10:13:02 04:37:05 START 00:01:58 // Track 4 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "USUM70839876" FILE "data.wav" 14:50:07 02:28:35 START 00:00:10 // Track 5 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "USUM70839877" FILE "data.wav" 17:18:42 02:24:39 START 00:02:05 // Track 6 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "USUM70839878" FILE "data.wav" 19:43:06 04:42:01 START 00:00:04 // Track 7 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "USUM70839879" FILE "data.wav" 24:25:07 04:30:68 START 00:01:34 // Track 8 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "USUM70839880" FILE "data.wav" 28:56:00 03:42:11 START 00:00:09 // Track 9 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "USUM70839881" FILE "data.wav" 32:38:11 03:23:47 START 00:02:34 // Track 10 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "USUM70839882" FILE "data.wav" 36:01:58 04:06:47 START 00:00:21 // Track 11 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "USUM70839883" FILE "data.wav" 40:08:30 03:51:21 START 00:00:23 // Track 12 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "USUM70839884" FILE "data.wav" 43:59:51 04:36:12 START 00:00:51 // Track 13 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "USUM70839885" FILE "data.wav" 48:35:63 04:17:71 START 00:01:08 whipper-0.9.0/whipper/test/dBAR-002-0000f21c-00027ef8-05021002.bin000066400000000000000000000000761357173224400224000ustar00rootroot00000000000000~ O(;.U~wҗwhipper-0.9.0/whipper/test/dBAR-011-0010e284-009228a3-9809ff0b.bin000066400000000000000000000070001357173224400225150ustar00rootroot00000000000000 ( b`F-@ށ)/ܼ6$tȕgE2CCR !:OZB#wȋͼ NPgK6M ( 24q7.Y0)$/;̝V]ܛ/g-I+ em%;F8fDAWʘXM ( jToȑP» ׾?Qt&_M7-xvȌ F#O,3Ȝ8y+f?Ee_ ( *D=qM}M6Eb .1WY;`Ȉ/+0ZțճF{&2zcOȡ ( @a+*AhPWlAp>!n@D*5A&j{qCA+vHUAE_A1>*A()P|A_!'?Rb# ( 8"ov:V5!w: :P^s:W&֓: j5:( ,9s8iD:Q8W{9b3zb?#-|c Dr^ N ( }] =™;>ɶ67\W,yOΓSPmI. 8B*7Pm Qh7T BX_y ( h Rtqs (WP >O# ؗV!: eT v, ) I% .$T` Wpƀ Ii}Ƴ1 8 OT ( 6# ãgG%E\5KFٽ`y&dpxC:0t`9{١ F~m7.j ( -WE!+~4>L `JFPV *z Jd+] w츟:nR{G4r:ML U ( +GX3/w{ < )1}6=/ GVp ]R3-F6Ԧ< ( /%/ŀY`~"'Zsr}),DzE>H=e[ &=#=wmzZ/[DAe!KG4d9c ( 64QCMP/]4h{ <A96Ow{ ]flfDƜ(N|2 7$LYbF Ô ( eN}ٔ`nsOۤ2Wb' _d&e qʶb**;"8J ( $m-bc"5k?iL""@ GjjM~}QSNT2%&ģ}\Lq*{糠ku0 ( {zH]24dσۛkEC!>p`P _#N[FƉim^7<ꖕ} ( 41xw.^62_ DM?I(P3[ jC.Պ Mp(hzcu0l'`~"Vd{r3"Hh8{= (  TFjfH0'{\u,» |^6UlwO|}bǭ.n8fmGZA ( &SwUV~.`,eqsP"{0 Mx ( ?;`Q͢sh6+PoB3n8E" g|+ ( yzS?ZPS_M`Dg6Lϖ;}B)4 ( wQD[ aC~OWCw rO5Wz ( ,ʉF)]-FLQ0jѣ ( GmᆠkRqտ *ۼ ǝ4]obo2 DL ( fmrYI,bT9,(DX}r$.*KT (  +4Iz0i5&~ 0=0plqsd *&( ( y~unK ( W ( )Q7whipper-0.9.0/whipper/test/dBAR-020-002e5023-029d8e49-040eaa14.bin000066400000000000000000000003011357173224400225570ustar00rootroot00000000000000#P.IԺD63mrknqB ?['cMGPoXE΀-M`^rklUn%mߘ;i੘SgBOG`pUV/mm jȽ*}TmtwPlkL_aCwhipper-0.9.0/whipper/test/gentlemen.fast.toc000066400000000000000000000021611357173224400212640ustar00rootroot00000000000000CD_DA CATALOG "0075596150125" // Track 1 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO FILE "data.wav" 0 03:05:62 // Track 2 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO FILE "data.wav" 03:05:62 03:53:53 // Track 3 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO FILE "data.wav" 06:59:40 03:36:70 // Track 4 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO FILE "data.wav" 10:36:35 04:14:42 // Track 5 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO FILE "data.wav" 14:51:02 05:48:05 // Track 6 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO FILE "data.wav" 20:39:07 04:21:23 // Track 7 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO FILE "data.wav" 25:00:30 03:30:50 // Track 8 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO FILE "data.wav" 28:31:05 05:46:00 // Track 9 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO FILE "data.wav" 34:17:05 04:10:22 // Track 10 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO FILE "data.wav" 38:27:27 04:51:65 // Track 11 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO FILE "data.wav" 43:19:17 05:40:03 whipper-0.9.0/whipper/test/jose.toc000066400000000000000000000046771357173224400173300ustar00rootroot00000000000000CD_DA CD_TEXT { LANGUAGE_MAP { 0: 9 } LANGUAGE 0 { TITLE "In Our Nature" PERFORMER "Jos\351 Gonz\341lez" GENRE { 0, 0, 0} SIZE_INFO { 1, 1, 10, 0, 12, 13, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 28, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0} } } // Track 1 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "SEVVX0700301" CD_TEXT { LANGUAGE 0 { TITLE "How Low" PERFORMER "Jos\351 Gonz\341lez" } } FILE "data.wav" 0 02:40:01 // Track 2 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "SEVVX0700302" CD_TEXT { LANGUAGE 0 { TITLE "Down The Line" PERFORMER "Jos\351 Gonz\341lez" } } FILE "data.wav" 02:40:01 03:10:62 // Track 3 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "SEVVX0700303" CD_TEXT { LANGUAGE 0 { TITLE "Killing For Love" PERFORMER "Jos\351 Gonz\341lez" } } FILE "data.wav" 05:50:63 03:02:67 // Track 4 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "SEVVX0700304" CD_TEXT { LANGUAGE 0 { TITLE "In Our Nature" PERFORMER "Jos\351 Gonz\341lez" } } FILE "data.wav" 08:53:55 02:42:51 // Track 5 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "SEVVX0700305" CD_TEXT { LANGUAGE 0 { TITLE "Teardrop" PERFORMER "Jos\351 Gonz\341lez" } } FILE "data.wav" 11:36:31 03:20:64 // Track 6 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "SEVVX0700306" CD_TEXT { LANGUAGE 0 { TITLE "Abram" PERFORMER "Jos\351 Gonz\341lez" } } FILE "data.wav" 14:57:20 01:58:56 START 00:12:24 // Track 7 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "SEVVX0700307" CD_TEXT { LANGUAGE 0 { TITLE "Time To Send Someone Away" PERFORMER "Jos\351 Gonz\341lez" } } FILE "data.wav" 16:56:01 02:49:68 START 00:02:05 // Track 8 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "SEVVX0700308" CD_TEXT { LANGUAGE 0 { TITLE "The Nest" PERFORMER "Jos\351 Gonz\341lez" } } FILE "data.wav" 19:45:69 02:23:66 // Track 9 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "SEVVX0700309" CD_TEXT { LANGUAGE 0 { TITLE "Fold" PERFORMER "Jos\351 Gonz\341lez" } } FILE "data.wav" 22:09:60 02:54:58 // Track 10 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "SEVVX0700310" CD_TEXT { LANGUAGE 0 { TITLE "Cycling Trivialities" PERFORMER "Jos\351 Gonz\341lez" } } FILE "data.wav" 25:04:43 08:09:16 whipper-0.9.0/whipper/test/kanye.cue000066400000000000000000000055411357173224400174550ustar00rootroot00000000000000REM GENRE "Hip Hop" REM DATE 2008 REM DISCID A90D2E0D REM COMMENT "ExactAudioCopy v0.99pb4" CATALOG 0602517931596 PERFORMER "Kanye West" TITLE "808s & Heartbreak" FILE "Kanye West - 808s & Heartbreak\Kanye West - Say You Will.wav" WAVE TRACK 01 AUDIO TITLE "Say You Will" PERFORMER "Kanye West" ISRC USUM70846386 INDEX 01 00:00:00 FILE "Kanye West - 808s & Heartbreak\Kanye West - Welcome To Heartbreak (Feat. Kid Cudi).wav" WAVE TRACK 02 AUDIO TITLE "Welcome To Heartbreak (Feat. Kid Cudi)" PERFORMER "Kanye West" ISRC USUM70846387 INDEX 01 00:00:00 TRACK 03 AUDIO TITLE "Heartless" PERFORMER "Kanye West" ISRC USUM70840511 INDEX 00 04:22:70 FILE "Kanye West - 808s & Heartbreak\Kanye West - Heartless.wav" WAVE INDEX 01 00:00:00 FILE "Kanye West - 808s & Heartbreak\Kanye West - Amazing (Feat. Young Jeezy).wav" WAVE TRACK 04 AUDIO TITLE "Amazing (Feat. Young Jeezy)" PERFORMER "Kanye West" ISRC USUM70846401 INDEX 01 00:00:00 FILE "Kanye West - 808s & Heartbreak\Kanye West - Love Lockdown.wav" WAVE TRACK 05 AUDIO TITLE "Love Lockdown" PERFORMER "Kanye West" ISRC USUM70837229 INDEX 01 00:00:00 TRACK 06 AUDIO TITLE "Paranoid (Feat. Mr. Hudson)" PERFORMER "Kanye West" ISRC USUM70846402 INDEX 00 04:30:23 FILE "Kanye West - 808s & Heartbreak\Kanye West - Paranoid (Feat. Mr. Hudson).wav" WAVE INDEX 01 00:00:00 FILE "Kanye West - 808s & Heartbreak\Kanye West - RoboCop.wav" WAVE TRACK 07 AUDIO TITLE "RoboCop" PERFORMER "Kanye West" ISRC USUM70846388 INDEX 01 00:00:00 TRACK 08 AUDIO TITLE "Street Lights" PERFORMER "Kanye West" ISRC USUM70846403 INDEX 00 04:34:27 FILE "Kanye West - 808s & Heartbreak\Kanye West - Street Lights.wav" WAVE INDEX 01 00:00:00 FILE "Kanye West - 808s & Heartbreak\Kanye West - Bad News.wav" WAVE TRACK 09 AUDIO TITLE "Bad News" PERFORMER "Kanye West" ISRC USUM70846389 INDEX 01 00:00:00 FILE "Kanye West - 808s & Heartbreak\Kanye West - See You In My Nightmares (Feat. Lil Wayne).wav" WAVE TRACK 10 AUDIO TITLE "See You In My Nightmares (Feat. Lil Wayne)" PERFORMER "Kanye West" ISRC USUM70846390 INDEX 01 00:00:00 TRACK 11 AUDIO TITLE "Coldest Winter" PERFORMER "Kanye West" ISRC USUM70846400 INDEX 00 04:18:09 FILE "Kanye West - 808s & Heartbreak\Kanye West - Coldest Winter.wav" WAVE INDEX 01 00:00:00 TRACK 12 AUDIO TITLE "Pinocchio Story (Freestyle Live From Singapore)" PERFORMER "Kanye West" ISRC USUM70846838 INDEX 00 02:44:25 FILE "Kanye West - 808s & Heartbreak\Kanye West - Pinocchio Story (Freestyle Live From Singapore).wav" WAVE INDEX 01 00:00:00 TRACK 13 MODEx/2xxx TITLE "Data Track" PERFORMER "Kanye West" INDEX 00 06:01:45 whipper-0.9.0/whipper/test/kings-separate.cue000066400000000000000000000037521357173224400212650ustar00rootroot00000000000000REM GENRE Alternative REM DATE 2008 REM DISCID 9809FF0B REM COMMENT "ExactAudioCopy v0.99pb4" PERFORMER "Kings of Leon" TITLE "Only By the Night" FILE "Kings of Leon - Only By the Night\Kings of Leon - Closer.wav" WAVE TRACK 01 AUDIO TITLE "Closer" PERFORMER "Kings of Leon" INDEX 01 00:00:00 FILE "Kings of Leon - Only By the Night\Kings of Leon - Crawl.wav" WAVE TRACK 02 AUDIO TITLE "Crawl" PERFORMER "Kings of Leon" INDEX 01 00:00:00 FILE "Kings of Leon - Only By the Night\Kings of Leon - Sex On Fire.wav" WAVE TRACK 03 AUDIO TITLE "Sex On Fire" PERFORMER "Kings of Leon" INDEX 01 00:00:00 FILE "Kings of Leon - Only By the Night\Kings of Leon - Use Somebody.wav" WAVE TRACK 04 AUDIO TITLE "Use Somebody" PERFORMER "Kings of Leon" INDEX 01 00:00:00 FILE "Kings of Leon - Only By the Night\Kings of Leon - Manhattan.wav" WAVE TRACK 05 AUDIO TITLE "Manhattan" PERFORMER "Kings of Leon" INDEX 01 00:00:00 FILE "Kings of Leon - Only By the Night\Kings of Leon - Revelry.wav" WAVE TRACK 06 AUDIO TITLE "Revelry" PERFORMER "Kings of Leon" INDEX 01 00:00:00 FILE "Kings of Leon - Only By the Night\Kings of Leon - 17.wav" WAVE TRACK 07 AUDIO TITLE "17" PERFORMER "Kings of Leon" INDEX 01 00:00:00 FILE "Kings of Leon - Only By the Night\Kings of Leon - Notion.wav" WAVE TRACK 08 AUDIO TITLE "Notion" PERFORMER "Kings of Leon" INDEX 01 00:00:00 FILE "Kings of Leon - Only By the Night\Kings of Leon - I Want You.wav" WAVE TRACK 09 AUDIO TITLE "I Want You" PERFORMER "Kings of Leon" INDEX 01 00:00:00 FILE "Kings of Leon - Only By the Night\Kings of Leon - Be Somebody.wav" WAVE TRACK 10 AUDIO TITLE "Be Somebody" PERFORMER "Kings of Leon" INDEX 01 00:00:00 FILE "Kings of Leon - Only By the Night\Kings of Leon - Cold Desert.wav" WAVE TRACK 11 AUDIO TITLE "Cold Desert" PERFORMER "Kings of Leon" INDEX 01 00:00:00 whipper-0.9.0/whipper/test/kings-single.cue000066400000000000000000000007031357173224400207330ustar00rootroot00000000000000FILE "dummy.wav" WAVE TRACK 01 AUDIO INDEX 01 00:00:00 TRACK 02 AUDIO INDEX 01 03:57:36 TRACK 03 AUDIO INDEX 01 08:03:67 TRACK 04 AUDIO INDEX 01 11:27:18 TRACK 05 AUDIO INDEX 01 15:18:00 TRACK 06 AUDIO INDEX 01 18:42:17 TRACK 07 AUDIO INDEX 01 22:03:72 TRACK 08 AUDIO INDEX 01 25:09:25 TRACK 09 AUDIO INDEX 01 28:10:13 TRACK 10 AUDIO INDEX 01 33:17:47 TRACK 11 AUDIO INDEX 01 37:04:58 whipper-0.9.0/whipper/test/ladyhawke.toc000066400000000000000000000033771357173224400203350ustar00rootroot00000000000000CD_ROM_XA CATALOG "0602517818866" // Track 1 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBUM70808708" FILE "data.wav" 0 03:26:53 // Track 2 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBUM70810780" FILE "data.wav" 03:26:53 03:35:46 START 00:00:34 // Track 3 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBUM70808705" FILE "data.wav" 07:02:24 04:15:03 START 00:00:17 // Track 4 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBUM70810804" FILE "data.wav" 11:17:27 03:26:60 START 00:00:64 // Track 5 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBUM70810795" FILE "data.wav" 14:44:12 03:17:34 START 00:02:04 // Track 6 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBUM70810805" FILE "data.wav" 18:01:46 04:03:13 START 00:01:06 // Track 7 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "AUUM70800167" FILE "data.wav" 22:04:59 03:40:53 START 00:00:50 // Track 8 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBUM70808467" FILE "data.wav" 25:45:37 03:47:38 START 00:00:08 // Track 9 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBUM70810807" FILE "data.wav" 29:33:00 03:44:32 START 00:01:43 // Track 10 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBUM70811315" FILE "data.wav" 33:17:32 02:36:03 START 00:00:40 // Track 11 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBUM70810809" FILE "data.wav" 35:53:35 03:34:50 START 00:00:50 // Track 12 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "GBUM70810814" FILE "data.wav" 39:28:10 06:31:21 START 00:00:72 // Track 13 TRACK MODE2_FORM_MIX NO COPY ZERO MODE2_FORM_MIX 00:02:00 DATAFILE "data_13" 00:43:54 // length in bytes: 7659744 START 00:02:00 whipper-0.9.0/whipper/test/release.08397059-86c1-463b-8ed0-cd596dbd174f.xml000066400000000000000000000050601357173224400245320ustar00rootroot00000000000000 Das Capital: The Songwriting Genius of Luke Haines and The Auteurs B00009XG2O Luke HainesHaines, Luke How Could I Be Wrong273800 Showgirl256466 Baader Meinhof183933 Lenny Valentino136133 Starstruck212333 Satan Wants Me189666 Unsolved Child Murder146800 Junk Shop Clothes166800 The Mitford Sisters302960 Bugger Bognor230573 Future Generation216266 whipper-0.9.0/whipper/test/release.93a6268c-ddf1-4898-bf93-fb862b1c5c5e.xml000066400000000000000000000047371357173224400247700ustar00rootroot00000000000000 Ladyhawke LadyhawkeLadyhawke Magic207000 Manipulating Woman215000 My Delirium255000 Better Than Sunday208000 Another Runaway196000 Love Don't Live Here242000 Back of the Van220000 Paris Is Burning229000 Professional Suicide223000 Dusk Till Dawn156000 Crazy World215000 Morning Dreams240000 whipper-0.9.0/whipper/test/release.c7d919f4-3ea0-4c4b-a230-b3605f069440.xml000066400000000000000000000047341357173224400245030ustar00rootroot00000000000000 Lamprey B00000581T Bettie ServeertBettie Serveert Keepsake378693 Ray Ray Rain262106 D. Feathers332626 Re-Feel-It238240 21 Days203826 Cybor*D241800 Tell Me, Sad318333 Crutches292373 Something So Wild171466 Totally Freaked Out250893 Silent Spring272533 whipper-0.9.0/whipper/test/silentalarm.result.pickle000066400000000000000000000075041357173224400226720ustar00rootroot00000000000000(lp0 (iwhipper.result.result TrackResult p1 (dp2 S'testcrc' p3 L133637600L sS'peak' p4 F0.651947021484375 sS'copycrc' p5 L133637600L sS'quality' p6 F1.0 sS'number' p7 I0 sS'filename' p8 V/home/thomas/Bloc Party - Silent Alarm/00. Bloc Party - Hidden Track One Audio.flac p9 sba(iwhipper.result.result TrackResult p10 (dp11 S'ARCRC' p12 L1726732487L sg3 L1476997036L sS'ARDBConfidence' p13 I66 sg7 I1 sS'ARDBMaxConfidence' p14 I66 sS'ARDBCRC' p15 I1726732487 sg4 F0.99993896484375 sg6 F1.0 sS'accurip' p16 I01 sg8 V/home/thomas/Bloc Party - Silent Alarm/01. Bloc Party - Like Eating Glass.flac p17 sS'pregap' p18 I15220 sg5 L1476997036L sba(iwhipper.result.result TrackResult p19 (dp20 g12 L3896378645L sg3 L2118180996L sg13 I65 sg7 I2 sg14 I65 sg15 L3896378645L sg4 F0.99908447265625 sg6 F1.0 sg16 I01 sg8 V/home/thomas/Bloc Party - Silent Alarm/02. Bloc Party - Helicopter.flac p21 sg18 I0 sg5 L2118180996L sba(iwhipper.result.result TrackResult p22 (dp23 g12 L1246554911L sg3 L2397618238L sg13 I66 sg7 I3 sg14 I66 sg15 I1246554911 sg4 F0.999969482421875 sg6 F1.0 sg16 I01 sg8 V/home/thomas/Bloc Party - Silent Alarm/03. Bloc Party - Positive Tension.flac p24 sg18 I0 sg5 L2397618238L sba(iwhipper.result.result TrackResult p25 (dp26 g12 L175751014L sg3 L1340624205L sg13 I65 sg7 I4 sg14 I65 sg15 I175751014 sg4 F0.9990234375 sg6 F1.0 sg16 I01 sg8 V/home/thomas/Bloc Party - Silent Alarm/04. Bloc Party - Banquet.flac p27 sg18 I0 sg5 L1340624205L sba(iwhipper.result.result TrackResult p28 (dp29 g12 L3375033750L sg3 L183201985L sg13 I66 sg7 I5 sg14 I66 sg15 L3375033750L sg4 F0.9990234375 sg6 F1.0 sg16 I01 sg8 V/home/thomas/Bloc Party - Silent Alarm/05. Bloc Party - Blue Light.flac p30 sg18 I72 sg5 L183201985L sba(iwhipper.result.result TrackResult p31 (dp32 g12 L3357757503L sg3 L221401921L sg13 I66 sg7 I6 sg14 I66 sg15 L3357757503L sg4 F0.9990234375 sg6 F1.0 sg16 I01 sg8 V/home/thomas/Bloc Party - Silent Alarm/06. Bloc Party - She's Hearing Voices.flac p33 sg18 I41 sg5 L221401921L sba(iwhipper.result.result TrackResult p34 (dp35 g12 L3964329421L sg3 L3133726276L sg13 I65 sg7 I7 sg14 I65 sg15 L3964329421L sg4 F0.999969482421875 sg6 F1.0 sg16 I01 sg8 V/home/thomas/Bloc Party - Silent Alarm/07. Bloc Party - This Modern Love.flac p36 sg18 I17 sg5 L3133726276L sba(iwhipper.result.result TrackResult p37 (dp38 g12 L1808393808L sg3 L2318646110L sg13 I66 sg7 I8 sg14 I66 sg15 I1808393808 sg4 F0.9990234375 sg6 F1.0 sg16 I01 sg8 V/home/thomas/Bloc Party - Silent Alarm/08. Bloc Party - The Pioneers.flac p39 sg18 I4 sg5 L2318646110L sba(iwhipper.result.result TrackResult p40 (dp41 g12 L4144642428L sg3 L3145161267L sg13 I66 sg7 I9 sg14 I66 sg15 L4144642428L sg4 F0.9990234375 sg6 F1.0 sg16 I01 sg8 V/home/thomas/Bloc Party - Silent Alarm/09. Bloc Party - Price of Gasoline.flac p42 sg18 I11 sg5 L3145161267L sba(iwhipper.result.result TrackResult p43 (dp44 g12 L4287362638L sg3 L3022257630L sg13 I65 sg7 I10 sg14 I65 sg15 L4287362638L sg4 F0.9990234375 sg6 F1.0 sg16 I01 sg8 V/home/thomas/Bloc Party - Silent Alarm/10. Bloc Party - So Here We Are.flac p45 sg18 I0 sg5 L3022257630L sba(iwhipper.result.result TrackResult p46 (dp47 g12 L4127263616L sg3 L2011827324L sg13 I65 sg7 I11 sg14 I65 sg15 L4127263616L sg4 F0.999481201171875 sg6 F1.0 sg16 I01 sg8 V/home/thomas/Bloc Party - Silent Alarm/11. Bloc Party - Luno.flac p48 sg18 I43 sg5 L2011827324L sba(iwhipper.result.result TrackResult p49 (dp50 g12 L2559991386L sg3 L933582879L sg13 I65 sg7 I12 sg14 I65 sg15 L2559991386L sg4 F0.999969482421875 sg6 F1.0 sg16 I01 sg8 V/home/thomas/Bloc Party - Silent Alarm/12. Bloc Party - Plans.flac p51 sg18 I116 sg5 L933582879L sba(iwhipper.result.result TrackResult p52 (dp53 g12 L2915053507L sg3 L1187281525L sg13 I66 sg7 I13 sg14 I66 sg15 L2915053507L sg4 F0.999969482421875 sg6 F1.0 sg16 I01 sg8 V/home/thomas/Bloc Party - Silent Alarm/13. Bloc Party - Compliments.flac p54 sg18 I22 sg5 L1187281525L sba.whipper-0.9.0/whipper/test/strokes-someday.eac.cue000066400000000000000000000005071357173224400222230ustar00rootroot00000000000000REM GENRE "Alternative Rock" REM DATE 2001 REM DISCID 0200BA01 REM COMMENT "ExactAudioCopy v0.99pb4" PERFORMER "The Strokes" TITLE "Someday" FILE "The Strokes - Someday\01 - The Strokes - Someday.wav" WAVE TRACK 01 AUDIO TITLE "Someday" PERFORMER "The Strokes" FLAGS DCP PREGAP 00:00:01 INDEX 01 00:00:00 whipper-0.9.0/whipper/test/strokes-someday.toc000066400000000000000000000002021357173224400214750ustar00rootroot00000000000000CD_DA // Track 1 TRACK AUDIO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO SILENCE 00:00:01 FILE "data.wav" 0 03:06:59 START 00:00:01 whipper-0.9.0/whipper/test/surferrosa.eac.corrected.cue000066400000000000000000000104241357173224400232350ustar00rootroot00000000000000REM GENRE Alternative REM DATE 1987 REM DISCID 350CAA15 REM COMMENT "ExactAudioCopy v0.99pb4" CATALOG 0000000000000 PERFORMER "Pixies" TITLE "Surfer Rosa & Come on Pilgrim" FILE "Pixies - Surfer Rosa & Come on Pilgrim\01 - Pixies - Bone Machine.wav" WAVE TRACK 01 AUDIO TITLE "Bone Machine" PERFORMER "Pixies" ISRC 000000000000 INDEX 00 00:00:00 INDEX 01 00:00:32 FILE "Pixies - Surfer Rosa & Come on Pilgrim\02 - Pixies - Break My Body.wav" WAVE TRACK 02 AUDIO TITLE "Break My Body" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\03 - Pixies - Something Against You.wav" WAVE TRACK 03 AUDIO TITLE "Something Against You" PERFORMER "Pixies" ISRC 000000000000 INDEX 00 00:00:00 INDEX 01 00:00:45 FILE "Pixies - Surfer Rosa & Come on Pilgrim\04 - Pixies - Broken Face.wav" WAVE TRACK 04 AUDIO TITLE "Broken Face" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\05 - Pixies - Gigantic.wav" WAVE TRACK 05 AUDIO TITLE "Gigantic" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\06 - Pixies - River Euphrates.wav" WAVE TRACK 06 AUDIO TITLE "River Euphrates" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\07 - Pixies - Where Is My Mind .wav" WAVE TRACK 07 AUDIO TITLE "Where Is My Mind?" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\08 - Pixies - Cactus.wav" WAVE TRACK 08 AUDIO TITLE "Cactus" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\09 - Pixies - Tony's Theme.wav" WAVE TRACK 09 AUDIO TITLE "Tony's Theme" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\10 - Pixies - Oh My Golly!.wav" WAVE TRACK 10 AUDIO TITLE "Oh My Golly!" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\11 - Pixies - Vamos.wav" WAVE TRACK 11 AUDIO TITLE "Vamos" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 INDEX 02 00:44:70 FILE "Pixies - Surfer Rosa & Come on Pilgrim\12 - Pixies - I'm Amazed.wav" WAVE TRACK 12 AUDIO TITLE "I'm Amazed" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\13 - Pixies - Brick is Red.wav" WAVE TRACK 13 AUDIO TITLE "Brick is Red" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\14 - Pixies - Caribou.wav" WAVE TRACK 14 AUDIO TITLE "Caribou" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\15 - Pixies - Vamos.wav" WAVE TRACK 15 AUDIO TITLE "Vamos" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\16 - Pixies - Isla de Encanta.wav" WAVE TRACK 16 AUDIO TITLE "Isla de Encanta" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\17 - Pixies - Ed is Dead.wav" WAVE TRACK 17 AUDIO TITLE "Ed is Dead" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\18 - Pixies - The Holyday Song.wav" WAVE TRACK 18 AUDIO TITLE "The Holyday Song" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\19 - Pixies - Nimrod's Son.wav" WAVE TRACK 19 AUDIO TITLE "Nimrod's Son" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\20 - Pixies - I've Been Tired.wav" WAVE TRACK 20 AUDIO TITLE "I've Been Tired" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\21 - Pixies - Levitate Me.wav" WAVE TRACK 21 AUDIO TITLE "Levitate Me" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 whipper-0.9.0/whipper/test/surferrosa.eac.currentgap.cue000066400000000000000000000104221357173224400234330ustar00rootroot00000000000000REM GENRE Alternative REM DATE 1987 REM DISCID 350CAA15 REM COMMENT "ExactAudioCopy v0.99pb4" CATALOG 0000000000000 PERFORMER "Pixies" TITLE "Surfer Rosa & Come on Pilgrim" FILE "Pixies - Surfer Rosa & Come on Pilgrim\01 - Pixies - Bone Machine.wav" WAVE TRACK 01 AUDIO TITLE "Bone Machine" PERFORMER "Pixies" ISRC 000000000000 PREGAP 00:00:32 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\02 - Pixies - Break My Body.wav" WAVE TRACK 02 AUDIO TITLE "Break My Body" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 TRACK 03 AUDIO TITLE "Something Against You" PERFORMER "Pixies" ISRC 000000000000 INDEX 00 02:05:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\03 - Pixies - Something Against You.wav" WAVE INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\04 - Pixies - Broken Face.wav" WAVE TRACK 04 AUDIO TITLE "Broken Face" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\05 - Pixies - Gigantic.wav" WAVE TRACK 05 AUDIO TITLE "Gigantic" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\06 - Pixies - River Euphrates.wav" WAVE TRACK 06 AUDIO TITLE "River Euphrates" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\07 - Pixies - Where Is My Mind .wav" WAVE TRACK 07 AUDIO TITLE "Where Is My Mind?" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\08 - Pixies - Cactus.wav" WAVE TRACK 08 AUDIO TITLE "Cactus" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\09 - Pixies - Tony's Theme.wav" WAVE TRACK 09 AUDIO TITLE "Tony's Theme" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\10 - Pixies - Oh My Golly!.wav" WAVE TRACK 10 AUDIO TITLE "Oh My Golly!" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\11 - Pixies - Vamos.wav" WAVE TRACK 11 AUDIO TITLE "Vamos" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 INDEX 02 00:44:70 FILE "Pixies - Surfer Rosa & Come on Pilgrim\12 - Pixies - I'm Amazed.wav" WAVE TRACK 12 AUDIO TITLE "I'm Amazed" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\13 - Pixies - Brick is Red.wav" WAVE TRACK 13 AUDIO TITLE "Brick is Red" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\14 - Pixies - Caribou.wav" WAVE TRACK 14 AUDIO TITLE "Caribou" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\15 - Pixies - Vamos.wav" WAVE TRACK 15 AUDIO TITLE "Vamos" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\16 - Pixies - Isla de Encanta.wav" WAVE TRACK 16 AUDIO TITLE "Isla de Encanta" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\17 - Pixies - Ed is Dead.wav" WAVE TRACK 17 AUDIO TITLE "Ed is Dead" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\18 - Pixies - The Holyday Song.wav" WAVE TRACK 18 AUDIO TITLE "The Holyday Song" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\19 - Pixies - Nimrod's Son.wav" WAVE TRACK 19 AUDIO TITLE "Nimrod's Son" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\20 - Pixies - I've Been Tired.wav" WAVE TRACK 20 AUDIO TITLE "I've Been Tired" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\21 - Pixies - Levitate Me.wav" WAVE TRACK 21 AUDIO TITLE "Levitate Me" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 whipper-0.9.0/whipper/test/surferrosa.eac.leftout.cue000066400000000000000000000104201357173224400227410ustar00rootroot00000000000000REM GENRE Alternative REM DATE 1987 REM DISCID 350CAA15 REM COMMENT "ExactAudioCopy v0.99pb4" CATALOG 0000000000000 PERFORMER "Pixies" TITLE "Surfer Rosa & Come on Pilgrim" FILE "Pixies - Surfer Rosa & Come on Pilgrim\01 - Pixies - Bone Machine.wav" WAVE TRACK 01 AUDIO TITLE "Bone Machine" PERFORMER "Pixies" ISRC 000000000000 PREGAP 00:00:32 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\02 - Pixies - Break My Body.wav" WAVE TRACK 02 AUDIO TITLE "Break My Body" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\03 - Pixies - Something Against You.wav" WAVE TRACK 03 AUDIO TITLE "Something Against You" PERFORMER "Pixies" ISRC 000000000000 PREGAP 00:00:45 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\04 - Pixies - Broken Face.wav" WAVE TRACK 04 AUDIO TITLE "Broken Face" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\05 - Pixies - Gigantic.wav" WAVE TRACK 05 AUDIO TITLE "Gigantic" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\06 - Pixies - River Euphrates.wav" WAVE TRACK 06 AUDIO TITLE "River Euphrates" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\07 - Pixies - Where Is My Mind .wav" WAVE TRACK 07 AUDIO TITLE "Where Is My Mind?" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\08 - Pixies - Cactus.wav" WAVE TRACK 08 AUDIO TITLE "Cactus" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\09 - Pixies - Tony's Theme.wav" WAVE TRACK 09 AUDIO TITLE "Tony's Theme" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\10 - Pixies - Oh My Golly!.wav" WAVE TRACK 10 AUDIO TITLE "Oh My Golly!" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\11 - Pixies - Vamos.wav" WAVE TRACK 11 AUDIO TITLE "Vamos" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 INDEX 02 00:44:70 FILE "Pixies - Surfer Rosa & Come on Pilgrim\12 - Pixies - I'm Amazed.wav" WAVE TRACK 12 AUDIO TITLE "I'm Amazed" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\13 - Pixies - Brick is Red.wav" WAVE TRACK 13 AUDIO TITLE "Brick is Red" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\14 - Pixies - Caribou.wav" WAVE TRACK 14 AUDIO TITLE "Caribou" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\15 - Pixies - Vamos.wav" WAVE TRACK 15 AUDIO TITLE "Vamos" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\16 - Pixies - Isla de Encanta.wav" WAVE TRACK 16 AUDIO TITLE "Isla de Encanta" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\17 - Pixies - Ed is Dead.wav" WAVE TRACK 17 AUDIO TITLE "Ed is Dead" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\18 - Pixies - The Holyday Song.wav" WAVE TRACK 18 AUDIO TITLE "The Holyday Song" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\19 - Pixies - Nimrod's Son.wav" WAVE TRACK 19 AUDIO TITLE "Nimrod's Son" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\20 - Pixies - I've Been Tired.wav" WAVE TRACK 20 AUDIO TITLE "I've Been Tired" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\21 - Pixies - Levitate Me.wav" WAVE TRACK 21 AUDIO TITLE "Levitate Me" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 whipper-0.9.0/whipper/test/surferrosa.eac.noncompliant.cue000066400000000000000000000104221357173224400237620ustar00rootroot00000000000000REM GENRE Alternative REM DATE 1987 REM DISCID 350CAA15 REM COMMENT "ExactAudioCopy v0.99pb4" CATALOG 0000000000000 PERFORMER "Pixies" TITLE "Surfer Rosa & Come on Pilgrim" FILE "Pixies - Surfer Rosa & Come on Pilgrim\01 - Pixies - Bone Machine.wav" WAVE TRACK 01 AUDIO TITLE "Bone Machine" PERFORMER "Pixies" ISRC 000000000000 PREGAP 00:00:32 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\02 - Pixies - Break My Body.wav" WAVE TRACK 02 AUDIO TITLE "Break My Body" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 TRACK 03 AUDIO TITLE "Something Against You" PERFORMER "Pixies" ISRC 000000000000 INDEX 00 02:05:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\03 - Pixies - Something Against You.wav" WAVE INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\04 - Pixies - Broken Face.wav" WAVE TRACK 04 AUDIO TITLE "Broken Face" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\05 - Pixies - Gigantic.wav" WAVE TRACK 05 AUDIO TITLE "Gigantic" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\06 - Pixies - River Euphrates.wav" WAVE TRACK 06 AUDIO TITLE "River Euphrates" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\07 - Pixies - Where Is My Mind .wav" WAVE TRACK 07 AUDIO TITLE "Where Is My Mind?" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\08 - Pixies - Cactus.wav" WAVE TRACK 08 AUDIO TITLE "Cactus" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\09 - Pixies - Tony's Theme.wav" WAVE TRACK 09 AUDIO TITLE "Tony's Theme" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\10 - Pixies - Oh My Golly!.wav" WAVE TRACK 10 AUDIO TITLE "Oh My Golly!" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\11 - Pixies - Vamos.wav" WAVE TRACK 11 AUDIO TITLE "Vamos" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 INDEX 02 00:44:70 FILE "Pixies - Surfer Rosa & Come on Pilgrim\12 - Pixies - I'm Amazed.wav" WAVE TRACK 12 AUDIO TITLE "I'm Amazed" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\13 - Pixies - Brick is Red.wav" WAVE TRACK 13 AUDIO TITLE "Brick is Red" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\14 - Pixies - Caribou.wav" WAVE TRACK 14 AUDIO TITLE "Caribou" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\15 - Pixies - Vamos.wav" WAVE TRACK 15 AUDIO TITLE "Vamos" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\16 - Pixies - Isla de Encanta.wav" WAVE TRACK 16 AUDIO TITLE "Isla de Encanta" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\17 - Pixies - Ed is Dead.wav" WAVE TRACK 17 AUDIO TITLE "Ed is Dead" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\18 - Pixies - The Holyday Song.wav" WAVE TRACK 18 AUDIO TITLE "The Holyday Song" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\19 - Pixies - Nimrod's Son.wav" WAVE TRACK 19 AUDIO TITLE "Nimrod's Son" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\20 - Pixies - I've Been Tired.wav" WAVE TRACK 20 AUDIO TITLE "I've Been Tired" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 FILE "Pixies - Surfer Rosa & Come on Pilgrim\21 - Pixies - Levitate Me.wav" WAVE TRACK 21 AUDIO TITLE "Levitate Me" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 00:00:00 whipper-0.9.0/whipper/test/surferrosa.eac.single.cue000066400000000000000000000051431357173224400225460ustar00rootroot00000000000000REM GENRE Alternative REM DATE 1987 REM DISCID 350CAA15 REM COMMENT "ExactAudioCopy v0.99pb4" CATALOG 0000000000000 PERFORMER "Pixies" TITLE "Surfer Rosa & Come on Pilgrim" FILE "Range.wav" WAVE TRACK 01 AUDIO TITLE "Bone Machine" PERFORMER "Pixies" ISRC 000000000000 INDEX 00 00:00:00 INDEX 01 00:00:32 TRACK 02 AUDIO TITLE "Break My Body" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 03:03:42 TRACK 03 AUDIO TITLE "Something Against You" PERFORMER "Pixies" ISRC 000000000000 INDEX 00 05:08:42 INDEX 01 05:09:12 TRACK 04 AUDIO TITLE "Broken Face" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 06:56:67 TRACK 05 AUDIO TITLE "Gigantic" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 08:27:00 TRACK 06 AUDIO TITLE "River Euphrates" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 12:21:70 TRACK 07 AUDIO TITLE "Where Is My Mind?" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 14:53:60 TRACK 08 AUDIO TITLE "Cactus" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 18:47:15 TRACK 09 AUDIO TITLE "Tony's Theme" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 21:03:70 TRACK 10 AUDIO TITLE "Oh My Golly!" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 22:56:15 TRACK 11 AUDIO TITLE "Vamos" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 24:43:32 INDEX 02 25:28:27 TRACK 12 AUDIO TITLE "I'm Amazed" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 29:49:20 TRACK 13 AUDIO TITLE "Brick is Red" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 31:31:27 TRACK 14 AUDIO TITLE "Caribou" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 33:32:20 TRACK 15 AUDIO TITLE "Vamos" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 36:46:45 TRACK 16 AUDIO TITLE "Isla de Encanta" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 39:40:22 TRACK 17 AUDIO TITLE "Ed is Dead" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 41:21:47 TRACK 18 AUDIO TITLE "The Holyday Song" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 43:51:47 TRACK 19 AUDIO TITLE "Nimrod's Son" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 46:06:10 TRACK 20 AUDIO TITLE "I've Been Tired" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 48:23:25 TRACK 21 AUDIO TITLE "Levitate Me" PERFORMER "Pixies" ISRC 000000000000 INDEX 01 51:24:07 whipper-0.9.0/whipper/test/surferrosa.toc000066400000000000000000000051171357173224400205510ustar00rootroot00000000000000CD_DA CATALOG "0000000000000" // Track 1 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "000000000000" SILENCE 00:00:32 FILE "data.wav" 0 03:03:10 START 00:00:32 // Track 2 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "000000000000" FILE "data.wav" 03:03:10 02:05:00 // Track 3 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "000000000000" FILE "data.wav" 05:08:10 01:48:25 START 00:00:45 // Track 4 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "000000000000" FILE "data.wav" 06:56:35 01:30:08 // Track 5 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "000000000000" FILE "data.wav" 08:26:43 03:54:70 // Track 6 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "000000000000" FILE "data.wav" 12:21:38 02:31:65 // Track 7 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "000000000000" FILE "data.wav" 14:53:28 03:53:30 // Track 8 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "000000000000" FILE "data.wav" 18:46:58 02:16:55 // Track 9 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "000000000000" FILE "data.wav" 21:03:38 01:52:20 // Track 10 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "000000000000" FILE "data.wav" 22:55:58 01:47:17 // Track 11 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "000000000000" FILE "data.wav" 24:43:00 05:05:63 INDEX 00:44:70 // Track 12 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "000000000000" FILE "data.wav" 29:48:63 01:42:07 // Track 13 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "000000000000" FILE "data.wav" 31:30:70 02:00:68 // Track 14 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "000000000000" FILE "data.wav" 33:31:63 03:14:25 // Track 15 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "000000000000" FILE "data.wav" 36:46:13 02:53:52 // Track 16 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "000000000000" FILE "data.wav" 39:39:65 01:41:25 // Track 17 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "000000000000" FILE "data.wav" 41:21:15 02:30:00 // Track 18 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "000000000000" FILE "data.wav" 43:51:15 02:14:38 // Track 19 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "000000000000" FILE "data.wav" 46:05:53 02:17:15 // Track 20 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "000000000000" FILE "data.wav" 48:22:68 03:00:57 // Track 21 TRACK AUDIO NO COPY NO PRE_EMPHASIS TWO_CHANNEL_AUDIO ISRC "000000000000" FILE "data.wav" 51:23:50 02:38:38 whipper-0.9.0/whipper/test/test_command_mblookup.py000066400000000000000000000022031357173224400225770ustar00rootroot00000000000000# vi:si:et:sw=4:sts=4:ts=4:set fileencoding=utf-8 """Tests for whipper.command.mblookup""" import os import pickle import unittest from whipper.command import mblookup class MBLookupTestCase(unittest.TestCase): """Test cases for whipper.command.mblookup.MBLookup""" @staticmethod def _mock_musicbrainz(discid, country=None, record=False): """Mock function for whipper.common.mbngs.musicbrainz function.""" filename = "whipper.discid.{}.pickle".format(discid) path = os.path.join(os.path.dirname(__file__), filename) with open(path, "rb") as p: return pickle.load(p) def testMissingReleaseType(self): """Test that lookup for release without a type set doesn't fail.""" # Using: Gustafsson, Österberg & Cowle - What's Up? 8 (disc 4) # https://musicbrainz.org/release/d8e6153a-2c47-4804-9d73-0aac1081c3b1 mblookup.musicbrainz = self._mock_musicbrainz discid = "xu338_M8WukSRi0J.KTlDoflB8Y-" # https://musicbrainz.org/cdtoc/xu338_M8WukSRi0J.KTlDoflB8Y- lookup = mblookup.MBLookup([discid], 'whipper mblookup', None) lookup.do() whipper-0.9.0/whipper/test/test_common_accurip.py000066400000000000000000000243461357173224400222630ustar00rootroot00000000000000# -*- Mode: Python; test-case-name: whipper.test.test_common_accurip -*- # vi:si:et:sw=4:sts=4:ts=4 import sys from io import StringIO from os import chmod, makedirs from os.path import dirname, exists, join from shutil import copy, rmtree from tempfile import mkdtemp from unittest import TestCase from whipper.common import accurip from whipper.common.accurip import ( calculate_checksums, get_db_entry, print_report, verify_result, _split_responses, EntryNotFound ) from whipper.result.result import RipResult, TrackResult class TestAccurateRipResponse(TestCase): @classmethod def setUpClass(cls): cls.path = 'c/1/2/dBAR-002-0000f21c-00027ef8-05021002.bin' with open(join(dirname(__file__), cls.path[6:]), 'rb') as f: cls.entry = _split_responses(f.read()) cls.other_path = '4/8/2/dBAR-011-0010e284-009228a3-9809ff0b.bin' def setUp(self): self.cache_dir = mkdtemp(suffix='whipper_accurip_cache_test') accurip._CACHE_DIR = self.cache_dir def cleanup(cachedir): chmod(cachedir, 0o755) rmtree(cachedir) self.addCleanup(cleanup, self.cache_dir) def test_uses_cache_dir(self): # copy normal entry into other entry's place makedirs(dirname(join(self.cache_dir, self.other_path))) copy( join(dirname(__file__), self.path[6:]), join(self.cache_dir, self.other_path) ) # ask cache for other entry and assert cached entry equals normal entry self.assertEqual(self.entry, get_db_entry(self.other_path)) def test_raises_entrynotfound_for_no_entry(self): with self.assertRaises(EntryNotFound): get_db_entry('definitely_a_404') def test_can_return_entry_without_saving(self): chmod(self.cache_dir, 0) self.assertEqual(get_db_entry(self.path), self.entry) chmod(self.cache_dir, 0o755) self.assertFalse(exists(join(self.cache_dir, self.path))) def test_retrieves_and_saves_accuraterip_entry(self): # for path, entry in zip(self.paths[0], self.entries): self.assertFalse(exists(join(self.cache_dir, self.path))) self.assertEqual(get_db_entry(self.path), self.entry) self.assertTrue(exists(join(self.cache_dir, self.path))) def test_AccurateRipResponse_parses_correctly(self): responses = get_db_entry(self.path) self.assertEqual(len(responses), 2) self.assertEqual(responses[0].num_tracks, 2) self.assertEqual(responses[0].discId1, '0000f21c') self.assertEqual(responses[0].discId2, '00027ef8') self.assertEqual(responses[0].cddbDiscId, '05021002') self.assertEqual(responses[0].confidences[0], 12) self.assertEqual(responses[0].confidences[1], 20) self.assertEqual(responses[0].checksums[0], '284fc705') self.assertEqual(responses[0].checksums[1], '9cc1f32e') self.assertEqual(responses[1].num_tracks, 2) self.assertEqual(responses[1].discId1, '0000f21c') self.assertEqual(responses[1].discId2, '00027ef8') self.assertEqual(responses[1].cddbDiscId, '05021002') self.assertEqual(responses[1].confidences[0], 6) self.assertEqual(responses[1].confidences[1], 6) self.assertEqual(responses[1].checksums[0], 'dc77f9ab') self.assertEqual(responses[1].checksums[1], 'dd97d2c3') # XXX: test arc.py class TestCalculateChecksums(TestCase): def test_returns_none_for_bad_files(self): self.assertEqual( calculate_checksums(['/does/not/exist']), {'v1': [None], 'v2': [None]} ) # TODO: test success when file exists class TestVerifyResult(TestCase): @classmethod def setUpClass(cls): path = 'c/1/2/dBAR-002-0000f21c-00027ef8-05021002.bin' with open(join(dirname(__file__), path[6:]), 'rb') as f: cls.responses = _split_responses(f.read()) cls.checksums = { 'v1': ['284fc705', '9cc1f32e'], 'v2': ['dc77f9ab', 'dd97d2c3'], } def setUp(self): self.result = RipResult() for n in range(1, 2+1): track = TrackResult() track.number = n self.result.tracks.append(track) def test_empty_result_returns_false(self): self.assertEqual( verify_result(RipResult(), self.responses, self.checksums), False ) def test_empty_responses_returns_false(self): self.assertEqual( verify_result(self.result, [], self.checksums), False ) # XXX: would this happen? def test_empty_checksums_returns_false(self): self.assertEqual( verify_result(self.result, self.responses, {}), False ) def test_wrong_checksums_returns_false(self): self.assertEqual( verify_result(self.result, self.responses, { 'v1': ['deadbeef', '89abcdef'], 'v2': ['76543210', '01234567'] }), False ) def test_incomplete_checksums(self): self.assertEqual( verify_result(self.result, self.responses, { 'v1': ['284fc705', '9cc1f32e'], 'v2': [None, 'dd97d2c3'], }), True ) self.assertEqual( verify_result(self.result, self.responses, { 'v1': ['284fc705', None], 'v2': ['dc77f9ab', 'dd97d2c3'], }), True ) self.assertEqual( verify_result(self.result, self.responses, { 'v1': ['284fc705', None], 'v2': [None, 'dd97d2c3'], }), True ) def test_matches_only_v1_or_v2_responses(self): self.assertEqual( verify_result( self.result, [self.responses[0]], self.checksums ), True ) self.assertEqual( verify_result( self.result, [self.responses[1]], self.checksums ), True ) def test_passes_with_htoa(self): htoa = TrackResult() htoa.number = 0 self.result.tracks.append(htoa) self.assertEqual( verify_result(self.result, self.responses, self.checksums), True ) def test_stores_accuraterip_results_on_result(self): self.assertEqual( verify_result(self.result, self.responses, self.checksums), True ) self.assertEqual(self.result.tracks[0].AR, { 'v1': { 'CRC': '284fc705', 'DBCRC': '284fc705', 'DBConfidence': 12, }, 'v2': { 'CRC': 'dc77f9ab', 'DBCRC': 'dc77f9ab', 'DBConfidence': 6 }, 'DBMaxConfidence': 12, 'DBMaxConfidenceCRC': '284fc705', }) self.assertEqual(self.result.tracks[1].AR, { 'v1': { 'CRC': '9cc1f32e', 'DBCRC': '9cc1f32e', 'DBConfidence': 20, }, 'v2': { 'CRC': 'dd97d2c3', 'DBCRC': 'dd97d2c3', 'DBConfidence': 6, }, 'DBMaxConfidence': 20, 'DBMaxConfidenceCRC': '9cc1f32e', }) class TestAccurateRipReport(TestCase): def setUp(self): sys.stdout = StringIO() self.result = RipResult() track = TrackResult() track.number = 1 track.AR = { 'v1': { 'CRC': '284fc705', 'DBCRC': '284fc705', 'DBConfidence': 12, }, 'v2': { 'CRC': 'dc77f9ab', 'DBCRC': 'dc77f9ab', 'DBConfidence': 4, }, 'DBMaxConfidence': 12, 'DBMaxConfidenceCRC': '284fc705', } self.result.tracks.append(track) def tearDown(self): sys.stdout = sys.__stdout__ def test_report_no_result(self): track = TrackResult() track.number = 1 self.result.tracks[0] = track print_report(self.result) self.assertEqual( sys.stdout.getvalue(), 'track 1: unknown (error)\n' ) def test_track_not_found(self): self.result.tracks[0].AR['DBMaxConfidence'] = None print_report(self.result) self.assertEqual( sys.stdout.getvalue(), 'track 1: rip NOT accurate (not found) ' ' v1 [284fc705], v2 [dc77f9ab], DB [notfound]\n' ) def test_htoa_not_tracked(self): self.result.tracks[0].number = 0 self.result.tracks[0].AR['v1']['CRC'] = None self.result.tracks[0].AR['v2']['CRC'] = None print_report(self.result) self.assertEqual( sys.stdout.getvalue(), 'track 0: unknown (not tracked)\n' ) def test_report_v1_only(self): self.result.tracks[0].AR['v2']['DBCRC'] = None self.result.tracks[0].AR['v2']['DBConfidence'] = None print_report(self.result) self.assertEqual( sys.stdout.getvalue(), 'track 1: rip accurate (max confidence 12)' ' v1 [284fc705], v2 [dc77f9ab], DB [284fc705]\n' ) def test_report_v2_only(self): self.result.tracks[0].AR['v1']['DBCRC'] = None self.result.tracks[0].AR['v1']['DBConfidence'] = None print_report(self.result) self.assertEqual( sys.stdout.getvalue(), 'track 1: rip accurate (confidence 4 of 12)' ' v1 [284fc705], v2 [dc77f9ab], DB [dc77f9ab]\n' ) def test_report_v1_and_v2_max_confidence(self): print_report(self.result) self.assertEqual( sys.stdout.getvalue(), 'track 1: rip accurate (max confidence 12)' ' v1 [284fc705], v2 [dc77f9ab], DB [284fc705, dc77f9ab]\n' ) def test_report_v1_and_v2(self): self.result.tracks[0].AR['DBMaxConfidence'] = 66 print_report(self.result) self.assertEqual( sys.stdout.getvalue(), 'track 1: rip accurate (confidence 12 of 66)' ' v1 [284fc705], v2 [dc77f9ab], DB [284fc705, dc77f9ab]\n' ) whipper-0.9.0/whipper/test/test_common_cache.py000066400000000000000000000011671357173224400216740ustar00rootroot00000000000000# -*- Mode: Python; test-case-name: whipper.test.test_common_cache -*- # vi:si:et:sw=4:sts=4:ts=4 import os from whipper.common import cache from whipper.test import common as tcommon class ResultCacheTestCase(tcommon.TestCase): def setUp(self): self.cache = cache.ResultCache( os.path.join(os.path.dirname(__file__), 'cache', 'result')) def testGetResult(self): result = self.cache.getRipResult('fe105a11') self.assertEqual(result.object.title, "The Writing's on the Wall") def testGetIds(self): ids = self.cache.getIds() self.assertEqual(ids, ['fe105a11']) whipper-0.9.0/whipper/test/test_common_common.py000066400000000000000000000041431357173224400221160ustar00rootroot00000000000000# -*- Mode: Python; test-case-name: whipper.test.test_common_common -*- # vi:si:et:sw=4:sts=4:ts=4 import os import tempfile from whipper.common import common from whipper.test import common as tcommon class ShrinkTestCase(tcommon.TestCase): def testSufjan(self): path = ('whipper/Sufjan Stevens - Illinois/02. Sufjan Stevens - ' 'The Black Hawk War, or, How to Demolish an Entire ' 'Civilization and Still Feel Good About Yourself in the ' 'Morning, or, We Apologize for the Inconvenience but ' 'You\'re Going to Have to Leave Now, or, "I Have Fought ' 'the Big Knives and Will Continue to Fight Them Until They ' 'Are Off Our Lands!".flac') shorter = common.shrinkPath(path) self.assertTrue(os.path.splitext(path)[0].startswith( os.path.splitext(shorter)[0])) self.failIfEquals(path, shorter) class FramesTestCase(tcommon.TestCase): def testFrames(self): self.assertEqual(common.framesToHMSF(123456), '00:27:26.06') class FormatTimeTestCase(tcommon.TestCase): def testFormatTime(self): self.assertEqual(common.formatTime(7202), '02:00:02.000') class GetRelativePathTestCase(tcommon.TestCase): def testRelativeOutputDirectory(self): directory = '.Placebo - Black Market Music (2000)' cue = './' + directory + '/Placebo - Black Market Music (2000)' track = './' + directory + '/01. Placebo - Taste in Men.flac' self.assertEqual(common.getRelativePath(track, cue), '01. Placebo - Taste in Men.flac') class GetRealPathTestCase(tcommon.TestCase): def testRealWithBackslash(self): fd, path = tempfile.mkstemp(suffix='back\\slash.flac') refPath = os.path.join(os.path.dirname(path), 'fake.cue') self.assertEqual(common.getRealPath(refPath, path), path) # same path, but with wav extension, will point to flac file wavPath = path[:-4] + 'wav' self.assertEqual(common.getRealPath(refPath, wavPath), path) os.close(fd) os.unlink(path) whipper-0.9.0/whipper/test/test_common_config.py000066400000000000000000000066441357173224400221030ustar00rootroot00000000000000# -*- Mode: Python; test-case-name: whipper.test.test_common_config -*- # vi:si:et:sw=4:sts=4:ts=4 import os import tempfile from whipper.common import config from whipper.test import common as tcommon class ConfigTestCase(tcommon.TestCase): def setUp(self): fd, self._path = tempfile.mkstemp(suffix='.whipper.test.config') os.close(fd) self._config = config.Config(self._path) def tearDown(self): os.unlink(self._path) def testAddReadOffset(self): self.assertRaises(KeyError, self._config.getReadOffset, 'PLEXTOR ', 'DVDR PX-L890SA', '1.05') self._config.setReadOffset('PLEXTOR ', 'DVDR PX-L890SA', '1.05', 6) # getting it from memory should work offset = self._config.getReadOffset( 'PLEXTOR ', 'DVDR PX-L890SA', '1.05') self.assertEqual(offset, 6) # and so should getting it after reading it again self._config.open() offset = self._config.getReadOffset( 'PLEXTOR ', 'DVDR PX-L890SA', '1.05') self.assertEqual(offset, 6) def testAddReadOffsetSpaced(self): self.assertRaises(KeyError, self._config.getReadOffset, 'Slimtype', 'eSAU208 2 ', 'ML03') self._config.setReadOffset('Slimtype', 'eSAU208 2 ', 'ML03', 6) # getting it from memory should work offset = self._config.getReadOffset( 'Slimtype', 'eSAU208 2 ', 'ML03') self.assertEqual(offset, 6) # and so should getting it after reading it again self._config.open() offset = self._config.getReadOffset( 'Slimtype', 'eSAU208 2 ', 'ML03') self.assertEqual(offset, 6) def testDefeatsCache(self): self.assertRaises(KeyError, self._config.getDefeatsCache, 'PLEXTOR ', 'DVDR PX-L890SA', '1.05') self._config.setDefeatsCache( 'PLEXTOR ', 'DVDR PX-L890SA', '1.05', False) defeats = self._config.getDefeatsCache( 'PLEXTOR ', 'DVDR PX-L890SA', '1.05') self.assertEqual(defeats, False) self._config.setDefeatsCache( 'PLEXTOR ', 'DVDR PX-L890SA', '1.05', True) defeats = self._config.getDefeatsCache( 'PLEXTOR ', 'DVDR PX-L890SA', '1.05') self.assertEqual(defeats, True) def test_get_musicbrainz_server(self): self.assertEqual(self._config.get_musicbrainz_server(), 'musicbrainz.org', msg='Default value is correct') self._config._parser.add_section('musicbrainz') self._config._parser.set('musicbrainz', 'server', '192.168.2.141:5000') self._config.write() self.assertEqual(self._config.get_musicbrainz_server(), '192.168.2.141:5000', msg='Correctly returns user-set value') self._config._parser.set('musicbrainz', 'server', '192.168.2.141:5000/hello/world') self._config.write() self.assertRaises(KeyError, self._config.get_musicbrainz_server) self._config._parser.set('musicbrainz', 'server', 'http://192.168.2.141:5000') self._config.write() self.assertRaises(KeyError, self._config.get_musicbrainz_server) self._config._parser.remove_section('musicbrainz') whipper-0.9.0/whipper/test/test_common_directory.py000066400000000000000000000010611357173224400226260ustar00rootroot00000000000000# -*- Mode: Python; test-case-name: whipper.test.test_common_directory -*- # vi:si:et:sw=4:sts=4:ts=4 from os.path import dirname, expanduser from whipper.common import directory from whipper.test import common class DirectoryTestCase(common.TestCase): HOME = expanduser('~') HOME_PARENT = dirname(HOME) def testAll(self): path = directory.config_path() self.assertTrue(path.startswith(DirectoryTestCase.HOME_PARENT)) path = directory.cache_path() self.assertTrue(path.startswith(DirectoryTestCase.HOME_PARENT)) whipper-0.9.0/whipper/test/test_common_drive.py000066400000000000000000000006661357173224400217450ustar00rootroot00000000000000# -*- Mode: Python; test-case-name: whipper.test.test_common_drive -*- # vi:si:et:sw=4:sts=4:ts=4 from whipper.test import common from whipper.common import drive class ListifyTestCase(common.TestCase): def testString(self): string = '/dev/sr0' self.assertEqual(drive._listify(string), [string, ]) def testList(self): lst = ['/dev/scd0', '/dev/sr0'] self.assertEqual(drive._listify(lst), lst) whipper-0.9.0/whipper/test/test_common_mbngs.py000066400000000000000000000260361357173224400217410ustar00rootroot00000000000000# -*- Mode: Python; test-case-name: whipper.test.test_common_mbngs -*- # vi:si:et:sw=4:sts=4:ts=4:set fileencoding=utf-8 import os import json import unittest from whipper.common import mbngs class MetadataTestCase(unittest.TestCase): # Generated with rip -R cd info def testMissingReleaseDate(self): # Using: The KLF - Space & Chill Out # https://musicbrainz.org/release/c56ff16e-1d81-47de-926f-ba22891bd2bd filename = 'whipper.release.c56ff16e-1d81-47de-926f-ba22891bd2bd.json' path = os.path.join(os.path.dirname(__file__), filename) handle = open(path, "rb") response = json.loads(handle.read().decode('utf-8')) handle.close() discid = "b.yqPuCBdsV5hrzDvYrw52iK_jE-" metadata = mbngs._getMetadata(response['release'], discid) self.assertFalse(metadata.release) def test2MeterSessies10(self): # various artists, multiple artists per track filename = 'whipper.release.a76714e0-32b1-4ed4-b28e-f86d99642193.json' path = os.path.join(os.path.dirname(__file__), filename) handle = open(path, "rb") response = json.loads(handle.read().decode('utf-8')) handle.close() discid = "f7XO36a7n1LCCskkCiulReWbwZA-" metadata = mbngs._getMetadata(response['release'], discid) self.assertEqual(metadata.artist, 'Various Artists') self.assertEqual(metadata.release, '2001-10-15') self.assertEqual(metadata.mbidArtist, ['89ad4ac3-39f7-470e-963a-56509c546377']) self.assertEqual(len(metadata.tracks), 18) track16 = metadata.tracks[15] self.assertEqual(track16.artist, 'Tom Jones & Stereophonics') self.assertEqual(track16.mbidArtist, [ '57c6f649-6cde-48a7-8114-2a200247601a', '0bfba3d3-6a04-4779-bb0a-df07df5b0558', ]) self.assertEqual(track16.sortName, 'Jones, Tom & Stereophonics') def testBalladOfTheBrokenSeas(self): # various artists disc filename = 'whipper.release.e32ae79a-336e-4d33-945c-8c5e8206dbd3.json' path = os.path.join(os.path.dirname(__file__), filename) handle = open(path, "rb") response = json.loads(handle.read().decode('utf-8')) handle.close() discid = "xAq8L4ELMW14.6wI6tt7QAcxiDI-" metadata = mbngs._getMetadata(response['release'], discid) self.assertEqual(metadata.artist, 'Isobel Campbell & Mark Lanegan') self.assertEqual(metadata.sortName, 'Campbell, Isobel & Lanegan, Mark') self.assertEqual(metadata.release, '2006-01-30') self.assertEqual(metadata.mbidArtist, [ 'd51f3a15-12a2-41a0-acfa-33b5eae71164', 'a9126556-f555-4920-9617-6e013f8228a7', ]) self.assertEqual(len(metadata.tracks), 12) track12 = metadata.tracks[11] self.assertEqual(track12.artist, 'Isobel Campbell & Mark Lanegan') self.assertEqual(track12.sortName, 'Campbell, Isobel' ' & Lanegan, Mark') self.assertEqual(track12.mbidArtist, [ 'd51f3a15-12a2-41a0-acfa-33b5eae71164', 'a9126556-f555-4920-9617-6e013f8228a7', ]) def testMalaInCuba(self): # single artist disc, but with multiple artists tracks # see https://github.com/thomasvs/morituri/issues/19 filename = 'whipper.release.61c6fd9b-18f8-4a45-963a-ba3c5d990cae.json' path = os.path.join(os.path.dirname(__file__), filename) handle = open(path, "rb") response = json.loads(handle.read().decode('utf-8')) handle.close() discid = "u0aKVpO.59JBy6eQRX2vYcoqQZ0-" metadata = mbngs._getMetadata(response['release'], discid) self.assertEqual(metadata.artist, 'Mala') self.assertEqual(metadata.sortName, 'Mala') self.assertEqual(metadata.release, '2012-09-17') self.assertEqual(metadata.mbidArtist, ['09f221eb-c97e-4da5-ac22-d7ab7c555bbb']) self.assertEqual(len(metadata.tracks), 14) track6 = metadata.tracks[5] self.assertEqual(track6.artist, 'Mala feat. Dreiser & Sexto Sentido') self.assertEqual(track6.sortName, 'Mala feat. Dreiser & Sexto Sentido') self.assertEqual(track6.mbidArtist, [ '09f221eb-c97e-4da5-ac22-d7ab7c555bbb', 'ec07a209-55ff-4084-bc41-9d4d1764e075', 'f626b92e-07b1-4a19-ad13-c09d690db66c', ]) def testUnknownArtist(self): """ check the received metadata for artists tagged with [unknown] and artists tagged with an alias in MusicBrainz see https://github.com/whipper-team/whipper/issues/155 """ # Using: CunninLynguists - Sloppy Seconds, Volume 1 # https://musicbrainz.org/release/8478d4da-0cda-4e46-ae8c-1eeacfa5cf37 filename = 'whipper.release.8478d4da-0cda-4e46-ae8c-1eeacfa5cf37.json' path = os.path.join(os.path.dirname(__file__), filename) handle = open(path, "rb") response = json.loads(handle.read().decode('utf-8')) handle.close() discid = "RhrwgVb0hZNkabQCw1dZIhdbMFg-" metadata = mbngs._getMetadata(response['release'], discid) self.assertEqual(metadata.artist, 'CunninLynguists') self.assertEqual(metadata.release, '2003') self.assertEqual(metadata.mbidArtist, ['69c4cc43-8163-41c5-ac81-30946d27bb69']) self.assertEqual(len(metadata.tracks), 30) track8 = metadata.tracks[7] self.assertEqual(track8.artist, '???') self.assertEqual(track8.sortName, '[unknown]') self.assertEqual(track8.mbidArtist, ['125ec42a-7229-4250-afc5-e057484327fe']) track9 = metadata.tracks[8] self.assertEqual(track9.artist, 'CunninLynguists feat. Tonedeff') self.assertEqual(track9.sortName, 'CunninLynguists feat. Tonedeff') self.assertEqual(track9.mbidArtist, [ '69c4cc43-8163-41c5-ac81-30946d27bb69', 'b3869d83-9fb5-4eac-b5ca-2d155fcbee12' ]) def testNenaAndKimWildSingle(self): """ check the received metadata for artists that differ between named on release and named in recording """ filename = 'whipper.release.f484a9fc-db21-4106-9408-bcd105c90047.json' path = os.path.join(os.path.dirname(__file__), filename) handle = open(path, "rb") response = json.loads(handle.read().decode('utf-8')) handle.close() discid = "X2c2IQ5vUy5x6Jh7Xi_DGHtA1X8-" metadata = mbngs._getMetadata(response['release'], discid) self.assertEqual(metadata.artist, 'Nena & Kim Wilde') self.assertEqual(metadata.release, '2003-05-19') self.assertEqual(metadata.mbidArtist, [ '38bfaa7f-ee98-48cb-acd0-946d7aeecd76', '4b462375-c508-432a-8c88-ceeec38b16ae', ]) self.assertEqual(len(metadata.tracks), 4) track1 = metadata.tracks[0] self.assertEqual(track1.artist, 'Nena & Kim Wilde') self.assertEqual(track1.sortName, 'Nena & Wilde, Kim') self.assertEqual(track1.mbidArtist, [ '38bfaa7f-ee98-48cb-acd0-946d7aeecd76', '4b462375-c508-432a-8c88-ceeec38b16ae', ]) self.assertEqual(track1.mbid, '1cc96e78-28ed-3820-b0b6-614c35b121ac') self.assertEqual(track1.mbidRecording, 'fde5622c-ce23-4ebb-975d-51d4a926f901') track2 = metadata.tracks[1] self.assertEqual(track2.artist, 'Nena & Kim Wilde') self.assertEqual(track2.sortName, 'Nena & Wilde, Kim') self.assertEqual(track2.mbidArtist, [ '38bfaa7f-ee98-48cb-acd0-946d7aeecd76', '4b462375-c508-432a-8c88-ceeec38b16ae', ]) self.assertEqual(track2.mbid, 'f16db4bf-9a34-3d5a-a975-c9375ab7a2ca') self.assertEqual(track2.mbidRecording, '5f19758e-7421-4c71-a599-9a9575d8e1b0') def testMissingReleaseGroupType(self): """Check that whipper doesn't break if there's no type.""" # Using: Gustafsson, Österberg & Cowle - What's Up? 8 (disc 4) # https://musicbrainz.org/release/d8e6153a-2c47-4804-9d73-0aac1081c3b1 filename = 'whipper.release.d8e6153a-2c47-4804-9d73-0aac1081c3b1.json' path = os.path.join(os.path.dirname(__file__), filename) handle = open(path, "rb") response = json.loads(handle.read().decode('utf-8')) handle.close() discid = "xu338_M8WukSRi0J.KTlDoflB8Y-" # disc 4 metadata = mbngs._getMetadata(response['release'], discid) self.assertEqual(metadata.releaseType, None) def testAllAvailableMetadata(self): """Check that all possible metadata gets assigned.""" # Using: David Rovics - The Other Side # https://musicbrainz.org/release/6109ceed-7e21-490b-b5ad-3a66b4e4cfbb filename = 'whipper.release.6109ceed-7e21-490b-b5ad-3a66b4e4cfbb.json' path = os.path.join(os.path.dirname(__file__), filename) handle = open(path, "rb") response = json.loads(handle.read().decode('utf-8')) handle.close() discid = "cHW1Uutl_kyWNaLJsLmTGTe4rnE-" metadata = mbngs._getMetadata(response['release'], discid) self.assertEqual(metadata.artist, 'David Rovics') self.assertEqual(metadata.sortName, 'Rovics, David') self.assertFalse(metadata.various) self.assertIsInstance(metadata.tracks, list) self.assertEqual(metadata.release, '2015') self.assertEqual(metadata.releaseTitle, 'The Other Side') self.assertEqual(metadata.releaseType, 'Album') self.assertEqual(metadata.mbid, '6109ceed-7e21-490b-b5ad-3a66b4e4cfbb') self.assertEqual(metadata.mbidReleaseGroup, '99850b41-a06e-4fb8-992c-75c191a77803') self.assertEqual(metadata.mbidArtist, ['4d56eb9f-13b3-4f05-9db7-50195378d49f']) self.assertEqual(metadata.url, 'https://musicbrainz.org/release' '/6109ceed-7e21-490b-b5ad-3a66b4e4cfbb') self.assertEqual(metadata.catalogNumber, '[none]') self.assertEqual(metadata.barcode, '700261430249') self.assertEqual(len(metadata.tracks), 16) track1 = metadata.tracks[0] self.assertEqual(track1.artist, 'David Rovics') self.assertEqual(track1.title, 'Waiting for the Hurricane') self.assertEqual(track1.duration, 176320) self.assertEqual(track1.mbid, '4116eea3-b9c2-452a-8d63-92f1e585b225') self.assertEqual(track1.sortName, 'Rovics, David') self.assertEqual(track1.mbidArtist, ['4d56eb9f-13b3-4f05-9db7-50195378d49f']) self.assertEqual(track1.mbidRecording, 'b191794d-b7c6-4d6f-971e-0a543959b5ad') self.assertEqual(track1.mbidWorks, ['90d5be68-0b29-45a3-ba01-c27ad78e3625']) whipper-0.9.0/whipper/test/test_common_path.py000066400000000000000000000016671357173224400215720ustar00rootroot00000000000000# -*- Mode: Python; test-case-name: whipper.test.test_common_path -*- # vi:si:et:sw=4:sts=4:ts=4 from whipper.common import path from whipper.test import common class FilterTestCase(common.TestCase): def setUp(self): self._filter = path.PathFilter(special=True) def testSlash(self): part = 'A Charm/A Blade' self.assertEqual(self._filter.filter(part), 'A Charm-A Blade') def testFat(self): part = 'A Word: F**k you?' self.assertEqual(self._filter.filter(part), 'A Word - F__k you_') def testSpecial(self): part = '<<< $&*!\' "()`{}[]spaceship>>>' self.assertEqual(self._filter.filter(part), '___ _____ ________spaceship___') def testGreatest(self): part = 'Greatest Ever! Soul: The Definitive Collection' self.assertEqual(self._filter.filter(part), 'Greatest Ever_ Soul - The Definitive Collection') whipper-0.9.0/whipper/test/test_common_program.py000066400000000000000000000025411357173224400222750ustar00rootroot00000000000000# -*- Mode: Python; test-case-name: whipper.test.test_common_program -*- # vi:si:et:sw=4:sts=4:ts=4 import unittest from whipper.common import program, mbngs, config from whipper.command.cd import DEFAULT_DISC_TEMPLATE class PathTestCase(unittest.TestCase): def testStandardTemplateEmpty(self): prog = program.Program(config.Config()) path = prog.getPath('/tmp', DEFAULT_DISC_TEMPLATE, 'mbdiscid', None) self.assertEqual(path, ('/tmp/unknown/Unknown Artist - mbdiscid/' 'Unknown Artist - mbdiscid')) def testStandardTemplateFilled(self): prog = program.Program(config.Config()) md = mbngs.DiscMetadata() md.artist = md.sortName = 'Jeff Buckley' md.title = 'Grace' path = prog.getPath('/tmp', DEFAULT_DISC_TEMPLATE, 'mbdiscid', md, 0) self.assertEqual(path, ('/tmp/unknown/Jeff Buckley - Grace/' 'Jeff Buckley - Grace')) def testIssue66TemplateFilled(self): prog = program.Program(config.Config()) md = mbngs.DiscMetadata() md.artist = md.sortName = 'Jeff Buckley' md.title = 'Grace' path = prog.getPath('/tmp', '%A/%d', 'mbdiscid', md, 0) self.assertEqual(path, '/tmp/Jeff Buckley/Grace') whipper-0.9.0/whipper/test/test_common_renamer.py000066400000000000000000000116731357173224400222650ustar00rootroot00000000000000# -*- Mode: Python; test-case-name: whipper.test.test_image_cue -*- # vi:si:et:sw=4:sts=4:ts=4 import os import tempfile import unittest from whipper.common import renamer class RenameInFileTestcase(unittest.TestCase): def setUp(self): (fd, self._path) = tempfile.mkstemp(suffix='.whipper.renamer.infile') os.write(fd, 'This is a test\nThis is another\n'.encode()) os.close(fd) def testVerify(self): o = renamer.RenameInFile(self._path, 'is is a', 'at was some') self.assertEqual(o.verify(), None) os.unlink(self._path) self.assertRaises(AssertionError, o.verify) def testDo(self): o = renamer.RenameInFile(self._path, 'is is a', 'at was some') o.do() with open(self._path) as f: output = f.read() self.assertEqual(output, 'That was some test\nThat was somenother\n') os.unlink(self._path) def testSerialize(self): o = renamer.RenameInFile(self._path, 'is is a', 'at was some') data = o.serialize() o2 = renamer.RenameInFile.deserialize(data) o2.do() with open(self._path) as f: output = f.read() self.assertEqual(output, 'That was some test\nThat was somenother\n') os.unlink(self._path) class RenameFileTestcase(unittest.TestCase): def setUp(self): (fd, self._source) = tempfile.mkstemp(suffix='.whipper.renamer.file') os.write(fd, 'This is a test\nThis is another\n'.encode()) os.close(fd) (fd, self._destination) = tempfile.mkstemp( suffix='.whipper.renamer.file') os.close(fd) os.unlink(self._destination) self._operation = renamer.RenameFile(self._source, self._destination) def testVerify(self): self.assertEqual(self._operation.verify(), None) handle = open(self._destination, 'w') handle.close() self.assertRaises(AssertionError, self._operation.verify) os.unlink(self._destination) self.assertEqual(self._operation.verify(), None) os.unlink(self._source) self.assertRaises(AssertionError, self._operation.verify) def testDo(self): self._operation.do() with open(self._destination) as f: output = f.read() self.assertEqual(output, 'This is a test\nThis is another\n') os.unlink(self._destination) def testSerialize(self): data = self._operation.serialize() o = renamer.RenameFile.deserialize(data) o.do() with open(self._destination) as f: output = f.read() self.assertEqual(output, 'This is a test\nThis is another\n') os.unlink(self._destination) class OperatorTestCase(unittest.TestCase): def setUp(self): self._statePath = tempfile.mkdtemp(suffix='.whipper.renamer.operator') self._operator = renamer.Operator(self._statePath, 'test') (fd, self._source) = tempfile.mkstemp( suffix='.whipper.renamer.operator') os.write(fd, 'This is a test\nThis is another\n'.encode()) os.close(fd) (fd, self._destination) = tempfile.mkstemp( suffix='.whipper.renamer.operator') os.close(fd) os.unlink(self._destination) self._operator.addOperation( renamer.RenameInFile(self._source, 'is is a', 'at was some')) self._operator.addOperation( renamer.RenameFile(self._source, self._destination)) def tearDown(self): os.system('rm -rf %s' % self._statePath) def testLoadNoneDone(self): self._operator.save() o = renamer.Operator(self._statePath, 'test') o.load() self.assertEqual(o._todo, self._operator._todo) self.assertEqual(o._done, []) os.unlink(self._source) def testLoadOneDone(self): self.assertEqual(len(self._operator._done), 0) self._operator.save() next(self._operator) self.assertEqual(len(self._operator._done), 1) o = renamer.Operator(self._statePath, 'test') o.load() self.assertEqual(len(o._done), 1) self.assertEqual(o._todo, self._operator._todo) self.assertEqual(o._done, self._operator._done) # now continue next(o) self.assertEqual(len(o._done), 2) os.unlink(self._destination) def testLoadOneInterrupted(self): self.assertEqual(len(self._operator._done), 0) self._operator.save() # cheat by doing a task without saving self._operator._todo[0].do() self.assertEqual(len(self._operator._done), 0) o = renamer.Operator(self._statePath, 'test') o.load() self.assertEqual(len(o._done), 0) self.assertEqual(o._todo, self._operator._todo) self.assertEqual(o._done, self._operator._done) # now continue, resuming next(o) self.assertEqual(len(o._done), 1) next(o) self.assertEqual(len(o._done), 2) os.unlink(self._destination) whipper-0.9.0/whipper/test/test_image_cue.py000066400000000000000000000051221357173224400211720ustar00rootroot00000000000000# -*- Mode: Python; test-case-name: whipper.test.test_image_cue -*- # vi:si:et:sw=4:sts=4:ts=4 import os import tempfile import unittest import whipper from whipper.image import table, cue from whipper.test import common class KingsSingleTestCase(unittest.TestCase): def setUp(self): self.cue = cue.CueFile(os.path.join(os.path.dirname(__file__), 'kings-single.cue')) self.cue.parse() self.assertEqual(len(self.cue.table.tracks), 11) def testGetTrackLength(self): t = self.cue.table.tracks[0] self.assertEqual(self.cue.getTrackLength(t), 17811) # last track has unknown length t = self.cue.table.tracks[-1] self.assertEqual(self.cue.getTrackLength(t), -1) class KingsSeparateTestCase(unittest.TestCase): def setUp(self): self.cue = cue.CueFile(os.path.join(os.path.dirname(__file__), 'kings-separate.cue')) self.cue.parse() self.assertEqual(len(self.cue.table.tracks), 11) def testGetTrackLength(self): # all tracks have unknown length t = self.cue.table.tracks[0] self.assertEqual(self.cue.getTrackLength(t), -1) t = self.cue.table.tracks[-1] self.assertEqual(self.cue.getTrackLength(t), -1) class KanyeMixedTestCase(unittest.TestCase): def setUp(self): self.cue = cue.CueFile(os.path.join(os.path.dirname(__file__), 'kanye.cue')) self.cue.parse() self.assertEqual(len(self.cue.table.tracks), 13) def testGetTrackLength(self): t = self.cue.table.tracks[0] self.assertEqual(self.cue.getTrackLength(t), -1) class WriteCueFileTestCase(unittest.TestCase): @staticmethod def testWrite(): fd, path = tempfile.mkstemp(suffix='.whipper.test.cue') os.close(fd) it = table.Table() t = table.Track(1) t.index(1, absolute=0, path='track01.wav', relative=0, counter=1) it.tracks.append(t) t = table.Track(2) t.index(0, absolute=1000, path='track01.wav', relative=1000, counter=1) t.index(1, absolute=2000, path='track02.wav', relative=0, counter=2) it.tracks.append(t) it.absolutize() it.leadout = 3000 common.diffStrings("""REM DISCID 0C002802 REM COMMENT "whipper %s" FILE "track01.wav" WAVE TRACK 01 AUDIO INDEX 01 00:00:00 TRACK 02 AUDIO INDEX 00 00:13:25 FILE "track02.wav" WAVE INDEX 01 00:00:00 """ % whipper.__version__, it.cue()) os.unlink(path) whipper-0.9.0/whipper/test/test_image_table.py000066400000000000000000000071001357173224400215030ustar00rootroot00000000000000# -*- Mode: Python; test-case-name: whipper.test.test_image_table -*- # vi:si:et:sw=4:sts=4:ts=4 from whipper.image import table from whipper.test import common as tcommon def h(i): return "0x%08x" % i class TrackTestCase(tcommon.TestCase): def testRepr(self): track = table.Track(1) self.assertEqual(repr(track), "") track.index(1, 100) self.assertTrue(repr(track.indexes[1]).startswith('