Merge branch 'release/0.7.3'

This commit is contained in:
JoeLametta
2018-12-14 23:35:57 +01:00
35 changed files with 631 additions and 518 deletions

19
.github/config.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
# Configuration for new-issue-welcome - https://github.com/behaviorbot/new-issue-welcome
# Comment to be posted to on first time issues
newIssueWelcomeComment: |
👋 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).
# Configuration for new-pr-welcome - https://github.com/behaviorbot/new-pr-welcome
# Comment to be posted to on PRs from first time contributors in your repository
newPRWelcomeComment: >
💖 Thanks for opening your first pull request here! 💖
# Configuration for first-pr-merge - https://github.com/behaviorbot/first-pr-merge
# Comment to be posted to on pull requests merged by a first time user
firstPRMergeComment: >
Congrats on merging your first pull request! 🎉🎉🎉

42
.github/stale.yml vendored Normal file
View File

@@ -0,0 +1,42 @@
# Configuration for probot-stale - https://github.com/probot/stale
# Number of days of inactivity before an Issue or Pull Request becomes stale
daysUntilStale: 30
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
daysUntilClose: 7
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
exemptLabels:
- Accepted
# Set to true to ignore issues in a project (defaults to false)
exemptProjects: false
# Set to true to ignore issues in a milestone (defaults to false)
exemptMilestones:
- "2.0"
- backlog
# Label to use when marking as stale
staleLabel: "Status: stale"
# Comment to post when marking as stale. Set to `false` to disable
markComment: >
This issue/pull request has been automatically marked as stale because it has not had
recent activity. It will be closed in 7 days if no further activity occurs. Thank you
for your contributions.
# Comment to post when removing the stale label.
unmarkComment: >
Thank you for updating this issue. It is no longer marked as stale.
# Comment to post when closing a stale Issue or Pull Request.
closeComment: |
This issue/pull request has been closed due to prolonged inactivity.
If you think this is an error, please leave a comment and we will gladly reopen it.
# Limit the number of actions per hour, from 1-30. Default is 30
limitPerRun: 30

View File

@@ -2,23 +2,59 @@
## [Unreleased](https://github.com/whipper-team/whipper/tree/HEAD)
[Full Changelog](https://github.com/whipper-team/whipper/compare/v0.7.2...HEAD)
[Full Changelog](https://github.com/whipper-team/whipper/compare/v0.7.3...HEAD)
## [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)
- CRITICAL:whipper.command.cd:output directory is a finished rip output directory [\#287](https://github.com/whipper-team/whipper/issues/287)
- Possible HTOA error [\#281](https://github.com/whipper-team/whipper/issues/281)
- Disc template KeyError [\#279](https://github.com/whipper-team/whipper/issues/279)
- Enhanced CD causes computer to freeze. [\#256](https://github.com/whipper-team/whipper/issues/256)
- pycdio & libcdio issues [\#238](https://github.com/whipper-team/whipper/issues/238)
- 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)
- Write musicbrainz\_discid tag when disc is unknown [\#280](https://github.com/whipper-team/whipper/issues/280)
- 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)
**Implemented enhancements:**
- Add whipper to Hydrogen Audio wiki's "Comparison of CD rippers" [\#317](https://github.com/whipper-team/whipper/issues/317)
- automatically build Docker images [\#301](https://github.com/whipper-team/whipper/issues/301)
**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:**
@@ -28,24 +64,28 @@
## [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)
**Implemented enhancements:**
- Transfer repository ownership to GitHub organization [\#306](https://github.com/whipper-team/whipper/issues/306)
- Add cdparanoia version to log file [\#267](https://github.com/whipper-team/whipper/issues/267)
- Remove whipper's retag feature [\#262](https://github.com/whipper-team/whipper/issues/262)
- Add a requirements.txt file [\#221](https://github.com/whipper-team/whipper/issues/221)
- Limit length of filenames [\#197](https://github.com/whipper-team/whipper/issues/197)
- Loggers [\#117](https://github.com/whipper-team/whipper/issues/117)
**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)
- yaml logger [\#292](https://github.com/whipper-team/whipper/issues/292)
- Add replaygain processing [\#285](https://github.com/whipper-team/whipper/issues/285)
- 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)
- whipper sometimes creates invalid cue sheets [\#265](https://github.com/whipper-team/whipper/issues/265)
- Make .cue and .m3u writing optional [\#259](https://github.com/whipper-team/whipper/issues/259)
- Add a requirements.txt file [\#221](https://github.com/whipper-team/whipper/issues/221)
**Merged pull requests:**
@@ -66,60 +106,49 @@
## [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:**
- Various ripping issues [\#179](https://github.com/whipper-team/whipper/issues/179)
- Simple message while reading TOC [\#257](https://github.com/whipper-team/whipper/issues/257)
- 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 -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))
- Removed reference to unused "profile = flac" config option \(issue \#99\) [\#231](https://github.com/whipper-team/whipper/pull/231) ([calumchisholm](https://github.com/calumchisholm))
**Fixed bugs:**
- whipper offset find exception [\#208](https://github.com/whipper-team/whipper/issues/208)
- cd rip is not able to rip the last track [\#203](https://github.com/whipper-team/whipper/issues/203)
- CD-ROM powers off during rip command. [\#189](https://github.com/whipper-team/whipper/issues/189)
- Various ripping issues [\#179](https://github.com/whipper-team/whipper/issues/179)
- "whipper image verify" abends on FLAC having ID3 tags \("TypeError: %x format: a number is required, not NoneType"\) [\#176](https://github.com/whipper-team/whipper/issues/176)
- whipper not picking up all settings in whipper.conf [\#99](https://github.com/whipper-team/whipper/issues/99)
- ImportError: libcdio.so.16: cannot open shared object file: No such file or directory [\#229](https://github.com/whipper-team/whipper/issues/229)
- 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))
**Closed issues:**
- Simple message while reading TOC [\#257](https://github.com/whipper-team/whipper/issues/257)
- Statement to your "only flac" decision [\#247](https://github.com/whipper-team/whipper/issues/247)
- How to choose device \(if there are more\)? [\#241](https://github.com/whipper-team/whipper/issues/241)
- Import Error No Module Named gobject Fedora 26 and 27 [\#228](https://github.com/whipper-team/whipper/issues/228)
- Make a 0.6.0 release [\#219](https://github.com/whipper-team/whipper/issues/219)
- CD-ROM powers off during rip command. [\#189](https://github.com/whipper-team/whipper/issues/189)
- 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)
- GUI frontend for whipper [\#40](https://github.com/whipper-team/whipper/issues/40)
**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:**
- Error: NotFoundException message displayed while ripping an unknown disc [\#198](https://github.com/whipper-team/whipper/issues/198)
- rename milestone 101010 to backlog [\#190](https://github.com/whipper-team/whipper/issues/190)
- 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)
- Declare supported Python version [\#152](https://github.com/whipper-team/whipper/issues/152)
- 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)
- 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)
- Support both AccurateRip V1 and AccurateRip V2 at the same time [\#18](https://github.com/whipper-team/whipper/issues/18)
- Test HTOA peak value against 0 \(integer comparison\) [\#224](https://github.com/whipper-team/whipper/pull/224) ([JoeLametta](https://github.com/JoeLametta))
**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)
- 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)
@@ -128,11 +157,26 @@
**Closed issues:**
- TRACK and FILE order in cue file [\#212](https://github.com/whipper-team/whipper/issues/212)
- 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)
- AttributeError: RipResult instance has no attribute 'profileName' [\#181](https://github.com/whipper-team/whipper/issues/181)
- .log, .cue, and .m3u file names [\#180](https://github.com/whipper-team/whipper/issues/180)
- Accurip verification step failure [\#178](https://github.com/whipper-team/whipper/issues/178)
- Whipper offset find failing [\#177](https://github.com/whipper-team/whipper/issues/177)
- using your own MusicBrainz server [\#172](https://github.com/whipper-team/whipper/issues/172)
- cdda2wav from cdrtools instead of cdparanoia [\#38](https://github.com/whipper-team/whipper/issues/38)
- 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)
- Declare supported Python version [\#152](https://github.com/whipper-team/whipper/issues/152)
- Identify media type in log file \(ie CD vs CD-R\) [\#137](https://github.com/whipper-team/whipper/issues/137)
- Accurate rip failures still exit 0 [\#126](https://github.com/whipper-team/whipper/issues/126)
- 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))
@@ -166,21 +210,29 @@
## [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)
**Implemented enhancements:**
- overly verbose warning logging [\#131](https://github.com/whipper-team/whipper/issues/131)
- Check that whipper deals properly with CD pre-emphasis [\#120](https://github.com/whipper-team/whipper/issues/120)
- Remove gstreamer dependency [\#29](https://github.com/whipper-team/whipper/issues/29)
**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)
- whipper offset find -o OFFSET not working [\#123](https://github.com/whipper-team/whipper/issues/123)
- Check that whipper deals properly with CD pre-emphasis [\#120](https://github.com/whipper-team/whipper/issues/120)
- FreeDB metadata not honored [\#119](https://github.com/whipper-team/whipper/issues/119)
- Difficulty getting flac encoding working. [\#118](https://github.com/whipper-team/whipper/issues/118)
- enabling external loggers triggers python errors [\#111](https://github.com/whipper-team/whipper/issues/111)
- additional tag creation [\#108](https://github.com/whipper-team/whipper/issues/108)
- False positive on HTOA [\#82](https://github.com/whipper-team/whipper/issues/82)
- 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))
@@ -195,13 +247,14 @@
## [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)
**Implemented enhancements:**
- Whipper attempts to rip with no CD inserted [\#81](https://github.com/whipper-team/whipper/issues/81)
**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:**
@@ -211,10 +264,10 @@
## [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)
**Implemented enhancements:**
**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)
- Fixed README broken links and added a better changelog [\#90](https://github.com/whipper-team/whipper/pull/90) ([JoeLametta](https://github.com/JoeLametta))
**Merged pull requests:**
@@ -226,6 +279,7 @@
- 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))
@@ -240,6 +294,11 @@
- 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)
**Closed issues:**
- ImportError: No module named log [\#64](https://github.com/whipper-team/whipper/issues/64)
- whatlogger no longer recognized [\#56](https://github.com/whipper-team/whipper/issues/56)
**Merged pull requests:**
- Invoke whipper by its name + Readme rewrite [\#70](https://github.com/whipper-team/whipper/pull/70) ([JoeLametta](https://github.com/JoeLametta))
@@ -261,6 +320,11 @@
- 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)
**Closed issues:**
- offset find fails [\#46](https://github.com/whipper-team/whipper/issues/46)
- Error launching rip cd rip command [\#41](https://github.com/whipper-team/whipper/issues/41)
**Merged pull requests:**
- Sox [\#48](https://github.com/whipper-team/whipper/pull/48) ([RecursiveForest](https://github.com/RecursiveForest))
@@ -269,14 +333,6 @@
## [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)
- 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)
**Fixed bugs:**
- whipper fails to build on bash-compgen [\#25](https://github.com/whipper-team/whipper/issues/25)
@@ -286,6 +342,18 @@
- 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)
- Error reading TOC [\#26](https://github.com/whipper-team/whipper/issues/26)
- Don't allow ripping without an explicit offset, and make pycdio a required dependency [\#23](https://github.com/whipper-team/whipper/issues/23)
- 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))

View File

@@ -1,4 +1,4 @@
Coverage.py 4.5.1 text report against whipper v0.7.2
Coverage.py 4.5.2 text report against whipper v0.7.3
$ coverage run --branch --omit='whipper/test/*' --source=whipper -m unittest discover
$ coverage report -m
@@ -8,42 +8,42 @@ Name Stmts Miss Branch BrPart Cover Missing
whipper/__init__.py 10 2 4 2 71% 9, 11, 8->9, 10->11
whipper/__main__.py 7 7 2 0 0% 4-14
whipper/command/__init__.py 0 0 0 0 100%
whipper/command/accurip.py 44 44 18 0 0% 21-96
whipper/command/accurip.py 43 43 18 0 0% 21-92
whipper/command/basecommand.py 69 53 30 0 16% 56-114, 121-130, 133, 136, 139, 142-145
whipper/command/cd.py 219 181 56 0 14% 71-79, 84-184, 187, 199, 222-276, 283-308, 311-488
whipper/command/drive.py 62 62 12 0 0% 21-122
whipper/command/cd.py 224 186 58 0 13% 71-79, 84-193, 196, 208, 231-284, 291-318, 321-491
whipper/command/drive.py 57 57 10 0 0% 21-107
whipper/command/image.py 38 38 6 0 0% 21-76
whipper/command/main.py 65 65 20 0 0% 4-109
whipper/command/main.py 68 68 22 0 0% 4-115
whipper/command/mblookup.py 28 28 8 0 0% 1-41
whipper/command/offset.py 111 111 32 0 0% 21-227
whipper/command/offset.py 110 110 32 0 0% 21-221
whipper/common/__init__.py 0 0 0 0 100%
whipper/common/accurip.py 133 5 54 5 95% 123, 134, 143-145, 116->123, 127->134, 160->163, 252->258, 261->267
whipper/common/cache.py 105 50 34 6 44% 66-90, 96, 99, 107-112, 115-116, 132, 144-149, 172-179, 203-208, 213-230, 95->96, 98->99, 131->132, 142->153, 143->144, 171->172
whipper/common/accurip.py 133 5 54 5 95% 121, 130, 139-141, 116->121, 125->130, 155->158, 246->252, 255->261
whipper/common/cache.py 105 50 34 6 44% 66-90, 96, 99, 107-112, 115-116, 132, 144-148, 171-178, 202-207, 212-228, 95->96, 98->99, 131->132, 142->152, 143->144, 170->171
whipper/common/checksum.py 26 14 2 0 43% 41-42, 45-46, 49-64
whipper/common/common.py 142 22 32 6 83% 50-51, 118-119, 142-143, 161-168, 180, 274-280, 316-320, 117->118, 130->133, 179->180, 189->196, 270->274, 314->322
whipper/common/config.py 92 8 18 4 89% 105-106, 124-125, 131, 142, 144, 146, 130->131, 141->142, 143->144, 145->146
whipper/common/common.py 150 28 38 6 78% 51-52, 119-120, 143-144, 162-169, 181, 275-280, 287-292, 329-333, 118->119, 131->134, 180->181, 190->197, 271->275, 327->335
whipper/common/config.py 92 8 18 4 89% 105-106, 124-125, 131, 141, 143, 145, 130->131, 140->141, 142->143, 144->145
whipper/common/directory.py 21 8 10 2 55% 29, 39, 44-51, 28->29, 38->39
whipper/common/drive.py 31 20 6 0 35% 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 159 53 58 7 66% 38-39, 45, 90-96, 158-159, 164-165, 209, 212, 215, 238-241, 250, 270-324, 157->158, 163->164, 208->209, 211->212, 214->215, 237->238, 247->250
whipper/common/mbngs.py 159 53 58 7 66% 38-39, 45, 90-96, 157-158, 163-164, 208, 211, 214, 237-239, 248, 268-322, 156->157, 162->163, 207->208, 210->211, 213->214, 236->237, 245->248
whipper/common/path.py 24 0 8 3 91% 42->45, 52->57, 62->67
whipper/common/program.py 344 263 108 5 20% 89-91, 97-105, 113-145, 154-159, 162, 166-170, 215, 226-227, 229-233, 249-264, 272-398, 409-459, 467-475, 478-494, 505-545, 557-574, 577-595, 598-608, 611-619, 81->84, 212->215, 225->226, 228->229, 235->239
whipper/common/program.py 337 259 110 5 20% 85-87, 93-100, 109-141, 150-155, 158, 162-166, 211, 222-223, 225-229, 245-260, 268-380, 391-442, 450-458, 461-476, 487-527, 539-556, 559-577, 580-590, 593-601, 77->80, 208->211, 221->222, 224->225, 231->235
whipper/common/renamer.py 102 2 16 1 97% 135, 158, 60->68
whipper/common/task.py 77 19 14 2 75% 47-52, 87-88, 91-94, 102, 116-117, 124, 130, 136, 142, 148, 85->87, 99->102
whipper/common/task.py 77 19 14 2 75% 47-52, 86-87, 90-93, 101, 114-115, 122, 128, 134, 140, 146, 84->86, 98->101
whipper/extern/__init__.py 0 0 0 0 100%
whipper/extern/asyncsub.py 130 71 66 12 40% 15-17, 32, 37-38, 47-84, 89-102, 115, 122, 134, 145, 151, 156-160, 164-176, 14->15, 35->37, 45->47, 110->113, 114->115, 121->122, 133->134, 139->141, 141->152, 144->145, 148->151, 163->164
whipper/extern/freedb.py 104 83 38 1 17% 49, 57-58, 61, 64, 84-162, 170-222, 56->57
whipper/extern/task/__init__.py 0 0 0 0 100%
whipper/extern/task/task.py 276 117 54 11 53% 26-27, 56, 60, 86, 152-154, 172-174, 182-198, 216-219, 239-240, 281-282, 285-291, 306-307, 315-317, 326-333, 339-355, 359, 362, 369-386, 397-398, 401-404, 408, 411, 426, 429-431, 447, 459, 504-509, 518-523, 534-542, 545-553, 556-557, 565, 570-572, 55->56, 59->60, 68->70, 151->152, 165->exit, 215->216, 229->231, 234->exit, 494->496, 531->534, 569->570
whipper/extern/task/task.py 277 116 54 11 54% 57, 61, 81, 87, 153-155, 174-176, 184-200, 218-221, 241-242, 283-284, 287-293, 308-309, 317-319, 328-335, 341-357, 361, 364, 371-388, 399-400, 403-406, 410, 413, 428, 431-433, 449, 461, 506-511, 520-525, 536-544, 547-555, 558-559, 567, 572-574, 56->57, 60->61, 69->71, 152->153, 166->exit, 217->218, 231->233, 236->exit, 496->498, 533->536, 571->572
whipper/image/__init__.py 0 0 0 0 100%
whipper/image/cue.py 91 9 20 3 89% 99, 116-117, 132-134, 159, 187, 205, 98->99, 115->116, 131->132
whipper/image/image.py 117 94 18 0 17% 49-57, 65-67, 74-107, 121-153, 156-172, 183-214
whipper/image/table.py 398 22 114 16 93% 237, 346-347, 499, 578, 664-665, 685-686, 695-698, 702-703, 750, 796-797, 799-800, 844-845, 850-852, 180->183, 498->499, 532->536, 555->558, 577->578, 585->592, 684->685, 693->699, 694->695, 723->728, 728->722, 749->750, 795->796, 798->799, 843->844, 849->850
whipper/image/toc.py 203 15 60 10 90% 134, 262-263, 279-282, 340-342, 364-366, 386, 410, 130->134, 213->221, 261->262, 278->279, 288->293, 324->331, 339->340, 363->364, 373->377, 405->410
whipper/image/image.py 117 94 18 0 17% 49-57, 65-67, 74-107, 121-153, 156-172, 183-213
whipper/image/table.py 398 22 114 16 93% 237, 346-347, 499, 578, 663-664, 684-685, 694-697, 701-702, 747, 793-794, 796-797, 841-842, 847-849, 180->183, 498->499, 532->536, 555->558, 577->578, 585->592, 683->684, 692->698, 693->694, 722->726, 726->721, 746->747, 792->793, 795->796, 840->841, 846->847
whipper/image/toc.py 203 16 60 10 90% 134, 261-262, 278-281, 339-341, 363-365, 385, 409, 439, 130->134, 212->220, 260->261, 277->278, 287->292, 323->330, 338->339, 362->363, 372->376, 404->409
whipper/program/__init__.py 0 0 0 0 100%
whipper/program/arc.py 38 15 12 4 58% 26-28, 32, 37-43, 52-58, 22->26, 31->32, 36->37, 45->52
whipper/program/cdparanoia.py 315 185 86 3 39% 48-50, 59-60, 124-126, 163-166, 199-200, 241-255, 258-310, 313-351, 354-358, 361-397, 452-504, 509-554, 587-590, 593, 600, 606, 611-616, 123->124, 599->600, 603->606
whipper/program/cdrdao.py 51 29 10 2 39% 25-47, 54-60, 70-72, 76-78, 86, 93, 69->70, 75->76
whipper/program/arc.py 38 15 12 4 58% 26-28, 32, 37-41, 48-54, 22->26, 31->32, 36->37, 43->48
whipper/program/cdparanoia.py 315 185 86 3 39% 48-50, 59-60, 124-126, 163-166, 199-200, 242-256, 259-310, 313-351, 354-358, 361-397, 452-504, 509-554, 587-590, 593, 600, 606, 611-616, 123->124, 599->600, 603->606
whipper/program/cdrdao.py 59 36 14 2 34% 26-56, 63-69, 79-81, 85-87, 95, 102, 78->79, 84->85
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 2 1 90% 36, 49, 48->49
@@ -52,4 +52,4 @@ whipper/result/__init__.py 0 0 0 0 100%
whipper/result/logger.py 148 148 48 0 0% 1-242
whipper/result/result.py 56 13 6 0 69% 112-116, 134, 144-145, 154-161
-----------------------------------------------------------------------------
TOTAL 3950 1900 1090 108 49%
TOTAL 3961 1910 1104 108 49%

View File

@@ -9,7 +9,7 @@ RUN apt-get update \
&& pip install pycdio==2.0.0
# libcdio-paranoia / libcdio-utils are wrongfully packaged in Debian, thus built manually
# see https://github.com/JoeLametta/whipper/pull/237#issuecomment-367985625
# see https://github.com/whipper-team/whipper/pull/237#issuecomment-367985625
RUN curl -o - 'https://ftp.gnu.org/gnu/libcdio/libcdio-2.0.0.tar.gz' | tar zxf - \
&& cd libcdio-2.0.0 \
&& autoreconf -fi \

View File

@@ -117,7 +117,8 @@ If you are building from a source tarball or checkout, you can choose to use whi
Whipper relies on the following packages in order to run correctly and provide all the supported features:
- [cd-paranoia](https://www.gnu.org/software/libcdio/), for the actual ripping
- To avoid bugs it's advised to use `cd-paranoia` **10.2+0.94+2-2**
- To avoid bugs it's advised to use `cd-paranoia` version **10.2+0.94+2-2**
- The package named `libcdio-utils`, available on Debian and Ubuntu, is affected by a bug: 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), [#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`
@@ -126,7 +127,7 @@ Whipper relies on the following packages in order to run correctly and provide a
- [python-setuptools](https://pypi.python.org/pypi/setuptools), for installation, plugins support
- [python-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 `pycdio` **0.20** or **0.21** with `libcdio`**0.90****0.94**. If using `libcdio` **0.83**, which is _too old_ to satisfy all the requirements of whipper, just stick to `pycdio` **0.17**. Altough it needs additional testing, `libcdio` **2.0.0** seems to work fine if used with `pycdio` **2.0.0**. All other combinations aren't guaranteed to work.
- To avoid bugs it's advised to use `pycdio` **0.20** or **0.21** with `libcdio`**0.90****0.94* or `pycdio` **2.0.0** with `libcdio` **2.0.0**. All other combinations won't probably work.
- [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
@@ -250,7 +251,7 @@ read_offset = 6 ; drive read offset in positive/negative frames (no leading +)
unknown = True
output_directory = ~/My Music
track_template = new/%%A/%%y - %%d/%%t - %%n ; note: the format char '%' must be represented '%%'
disc_template = %(track_template)s
disc_template = new/%%A/%%y - %%d/%%A - %%d
# ...
```
@@ -265,14 +266,31 @@ python2 -m whipper -h
## Logger plugins
Whipper supports using external logger plugins to write rip `.log` files.
Whipper allows using external logger plugins to customize the template of `.log` files.
List available plugins with `whipper cd rip -h`. Specify a logger to rip with by passing `-L loggername`:
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 what
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/python2.7/dist-packages/whipper/plugins`
- `/usr/lib/python2.7/dist-packages/whipper/plugins`
### Official logger plugins
I suggest using whipper's default logger unless you've got particular requirements.

2
TODO
View File

@@ -1,6 +1,6 @@
TODO:
Please see https://github.com/JoeLametta/whipper/milestones for further
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.

View File

@@ -5,9 +5,9 @@ setup(
name="whipper",
version=whipper_version,
description="a secure cd ripper preferring accuracy over speed",
author=['Thomas Vander Stichele', 'Joe Lametta', 'Samantha Baldwin'],
maintainer=['Joe Lametta', 'Samantha Baldwin'],
url='https://github.com/JoeLametta/whipper',
author=['Thomas Vander Stichele', 'The Whipper Team'],
maintainer=['The Whipper Team'],
url='https://github.com/whipper-team/whipper',
license='GPL3',
packages=find_packages(),
entry_points={

View File

@@ -2,9 +2,9 @@ import logging
import os
import sys
__version__ = '0.7.2'
__version__ = '0.7.3'
level = logging.WARNING
level = logging.INFO
if 'WHIPPER_DEBUG' in os.environ:
level = os.environ['WHIPPER_DEBUG'].upper()
if 'WHIPPER_LOGFILE' in os.environ:

View File

@@ -18,8 +18,6 @@
# You should have received a copy of the GNU General Public License
# along with whipper. If not, see <http://www.gnu.org/licenses/>.
import sys
from whipper.command.basecommand import BaseCommand
from whipper.common.accurip import get_db_entry, ACCURATERIP_URL
@@ -42,18 +40,16 @@ retrieves and display accuraterip data from the given URL
count = responses[0].num_tracks
sys.stdout.write("Found %d responses for %d tracks\n\n" % (
len(responses), count))
logger.info("found %d responses for %d tracks", len(responses), count)
for (i, r) in enumerate(responses):
if r.num_tracks != count:
sys.stdout.write(
"Warning: response %d has %d tracks instead of %d\n" % (
i, 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):
sys.stdout.write("Track %d:\n" % (track + 1))
print("Track %d:" % (track + 1))
checksums = {}
for (i, r) in enumerate(responses):
@@ -82,9 +78,9 @@ retrieves and display accuraterip data from the given URL
sortedChecksums.reverse()
for highest, checksum in sortedChecksums:
sys.stdout.write(" %d result(s) for checksum %s: %s\n" % (
len(checksums[checksum]), checksum,
str(checksums[checksum])))
print(" %d result(s) for checksum %s: %s" % (
len(checksums[checksum]),
checksum, checksums[checksum]))
class AccuRip(BaseCommand):

View File

@@ -22,12 +22,12 @@ import argparse
import cdio
import os
import glob
import sys
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
@@ -83,29 +83,28 @@ class _CD(BaseCommand):
def do(self):
self.config = config.Config()
self.program = program.Program(self.config,
record=self.options.record,
stdout=sys.stdout)
record=self.options.record)
self.runner = task.SyncRunner()
# if the device is mounted (data session), unmount it
self.device = self.options.device
sys.stdout.write('Checking device %s\n' % self.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
print("Reading TOC...")
logger.info("reading TOC...")
self.ittoc = self.program.getFastToc(self.runner, self.device)
# already show us some info based on this
self.program.getRipResult(self.ittoc.getCDDBDiscId())
sys.stdout.write("CDDB disc id: %s\n" % self.ittoc.getCDDBDiscId())
print("CDDB disc id: %s" % self.ittoc.getCDDBDiscId())
self.mbdiscid = self.ittoc.getMusicBrainzDiscId()
sys.stdout.write("MusicBrainz disc id %s\n" % self.mbdiscid)
print("MusicBrainz disc id %s" % self.mbdiscid)
sys.stdout.write("MusicBrainz lookup URL %s\n" %
self.ittoc.getMusicBrainzSubmitURL())
print("MusicBrainz lookup URL %s" %
self.ittoc.getMusicBrainzSubmitURL())
self.program.metadata = (
self.program.getMusicBrainz(self.ittoc, self.mbdiscid,
@@ -119,12 +118,12 @@ class _CD(BaseCommand):
cddbid = self.ittoc.getCDDBValues()
cddbmd = self.program.getCDDB(cddbid)
if cddbmd:
sys.stdout.write('FreeDB identifies disc as %s\n' % 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 not passed")
"--unknown argument not passed")
return -1
self.program.result.isCdr = cdrdao.DetectCdr(self.device)
@@ -134,11 +133,21 @@ class _CD(BaseCommand):
"--cdr not passed")
return -1
# Change working directory before cdrdao's task
if self.options.working_directory is not None:
os.chdir(os.path.expanduser(self.options.working_directory))
out_bpath = self.options.output_directory.decode('utf-8')
# Needed to preserve cdrdao's tocfile
out_fpath = self.program.getPath(out_bpath,
self.options.disc_template,
self.mbdiscid,
self.program.metadata)
# now, read the complete index table, which is slower
self.itable = self.program.getTable(self.runner,
self.ittoc.getCDDBDiscId(),
self.ittoc.getMusicBrainzDiscId(),
self.device, self.options.offset)
self.device, self.options.offset,
out_fpath)
assert self.itable.getCDDBDiscId() == self.ittoc.getCDDBDiscId(), \
"full table's id %s differs from toc id %s" % (
@@ -167,7 +176,7 @@ class _CD(BaseCommand):
self.program.result.cdparanoiaDefeatsCache = \
self.config.getDefeatsCache(*info)
except KeyError as e:
logger.debug('Got key error: %r' % (e, ))
logger.debug('got key error: %r', (e, ))
self.program.result.artist = self.program.metadata \
and self.program.metadata.artist \
or 'Unknown Artist'
@@ -225,8 +234,7 @@ Log files will log the path to tracks relative to this directory.
if info:
try:
default_offset = config.Config().getReadOffset(*info)
sys.stdout.write("Using configured read offset %d\n" %
default_offset)
logger.info("using configured read offset %d", default_offset)
except KeyError:
pass
@@ -235,8 +243,8 @@ Log files will log the path to tracks relative to this directory.
self.parser.add_argument('-L', '--logger',
action="store", dest="logger",
default='whipper',
help="logger to use (choose from '"
"', '".join(loggers) + "')")
help=("logger to use (choose from: '%s" %
"', '".join(loggers) + "')"))
# FIXME: get from config
self.parser.add_argument('-o', '--offset',
action="store", dest="offset",
@@ -285,7 +293,9 @@ Log files will log the path to tracks relative to this directory.
self.options.track_template = self.options.track_template.decode(
'utf-8')
validate_template(self.options.track_template, 'track')
self.options.disc_template = self.options.disc_template.decode('utf-8')
validate_template(self.options.disc_template, 'disc')
if self.options.offset is None:
raise ValueError("Drive offset is unconfigured.\n"
@@ -324,26 +334,24 @@ Log files will log the path to tracks relative to this directory.
if logs:
msg = ("output directory %s is a finished rip" %
dirname.encode('utf-8'))
logger.critical(msg)
logger.debug(msg)
raise RuntimeError(msg)
else:
sys.stdout.write("output directory %s already exists\n" %
dirname.encode('utf-8'))
else:
print("creating output directory %s" % dirname.encode('utf-8'))
logger.info("creating output directory %s",
dirname.encode('utf-8'))
os.makedirs(dirname)
# FIXME: turn this into a method
def _ripIfNotRipped(number):
logger.debug('ripIfNotRipped for track %d' % 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' %
logger.debug('ripIfNotRipped have trackresult, path %r',
trackResult.filename)
path = self.program.getPath(self.program.outdir,
@@ -351,7 +359,7 @@ Log files will log the path to tracks relative to this directory.
self.mbdiscid,
self.program.metadata,
track_number=number) + '.flac'
logger.debug('ripIfNotRipped: path %r' % path)
logger.debug('ripIfNotRipped: path %r', path)
trackResult.number = number
assert isinstance(path, unicode), "%r is not unicode" % path
@@ -368,18 +376,18 @@ Log files will log the path to tracks relative to this directory.
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.debug('previous result %r, expected %r',
trackResult.filename, path)
sys.stdout.write('Verifying track %d of %d: %s\n' % (
number, len(self.itable.tracks),
os.path.basename(path).encode('utf-8')))
logger.info('verifying track %d of %d: %s',
number, len(self.itable.tracks),
os.path.basename(path).encode('utf-8'))
if not self.program.verifyTrack(self.runner, trackResult):
sys.stdout.write('Verification failed, reripping...\n')
logger.warning('verification failed, reripping...')
os.unlink(path)
if not os.path.exists(path):
logger.debug('path %r does not exist, ripping...' % path)
logger.debug('path %r does not exist, ripping...', path)
tries = 0
# we reset durations for test and copy here
trackResult.testduration = 0.0
@@ -389,9 +397,9 @@ Log files will log the path to tracks relative to this directory.
tries += 1
if tries > 1:
extra = " (try %d)" % tries
sys.stdout.write('Ripping track %d of %d%s: %s\n' % (
number, len(self.itable.tracks), extra,
os.path.basename(path).encode('utf-8')))
logger.info('ripping track %d of %d%s: %s',
number, len(self.itable.tracks), extra,
os.path.basename(path).encode('utf-8'))
try:
logger.debug('ripIfNotRipped: track %d, try %d',
number, tries)
@@ -399,7 +407,7 @@ Log files will log the path to tracks relative to this directory.
offset=int(self.options.offset),
device=self.device,
taglist=self.program.getTagList(
number),
number, self.mbdiscid),
overread=self.options.overread,
what='track %d of %d%s' % (
number,
@@ -407,43 +415,37 @@ Log files will log the path to tracks relative to this directory.
extra))
break
except Exception as e:
logger.debug('Got exception %r on try %d',
e, tries)
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))
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:
sys.stdout.write('CRCs match for track %d\n' % number)
logger.info('CRCs match for track %d', number)
else:
raise RuntimeError(
"CRCs did not match for track %d\n" % number
"CRCs did not match for track %d" % number
)
sys.stdout.write(
'Peak level: {}\n'.format(trackResult.peak))
sys.stdout.write(
'Rip quality: {:.2%}\n'.format(trackResult.quality))
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)
logger.debug('HTOA peak %r is equal to the SILENT '
'threshold, disregarding', trackResult.peak)
self.itable.setFile(1, 0, None,
self.ittoc.getTrackStart(1), number)
logger.debug('Unlinking %r', trackResult.filename)
logger.debug('unlinking %r', trackResult.filename)
os.unlink(trackResult.filename)
trackResult.filename = None
sys.stdout.write(
'HTOA discarded, contains digital silence\n')
logger.info('HTOA discarded, contains digital silence')
else:
self.itable.setFile(1, 0, trackResult.filename,
self.ittoc.getTrackStart(1), number)
@@ -457,14 +459,15 @@ Log files will log the path to tracks relative to this directory.
htoa = self.program.getHTOA()
if htoa:
start, stop = htoa
print('found Hidden Track One Audio from frame %d to %d' % (
start, stop))
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:
print('skipping data track %d, not implemented' % (i + 1))
logger.warning('skipping data track %d, not implemented',
i + 1)
# FIXME: make it work for now
track.indexes[1].relative = 0
continue
@@ -479,7 +482,7 @@ Log files will log the path to tracks relative to this directory.
try:
self.program.verifyImage(self.runner, self.ittoc)
except accurip.EntryNotFound:
print('AccurateRip entry not found')
logger.warning('AccurateRip entry not found')
accurip.print_report(self.program.result)

View File

@@ -18,8 +18,6 @@
# You should have received a copy of the GNU General Public License
# along with whipper. If not, see <http://www.gnu.org/licenses/>.
import sys
from whipper.command.basecommand import BaseCommand
from whipper.common import config, drive
from whipper.extern.task import task
@@ -40,24 +38,21 @@ class Analyze(BaseCommand):
runner.run(t)
if t.defeatsCache is None:
sys.stdout.write(
'Cannot analyze the drive. Is there a CD in it?\n')
logger.critical('cannot analyze the drive: is there a CD in it?')
return
if not t.defeatsCache:
sys.stdout.write(
'cdparanoia cannot defeat the audio cache on this drive.\n')
logger.info('cdparanoia cannot defeat the audio cache '
'on this drive')
else:
sys.stdout.write(
'cdparanoia can defeat the audio cache on this drive.\n')
logger.info('cdparanoia can defeat the audio cache on this drive')
info = drive.getDeviceInfo(self.options.device)
if not info:
sys.stdout.write('Drive caching behaviour not saved:'
'could not get device info (requires pycdio).\n')
logger.error('drive caching behaviour not saved: '
'could not get device info')
return
sys.stdout.write(
'Adding drive cache behaviour to configuration file.\n')
logger.info('adding drive cache behaviour to configuration file')
config.Config().setDefeatsCache(
info[0], info[1], info[2], t.defeatsCache)
@@ -72,48 +67,38 @@ class List(BaseCommand):
self.config = config.Config()
if not paths:
sys.stdout.write('No drives found.\n')
sys.stdout.write('Create /dev/cdrom if you have a CD drive, \n')
sys.stdout.write('or install pycdio for better detection.\n')
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:
sys.stdout.write(
'Install pycdio for vendor/model/release detection.\n')
logger.error('install pycdio for vendor/model/release detection')
return
for path in paths:
vendor, model, release = drive.getDeviceInfo(path)
sys.stdout.write(
"drive: %s, vendor: %s, model: %s, release: %s\n" % (
path, vendor, model, release))
print("drive: %s, vendor: %s, model: %s, release: %s" % (
path, vendor, model, release))
try:
offset = self.config.getReadOffset(
vendor, model, release)
sys.stdout.write(
" Configured read offset: %d\n" % offset)
print(" Configured read offset: %d" % offset)
except KeyError:
# Note spaces at the beginning for pretty terminal output
sys.stdout.write(" "
"No read offset found. "
"Run 'whipper offset find'\n")
logger.warning("no read offset found. "
"Run 'whipper offset find'")
try:
defeats = self.config.getDefeatsCache(
vendor, model, release)
sys.stdout.write(
" Can defeat audio cache: %s\n" % defeats)
print(" Can defeat audio cache: %s" % defeats)
except KeyError:
sys.stdout.write(
" Unknown whether audio cache can be defeated. "
"Run 'whipper drive analyze'\n")
if not paths:
sys.stdout.write('No drives found.\n')
logger.warning("unknown whether audio cache can be "
"defeated. Run 'whipper drive analyze'")
class Drive(BaseCommand):

View File

@@ -5,9 +5,9 @@ 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
@@ -19,23 +19,30 @@ logger = logging.getLogger(__name__)
def main():
try:
server = config.Config().get_musicbrainz_server()
except KeyError as e:
sys.stderr.write('whipper: %s\n' % str(e))
sys.exit()
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([directory.data_path('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:
sys.stderr.write('whipper: error: %s\n' % e)
logger.critical("SystemError: %s", e)
if (isinstance(e, common.EjectError) and
cmd.options.eject in ('failure', 'always')):
eject_device(e.device)
@@ -51,18 +58,17 @@ def main():
if isinstance(e.exception, ImportError):
raise ImportError(e.exception)
elif isinstance(e.exception, common.MissingDependencyException):
sys.stderr.write('whipper: error: missing dependency "%s"\n' %
e.exception.dependency)
logger.critical('missing dependency "%s"', e.exception.dependency)
return 255
if isinstance(e.exception, common.EmptyError):
logger.debug("EmptyError: %r", str(e.exception))
sys.stderr.write('whipper: error: Could not create encoded file.\n') # noqa: E501
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
sys.stderr.write(e.exceptionMessage)
logger.critical(e.exceptionMessage)
return 255
return ret if ret else 0

View File

@@ -20,7 +20,6 @@
import argparse
import os
import sys
import tempfile
import logging
from whipper.command.basecommand import BaseCommand
@@ -71,7 +70,7 @@ CD in the AccurateRip database."""
else:
self._offsets.append(int(b))
logger.debug('Trying with offsets %r', self._offsets)
logger.debug('trying with offsets %r', self._offsets)
def do(self):
runner = ctask.SyncRunner()
@@ -79,7 +78,7 @@ CD in the AccurateRip database."""
device = self.options.device
# if necessary, load and unmount
sys.stdout.write('Checking device %s\n' % device)
logger.info('checking device %s', device)
utils.load_device(device)
utils.unmount_device(device)
@@ -93,10 +92,12 @@ CD in the AccurateRip database."""
try:
responses = accurip.get_db_entry(table.accuraterip_path())
except accurip.EntryNotFound:
print('Accuraterip entry not found')
logger.warning("AccurateRip entry not found: drive offset "
"can't be determined, try again with another disc")
return
if responses:
logger.debug('%d AccurateRip responses found.' % len(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)
@@ -114,36 +115,32 @@ CD in the AccurateRip database."""
return None, None
for offset in self._offsets:
sys.stdout.write('Trying read offset %d ...\n' % offset)
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):
if isinstance(e.exception, common.MissingDependencyException):
raise e
if isinstance(e.exception, cdparanoia.FileSizeError):
sys.stdout.write(
'WARNING: cannot rip with offset %d...\n' % offset)
logger.warning('cannot rip with offset %d...', offset)
continue
logger.warning("Unknown task exception for offset %d: %r" % (
offset, e))
sys.stdout.write(
'WARNING: cannot rip with offset %d...\n' % offset)
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 %s' % archecksums)
logger.debug('AR checksums calculated: %s %s', archecksums)
c, i = match(archecksums, 1, responses)
if c:
count = 1
logger.debug('MATCHED against response %d' % i)
sys.stdout.write(
'Offset of device is likely %d, confirming ...\n' %
offset)
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
@@ -152,31 +149,30 @@ CD in the AccurateRip database."""
archecksums = self._arcs(runner, table, track, offset)
except task.TaskException as e:
if isinstance(e.exception, cdparanoia.FileSizeError):
sys.stdout.write(
'WARNING: cannot rip with offset %d...\n' %
offset)
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))
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:
sys.stdout.write(
'Only %d of %d tracks matched, continuing ...\n' % (
count, len(table.tracks)))
logger.warning('only %d of %d tracks matched, '
'continuing...', count,
len(table.tracks))
sys.stdout.write('No matching offset found.\n')
sys.stdout.write('Consider trying again with a different disc.\n')
logger.error('no matching offset found. '
'Consider trying again with a different disc')
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)
logger.debug('ripping track %r with offset %d...', track, offset)
fd, path = tempfile.mkstemp(
suffix=u'.track%02d.offset%d.whipper.wav' % (
@@ -203,17 +199,15 @@ CD in the AccurateRip database."""
return ("%08x" % v1, "%08x" % v2)
def _foundOffset(self, device, offset):
sys.stdout.write('\nRead offset of device is: %d.\n' %
offset)
print('\nRead offset of device is: %d.' % offset)
info = drive.getDeviceInfo(device)
if not info:
sys.stdout.write(
'Offset not saved: could not get '
'device info (requires pycdio).\n')
logger.error('offset not saved: '
'could not get device info (requires pycdio)')
return
sys.stdout.write('Adding read offset to configuration file.\n')
logger.info('adding read offset to configuration file')
config.Config().setReadOffset(info[0], info[1], info[2],
offset)

View File

@@ -107,17 +107,15 @@ def calculate_checksums(track_paths):
track_count = len(track_paths)
v1_checksums = []
v2_checksums = []
logger.debug('checksumming %d tracks' % track_count)
logger.debug('checksumming %d tracks', track_count)
# This is done sequentially because it is very fast.
for i, path in enumerate(track_paths):
v1_sum = accuraterip_checksum(
path, i+1, track_count, wave=True, v2=False
)
if not v1_sum:
logger.error(
'could not calculate AccurateRip v1 checksum for track %d %r' %
(i+1, path)
)
logger.error('could not calculate AccurateRip v1 checksum '
'for track %d %r', i + 1, path)
v1_checksums.append(None)
else:
v1_checksums.append("%08x" % v1_sum)
@@ -125,10 +123,8 @@ def calculate_checksums(track_paths):
path, i+1, track_count, wave=True, v2=True
)
if not v2_sum:
logger.error(
'could not calculate AccurateRip v2 checksum for track %d %r' %
(i+1, path)
)
logger.error('could not calculate AccurateRip v2 checksum '
'for track %d %r', i + 1, path)
v2_checksums.append(None)
else:
v2_checksums.append("%08x" % v2_sum)
@@ -141,12 +137,11 @@ def _download_entry(path):
try:
resp = requests.get(url)
except requests.exceptions.ConnectionError as e:
logger.error('error retrieving AccurateRip entry: %r' % e)
logger.error('error retrieving AccurateRip entry: %r', e)
return None
if not resp.ok:
logger.error('error retrieving AccurateRip entry: %s %s %r' % (
resp.status_code, resp.reason, resp
))
logger.error('error retrieving AccurateRip entry: %s %s %r',
resp.status_code, resp.reason, resp)
return None
return resp.content
@@ -158,7 +153,7 @@ def _save_entry(raw_entry, path):
makedirs(dirname(path))
except OSError as e:
if e.errno != EEXIST:
logger.error('could not save entry to %s: %r' % (path, str(e)))
logger.error('could not save entry to %s: %s', path, e)
return
open(path, 'wb').write(raw_entry)
@@ -211,10 +206,9 @@ def _match_responses(tracks, responses):
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'])
)
' 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])
@@ -240,7 +234,7 @@ def verify_result(result, responses, checksums):
def print_report(result):
"""
Print AccurateRip verification results to stdout.
Print AccurateRip verification results.
"""
for i, track in enumerate(result.tracks):
status = 'rip NOT accurate'
@@ -268,9 +262,7 @@ def print_report(result):
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
)
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]' % (

View File

@@ -87,7 +87,7 @@ class Persister:
handle.close()
# do an atomic move
shutil.move(path, self._path)
logger.debug('saved persisted object to %r' % self._path)
logger.debug('saved persisted object to %r', self._path)
def _unpickle(self, default=None):
self.object = default
@@ -103,7 +103,7 @@ class Persister:
try:
self.object = pickle.load(handle)
logger.debug('loaded persisted object from %r' % self._path)
logger.debug('loaded persisted object from %r', self._path)
except Exception as e:
# TODO: restrict kind of caught exceptions?
# can fail for various reasons; in that case, pretend we didn't
@@ -143,9 +143,8 @@ class PersistedCache:
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)
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()
@@ -216,12 +215,11 @@ class TableCache:
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()))
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)
logger.debug('no valid cached table found for %r', cddbdiscid)
if not ptable.object:
# get an empty persistable from the writable location

View File

@@ -22,6 +22,7 @@
import os
import os.path
import math
import re
import subprocess
import unicodedata
@@ -262,8 +263,8 @@ def getRelativePath(targetPath, collectionPath):
Used to determine the path to use in .cue/.m3u files
"""
logger.debug('getRelativePath: target %r, collection %r' % (
targetPath, collectionPath))
logger.debug('getRelativePath: target %r, collection %r',
targetPath, collectionPath)
targetDir = os.path.dirname(targetPath)
collectionDir = os.path.dirname(collectionPath)
@@ -274,12 +275,24 @@ def getRelativePath(targetPath, collectionPath):
rel = os.path.relpath(
targetDir + os.path.sep,
collectionDir + os.path.sep)
logger.debug(
'getRelativePath: target and collection in different dir, %r' % rel
)
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'%[^A,R,S,X,d,r,x,y]', template)
elif kind == 'track':
matches = re.findall(r'%[^A,R,S,X,a,d,n,r,s,t,x,y]', template)
if '%' in template and matches:
raise ValueError(kind + ' template string contains invalid '
'variable(s): {}'.format(', '.join(matches)))
class VersionGetter(object):
"""
I get the version of a program by looking for it in command output

View File

@@ -47,8 +47,8 @@ class Config:
with codecs.open(self._path, 'r', encoding='utf-8') as f:
self._parser.readfp(f)
logger.info('Loaded %d sections from config file' %
len(self._parser.sections()))
logger.debug('loaded %d sections from config file',
len(self._parser.sections()))
def write(self):
fd, path = tempfile.mkstemp(suffix=u'.whipperrc')
@@ -130,14 +130,13 @@ class Config:
if not name.startswith('drive:'):
continue
logger.debug('Looking at section %r' % name)
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]
))
logger.debug("%s: '%s' versus '%s'",
key, locals()[key], conf[key])
if vendor.strip() != conf['vendor']:
continue
if model.strip() != conf['model']:

View File

@@ -36,7 +36,7 @@ def getAllDevicePaths():
# see https://savannah.gnu.org/bugs/index.php?38477
return [str(dev) for dev in _getAllDevicePathsPyCdio()]
except ImportError:
logger.info('Cannot import pycdio')
logger.info('cannot import pycdio')
return _getAllDevicePathsStatic()

View File

@@ -93,7 +93,7 @@ def _record(record, which, name, what):
handle = open(filename, 'w')
handle.write(json.dumps(what))
handle.close()
logger.info('Wrote %s %s to %s', which, name, filename)
logger.info('wrote %s %s to %s', which, name, filename)
# credit is of the form [dict, str, dict, ... ]
# e.g. [
@@ -152,10 +152,9 @@ def _getMetadata(releaseShort, release, discid, country=None):
@rtype: L{DiscMetadata} or None
"""
logger.debug('getMetadata for release id %r',
release['id'])
logger.debug('getMetadata for release id %r', release['id'])
if not release['id']:
logger.warning('No id for release %r', release)
logger.warning('no id for release %r', release)
return None
assert release['id'], 'Release does not have an id'
@@ -183,7 +182,7 @@ def _getMetadata(releaseShort, release, discid, country=None):
discMD.artist = albumArtistName
discMD.sortName = discCredit.getSortName()
if 'date' not in release:
logger.warning("Release with ID '%s' (%s - %s) does not have a date",
logger.warning("release with ID '%s' (%s - %s) does not have a date",
release['id'], discMD.artist, release['title'])
else:
discMD.release = release['date']
@@ -235,9 +234,8 @@ def _getMetadata(releaseShort, release, discid, country=None):
# 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))
logger.warning('track %r (%r) does not have duration',
track.title, track.mbid)
tainted = True
else:
duration += track.duration
@@ -271,7 +269,7 @@ def musicbrainz(discid, country=None, record=False):
import musicbrainzngs
musicbrainzngs.set_useragent("whipper", whipper.__version__,
"https://github.com/JoeLametta/whipper")
"https://github.com/whipper-team/whipper")
ret = []
try:
@@ -297,8 +295,8 @@ def musicbrainz(discid, country=None, record=False):
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']))
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
@@ -309,7 +307,7 @@ def musicbrainz(discid, country=None, record=False):
_record(record, 'release', release['id'], res)
releaseDetail = res['release']
formatted = json.dumps(releaseDetail, sort_keys=False, indent=4)
logger.debug('release %s' % formatted)
logger.debug('release %s', formatted)
md = _getMetadata(release, releaseDetail, discid, country)
if md:

View File

@@ -25,7 +25,6 @@ Common functionality and class for all programs using whipper.
import musicbrainzngs
import re
import os
import sys
import time
from whipper.common import accurip, cache, checksum, common, mbngs, path
@@ -59,15 +58,12 @@ class Program:
outdir = None
result = None
_stdout = None
def __init__(self, config, record=False, stdout=sys.stdout):
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._stdout = stdout
self._config = config
d = {}
@@ -87,7 +83,7 @@ class Program:
def setWorkingDirectory(self, workingDirectory):
if workingDirectory:
logger.info('Changing to working directory %s' % workingDirectory)
logger.info('changing to working directory %s', workingDirectory)
os.chdir(workingDirectory)
def getFastToc(self, runner, device):
@@ -97,14 +93,14 @@ class Program:
from pkg_resources import parse_version as V
version = cdrdao.getCDRDAOVersion()
if V(version) < V('1.2.3rc2'):
sys.stdout.write('Warning: cdrdao older than 1.2.3 has a '
'pre-gap length bug.\n'
'See http://sourceforge.net/tracker/?func=detail&aid=604751&group_id=2171&atid=102171\n') # noqa: E501
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
toc = cdrdao.ReadTOCTask(device).table
assert toc.hasTOC()
return toc
def getTable(self, runner, cddbdiscid, mbdiscid, device, offset):
def getTable(self, runner, cddbdiscid, mbdiscid, device, offset,
out_path):
"""
Retrieve the Table either from the cache or the drive.
@@ -123,24 +119,24 @@ class Program:
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.ReadTableTask(device)
logger.debug('getTable: cddbdiscid %s, mbdiscid %s not in cache '
'for offset %s, reading table', cddbdiscid, mbdiscid,
offset)
t = cdrdao.ReadTableTask(device, out_path)
itable = t.table
tdict[offset] = itable
ptable.persist(tdict)
logger.debug('getTable: read table %r' % itable)
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)
'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' %
logger.debug('getTable: returning table with mb id %s',
itable.getMusicBrainzDiscId())
return itable
@@ -252,12 +248,12 @@ class Program:
return [item['DTITLE'] for item in md if 'DTITLE' in item] or None
except ValueError as e:
self._stdout.write("WARNING: CDDB protocol error: %s\n" % 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':
self._stdout.write("WARNING: CDDB network error: %r\n" % (e, ))
logger.warning("CDDB network error: %r", (e, ))
else:
raise
@@ -269,7 +265,7 @@ class Program:
@type ittoc: L{whipper.image.table.Table}
"""
# look up disc on MusicBrainz
self._stdout.write('Disc duration: %s, %d audio tracks\n' % (
print('Disc duration: %s, %d audio tracks' % (
common.formatTime(ittoc.duration() / 1000.0),
ittoc.getAudioTracks()))
logger.debug('MusicBrainz submit url: %r',
@@ -286,41 +282,37 @@ class Program:
record=self._record)
break
except mbngs.NotFoundException as e:
logger.warning("release not found: %r" % (e, ))
logger.warning("release not found: %r", (e, ))
break
except musicbrainzngs.NetworkError as e:
logger.warning("network error: %r" % (e, ))
logger.warning("network error: %r", (e, ))
break
except mbngs.MusicBrainzException as e:
logger.warning("musicbrainz exception: %r" % (e, ))
logger.warning("musicbrainz exception: %r", (e, ))
time.sleep(5)
continue
if not metadatas:
self._stdout.write('Continuing without metadata\n')
logger.warning('continuing without metadata')
if metadatas:
deltas = {}
self._stdout.write('\nMatching releases:\n')
print('\nMatching releases:')
for metadata in metadatas:
self._stdout.write('\n')
self._stdout.write('Artist : %s\n' %
metadata.artist.encode('utf-8'))
self._stdout.write('Title : %s\n' %
metadata.title.encode('utf-8'))
self._stdout.write('Duration: %s\n' %
common.formatTime(metadata.duration /
1000.0))
self._stdout.write('URL : %s\n' % metadata.url)
self._stdout.write('Release : %s\n' % metadata.mbid)
self._stdout.write('Type : %s\n' % metadata.releaseType)
print('\nArtist : %s' % metadata.artist.encode('utf-8'))
print('Title : %s' % metadata.title.encode('utf-8'))
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:
self._stdout.write("Barcode : %s\n" % metadata.barcode)
print("Barcode : %s" % metadata.barcode)
if metadata.catalogNumber:
self._stdout.write("Cat no : %s\n" %
metadata.catalogNumber)
print("Cat no : %s" %
metadata.catalogNumber.encode('utf-8'))
delta = abs(metadata.duration - ittoc.duration())
if delta not in deltas:
@@ -343,20 +335,15 @@ class Program:
if release:
metadatas = [m for m in metadatas if m.url.endswith(release)]
logger.debug('Asked for release %r, only kept %r',
release, metadatas)
logger.debug('asked for release %r, only kept %r', release,
metadatas)
if len(metadatas) == 1:
self._stdout.write('\n')
self._stdout.write('Picked requested release id %s\n' %
release)
self._stdout.write('Artist : %s\n' %
metadatas[0].artist.encode('utf-8'))
self._stdout.write('Title : %s\n' %
metadatas[0].title.encode('utf-8'))
logger.info('picked requested release id %s', release)
print('Artist: %s' % metadatas[0].artist.encode('utf-8'))
print('Title : %s' % metadatas[0].title.encode('utf-8'))
elif not metadatas:
self._stdout.write(
"Requested release id '%s', "
"but none of the found releases match\n" % release)
logger.warning("requested release id '%s', but none of "
"the found releases match", release)
return
else:
if lowest:
@@ -368,36 +355,31 @@ class Program:
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))
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))
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):
self._stdout.write('\n')
self._stdout.write('Picked closest match in duration.\n')
self._stdout.write('Others may be wrong in MusicBrainz, '
'please correct.\n')
self._stdout.write('Artist : %s\n' %
artist.encode('utf-8'))
self._stdout.write('Title : %s\n' %
metadatas[0].title.encode('utf-8'))
logger.warning('picked closest match in duration. '
'Others may be wrong in MusicBrainz, '
'please correct')
print('Artist : %s' % artist.encode('utf-8'))
print('Title : %s' % metadatas[0].title.encode('utf-8'))
# Select one of the returned releases. We just pick the first one.
ret = metadatas[0]
else:
self._stdout.write(
'Submit this disc to MusicBrainz at the above URL.\n')
print('Submit this disc to MusicBrainz at the above URL.')
ret = None
self._stdout.write('\n')
print('')
return ret
def getTagList(self, number):
def getTagList(self, number, mbdiscid):
"""
Based on the metadata, get a dict of tags for the given track.
@@ -417,7 +399,6 @@ class Program:
disc = self.metadata.title
mbidAlbum = self.metadata.mbid
mbidTrackAlbum = self.metadata.mbidArtist
mbDiscId = self.metadata.discid
if number > 0:
try:
@@ -427,7 +408,7 @@ class Program:
mbidTrack = track.mbid
mbidTrackArtist = track.mbidArtist
except IndexError as e:
print('ERROR: no track %d found, %r' % (number, e))
logger.error('no track %d found, %r', number, e)
raise
else:
# htoa defaults to disc's artist
@@ -435,6 +416,9 @@ class Program:
tags = {}
if number > 0:
tags['MUSICBRAINZ_DISCID'] = mbdiscid
if self.metadata and not self.metadata.various:
tags['ALBUMARTIST'] = albumArtist
tags['ARTIST'] = trackArtist
@@ -452,7 +436,6 @@ class Program:
tags['MUSICBRAINZ_ARTISTID'] = mbidTrackArtist
tags['MUSICBRAINZ_ALBUMID'] = mbidAlbum
tags['MUSICBRAINZ_ALBUMARTISTID'] = mbidTrackAlbum
tags['MUSICBRAINZ_DISCID'] = mbDiscId
# TODO/FIXME: ISRC tag
@@ -482,15 +465,14 @@ class Program:
runner.run(t)
except task.TaskException as e:
if isinstance(e.exception, common.MissingFrames):
logger.warning('missing frames for %r' % trackResult.filename)
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)
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,
@@ -526,10 +508,10 @@ class Program:
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))
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
@@ -542,7 +524,7 @@ class Program:
if trackResult.filename != t.path:
trackResult.filename = t.path
logger.info('Filename changed to %r', trackResult.filename)
logger.info('filename changed to %r', trackResult.filename)
def verifyImage(self, runner, table):
"""
@@ -563,7 +545,7 @@ class Program:
return False
responses = accurip.get_db_entry(table.accuraterip_path())
logger.info('%d AccurateRip response(s) found' % len(responses))
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)

View File

@@ -51,8 +51,7 @@ class PopenTask(task.Task):
raise
logger.debug('Started %r with pid %d', self.command,
self._popen.pid)
logger.debug('started %r with pid %d', self.command, self._popen.pid)
self.schedule(1.0, self._read, runner)
@@ -89,7 +88,7 @@ class PopenTask(task.Task):
self._done()
except Exception as e:
logger.debug('exception during _read(): %r', str(e))
logger.debug('exception during _read(): %s', e)
self.setException(e)
self.stop()
@@ -97,10 +96,9 @@ class PopenTask(task.Task):
assert self._popen.returncode is not None, "No returncode"
if self._popen.returncode >= 0:
logger.debug('Return code was %d', self._popen.returncode)
logger.debug('return code was %d', self._popen.returncode)
else:
logger.debug('Terminated with signal %d',
-self._popen.returncode)
logger.debug('terminated with signal %d', -self._popen.returncode)
self.setProgress(1.0)
@@ -113,7 +111,7 @@ class PopenTask(task.Task):
return
def abort(self):
logger.debug('Aborting, sending SIGTERM to %d', self._popen.pid)
logger.debug('aborting, sending SIGTERM to %d', self._popen.pid)
os.kill(self._popen.pid, signal.SIGTERM)
# self.stop()

View File

@@ -18,6 +18,7 @@
# You should have received a copy of the GNU General Public License
# along with whipper. If not, see <http://www.gnu.org/licenses/>.
from __future__ import print_function
import logging
import sys
@@ -166,7 +167,8 @@ class Task(LogStub):
value >= 1.0 or value == 0.0):
self.progress = value
self._notifyListeners('progressed', value)
self.log('notifying progress: %r on %r', value, self.description)
self.debug('notifying progress: %r on %r',
value, self.description)
def setDescription(self, description):
if description != self.description:
@@ -366,23 +368,23 @@ class BaseMultiTask(Task, ITaskListener):
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.log('BaseMultiTask.stopped: task %r (%d of %d)',
task, self.tasks.index(task) + 1, len(self.tasks))
self.debug('BaseMultiTask.stopped: task %r (%d of %d)',
task, self.tasks.index(task) + 1, len(self.tasks))
if task.exception:
self.log('BaseMultiTask.stopped: exception %r',
task.exceptionMessage)
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.log('BaseMultiTask.stopped: all tasks done')
self.debug('BaseMultiTask.stopped: all tasks done')
self.stop()
return
# pick another
self.log('BaseMultiTask.stopped: pick next task')
self.debug('BaseMultiTask.stopped: pick next task')
self.schedule(0, self.next)
@@ -511,8 +513,8 @@ class SyncRunner(TaskRunner, ITaskListener):
def schedule(self, task, delta, callable, *args, **kwargs):
def c():
try:
self.log('schedule: calling %r(*args=%r, **kwargs=%r)',
callable, args, kwargs)
self.debug('schedule: calling %r(*args=%r, **kwargs=%r)',
callable, args, kwargs)
callable(*args, **kwargs)
return False
except Exception as e:
@@ -521,8 +523,8 @@ class SyncRunner(TaskRunner, ITaskListener):
task.setException(e)
self.stopped(task)
raise
self.log('schedule: scheduling %r(*args=%r, **kwargs=%r)',
callable, args, kwargs)
self.debug('schedule: scheduling %r(*args=%r, **kwargs=%r)',
callable, args, kwargs)
gobject.timeout_add(int(delta * 1000L), c)
@@ -539,15 +541,15 @@ class SyncRunner(TaskRunner, ITaskListener):
self._task.description, 100.0))
else:
# clear with whitespace
sys.stdout.write("%s\r" % (' ' * self._longest, ))
print(("%s\r" % (' ' * self._longest, )), end='')
def _output(self, what, newline=False, ret=True):
sys.stdout.write(what)
sys.stdout.write(' ' * (self._longest - len(what)))
print(what, end='')
print((' ' * (self._longest - len(what))), end='')
if ret:
sys.stdout.write('\r')
print('\r', end='')
if newline:
sys.stdout.write('\n')
print('')
sys.stdout.flush()
if len(what) > self._longest:
self._longest = len(what)

View File

@@ -85,7 +85,7 @@ class CueFile(object):
currentTrack = None
counter = 0
logger.info('Parsing .cue file %r', self._path)
logger.info('parsing .cue file %r', self._path)
handle = codecs.open(self._path, 'r', 'utf-8')
for number, line in enumerate(handle.readlines()):

View File

@@ -135,7 +135,7 @@ class ImageVerifyTask(task.MultiSeparateTask):
self.addTask(taskk)
self._tasks.append((0, track, taskk))
except (KeyError, IndexError):
logger.debug('no htoa track')
logger.debug('no HTOA track')
for trackIndex, track in enumerate(cue.table.tracks):
logger.debug('verifying track %d', trackIndex + 1)
@@ -155,8 +155,8 @@ class ImageVerifyTask(task.MultiSeparateTask):
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))
logger.debug('subtask %r had exception %r, shutting down',
taskk, taskk.exception)
self.setException(taskk.exception)
break
@@ -195,17 +195,16 @@ class ImageEncodeTask(task.MultiSeparateTask):
root, ext = 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'))
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')
logger.debug('encoding HTOA track')
add(htoa)
except (KeyError, IndexError):
logger.debug('no htoa track')
logger.debug('no HTOA track')
pass
for trackIndex, track in enumerate(cue.table.tracks):

View File

@@ -333,8 +333,8 @@ class Table(object):
@returns: the 28-character base64-encoded disc ID
"""
if self.mbdiscid:
logger.debug('getMusicBrainzDiscId: returning cached %r'
% self.mbdiscid)
logger.debug('getMusicBrainzDiscId: returning cached %r',
self.mbdiscid)
return self.mbdiscid
values = self._getMusicBrainzValues()
@@ -381,7 +381,7 @@ class Table(object):
assert len(result) == 28, \
"Result should be 28 characters, not %d" % len(result)
logger.debug('getMusicBrainzDiscId: returning %r' % result)
logger.debug('getMusicBrainzDiscId: returning %r', result)
self.mbdiscid = result
return result
@@ -489,7 +489,7 @@ class Table(object):
targetPath = common.getRelativePath(path, cuePath)
line = 'FILE "%s" WAVE' % targetPath
lines.append(line)
logger.debug('writeFile: %r' % line)
logger.debug('writeFile: %r', line)
# header
main = ['PERFORMER', 'TITLE']
@@ -530,11 +530,11 @@ class Table(object):
counter = index.counter
if index.path:
logger.debug('counter %d, writeFile' % counter)
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))
logger.debug('track i %r, track %r', i, track)
# FIXME: skip data tracks for now
if not track.audio:
continue
@@ -545,7 +545,7 @@ class Table(object):
for number in indexes:
index = track.indexes[number]
logger.debug('index %r, %r' % (number, index))
logger.debug('index %r, %r', number, index)
# any time the source counter changes to a higher value,
# write a FILE statement
@@ -553,9 +553,9 @@ class Table(object):
# at counter 0 here
if index.counter > counter:
if index.path:
logger.debug('counter %d, writeFile' % counter)
logger.debug('counter %d, writeFile', counter)
writeFile(index.path)
logger.debug('setting counter to index.counter %r' %
logger.debug('setting counter to index.counter %r',
index.counter)
counter = index.counter
@@ -564,7 +564,7 @@ class Table(object):
wroteTrack = True
line = " TRACK %02d %s" % (i + 1, 'AUDIO')
lines.append(line)
logger.debug('%r' % line)
logger.debug('%r', line)
for key in CDTEXT_FIELDS:
if key in track.cdtext:
@@ -620,7 +620,7 @@ class Table(object):
while True:
track = self.tracks[t - 1]
index = track.getIndex(i)
logger.debug('Clearing path on track %d, index %d', t, i)
logger.debug('clearing path on track %d, index %d', t, i)
index.path = None
index.relative = None
try:
@@ -639,9 +639,8 @@ class Table(object):
@type track: C{int}
@type index: C{int}
"""
logger.debug('setFile: track %d, index %d, path %r, '
'length %r, counter %r', track, index, path, length,
counter)
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]
@@ -654,9 +653,9 @@ class Table(object):
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)
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]
@@ -682,13 +681,13 @@ class Table(object):
assert track.number == t
assert index.number == i
if index.counter is None:
logger.debug('Track %d, index %d has no counter', t, i)
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)
logger.debug('track %d, index %d has a different counter',
t, i)
break
logger.debug('Setting absolute offset %d on track %d, index %d',
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:
@@ -722,18 +721,16 @@ class Table(object):
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))
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))
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)
logger.debug('fixing leadout, now %d', self.leadout)
def _getSessionGap(self, session):
# From cdrecord multi-session info:
@@ -841,13 +838,13 @@ class Table(object):
Check if this table can be used to generate a .cue file
"""
if not self.hasTOC():
logger.debug('No TOC, cannot cue')
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 '
logger.debug('track %02d, Index %02d does not '
'have relative', t.number, i.number)
return False

View File

@@ -109,8 +109,8 @@ class Sources:
@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))
logger.debug('appending source, counter %d, abs offset %d, '
'source %r', counter, offset, source)
self._sources.append((counter, offset, source))
def get(self, offset):
@@ -152,8 +152,8 @@ class TocFile(object):
absolute = absoluteOffset + trackOffset
# this may be in a new source, so calculate relative
c, o, s = self._sources.get(absolute)
logger.debug('at abs offset %d, we are in source %r' % (
absolute, s))
logger.debug('at abs offset %d, we are in source %r',
absolute, s)
counterStart = self._sources.getCounterStart(c)
relative = absolute - counterStart
@@ -161,10 +161,9 @@ class TocFile(object):
absolute=absolute,
relative=relative,
counter=c)
logger.debug(
'[track %02d index %02d] trackOffset %r, added %r',
currentTrack.number, i, trackOffset,
currentTrack.getIndex(i))
logger.debug('[track %02d index %02d] trackOffset %r, added %r',
currentTrack.number, i, trackOffset,
currentTrack.getIndex(i))
def parse(self):
currentFile = None
@@ -209,11 +208,11 @@ class TocFile(object):
# 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)
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',
logger.debug('found track CD-Text %s: %r',
key, value)
currentTrack.cdtext[key] = value
@@ -221,7 +220,7 @@ class TocFile(object):
m = _CATALOG_RE.search(line)
if m:
self.table.catalog = m.group('catalog')
logger.debug("Found catalog number %s", self.table.catalog)
logger.debug("found catalog number %s", self.table.catalog)
# look for TRACK lines
m = _TRACK_RE.search(line)
@@ -260,23 +259,23 @@ class TocFile(object):
m = _PRE_EMPHASIS_RE.search(line)
if m:
currentTrack.pre_emphasis = True
logger.debug('Track has PRE_EMPHASIS')
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)
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)
logger.debug('silence of %r', length)
self._sources.append(counter, absoluteOffset, None)
if currentFile is not None:
logger.debug('SILENCE after FILE, increasing counter')
logger.debug('silence after file, increasing counter')
counter += 1
relativeOffset = 0
currentFile = None
@@ -286,7 +285,7 @@ class TocFile(object):
m = _ZERO_RE.search(line)
if m:
if currentFile is not None:
logger.debug('ZERO after FILE, increasing counter')
logger.debug('zero after file, increasing counter')
counter += 1
relativeOffset = 0
currentFile = None
@@ -299,13 +298,13 @@ class TocFile(object):
filePath = m.group('name')
start = m.group('start')
length = m.group('length')
logger.debug('FILE %s, start %r, length %r',
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, '
logger.debug('track %d, switched to new file, '
'increased counter to %d',
trackNumber, counter)
currentFile = File(filePath, common.msfToFrames(start),
@@ -319,12 +318,12 @@ class TocFile(object):
if m:
filePath = m.group('name')
length = m.group('length')
logger.debug('FILE %s, length %r',
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, '
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
@@ -343,8 +342,8 @@ class TocFile(object):
length = common.msfToFrames(m.group('length'))
c, o, s = self._sources.get(absoluteOffset)
logger.debug('at abs offset %d, we are in source %r' % (
absoluteOffset, s))
logger.debug('at abs offset %d, we are in source %r',
absoluteOffset, s)
counterStart = self._sources.getCounterStart(c)
relativeOffset = absoluteOffset - counterStart

View File

@@ -36,17 +36,13 @@ def accuraterip_checksum(f, track_number, total_tracks, wave=False, v2=False):
if not wave:
flac.wait()
if flac.returncode != 0:
logger.warning(
'ARC calculation failed: flac return code is non zero: %r' %
flac.returncode
)
logger.warning('ARC calculation failed: flac '
'return code is non zero: %r', flac.returncode)
return None
if arc.returncode != 0:
logger.warning(
'ARC calculation failed: arc return code is non zero: %r' %
arc.returncode
)
logger.warning('ARC calculation failed: '
'arc return code is non zero: %r', arc.returncode)
return None
try:

View File

@@ -121,8 +121,8 @@ class ProgressParser:
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))
logger.debug('THOMAS: not a multiple of %d: %d',
common.WORDS_PER_FRAME, wordOffset)
return
frameOffset = wordOffset / common.WORDS_PER_FRAME
@@ -190,18 +190,19 @@ class ProgressParser:
"""
frames = self.stop - self.start + 1 # + 1 since stop is inclusive
reads = self.reads
logger.debug('getTrackQuality: frames %d, reads %d' % (frames, reads))
logger.debug('getTrackQuality: frames %d, reads %d', frames, reads)
# don't go over a 100%; we know cdparanoia reads each frame at least
# twice
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:
return 0
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.
@@ -271,12 +272,11 @@ class ReadTrackTask(task.Task):
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)
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:
@@ -291,7 +291,7 @@ class ReadTrackTask(task.Task):
startTrack, common.framesToHMSF(startOffset),
stopTrack, common.framesToHMSF(stopOffset)),
self.path])
logger.debug('Running %s' % (" ".join(argv), ))
logger.debug('running %s', (" ".join(argv), ))
try:
self._popen = asyncsub.Popen(argv,
bufsize=bufsize,
@@ -371,7 +371,7 @@ class ReadTrackTask(task.Task):
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' % (
logger.warning('%d frames difference', (
(size - expected) / common.BYTES_PER_FRAME))
else:
logger.warning('non-integral amount of frames difference')
@@ -451,7 +451,7 @@ class ReadVerifyTrackTask(task.MultiSeparateTask):
"""
task.MultiSeparateTask.__init__(self)
logger.debug('Creating read and verify task on %r', path)
logger.debug('creating read and verify task on %r', path)
if taglist:
logger.debug('read and verify with taglist %r', taglist)
@@ -520,12 +520,12 @@ class ReadVerifyTrackTask(task.MultiSeparateTask):
self.testchecksum = c1 = self.tasks[1].checksum
self.copychecksum = c2 = self.tasks[3].checksum
if c1 == c2:
logger.info('Checksums match, %08x' % c1)
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))
logger.info('checksums do not match, %08x %08x',
c1, c2)
self.exception = ChecksumException(
'read and verify failed: test checksum')
@@ -538,11 +538,11 @@ class ReadVerifyTrackTask(task.MultiSeparateTask):
if not self.exception:
try:
logger.debug('Moving to final path %r', self.path)
logger.debug('moving to final path %r', self.path)
os.rename(self._tmppath, self.path)
except Exception as e:
logger.debug('Exception while moving to final '
'path %r: %r', self.path, str(e))
logger.debug('exception while moving to final '
'path %r: %s', self.path, e)
self.exception = e
else:
os.unlink(self._tmppath)

View File

@@ -1,9 +1,10 @@
import os
import re
import shutil
import tempfile
from subprocess import Popen, PIPE
from whipper.common.common import EjectError
from whipper.common.common import EjectError, truncate_filename
from whipper.image.toc import TocFile
import logging
@@ -12,7 +13,7 @@ logger = logging.getLogger(__name__)
CDRDAO = 'cdrdao'
def read_toc(device, fast_toc=False):
def read_toc(device, fast_toc=False, toc_path=None):
"""
Return cdrdao-generated table of contents for 'device'.
"""
@@ -43,6 +44,14 @@ def read_toc(device, fast_toc=False):
toc = TocFile(tocfile)
toc.parse()
if toc_path is not None:
t_comp = os.path.abspath(toc_path).split(os.sep)
t_dirn = os.sep.join(t_comp[:-1])
# If the output path doesn't exist, make it recursively
if not os.path.isdir(t_dirn):
os.makedirs(t_dirn)
t_dst = truncate_filename(os.path.join(t_dirn, t_comp[-1] + '.toc'))
shutil.copy(tocfile, os.path.join(t_dirn, t_dst))
os.unlink(tocfile)
return toc
@@ -68,7 +77,7 @@ def version():
out, err = cdrdao.communicate()
if cdrdao.returncode != 1:
logger.warning("cdrdao version detection failed: "
"return code is " + str(cdrdao.returncode))
"return code is %s", cdrdao.returncode)
return None
m = re.compile(r'^Cdrdao version (?P<version>.*) - \(C\)').search(
err.decode('utf-8'))
@@ -86,11 +95,11 @@ def ReadTOCTask(device):
return read_toc(device, fast_toc=True)
def ReadTableTask(device):
def ReadTableTask(device, toc_path=None):
"""
stopgap morituri-insanity compatibility layer
"""
return read_toc(device)
return read_toc(device, toc_path=toc_path)
def getCDRDAOVersion():

View File

@@ -20,7 +20,7 @@ def peak_level(track_path):
sox = Popen([SOX, track_path, "-n", "stats", "-b", "16"], stderr=PIPE)
out, err = sox.communicate()
if sox.returncode:
logger.warning("SoX peak detection failed: " + str(sox.returncode))
logger.warning("SoX peak detection failed: %s", sox.returncode)
return None
# relevant captured lines looks like this:
# Min level -26215

View File

@@ -89,7 +89,7 @@ class WhipperLogger(result.Logger):
htoastart = htoa.absolute
htoaend = table.getTrackEnd(0)
htoalength = table.tracks[0].getIndex(1).absolute - htoastart
lines.append(" 00:")
lines.append(" 0:")
lines.append(" Start: %s" % common.framesToMSF(htoastart))
lines.append(" Length: %s" % common.framesToMSF(htoalength))
lines.append(" Start sector: %d" % htoastart)
@@ -103,7 +103,7 @@ class WhipperLogger(result.Logger):
start = t.getIndex(1).absolute
length = table.getTrackLength(t.number)
end = table.getTrackEnd(t.number)
lines.append(" %02d:" % t.number)
lines.append(" %d:" % t.number)
lines.append(" Start: %s" % common.framesToMSF(start))
lines.append(" Length: %s" % common.framesToMSF(length))
lines.append(" Start sector: %d" % start)
@@ -166,7 +166,7 @@ class WhipperLogger(result.Logger):
lines = []
# Track number
lines.append(" %02d:" % trackResult.number)
lines.append(" %d:" % trackResult.number)
# Filename (including path) of ripped track
lines.append(" Filename: %s" % trackResult.filename)

View File

@@ -78,8 +78,8 @@ class TestAccurateRipResponse(TestCase):
self.assertEqual(responses[1].discId1, '0000f21c')
self.assertEqual(responses[1].discId2, '00027ef8')
self.assertEqual(responses[1].cddbDiscId, '05021002')
self.assertEqual(responses[1].confidences[0], 4)
self.assertEqual(responses[1].confidences[1], 4)
self.assertEqual(responses[1].confidences[0], 5)
self.assertEqual(responses[1].confidences[1], 5)
self.assertEqual(responses[1].checksums[0], 'dc77f9ab')
self.assertEqual(responses[1].checksums[1], 'dd97d2c3')
@@ -203,7 +203,7 @@ class TestVerifyResult(TestCase):
'v2': {
'CRC': 'dc77f9ab',
'DBCRC': 'dc77f9ab',
'DBConfidence': 4,
'DBConfidence': 5,
},
'DBMaxConfidence': 12,
'DBMaxConfidenceCRC': '284fc705',
@@ -217,7 +217,7 @@ class TestVerifyResult(TestCase):
'v2': {
'CRC': 'dd97d2c3',
'DBCRC': 'dd97d2c3',
'DBConfidence': 4,
'DBConfidence': 5,
},
'DBMaxConfidence': 20,
'DBMaxConfidenceCRC': '9cc1f32e',

View File

@@ -117,7 +117,7 @@ class MetadataTestCase(unittest.TestCase):
check the received metadata for artists tagged with [unknown]
and artists tagged with an alias in MusicBrainz
see https://github.com/JoeLametta/whipper/issues/155
see https://github.com/whipper-team/whipper/issues/155
"""
filename = 'whipper.release.38b05c7d-65fe-4dc0-9c10-33a391b86703.json'
path = os.path.join(os.path.dirname(__file__), filename)