Merge branch 'develop'

This commit is contained in:
JoeLametta
2019-12-04 13:29:08 +00:00
49 changed files with 578 additions and 660 deletions

19
.github/config.yml vendored
View File

@@ -1,19 +0,0 @@
# 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
View File

@@ -1,42 +0,0 @@
# 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

16
.github/workflows/greetings.yml vendored Normal file
View File

@@ -0,0 +1,16 @@
name: Greetings
on: [pull_request, issues]
jobs:
greeting:
runs-on: ubuntu-latest
steps:
- uses: actions/first-interaction@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
issue-message: |
👋 Thanks for opening your first issue here! If you're reporting a 🐞 bug, please make sure you include steps to reproduce it. We get a lot of issues on this repo, so please be patient and we will get back to you as soon as we can.
To help make it easier for us to investigate your issue, please follow the [contributing instructions](https://github.com/whipper-team/whipper#bug-reports--feature-requests).
pr-message: '💖 Thanks for opening your first pull request here! 💖'

View File

@@ -3,7 +3,7 @@ sudo: required
language: python
python:
- "2.7"
- "3.5"
virtualenv:
system_site_packages: false

View File

@@ -2,11 +2,38 @@
## [Unreleased](https://github.com/whipper-team/whipper/tree/HEAD)
[Full Changelog](https://github.com/whipper-team/whipper/compare/v0.8.0...HEAD)
[Full Changelog](https://github.com/whipper-team/whipper/compare/v0.9.0...HEAD)
## [v0.9.0](https://github.com/whipper-team/whipper/tree/v0.9.0) (2019-11-04)
[Full Changelog](https://github.com/whipper-team/whipper/compare/v0.8.0...v0.9.0)
**Fixed bugs:**
- Fix regression introduced due to Python 3 port [\#424](https://github.com/whipper-team/whipper/issues/424)
- Properly tagging releases on dockerhub [\#423](https://github.com/whipper-team/whipper/issues/423)
- Test failure when building a release [\#420](https://github.com/whipper-team/whipper/issues/420)
- Dockerfile is missing ruamel.yaml [\#419](https://github.com/whipper-team/whipper/issues/419)
- Port to Python 3 [\#78](https://github.com/whipper-team/whipper/issues/78)
**Closed issues:**
- Why is CD-Text if found not used for naming Disk and Tracks? [\#397](https://github.com/whipper-team/whipper/issues/397)
**Merged pull requests:**
- Python 3 port [\#411](https://github.com/whipper-team/whipper/pull/411) ([ddevault](https://github.com/ddevault))
## [v0.8.0](https://github.com/whipper-team/whipper/tree/v0.8.0) (2019-10-27)
[Full Changelog](https://github.com/whipper-team/whipper/compare/v0.7.3...v0.8.0)
**Implemented enhancements:**
- Include MusicBrainz Release ID in the log file [\#381](https://github.com/whipper-team/whipper/issues/381)
- Specify supported version\(s\) of Python in setup.py [\#378](https://github.com/whipper-team/whipper/pull/378) ([Freso](https://github.com/Freso))
**Fixed bugs:**
- whipper bails out if MusicBrainz release group doesnt have a type [\#396](https://github.com/whipper-team/whipper/issues/396)
@@ -22,10 +49,7 @@
- Separate out Release in log into two value map [\#416](https://github.com/whipper-team/whipper/issues/416)
- Network issue [\#412](https://github.com/whipper-team/whipper/issues/412)
- RequestsDependencyWarning: urllib3 \(1.25.2\) or chardet \(3.0.4\) doesn't match a supported version [\#400](https://github.com/whipper-team/whipper/issues/400)
- Run script after rip [\#394](https://github.com/whipper-team/whipper/issues/394)
- Add git/mercurial dependency to the README [\#386](https://github.com/whipper-team/whipper/issues/386)
- Include MusicBrainz Release ID in the log file [\#381](https://github.com/whipper-team/whipper/issues/381)
- Rip while entering MusicBrainz data [\#360](https://github.com/whipper-team/whipper/issues/360)
- Doesn't eject - "eject: unable to eject" \(but manual eject works\) [\#355](https://github.com/whipper-team/whipper/issues/355)
- Note in the whipper output/log if development version was used [\#337](https://github.com/whipper-team/whipper/issues/337)
- fedora 29, whipper 0.72, Error While Executing Any Command [\#332](https://github.com/whipper-team/whipper/issues/332)
@@ -48,7 +72,6 @@
- Use eject value of the class again [\#391](https://github.com/whipper-team/whipper/pull/391) ([gorgobacka](https://github.com/gorgobacka))
- Convert documentation from epydoc to reStructuredText [\#387](https://github.com/whipper-team/whipper/pull/387) ([JoeLametta](https://github.com/JoeLametta))
- Include MusicBrainz Release URL in log output [\#382](https://github.com/whipper-team/whipper/pull/382) ([Freso](https://github.com/Freso))
- Specify supported version\(s\) of Python in setup.py [\#378](https://github.com/whipper-team/whipper/pull/378) ([Freso](https://github.com/Freso))
- Fix critical regressions introduced in 3e79032 and 16b0d8d [\#371](https://github.com/whipper-team/whipper/pull/371) ([JoeLametta](https://github.com/JoeLametta))
- Use git to get whipper's version [\#370](https://github.com/whipper-team/whipper/pull/370) ([Freso](https://github.com/Freso))
- Handle artist MBIDs as multivalue tags [\#367](https://github.com/whipper-team/whipper/pull/367) ([Freso](https://github.com/Freso))
@@ -64,16 +87,14 @@
- accuraterip-checksum: convert to python C extension [\#274](https://github.com/whipper-team/whipper/pull/274) ([mtdcr](https://github.com/mtdcr))
## [v0.7.3](https://github.com/whipper-team/whipper/tree/v0.7.3) (2018-12-14)
[Full Changelog](https://github.com/whipper-team/whipper/compare/v0.7.2...v0.7.3)
**Fixed bugs:**
- Error when parsing log file due to left pad track number [\#340](https://github.com/whipper-team/whipper/issues/340)
- Failing AccurateRipResponse tests [\#333](https://github.com/whipper-team/whipper/issues/333)
- 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)
- 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)
@@ -104,6 +125,7 @@
- Preserve ToC file generated by cdrdao [\#321](https://github.com/whipper-team/whipper/pull/321) ([JoeLametta](https://github.com/JoeLametta))
## [v0.7.2](https://github.com/whipper-team/whipper/tree/v0.7.2) (2018-10-31)
[Full Changelog](https://github.com/whipper-team/whipper/compare/v0.7.1...v0.7.2)
**Fixed bugs:**
@@ -122,6 +144,7 @@
- Add AppStream metainfo.xml file [\#318](https://github.com/whipper-team/whipper/pull/318) ([Freso](https://github.com/Freso))
## [v0.7.1](https://github.com/whipper-team/whipper/tree/v0.7.1) (2018-10-23)
[Full Changelog](https://github.com/whipper-team/whipper/compare/v0.7.0...v0.7.1)
**Fixed bugs:**
@@ -139,12 +162,8 @@
- 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:**
@@ -164,26 +183,25 @@
- Add Dockerfile [\#237](https://github.com/whipper-team/whipper/pull/237) ([thomas-mc-work](https://github.com/thomas-mc-work))
## [v0.7.0](https://github.com/whipper-team/whipper/tree/v0.7.0) (2018-04-09)
[Full Changelog](https://github.com/whipper-team/whipper/compare/v0.6.0...v0.7.0)
**Implemented enhancements:**
- Simple message while reading TOC [\#257](https://github.com/whipper-team/whipper/issues/257)
**Fixed bugs:**
- cd rip is not able to rip the last track [\#203](https://github.com/whipper-team/whipper/issues/203)
- 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)
**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)
- 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:**
@@ -199,13 +217,19 @@
- Removed reference to unused "profile = flac" config option \(issue \#99\) [\#231](https://github.com/whipper-team/whipper/pull/231) ([calumchisholm](https://github.com/calumchisholm))
## [v0.6.0](https://github.com/whipper-team/whipper/tree/v0.6.0) (2018-02-02)
[Full Changelog](https://github.com/whipper-team/whipper/compare/v0.5.1...v0.6.0)
**Implemented enhancements:**
- Declare supported Python version [\#152](https://github.com/whipper-team/whipper/issues/152)
**Fixed bugs:**
- Error: NotFoundException message displayed while ripping an unknown disc [\#198](https://github.com/whipper-team/whipper/issues/198)
- whipper doesn't name files .flac, which leads to it not being able to find ripped files [\#194](https://github.com/whipper-team/whipper/issues/194)
- Issues with finding offset [\#182](https://github.com/whipper-team/whipper/issues/182)
- cdparanoia toc does not agree with cdrdao-toc, cd-paranoia also reports different \(but better\) lengths [\#175](https://github.com/whipper-team/whipper/issues/175)
- failing unittests in systemd-nspawn container [\#157](https://github.com/whipper-team/whipper/issues/157)
- Update doc/release or remove it [\#149](https://github.com/whipper-team/whipper/issues/149)
- Test HTOA peak value against 0 \(integer equality\) [\#143](https://github.com/whipper-team/whipper/issues/143)
@@ -217,18 +241,12 @@
**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)
- 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)
@@ -258,8 +276,10 @@
- Add flake8 testing to CI [\#151](https://github.com/whipper-team/whipper/pull/151) ([Freso](https://github.com/Freso))
- Clean up files in misc/ [\#150](https://github.com/whipper-team/whipper/pull/150) ([Freso](https://github.com/Freso))
- Update .gitignore [\#148](https://github.com/whipper-team/whipper/pull/148) ([Freso](https://github.com/Freso))
- Fix references to morituri. [\#109](https://github.com/whipper-team/whipper/pull/109) ([Freso](https://github.com/Freso))
## [v0.5.1](https://github.com/whipper-team/whipper/tree/v0.5.1) (2017-04-24)
[Full Changelog](https://github.com/whipper-team/whipper/compare/v0.5.0...v0.5.1)
**Fixed bugs:**
@@ -267,6 +287,7 @@
- 0.5.0 Release init.py version number not updated [\#147](https://github.com/whipper-team/whipper/issues/147)
## [v0.5.0](https://github.com/whipper-team/whipper/tree/v0.5.0) (2017-04-24)
[Full Changelog](https://github.com/whipper-team/whipper/compare/v0.4.2...v0.5.0)
**Fixed bugs:**
@@ -283,13 +304,9 @@
- `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:**
@@ -304,6 +321,7 @@
- Replace rip command suggestions with 'whipper' [\#114](https://github.com/whipper-team/whipper/pull/114) ([JoeLametta](https://github.com/JoeLametta))
## [v0.4.2](https://github.com/whipper-team/whipper/tree/v0.4.2) (2017-01-08)
[Full Changelog](https://github.com/whipper-team/whipper/compare/v0.4.1...v0.4.2)
**Fixed bugs:**
@@ -321,6 +339,7 @@
- Update links to Arch Linux AUR packages in README. [\#103](https://github.com/whipper-team/whipper/pull/103) ([Freso](https://github.com/Freso))
## [v0.4.1](https://github.com/whipper-team/whipper/tree/v0.4.1) (2017-01-06)
[Full Changelog](https://github.com/whipper-team/whipper/compare/v0.4.0...v0.4.1)
**Closed issues:**
@@ -330,7 +349,6 @@
**Merged pull requests:**
- Fix references to morituri. [\#109](https://github.com/whipper-team/whipper/pull/109) ([Freso](https://github.com/Freso))
- Small cleanups of setup.py [\#102](https://github.com/whipper-team/whipper/pull/102) ([Freso](https://github.com/Freso))
- Persist False value for defeats\_cache correctly [\#98](https://github.com/whipper-team/whipper/pull/98) ([ribbons](https://github.com/ribbons))
- Update suggested commands given by `drive list` [\#97](https://github.com/whipper-team/whipper/pull/97) ([ribbons](https://github.com/ribbons))
@@ -347,6 +365,7 @@
- Use soxi instead of gstreamer to determine a track's length [\#67](https://github.com/whipper-team/whipper/pull/67) ([chrysn](https://github.com/chrysn))
## [v0.4.0](https://github.com/whipper-team/whipper/tree/v0.4.0) (2016-11-08)
[Full Changelog](https://github.com/whipper-team/whipper/compare/v0.3.0...v0.4.0)
**Fixed bugs:**
@@ -354,18 +373,12 @@
- 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))
- do not recalculate musicbrainz disc id for every getMusicBrainzDiscId… [\#69](https://github.com/whipper-team/whipper/pull/69) ([RecursiveForest](https://github.com/RecursiveForest))
- Directory [\#62](https://github.com/whipper-team/whipper/pull/62) ([RecursiveForest](https://github.com/RecursiveForest))
- undelete overzealously removed plugin initialisation [\#61](https://github.com/whipper-team/whipper/pull/61) ([RecursiveForest](https://github.com/RecursiveForest))
- Readme rewrite [\#60](https://github.com/whipper-team/whipper/pull/60) ([RecursiveForest](https://github.com/RecursiveForest))
- README.md: drop executable flag [\#55](https://github.com/whipper-team/whipper/pull/55) ([chrysn](https://github.com/chrysn))
- nuke-autohell [\#54](https://github.com/whipper-team/whipper/pull/54) ([RecursiveForest](https://github.com/RecursiveForest))
- standardise program/sox.py formatting, add test case, docstring [\#53](https://github.com/whipper-team/whipper/pull/53) ([RecursiveForest](https://github.com/RecursiveForest))
@@ -373,6 +386,7 @@
- use setuptools, remove autohell, use raw make for src/ [\#51](https://github.com/whipper-team/whipper/pull/51) ([RecursiveForest](https://github.com/RecursiveForest))
## [v0.3.0](https://github.com/whipper-team/whipper/tree/v0.3.0) (2016-10-17)
[Full Changelog](https://github.com/whipper-team/whipper/compare/v0.2.4...v0.3.0)
**Fixed bugs:**
@@ -380,19 +394,19 @@
- 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))
- Fast accuraterip checksum [\#37](https://github.com/whipper-team/whipper/pull/37) ([MerlijnWajer](https://github.com/MerlijnWajer))
## [v0.2.4](https://github.com/whipper-team/whipper/tree/v0.2.4) (2016-10-09)
[Full Changelog](https://github.com/whipper-team/whipper/compare/v0.2.3...v0.2.4)
**Implemented enhancements:**
- Don't allow ripping without an explicit offset, and make pycdio a required dependency [\#23](https://github.com/whipper-team/whipper/issues/23)
**Fixed bugs:**
- whipper fails to build on bash-compgen [\#25](https://github.com/whipper-team/whipper/issues/25)
@@ -407,8 +421,6 @@
- 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)
@@ -428,15 +440,21 @@
- Fork [\#6](https://github.com/whipper-team/whipper/pull/6) ([abendebury](https://github.com/abendebury))
## [v0.2.3](https://github.com/whipper-team/whipper/tree/v0.2.3) (2014-07-16)
[Full Changelog](https://github.com/whipper-team/whipper/compare/v0.2.2...v0.2.3)
## [v0.2.2](https://github.com/whipper-team/whipper/tree/v0.2.2) (2013-07-30)
[Full Changelog](https://github.com/whipper-team/whipper/compare/v0.2.1...v0.2.2)
## [v0.2.1](https://github.com/whipper-team/whipper/tree/v0.2.1) (2013-07-15)
[Full Changelog](https://github.com/whipper-team/whipper/compare/v0.2.0...v0.2.1)
## [v0.2.0](https://github.com/whipper-team/whipper/tree/v0.2.0) (2013-01-20)
[Full Changelog](https://github.com/whipper-team/whipper/compare/20421488be8a82606f7ae82a16c9d8bc015b9e01...v0.2.0)
\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*

View File

@@ -1,4 +1,4 @@
Coverage.py 4.5.4 text report against whipper v0.8.0
Coverage.py 4.5.4 text report against whipper v0.9.0
$ coverage run --branch --omit='whipper/test/*' --source=whipper -m unittest discover
$ coverage report -m
@@ -10,46 +10,46 @@ whipper/__main__.py 7 7 2 0 0% 4-14
whipper/command/__init__.py 0 0 0 0 100%
whipper/command/accurip.py 41 41 18 0 0% 21-90
whipper/command/basecommand.py 69 29 30 8 53% 70, 72, 76, 82-88, 98-102, 107-114, 127, 129, 133, 139, 142-145, 68->70, 71->72, 75->76, 80->82, 96->98, 106->107, 126->127, 128->129
whipper/command/cd.py 227 189 60 0 13% 72-80, 85-196, 199, 212, 236-288, 295-322, 325-496
whipper/command/cd.py 227 189 60 0 13% 72-80, 85-196, 199, 212, 236-288, 295-321, 324-493
whipper/command/drive.py 57 57 10 0 0% 21-107
whipper/command/image.py 38 38 6 0 0% 21-76
whipper/command/main.py 68 68 22 0 0% 4-116
whipper/command/image.py 37 37 6 0 0% 21-75
whipper/command/main.py 68 68 24 0 0% 4-116
whipper/command/mblookup.py 29 3 8 2 86% 21-23, 35->37, 37->28
whipper/command/offset.py 110 110 32 0 0% 21-219
whipper/common/__init__.py 0 0 0 0 100%
whipper/common/accurip.py 132 5 54 5 95% 119, 125, 134-136, 114->119, 120->125, 150->153, 241->247, 250->256
whipper/common/cache.py 104 49 34 6 44% 66-90, 96, 99, 108-111, 114-115, 131, 143-147, 170-177, 201-206, 211-227, 95->96, 98->99, 130->131, 141->151, 142->143, 169->170
whipper/common/accurip.py 132 5 62 4 95% 118, 124, 133-135, 113->118, 119->124, 241->247, 251->257
whipper/common/cache.py 100 48 34 5 44% 66-90, 96, 99, 107-110, 113-114, 138-142, 165-172, 196-201, 206-222, 95->96, 98->99, 136->146, 137->138, 164->165
whipper/common/checksum.py 26 14 2 0 43% 41-42, 45-46, 49-64
whipper/common/common.py 150 28 38 6 78% 51-52, 119-120, 143-144, 162-169, 181, 274-279, 286-291, 328-332, 118->119, 131->134, 180->181, 190->197, 271->274, 326->334
whipper/common/config.py 91 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/config.py 90 8 18 4 89% 104-105, 123-124, 130, 140, 142, 144, 129->130, 139->140, 141->142, 143->144
whipper/common/directory.py 18 5 4 0 68% 42-48
whipper/common/drive.py 31 20 8 0 33% 35-40, 44-50, 54-60, 64-71
whipper/common/encode.py 44 23 2 0 46% 37-38, 41-42, 45-46, 53-56, 59-60, 63-64, 76-77, 80-81, 84-91
whipper/common/mbngs.py 174 52 66 7 70% 38-39, 45, 93-99, 174-175, 180-181, 227, 233, 258-260, 269, 289-344, 159->158, 173->174, 179->180, 226->227, 232->233, 257->258, 266->269
whipper/common/path.py 24 0 8 3 91% 42->45, 52->57, 62->67
whipper/common/program.py 346 268 112 5 19% 85-87, 93-104, 113-147, 156-161, 164, 169-173, 218, 229-230, 232-236, 253-268, 276-387, 398-456, 464-472, 476-491, 502-542, 554-571, 574-592, 595-605, 608-616, 76->79, 215->218, 228->229, 231->232, 238->242
whipper/common/path.py 24 0 8 3 91% 42->45, 52->56, 60->65
whipper/common/program.py 345 267 117 5 19% 85-87, 93-104, 113-147, 156-161, 164, 169-173, 218, 229-230, 232-236, 253-268, 276-386, 397-455, 463-471, 475-490, 501-540, 552-569, 572-590, 593-603, 606-614, 76->79, 215->218, 228->229, 231->232, 238->242
whipper/common/renamer.py 102 2 16 1 97% 133, 156, 58->66
whipper/common/task.py 77 19 14 2 75% 47-52, 86-87, 91-94, 102, 115-116, 123, 129, 135, 141, 147, 84->86, 99->102
whipper/common/task.py 77 15 14 2 79% 47-52, 86-87, 102, 115-116, 123, 129, 135, 141, 147, 84->86, 99->102
whipper/extern/__init__.py 0 0 0 0 100%
whipper/extern/asyncsub.py 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-163, 171-223, 56->57
whipper/extern/asyncsub.py 112 55 58 11 46% 15-17, 32, 37-38, 47-84, 89-102, 115, 122, 134, 145, 151, 14->15, 35->37, 45->47, 110->113, 114->115, 121->122, 133->134, 139->141, 141->152, 144->145, 148->151
whipper/extern/freedb.py 90 72 42 0 17% 46, 54, 74-153, 160-199
whipper/extern/task/__init__.py 0 0 0 0 100%
whipper/extern/task/task.py 271 115 54 11 53% 54, 58, 79, 87, 153-155, 174-176, 184-200, 218-221, 242-243, 284-285, 288-294, 309-310, 318-320, 329-336, 342-359, 363, 366, 373-390, 401-402, 405-408, 412, 415, 430, 433-435, 451, 463, 509-514, 521-526, 535-543, 546-554, 557-558, 566, 571-573, 53->54, 57->58, 66->68, 152->153, 166->exit, 217->218, 231->233, 236->exit, 498->500, 532->535, 570->571
whipper/extern/task/task.py 270 115 56 11 53% 53, 59, 78, 86, 152-154, 173-175, 183-199, 217-220, 241-242, 283-284, 287-293, 308-309, 317-319, 328-335, 341-358, 362, 365, 372-389, 400-401, 404-407, 411, 414, 429, 432-434, 450, 462, 508-513, 520-525, 534-542, 545-553, 556-557, 565, 570-572, 52->53, 56->59, 65->67, 151->152, 165->exit, 216->217, 230->232, 235->exit, 497->499, 531->534, 569->570
whipper/image/__init__.py 0 0 0 0 100%
whipper/image/cue.py 91 9 20 3 89% 99, 116-117, 132-134, 159, 187, 205, 98->99, 115->116, 131->132
whipper/image/cue.py 91 9 20 3 89% 98, 115-116, 131-133, 158, 186, 204, 97->98, 114->115, 130->131
whipper/image/image.py 116 93 18 0 17% 49-57, 65-67, 74-107, 121-154, 157-173, 184-214
whipper/image/table.py 395 18 114 16 93% 238, 497, 576, 661-662, 682-683, 692-695, 746, 792-793, 795-796, 840-841, 846-848, 181->184, 496->497, 530->534, 553->556, 575->576, 583->590, 681->682, 690->696, 691->692, 720->724, 724->719, 745->746, 791->792, 794->795, 839->840, 845->846
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/image/table.py 394 18 120 16 93% 240, 499, 578, 663-664, 684-685, 694-697, 748, 794-795, 797-798, 842-843, 848-850, 180->183, 498->499, 532->536, 555->558, 577->578, 585->592, 683->684, 692->698, 693->694, 722->726, 726->721, 747->748, 793->794, 796->797, 841->842, 847->848
whipper/image/toc.py 203 16 60 10 90% 133, 260-261, 277-280, 338-340, 362-364, 384, 408, 438, 129->133, 211->219, 259->260, 276->277, 286->291, 322->329, 337->338, 361->362, 371->375, 403->408
whipper/program/__init__.py 0 0 0 0 100%
whipper/program/arc.py 3 0 0 0 100%
whipper/program/cdparanoia.py 309 180 78 2 39% 48-50, 59-60, 124-126, 198-199, 239-253, 256-306, 309-347, 350-354, 357-393, 447-500, 505-552, 586-589, 592, 599, 607-612, 123->124, 598->599
whipper/program/cdrdao.py 114 75 34 2 28% 33-58, 80-86, 90-105, 108-137, 140-144, 147-161, 168-171, 181-183, 187-189, 180->181, 186->187
whipper/program/cdparanoia.py 307 179 78 2 39% 48-50, 59-60, 124-126, 198-199, 239-253, 256-306, 309-347, 350-354, 357-393, 447-499, 504-551, 585-588, 591, 598, 606-611, 123->124, 597->598
whipper/program/cdrdao.py 113 74 32 2 28% 33-58, 80-86, 90-105, 108-137, 140-144, 147-160, 167-170, 180-182, 186-188, 179->180, 185->186
whipper/program/flac.py 9 5 0 0 44% 12-19
whipper/program/sox.py 17 4 4 2 71% 18-19, 23-24, 17->18, 22->23
whipper/program/soxi.py 28 2 2 1 90% 36, 49, 48->49
whipper/program/soxi.py 28 2 4 1 91% 36, 49, 48->49
whipper/program/utils.py 23 16 2 0 28% 12-17, 25-31, 42-47
whipper/result/__init__.py 0 0 0 0 100%
whipper/result/logger.py 144 23 40 16 78% 68, 84-92, 112, 123, 128, 130, 134-135, 143, 202, 240, 244-245, 252-253, 67->68, 83->84, 111->112, 122->123, 127->128, 129->130, 133->134, 142->143, 201->202, 213->217, 217->222, 222->226, 226->230, 234->244, 236->240, 249->252
whipper/result/result.py 57 13 6 0 70% 115-119, 137, 148-149, 158-165
-----------------------------------------------------------------------------
TOTAL 3997 1766 1108 129 53%
TOTAL 3950 1727 1123 123 53%

View File

@@ -1,53 +1,73 @@
FROM debian:buster
RUN apt-get update \
&& apt-get install -y cdrdao git python-gobject-2 python-musicbrainzngs python-mutagen \
python-setuptools python-requests libsndfile1-dev flac sox \
libiso9660-dev python-pip swig make pkgconf \
eject locales \
autoconf libtool curl \
&& pip install pycdio==2.1.0
RUN apt-get update && apt-get install --no-install-recommends -y \
autoconf \
automake \
cdrdao \
bzip2 \
curl \
eject \
flac \
gir1.2-glib-2.0 \
git \
libiso9660-dev \
libsndfile1-dev \
libtool \
locales \
make \
pkgconf \
python3-dev \
python3-gi \
python3-musicbrainzngs \
python3-mutagen \
python3-pip \
python3-requests \
python3-ruamel.yaml \
python3-setuptools \
sox \
swig \
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
&& pip3 --no-cache-dir install pycdio==2.1.0
# libcdio-paranoia / libcdio-utils are wrongfully packaged in Debian, thus built manually
# see https://github.com/whipper-team/whipper/pull/237#issuecomment-367985625
RUN curl -o - 'https://ftp.gnu.org/gnu/libcdio/libcdio-2.1.0.tar.bz2' | tar jxf - \
&& cd libcdio-2.1.0 \
&& autoreconf -fi \
&& ./configure --disable-dependency-tracking --disable-cxx --disable-example-progs --disable-static \
&& make install \
&& cd .. \
&& rm -rf libcdio-2.1.0
&& cd libcdio-2.1.0 \
&& autoreconf -fi \
&& ./configure --disable-dependency-tracking --disable-cxx --disable-example-progs --disable-static \
&& make install \
&& cd .. \
&& rm -rf libcdio-2.1.0
# Install cd-paranoia from tarball
RUN curl -o - 'https://ftp.gnu.org/gnu/libcdio/libcdio-paranoia-10.2+2.0.0.tar.bz2' | tar jxf - \
&& cd libcdio-paranoia-10.2+2.0.0 \
&& autoreconf -fi \
&& ./configure --disable-dependency-tracking --disable-example-progs --disable-static \
&& make install \
&& cd .. \
&& rm -rf libcdio-paranoia-10.2+2.0.0
&& cd libcdio-paranoia-10.2+2.0.0 \
&& autoreconf -fi \
&& ./configure --disable-dependency-tracking --disable-example-progs --disable-static \
&& make install \
&& cd .. \
&& rm -rf libcdio-paranoia-10.2+2.0.0
RUN ldconfig
# add user
RUN useradd -m worker -G cdrom \
&& mkdir -p /output /home/worker/.config/whipper \
&& chown worker: /output /home/worker/.config/whipper
&& mkdir -p /output /home/worker/.config/whipper \
&& chown worker: /output /home/worker/.config/whipper
VOLUME ["/home/worker/.config/whipper", "/output"]
# setup locales + cleanup
RUN echo "LC_ALL=en_US.UTF-8" >> /etc/environment \
&& echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen \
&& echo "LANG=en_US.UTF-8" > /etc/locale.conf \
&& locale-gen en_US.UTF-8 \
&& apt-get clean && apt-get autoremove -y
&& echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen \
&& echo "LANG=en_US.UTF-8" > /etc/locale.conf \
&& locale-gen en_US.UTF-8
# install whipper
RUN mkdir /whipper
COPY . /whipper/
RUN cd /whipper && python2 setup.py install \
&& rm -rf /whipper \
&& whipper -v
RUN cd /whipper && python3 setup.py install \
&& rm -rf /whipper \
&& whipper -v
ENV LC_ALL=en_US.UTF-8
ENV LANG=en_US

View File

@@ -8,14 +8,12 @@
[![GitHub Issues](https://img.shields.io/github/issues/whipper-team/whipper.svg)](https://github.com/whipper-team/whipper/issues)
[![GitHub contributors](https://img.shields.io/github/contributors/whipper-team/whipper.svg)](https://github.com/whipper-team/whipper/graphs/contributors)
Whipper is a Python 2.7 CD-DA ripper based on the [morituri project](https://github.com/thomasvs/morituri) (_CDDA ripper for *nix systems aiming for accuracy over speed_). It started just as a fork of morituri - which development seems to have halted - merging old ignored pull requests, improving it with bugfixes and new features. Nowadays whipper's codebase diverges significantly from morituri's one.
Whipper is a Python 3 (3.5+) CD-DA ripper based on the [morituri project](https://github.com/thomasvs/morituri) (_CDDA ripper for *nix systems aiming for accuracy over speed_). It started just as a fork of morituri - which development seems to have halted - merging old ignored pull requests, improving it with bugfixes and new features. Nowadays whipper's codebase diverges significantly from morituri's one.
Whipper is currently developed and tested _only_ on Linux distributions but _may_ work fine on other *nix OSes too.
In order to track whipper's latest changes it's advised to check its commit history (README and [CHANGELOG](#changelog) files may not be comprehensive).
We've nearly completed porting the codebase to Python 3 (Python 2 won't be supported anymore in future releases). If you would like to follow the progress of the port e/o help us with it, please check [pull request #411](https://github.com/whipper-team/whipper/pull/411).
## Table of content
- [Rationale](#rationale)
@@ -27,8 +25,7 @@ We've nearly completed porting the codebase to Python 3 (Python 2 won't be suppo
- [Building](#building)
1. [Required dependencies](#required-dependencies)
2. [Fetching the source code](#fetching-the-source-code)
3. [Building the bundled dependencies](#building-the-bundled-dependencies)
4. [Finalizing the build](#finalizing-the-build)
3. [Finalizing the build](#finalizing-the-build)
- [Usage](#usage)
- [Getting started](#getting-started)
- [Configuration file documentation](#configuration-file-documentation)
@@ -75,7 +72,7 @@ Whipper still isn't available as an official package in every Linux distribution
### Docker
You can easily install whipper without needing to care about the required dependencies by making use of the automatically built images hosted on Docker Hub:
You can easily install whipper without needing to care about the required dependencies by making use of the automatically built images hosted on [Docker Hub](https://hub.docker.com/r/whipperteam/whipper):
`docker pull whipperteam/whipper`
@@ -96,7 +93,7 @@ You should put this e.g. into your `.bash_aliases`. Also keep in mind to substit
Make sure you create the configuration directory:
`mkdir -p ~/.config/whipper ${PWD}/output`
`mkdir -p ~/.config/whipper "${PWD}"/output`
Finally you can test the correct installation:
@@ -111,7 +108,7 @@ This is a noncomprehensive summary which shows whipper's packaging status (unoff
[![Packaging status](https://repology.org/badge/vertical-allrepos/whipper.svg)](https://repology.org/metapackage/whipper)
Someone also packaged whipper as snap: [unofficial snap on snapcraft](https://snapcraft.io/whipper).
There's also an [unoffical snap package on snapcraft](https://snapcraft.io/whipper).
In case you decide to install whipper using an unofficial repository just keep in mind it is your responsibility to verify that the provided content is safe to use.
@@ -123,33 +120,34 @@ 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
- [cd-paranoia](https://github.com/rocky/libcdio-paranoia), for the actual ripping
- To avoid bugs it's advised to use `cd-paranoia` versions ≥ **10.2+0.94+2-2**
- The package named `libcdio-utils`, available on Debian and Ubuntu, is affected by a bug (except for Debian testing/sid): it doesn't include the `cd-paranoia` binary (needed by whipper). For more details see: [#888053 (Debian)](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=888053), [#889803 (Debian)](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=889803) and [#1750264 (Ubuntu)](https://bugs.launchpad.net/ubuntu/+source/libcdio/+bug/1750264).
- [cdrdao](http://cdrdao.sourceforge.net/), for session, TOC, pre-gap, and ISRC extraction
- [GObject Introspection](https://wiki.gnome.org/Projects/GObjectIntrospection), to provide GLib-2.0 methods used by `task.py`
- [PyGObject](https://pypi.org/project/PyGObject/), required by `task.py`
- [python-musicbrainzngs](https://github.com/alastair/python-musicbrainzngs), for metadata lookup
- [python-mutagen](https://pypi.python.org/pypi/mutagen), for tagging support
- [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
- [musicbrainzngs](https://pypi.org/project/musicbrainzngs/), for metadata lookup
- [mutagen](https://pypi.python.org/pypi/mutagen), for tagging support
- [setuptools](https://pypi.python.org/pypi/setuptools), for installation, plugins support
- [requests](https://pypi.python.org/pypi/requests), for retrieving AccurateRip database entries
- [pycdio](https://pypi.python.org/pypi/pycdio/), for drive identification (required for drive offset and caching behavior to be stored in the configuration file).
- To avoid bugs it's advised to use the most recent `pycdio` version with the corresponding `libcdio` release or, if stuck to old pycdio versions, **0.20**/**0.21** with `libcdio`**0.90****0.94**. All other combinations won't probably work.
- [ruamel.yaml](https://pypi.org/project/ruamel.yaml/), for generating well formed YAML report logfiles
- [libsndfile](http://www.mega-nerd.com/libsndfile/), for reading wav files
- [flac](https://xiph.org/flac/), for reading flac files
- [sox](http://sox.sourceforge.net/), for track peak detection
- [git](https://git-scm.com/) or [mercurial](https://www.mercurial-scm.org/)
- Required either when running whipper without installing it or when building it from its source code (code cloned from a git/mercurial repository).
Some dependencies aren't available in the PyPI. They can be probably installed using your distribution's package manager:
- [cd-paranoia](https://www.gnu.org/software/libcdio/)
- [cd-paranoia](https://github.com/rocky/libcdio-paranoia)
- [cdrdao](http://cdrdao.sourceforge.net/)
- [GObject Introspection](https://wiki.gnome.org/Projects/GObjectIntrospection)
- [libsndfile](http://www.mega-nerd.com/libsndfile/)
- [flac](https://xiph.org/flac/)
- [sox](http://sox.sourceforge.net/)
- [git](https://git-scm.com/) or [mercurial](https://www.mercurial-scm.org/)
- Required either when running whipper without installing it or when building it from its source code (code cloned from a git/mercurial repository).
PyPI installable dependencies are listed in the [requirements.txt](https://github.com/whipper-team/whipper/blob/master/requirements.txt) file and can be installed issuing the following command:
@@ -164,22 +162,9 @@ git clone https://github.com/whipper-team/whipper.git
cd whipper
```
### Building the bundled dependencies
Whipper uses and packages a slightly different version of the `accuraterip-checksum` tool:
You can edit the install path in `config.mk`
```bash
cd src
make
sudo make install
cd ..
```
### Finalizing the build
Install whipper: `python2 setup.py install`
Install whipper: `python3 setup.py install`
Note that, depending on the chosen installation path, this command may require elevated rights.
@@ -232,7 +217,7 @@ The configuration file is stored in `$XDG_CONFIG_HOME/whipper/whipper.conf`, or
See [XDG Base Directory
Specification](http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html)
and [ConfigParser](https://docs.python.org/2/library/configparser.html).
and [ConfigParser](https://docs.python.org/3/library/configparser.html).
The configuration file consists of newline-delineated `[sections]`
containing `key = value` pairs. The sections `[main]` and
@@ -271,7 +256,7 @@ To make it easier for developers, you can run whipper straight from the
source checkout:
```bash
python2 -m whipper -h
python3 -m whipper -h
```
## Logger plugins
@@ -298,8 +283,10 @@ Whipper searches for logger plugins in the following paths:
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`
- `/usr/local/lib/python3.X/dist-packages/whipper/plugins`
- `/usr/lib/python3.X/dist-packages/whipper/plugins`
Where `X` stands for the minor version of the Python 3 release available on the system.
### Official logger plugins
@@ -336,7 +323,7 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Make sure you have the latest copy from our [git
repository](https://github.com/whipper-team/whipper). Where possible,
please include tests for new or changed functionality. You can run tests
with `python -m unittest discover` from your source checkout.
with `python3 -m unittest discover` from your source checkout.
### Developer Certificate of Origin (DCO)
@@ -410,7 +397,7 @@ gzip whipper.log
And attach the gzipped log file to your bug report.
Without `WHIPPER_LOGFILE` set, logging messages will go to stderr. `WHIPPER_DEBUG` accepts a string of the [default python logging levels](https://docs.python.org/2/library/logging.html#logging-levels).
Without `WHIPPER_LOGFILE` set, logging messages will go to stderr. `WHIPPER_DEBUG` accepts a string of the [default python logging levels](https://docs.python.org/3/library/logging.html#logging-levels).
## Credits

View File

@@ -6,13 +6,12 @@
import sys
import BeautifulSoup
from bs4 import BeautifulSoup
handle = open(sys.argv[1])
with open(sys.argv[1]) as f:
doc = f.read()
doc = handle.read()
soup = BeautifulSoup.BeautifulSoup(doc)
soup = BeautifulSoup(doc, features='html.parser')
offsets = {} # offset -> total count
@@ -50,18 +49,18 @@ for count, offset in counts:
# now format it for code inclusion
lines = []
line = 'OFFSETS = "'
line = 'OFFSETS = ("'
for offset in offsets:
line += offset + ", "
line += offset + ', '
if len(line) > 60:
line += "\" + \\"
line += '"'
lines.append(line)
line = ' "'
line = ' "'
# get last line too, trimming the comma and adding the quote
if len(line) > 11:
line = line[:-2] + '"'
line = line[:-2] + '")'
lines.append(line)
print("\n".join(lines))
print('\n'.join(lines))

View File

@@ -8,7 +8,7 @@ setup(
maintainer=['The Whipper Team'],
url='https://github.com/whipper-team/whipper',
license='GPL3',
python_requires='>=2.7,<3',
python_requires='>=3.5',
packages=find_packages(),
setup_requires=['setuptools_scm'],
ext_modules=[

View File

@@ -147,7 +147,13 @@ static PyMethodDef accuraterip_methods[] = {
{ NULL, NULL, 0, NULL },
};
PyMODINIT_FUNC initaccuraterip(void)
static struct PyModuleDef accuraterip_module = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "accuraterip",
.m_methods = accuraterip_methods,
};
PyMODINIT_FUNC PyInit_accuraterip(void)
{
Py_InitModule("accuraterip", accuraterip_methods);
return PyModule_Create(&accuraterip_module);
}

View File

@@ -37,8 +37,8 @@ logger = logging.getLogger(__name__)
SILENT = 0
MAX_TRIES = 5
DEFAULT_TRACK_TEMPLATE = u'%r/%A - %d/%t. %a - %n'
DEFAULT_DISC_TEMPLATE = u'%r/%A - %d/%A - %d'
DEFAULT_TRACK_TEMPLATE = '%r/%A - %d/%t. %a - %n'
DEFAULT_DISC_TEMPLATE = '%r/%A - %d/%A - %d'
TEMPLATE_DESCRIPTION = '''
Tracks are named according to the track template, filling in the variables
@@ -137,7 +137,7 @@ class _CD(BaseCommand):
if getattr(self.options, 'working_directory', False):
os.chdir(os.path.expanduser(self.options.working_directory))
if hasattr(self.options, 'output_directory'):
out_bpath = self.options.output_directory.decode('utf-8')
out_bpath = self.options.output_directory
# Needed to preserve cdrdao's tocfile
out_fpath = self.program.getPath(out_bpath,
self.options.disc_template,
@@ -295,10 +295,9 @@ Log files will log the path to tracks relative to this directory.
self.options.output_directory = os.path.expanduser(
self.options.output_directory)
self.options.track_template = self.options.track_template.decode(
'utf-8')
self.options.track_template = self.options.track_template
validate_template(self.options.track_template, 'track')
self.options.disc_template = self.options.disc_template.decode('utf-8')
self.options.disc_template = self.options.disc_template
validate_template(self.options.disc_template, 'disc')
if self.options.offset is None:
@@ -323,7 +322,7 @@ Log files will log the path to tracks relative to this directory.
def doCommand(self):
self.program.setWorkingDirectory(self.options.working_directory)
self.program.outdir = self.options.output_directory.decode('utf-8')
self.program.outdir = self.options.output_directory
self.program.result.offset = int(self.options.offset)
self.program.result.overread = self.options.overread
self.program.result.logger = self.options.logger
@@ -336,13 +335,11 @@ Log files will log the path to tracks relative to this directory.
if os.path.exists(dirname):
logs = glob.glob(os.path.join(dirname, '*.log'))
if logs:
msg = ("output directory %s is a finished rip" %
dirname.encode('utf-8'))
msg = ("output directory %s is a finished rip" % dirname)
logger.debug(msg)
raise RuntimeError(msg)
else:
logger.info("creating output directory %s",
dirname.encode('utf-8'))
logger.info("creating output directory %s", dirname)
os.makedirs(dirname)
# FIXME: turn this into a method
@@ -366,7 +363,7 @@ Log files will log the path to tracks relative to this directory.
logger.debug('ripIfNotRipped: path %r', path)
trackResult.number = number
assert isinstance(path, unicode), "%r is not unicode" % path
assert isinstance(path, str), "%r is not str" % path
trackResult.filename = path
if number > 0:
trackResult.pregap = self.itable.tracks[number - 1].getPregap()
@@ -385,7 +382,7 @@ Log files will log the path to tracks relative to this directory.
logger.info('verifying track %d of %d: %s',
number, len(self.itable.tracks),
os.path.basename(path).encode('utf-8'))
os.path.basename(path))
if not self.program.verifyTrack(self.runner, trackResult):
logger.warning('verification failed, reripping...')
os.unlink(path)
@@ -403,7 +400,7 @@ Log files will log the path to tracks relative to this directory.
extra = " (try %d)" % tries
logger.info('ripping track %d of %d%s: %s',
number, len(self.itable.tracks), extra,
os.path.basename(path).encode('utf-8'))
os.path.basename(path))
try:
logger.debug('ripIfNotRipped: track %d, try %d',
number, tries)

View File

@@ -45,7 +45,6 @@ Verifies the image from the given .cue files against the AccurateRip database.
runner = task.SyncRunner()
for arg in self.options.cuefile:
arg = arg.decode('utf-8')
cueImage = image.Image(arg)
cueImage.setup(runner)

View File

@@ -17,7 +17,7 @@ Example disc id: KnpGsLhvH.lPrNc1PBL21lb9Bg4-"""
def do(self):
try:
discId = unicode(self.options.mbdiscid)
discId = str(self.options.mbdiscid)
except IndexError:
print('Please specify a MusicBrainz disc id.')
return 3
@@ -29,7 +29,7 @@ Example disc id: KnpGsLhvH.lPrNc1PBL21lb9Bg4-"""
print('- Release %d:' % (i + 1, ))
print(' Artist: %s' % md.artist.encode('utf-8'))
print(' Title: %s' % md.title.encode('utf-8'))
print(' Type: %s' % unicode(md.releaseType).encode('utf-8')) # noqa: E501
print(' Type: %s' % str(md.releaseType).encode('utf-8')) # noqa: E501
print(' URL: %s' % md.url)
print(' Tracks: %d' % len(md.tracks))
if md.catalogNumber:

View File

@@ -37,13 +37,13 @@ OFFSETS = ("+6, +667, +48, +102, +12, +30, +103, +618, +96, +594, "
"+99, +97, +600, +676, +690, +1292, +702, +686, -24, "
"+704, +697, +572, +1182, +688, +91, -491, +145, +689, "
"+564, +708, +86, +355, +79, -496, +679, -1164, 0, "
"+1160, -436, +694, +684, +94, +1194, +106, +681, +117, "
"+692, +943, +92, +680, +678, +682, +1268, +1279, +1473, "
"-582, -54, +674, +687, +1272, +1263, +1508, +675, "
"+534, +740, +122, -489, +974, +976, +1303, +108, +1130, "
"+111, +739, +732, -589, -495, -494, +975, +961, +935, "
"+87, +668, +234, +1776, +138, +1364, +1336, +1262, "
"+1127")
"+1160, -436, +694, +684, +94, +1194, +106, +681, "
"+117, +692, +943, +92, +680, +678, +682, +1268, +1279, "
"+1473, -582, -54, +674, +687, +1272, +1263, +1508, "
"+675, +534, +740, +122, -489, +974, +976, +1303, "
"+108, +1130, +111, +739, +732, -589, -495, -494, "
"+975, +961, +935, +87, +668, +234, +1776, +138, +1364, "
"+1336, +1262, +1127")
class Find(BaseCommand):
@@ -177,7 +177,7 @@ CD in the AccurateRip database."""
logger.debug('ripping track %r with offset %d...', track, offset)
fd, path = tempfile.mkstemp(
suffix=u'.track%02d.offset%d.whipper.wav' % (
suffix='.track%02d.offset%d.whipper.wav' % (
track, offset))
os.close(fd)

View File

@@ -21,7 +21,6 @@
import requests
import struct
from errno import EEXIST
from os import makedirs
from os.path import dirname, exists, join
@@ -40,7 +39,7 @@ class EntryNotFound(Exception):
pass
class _AccurateRipResponse(object):
class _AccurateRipResponse:
"""
An AccurateRip response contains a collection of metadata identifying a
particular digital audio compact disc.
@@ -60,7 +59,7 @@ class _AccurateRipResponse(object):
position, so track 1 will have array index 0, track 2 will have array
index 1, and so forth. HTOA and other hidden tracks are not included.
"""
self.num_tracks = struct.unpack("B", data[0])[0]
self.num_tracks = data[0]
self.discId1 = "%08x" % struct.unpack("<L", data[1:5])[0]
self.discId2 = "%08x" % struct.unpack("<L", data[5:9])[0]
self.cddbDiscId = "%08x" % struct.unpack("<L", data[9:13])[0]
@@ -69,7 +68,7 @@ class _AccurateRipResponse(object):
self.checksums = []
pos = 13
for _ in range(self.num_tracks):
confidence = struct.unpack("B", data[pos])[0]
confidence = data[pos]
checksum = "%08x" % struct.unpack("<L", data[pos + 1:pos + 5])[0]
self.confidences.append(confidence)
self.checksums.append(checksum)
@@ -88,7 +87,7 @@ class _AccurateRipResponse(object):
def _split_responses(raw_entry):
responses = []
while raw_entry:
track_count = struct.unpack("B", raw_entry[0])[0]
track_count = raw_entry[0]
nbytes = 1 + 12 + track_count * (1 + 8)
responses.append(_AccurateRipResponse(raw_entry[:nbytes]))
raw_entry = raw_entry[nbytes:]
@@ -143,14 +142,13 @@ def _download_entry(path):
def _save_entry(raw_entry, path):
logger.debug('saving AccurateRip entry to %s', path)
# XXX: os.makedirs(exist_ok=True) in py3
try:
makedirs(dirname(path))
makedirs(dirname(path), exist_ok=True)
except OSError as e:
if e.errno != EEXIST:
logger.error('could not save entry to %s: %s', path, e)
return
open(path, 'wb').write(raw_entry)
logger.error('could not save entry to %s: %s', path, e)
return
with open(path, 'wb') as f:
f.write(raw_entry)
def get_db_entry(path):
@@ -163,7 +161,8 @@ def get_db_entry(path):
cached_path = join(_CACHE_DIR, path)
if exists(cached_path):
logger.debug('found accuraterip entry at %s', cached_path)
raw_entry = open(cached_path, 'rb').read()
with open(cached_path, 'rb') as f:
raw_entry = f.read()
else:
raw_entry = _download_entry(path)
if raw_entry:
@@ -196,7 +195,8 @@ def _match_responses(tracks, responses):
for i, track in enumerate(tracks):
for v in ('v1', 'v2'):
if track.AR[v]['CRC'] == r.checksums[i]:
if r.confidences[i] > track.AR[v]['DBConfidence']:
if (track.AR[v]['DBConfidence'] is None or
r.confidences[i] > track.AR[v]['DBConfidence']):
track.AR[v]['DBCRC'] = r.checksums[i]
track.AR[v]['DBConfidence'] = r.confidences[i]
logger.debug(
@@ -245,7 +245,8 @@ def print_report(result):
track.AR['v2']['DBCRC']
) if _f])
max_conf = max(
[track.AR[v]['DBConfidence'] for v in ('v1', 'v2')]
[track.AR[v]['DBConfidence'] for v in ('v1', 'v2')
if track.AR[v]['DBConfidence'] is not None], default=None
)
if max_conf:
if max_conf < track.AR['DBMaxConfidence']:

View File

@@ -98,17 +98,16 @@ class Persister:
if not os.path.exists(self._path):
return
handle = open(self._path)
import pickle
try:
self.object = pickle.load(handle)
logger.debug('loaded persisted object from %r', self._path)
# FIXME: catching too general exception (Exception)
except Exception as e:
# can fail for various reasons; in that case, pretend we didn't
# load it
logger.debug(e)
with open(self._path, 'rb') as handle:
import pickle
try:
self.object = pickle.load(handle)
logger.debug('loaded persisted object from %r', self._path)
# FIXME: catching too general exception (Exception)
except Exception as e:
# can fail for various reasons; in that case, pretend we didn't
# load it
logger.debug(e)
def delete(self):
self.object = None
@@ -124,11 +123,7 @@ class PersistedCache:
def __init__(self, path):
self.path = path
try:
os.makedirs(self.path)
except OSError as e:
if e.errno != os.errno.EEXIST: # FIXME: errno 17 is 'File Exists'
raise
os.makedirs(self.path, exist_ok=True)
def _getPath(self, key):
return os.path.join(self.path, '%s.pickle' % key)

View File

@@ -165,7 +165,7 @@ def truncate_filename(path):
fn_lim = os.pathconf(p.encode('utf-8'), 'PC_NAME_MAX')
f_max = fn_lim - len(e.encode('utf-8'))
f = unicodedata.normalize('NFC', f)
f_trunc = unicode(f.encode('utf-8')[:f_max], 'utf-8', errors='ignore')
f_trunc = f.encode()[:f_max].decode('utf-8', errors='ignore')
return os.path.join(p, f_trunc + e)
@@ -196,7 +196,7 @@ def shrinkPath(path):
name = " ".join(pieces)
# ext includes period
parts[-1] = u'%s%s' % (name, ext)
parts[-1] = '%s%s' % (name, ext)
path = os.path.join(*parts)
return path
@@ -209,11 +209,11 @@ def getRealPath(refPath, filePath):
:param refPath: path to the file from which the track is referenced;
for example, path to the .cue file in the same directory
:type refPath: unicode
:type refPath: str
:type filePath: unicode
:type filePath: str
"""
assert isinstance(filePath, unicode), "%r is not unicode" % filePath
assert isinstance(filePath, str), "%r is not str" % filePath
if os.path.exists(filePath):
return filePath
@@ -292,7 +292,7 @@ def validate_template(template, kind):
'variable(s): {}'.format(', '.join(matches)))
class VersionGetter(object):
class VersionGetter:
"""
I get the version of a program by looking for it in command output
according to a regexp.
@@ -317,11 +317,11 @@ class VersionGetter(object):
version = "(Unknown)"
try:
p = asyncsub.Popen(self._args,
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, close_fds=True)
p.wait()
output = asyncsub.recv_some(p, e=0, stderr=1)
with asyncsub.Popen(self._args,
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, close_fds=True) as p:
p.wait()
output = asyncsub.recv_some(p, e=0, stderr=1).decode()
vre = self._regexp.search(output)
if vre:
version = self._expander % vre.groupdict()

View File

@@ -18,13 +18,12 @@
# You should have received a copy of the GNU General Public License
# along with whipper. If not, see <http://www.gnu.org/licenses/>.
import ConfigParser
import codecs
import configparser
import os.path
import shutil
import tempfile
import urllib
from urlparse import urlparse
from urllib.parse import urlparse, quote
from whipper.common import directory
@@ -37,7 +36,7 @@ class Config:
def __init__(self, path=None):
self._path = path or directory.config_path()
self._parser = ConfigParser.SafeConfigParser()
self._parser = configparser.ConfigParser()
self.open()
@@ -45,13 +44,13 @@ class Config:
# Open the file with the correct encoding
if os.path.exists(self._path):
with codecs.open(self._path, 'r', encoding='utf-8') as f:
self._parser.readfp(f)
self._parser.read_file(f)
logger.debug('loaded %d sections from config file',
len(self._parser.sections()))
def write(self):
fd, path = tempfile.mkstemp(suffix=u'.whipperrc')
fd, path = tempfile.mkstemp(suffix='.whipperrc')
handle = os.fdopen(fd, 'w')
self._parser.write(handle)
handle.close()
@@ -64,7 +63,7 @@ class Config:
method = getattr(self._parser, methodName)
try:
return method(section, option)
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
except (configparser.NoSectionError, configparser.NoOptionError):
return None
def get(self, section, option):
@@ -102,7 +101,7 @@ class Config:
try:
return int(self._parser.get(section, 'read_offset'))
except ConfigParser.NoOptionError:
except configparser.NoOptionError:
raise KeyError("Could not find read_offset for %s/%s/%s" % (
vendor, model, release))
@@ -121,7 +120,7 @@ class Config:
try:
return self._parser.get(section, 'defeats_cache') == 'True'
except ConfigParser.NoOptionError:
except configparser.NoOptionError:
raise KeyError("Could not find defeats_cache for %s/%s/%s" % (
vendor, model, release))
@@ -153,7 +152,7 @@ class Config:
try:
section = self._findDriveSection(vendor, model, release)
except KeyError:
section = 'drive:' + urllib.quote('%s:%s:%s' % (
section = 'drive:' + quote('%s:%s:%s' % (
vendor, model, release))
self._parser.add_section(section)
for key in ['vendor', 'model', 'release']:

View File

@@ -19,33 +19,30 @@
# along with whipper. If not, see <http://www.gnu.org/licenses/>.
from os import getenv, makedirs
from os.path import join, expanduser, exists
from os.path import join, expanduser
def config_path():
path = join(getenv('XDG_CONFIG_HOME') or join(expanduser('~'), u'.config'),
u'whipper')
if not exists(path):
makedirs(path)
return join(path, u'whipper.conf')
path = join(getenv('XDG_CONFIG_HOME') or join(expanduser('~'), '.config'),
'whipper')
makedirs(path, exist_ok=True)
return join(path, 'whipper.conf')
def cache_path(name=None):
path = join(getenv('XDG_CACHE_HOME') or join(expanduser('~'), u'.cache'),
u'whipper')
path = join(getenv('XDG_CACHE_HOME') or join(expanduser('~'), '.cache'),
'whipper')
if name:
path = join(path, name)
if not exists(path):
makedirs(path)
makedirs(path, exist_ok=True)
return path
def data_path(name=None):
path = join(getenv('XDG_DATA_HOME') or
join(expanduser('~'), u'.local/share'),
u'whipper')
join(expanduser('~'), '.local/share'),
'whipper')
if name:
path = join(path, name)
if not exists(path):
makedirs(path)
makedirs(path, exist_ok=True)
return path

View File

@@ -21,7 +21,7 @@
"""
Handles communication with the MusicBrainz server using NGS.
"""
import urllib2
from urllib.error import HTTPError
import whipper
@@ -45,7 +45,7 @@ class NotFoundException(MusicBrainzException):
return "Disc not found in MusicBrainz"
class TrackMetadata(object):
class TrackMetadata:
artist = None
title = None
duration = None # in ms
@@ -56,12 +56,12 @@ class TrackMetadata(object):
mbidWorks = []
class DiscMetadata(object):
class DiscMetadata:
"""
:param artist: artist(s) name
:param sortName: release artist sort name
:param release: earliest release date, in YYYY-MM-DD
:type release: unicode
:type release: str
:param title: title of the disc (with disambiguation)
:param releaseTitle: title of the release (without disambiguation)
:type tracks: list of :any:`TrackMetadata`
@@ -152,7 +152,7 @@ def _getWorks(recording):
"""Get "performance of" works out of a recording."""
works = []
valid_work_rel_types = [
u'a3005666-a872-32c3-ad06-98af558e99b0', # "Performance"
'a3005666-a872-32c3-ad06-98af558e99b0', # "Performance"
]
if 'work-relation-list' in recording:
for work in recording['work-relation-list']:
@@ -298,7 +298,7 @@ def musicbrainz(discid, country=None, record=False):
result = musicbrainzngs.get_releases_by_discid(
discid, includes=["artists", "recordings", "release-groups"])
except musicbrainzngs.ResponseError as e:
if isinstance(e.cause, urllib2.HTTPError):
if isinstance(e.cause, HTTPError):
if e.cause.code == 404:
raise NotFoundException(e)
else:

View File

@@ -21,7 +21,7 @@
import re
class PathFilter(object):
class PathFilter:
"""
I filter path components for safe storage on file systems.
"""
@@ -50,18 +50,16 @@ class PathFilter(object):
# change all fancy single/double quotes to normal quotes
if self._quotes:
path = re.sub(ur'[\xc2\xb4\u2018\u2019\u201b]', "'", path,
re.UNICODE)
path = re.sub(ur'[\u201c\u201d\u201f]', '"', path, re.UNICODE)
path = re.sub(r'[\xc2\xb4\u2018\u2019\u201b]', "'", path)
path = re.sub(r'[\u201c\u201d\u201f]', '"', path)
if self._special:
path = separators(path)
path = re.sub(r'[*?&!\'\"$()`{}\[\]<>]',
'_', path, re.UNICODE)
path = re.sub(r'[*?&!\'\"$()`{}\[\]<>]', '_', path)
if self._fat:
path = separators(path)
# : and | already gone, but leave them here for reference
path = re.sub(r'[:*?"<>|]', '_', path, re.UNICODE)
path = re.sub(r'[:*?"<>|]', '_', path)
return path

View File

@@ -47,7 +47,7 @@ class Program:
:vartype metadata: mbngs.DiscMetadata
:cvar result: the rip's result
:vartype result: result.RipResult
:vartype outdir: unicode
:vartype outdir: str
:vartype config: whipper.common.config.Config
"""
@@ -197,8 +197,8 @@ class Program:
- %x: audio extension, lowercase
- %X: audio extension, uppercase
"""
assert isinstance(outdir, unicode), "%r is not unicode" % outdir
assert isinstance(template, unicode), "%r is not unicode" % template
assert isinstance(outdir, str), "%r is not str" % outdir
assert isinstance(template, str), "%r is not str" % template
v = {}
v['A'] = 'Unknown Artist'
v['d'] = mbdiscid # fallback for title
@@ -228,7 +228,7 @@ class Program:
if metadata.releaseType:
v['R'] = metadata.releaseType
v['r'] = metadata.releaseType.lower()
if track_number > 0:
if track_number is not None and track_number > 0:
v['a'] = self._filter.filter(
metadata.tracks[track_number - 1].artist)
v['s'] = self._filter.filter(
@@ -307,8 +307,8 @@ class Program:
print('\nMatching releases:')
for metadata in metadatas:
print('\nArtist : %s' % metadata.artist.encode('utf-8'))
print('Title : %s' % metadata.title.encode('utf-8'))
print('\nArtist : %s' % metadata.artist)
print('Title : %s' % metadata.title)
print('Duration: %s' % common.formatTime(
metadata.duration / 1000.0))
print('URL : %s' % metadata.url)
@@ -318,8 +318,7 @@ class Program:
print("Barcode : %s" % metadata.barcode)
# TODO: Add test for non ASCII catalog numbers: see issue #215
if metadata.catalogNumber:
print("Cat no : %s" %
metadata.catalogNumber.encode('utf-8'))
print("Cat no : %s" % metadata.catalogNumber)
delta = abs(metadata.duration - ittoc.duration())
if delta not in deltas:
@@ -334,7 +333,7 @@ class Program:
if prompt:
guess = (deltas[lowest])[0].mbid
release = raw_input(
release = input(
"\nPlease select a release [%s]: " % guess)
if not release:
@@ -346,8 +345,8 @@ class Program:
metadatas)
if len(metadatas) == 1:
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'))
print('Artist: %s' % metadatas[0].artist)
print('Title : %s' % metadatas[0].title)
elif not metadatas:
logger.warning("requested release id '%s', but none of "
"the found releases match", release)
@@ -374,8 +373,8 @@ class Program:
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'))
print('Artist : %s' % artist)
print('Title : %s' % metadatas[0].title)
# Select one of the returned releases. We just pick the first one.
ret = metadatas[0]
@@ -395,10 +394,10 @@ class Program:
:rtype: dict
"""
trackArtist = u'Unknown Artist'
releaseArtist = u'Unknown Artist'
disc = u'Unknown Disc'
title = u'Unknown Track'
trackArtist = 'Unknown Artist'
releaseArtist = 'Unknown Artist'
disc = 'Unknown Disc'
title = 'Unknown Track'
if self.metadata:
trackArtist = self.metadata.artist
@@ -435,7 +434,7 @@ class Program:
tags['TITLE'] = title
tags['ALBUM'] = disc
tags['TRACKNUMBER'] = u'%s' % number
tags['TRACKNUMBER'] = '%s' % number
if self.metadata:
if self.metadata.release is not None:
@@ -506,8 +505,7 @@ class Program:
stop = self.result.table.getTrackEnd(trackResult.number)
dirname = os.path.dirname(trackResult.filename)
if not os.path.exists(dirname):
os.makedirs(dirname)
os.makedirs(dirname, exist_ok=True)
if not what:
what = 'track %d' % (trackResult.number, )
@@ -573,7 +571,7 @@ class Program:
def write_m3u(self, discname):
m3uPath = common.truncate_filename(discname + '.m3u')
with open(m3uPath, 'w') as f:
f.write(u'#EXTM3U\n'.encode('utf-8'))
f.write('#EXTM3U\n')
for track in self.result.tracks:
if not track.filename:
# false positive htoa
@@ -586,10 +584,10 @@ class Program:
common.FRAMES_PER_SECOND)
target_path = common.getRelativePath(track.filename, m3uPath)
u = u'#EXTINF:%d,%s\n' % (length, target_path)
f.write(u.encode('utf-8'))
u = '#EXTINF:%d,%s\n' % (length, target_path)
f.write(u)
u = '%s\n' % target_path
f.write(u.encode('utf-8'))
f.write(u)
def writeCue(self, discName):
assert self.result.table.canCue()
@@ -597,7 +595,7 @@ class Program:
logger.debug('write .cue file to %s', cuePath)
handle = open(cuePath, 'w')
# FIXME: do we always want utf-8 ?
handle.write(self.result.table.cue(cuePath).encode('utf-8'))
handle.write(self.result.table.cue(cuePath))
handle.close()
self.cuePath = cuePath
@@ -608,7 +606,7 @@ class Program:
logPath = common.truncate_filename(discName + '.log')
handle = open(logPath, 'w')
log = txt_logger.log(self.result)
handle.write(log.encode('utf-8'))
handle.write(log)
handle.close()
self.logPath = logPath

View File

@@ -24,7 +24,7 @@ import tempfile
"""Rename files on file system and inside metafiles in a resumable way."""
class Operator(object):
class Operator:
def __init__(self, statePath, key):
self._todo = []
@@ -91,7 +91,7 @@ class Operator(object):
Execute the operations
"""
def next(self):
def __next__(self):
operation = self._todo[len(self._done)]
if self._resuming:
operation.redo()
@@ -116,7 +116,7 @@ class FileRenamer(Operator):
"""
class Operation(object):
class Operation:
def verify(self):
"""
@@ -199,7 +199,8 @@ class RenameInFile(Operation):
(fd, name) = tempfile.mkstemp(suffix='.whipper')
for s in handle:
os.write(fd, s.replace(self._source, self._destination))
os.write(fd,
s.replace(self._source, self._destination).encode())
os.close(fd)
os.rename(name, self._path)

View File

@@ -11,7 +11,7 @@ import sys
PIPE = subprocess.PIPE
if subprocess.mswindows:
if sys.platform == 'win32':
from win32file import ReadFile, WriteFile
from win32pipe import PeekNamedPipe
import msvcrt
@@ -42,7 +42,7 @@ class Popen(subprocess.Popen):
getattr(self, which).close()
setattr(self, which, None)
if subprocess.mswindows:
if sys.platform == 'win32':
def send(self, in_put):
if not self.stdin:
@@ -149,28 +149,4 @@ def recv_some(p, t=.1, e=1, tr=5, stderr=0):
y.append(r)
else:
time.sleep(max((x - time.time()) / tr, 0))
return ''.join(y)
def send_all(p, data):
while data:
sent = p.send(data)
if sent is None:
raise Exception(message)
data = buffer(data, sent)
if __name__ == '__main__':
if sys.platform == 'win32':
shell, commands, tail = ('cmd', ('dir /w', 'echo HELLO WORLD'), '\r\n')
else:
shell, commands, tail = ('sh', ('ls', 'echo HELLO WORLD'), '\n')
a = Popen(shell, stdin=PIPE, stdout=PIPE)
print(recv_some(a))
for cmd in commands:
send_all(a, cmd + tail)
print(recv_some(a))
send_all(a, 'exit' + tail)
print(recv_some(a, e=0))
a.wait()
return ''.join(x.decode() for x in y).encode()

View File

@@ -17,16 +17,13 @@
# USA
import sys
def digit_sum(i):
"""returns the sum of all digits for the given integer"""
return sum(map(int, str(i)))
class DiscID(object):
class DiscID:
def __init__(self, offsets, total_length, track_count, playable_length):
"""offsets is a list of track offsets, in CD frames
total_length is the total length of the disc, in seconds
@@ -53,15 +50,8 @@ class DiscID(object):
"track_count",
"playable_length"]]))
if sys.version_info[0] >= 3:
def __str__(self):
return self.__unicode__()
else:
def __str__(self):
return self.__unicode__().encode('ascii')
def __unicode__(self):
return u"{:08X}".format(int(self))
def __str__(self):
return "{:08X}".format(int(self))
def __int__(self):
digit_sum_ = sum([digit_sum(o // 75) for o in self.offsets])
@@ -90,11 +80,11 @@ def perform_lookup(disc_id, freedb_server, freedb_port):
query = freedb_command(freedb_server,
freedb_port,
u"query",
*([disc_id.__unicode__(),
u"{:d}".format(disc_id.track_count)] +
[u"{:d}".format(o) for o in disc_id.offsets] +
[u"{:d}".format(disc_id.playable_length)]))
"query",
*([disc_id.__str__(),
"{:d}".format(disc_id.track_count)] +
["{:d}".format(o) for o in disc_id.offsets] +
["{:d}".format(disc_id.playable_length)]))
line = next(query)
response = RESPONSE.match(line)
@@ -116,7 +106,7 @@ def perform_lookup(disc_id, freedb_server, freedb_port):
elif (code == 211) or (code == 210):
# multiple exact or inexact matches
line = next(query)
while not line.startswith(u"."):
while not line.startswith("."):
match = QUERY_RESULT.match(line)
if match is not None:
matches.append((match.group(1),
@@ -140,7 +130,7 @@ def perform_lookup(disc_id, freedb_server, freedb_port):
query = freedb_command(freedb_server,
freedb_port,
u"read",
"read",
category,
disc_id)
@@ -149,8 +139,8 @@ def perform_lookup(disc_id, freedb_server, freedb_port):
# FIXME: check response code here
freedb = {}
line = next(query)
while not line.startswith(u"."):
if not line.startswith(u"#"):
while not line.startswith("."):
if not line.startswith("#"):
entry = FREEDB_LINE.match(line)
if entry is not None:
if entry.group(1) in freedb:
@@ -165,52 +155,38 @@ def perform_lookup(disc_id, freedb_server, freedb_port):
def freedb_command(freedb_server, freedb_port, cmd, *args):
"""given a freedb_server string, freedb_port int,
command unicode string and argument unicode strings,
yields a list of Unicode strings"""
command string and argument strings, yields a list of strings"""
try:
from urllib.request import urlopen
from urllib.error import URLError
except ImportError:
from urllib2 import urlopen, URLError
try:
from urllib.parse import urlencode
except ImportError:
from urllib import urlencode
from urllib.error import URLError
from urllib.request import urlopen
from urllib.parse import urlencode
from socket import getfqdn
from whipper import __version__ as VERSION
from sys import version_info
PY3 = version_info[0] >= 3
# some debug type checking
assert(isinstance(cmd, str if PY3 else unicode))
assert(isinstance(cmd, str))
for arg in args:
assert(isinstance(arg, str if PY3 else unicode))
assert(isinstance(arg, str))
POST = []
# generate query to post with arguments in specific order
if len(args) > 0:
POST.append((u"cmd", u"cddb {} {}".format(cmd, " ".join(args))))
POST.append(("cmd", "cddb {} {}".format(cmd, " ".join(args))))
else:
POST.append((u"cmd", u"cddb {}".format(cmd)))
POST.append(("cmd", "cddb {}".format(cmd)))
POST.append(
(u"hello",
u"user {} {} {}".format(
getfqdn() if PY3 else getfqdn().decode("UTF-8", "replace"),
u"whipper",
VERSION if PY3 else VERSION.decode("ascii"))))
("hello",
"user {} {} {}".format(getfqdn(), "whipper", VERSION)))
POST.append((u"proto", u"6"))
POST.append(("proto", "6"))
try:
# get Request object from post
request = urlopen(
"http://{}:{:d}/~cddb/cddb.cgi".format(freedb_server, freedb_port),
urlencode(POST).encode("UTF-8") if (version_info[0] >= 3) else
urlencode(POST))
urlencode(POST).encode())
except URLError as e:
raise ValueError(str(e))
try:

View File

@@ -18,7 +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/>.
from __future__ import print_function
import logging
import sys
@@ -69,7 +68,7 @@ def _getExceptionMessage(exception, frame=-1, filename=None):
% locals()
class LogStub(object):
class LogStub:
"""
I am a stub for a log interface.
"""
@@ -244,7 +243,7 @@ class Task(LogStub):
# FIXME: should this become a real interface, like in zope ?
class ITaskListener(object):
class ITaskListener:
"""
I am an interface for objects listening to tasks.
"""
@@ -484,7 +483,7 @@ class SyncRunner(TaskRunner, ITaskListener):
self._task.addListener(self)
# only start the task after going into the mainloop,
# otherwise the task might complete before we are in it
GLib.timeout_add(0L, self._startWrap, self._task)
GLib.timeout_add(0, self._startWrap, self._task)
self.debug('run loop')
self._loop.run()
@@ -525,7 +524,7 @@ class SyncRunner(TaskRunner, ITaskListener):
self.stopped(task)
raise
GLib.timeout_add(int(delta * 1000L), c)
GLib.timeout_add(int(delta * 1000), c)
# ITaskListener methods
def progressed(self, task, value):

View File

@@ -25,7 +25,6 @@ See http://digitalx.org/cuesheetsyntax.php
"""
import re
import codecs
from whipper.common import common
from whipper.image import table
@@ -58,7 +57,7 @@ _INDEX_RE = re.compile(r"""
""", re.VERBOSE)
class CueFile(object):
class CueFile:
"""
I represent a .cue file as an object.
@@ -69,9 +68,9 @@ class CueFile(object):
def __init__(self, path):
"""
:type path: unicode
:type path: str
"""
assert isinstance(path, unicode), "%r is not unicode" % path
assert isinstance(path, str), "%r is not str" % path
self._path = path
self._rems = {}
@@ -86,9 +85,9 @@ class CueFile(object):
counter = 0
logger.info('parsing .cue file %r', self._path)
handle = codecs.open(self._path, 'r', 'utf-8')
for number, line in enumerate(handle.readlines()):
with open(self._path) as f:
content = f.readlines()
for number, line in enumerate(content):
line = line.rstrip()
m = _REM_RE.search(line)
@@ -137,9 +136,9 @@ class CueFile(object):
minutes = int(m.expand('\\2'))
seconds = int(m.expand('\\3'))
frames = int(m.expand('\\4'))
frameOffset = frames \
+ seconds * common.FRAMES_PER_SECOND \
+ minutes * common.FRAMES_PER_SECOND * 60
frameOffset = int(frames
+ seconds * common.FRAMES_PER_SECOND
+ minutes * common.FRAMES_PER_SECOND * 60)
logger.debug('found index %d of track %r in %r:%d',
indexNumber, currentTrack, currentFile.path,
@@ -182,7 +181,7 @@ class CueFile(object):
"""
Translate the .cue's FILE to an existing path.
:type path: unicode
:type path: str
"""
return common.getRealPath(self._path, path)
@@ -194,9 +193,9 @@ class File:
def __init__(self, path, file_format):
"""
:type path: unicode
:type path: str
"""
assert isinstance(path, unicode), "%r is not unicode" % path
assert isinstance(path, str), "%r is not str" % path
self.path = path
self.format = file_format

View File

@@ -34,7 +34,7 @@ import logging
logger = logging.getLogger(__name__)
class Image(object):
class Image:
"""
:ivar table: The Table of Contents for this image.
:vartype table: table.Table
@@ -43,10 +43,10 @@ class Image(object):
def __init__(self, path):
"""
:type path: unicode
:type path: str
:param path: .cue path
"""
assert isinstance(path, unicode), "%r is not unicode" % path
assert isinstance(path, str), "%r is not str" % path
self._path = path
self.cue = cue.CueFile(path)
@@ -62,7 +62,7 @@ class Image(object):
:param path: .cue path
"""
assert isinstance(path, unicode), "%r is not unicode" % path
assert isinstance(path, str), "%r is not str" % path
return self.cue.getRealPath(path)
@@ -130,7 +130,7 @@ class ImageVerifyTask(task.MultiSeparateTask):
htoa = cue.table.tracks[0].indexes[0]
track = cue.table.tracks[0]
path = image.getRealPath(htoa.path)
assert isinstance(path, unicode), "%r is not unicode" % path
assert isinstance(path, str), "%r is not str" % path
logger.debug('schedule scan of audio length of %r', path)
taskk = AudioLengthTask(path)
self.addTask(taskk)
@@ -145,7 +145,7 @@ class ImageVerifyTask(task.MultiSeparateTask):
if length == -1:
path = image.getRealPath(index.path)
assert isinstance(path, unicode), "%r is not unicode" % path
assert isinstance(path, str), "%r is not str" % path
logger.debug('schedule scan of audio length of %r', path)
taskk = AudioLengthTask(path)
self.addTask(taskk)
@@ -167,7 +167,7 @@ class ImageVerifyTask(task.MultiSeparateTask):
"in debug log (set RIP_DEBUG=4)")
index = track.indexes[1]
assert taskk.length % common.SAMPLES_PER_FRAME == 0
end = taskk.length / common.SAMPLES_PER_FRAME
end = taskk.length // common.SAMPLES_PER_FRAME
self.lengths[trackIndex] = end - index.relative
task.MultiSeparateTask.stop(self)
@@ -192,7 +192,7 @@ class ImageEncodeTask(task.MultiSeparateTask):
def add(index):
path = image.getRealPath(index.path)
assert isinstance(path, unicode), "%r is not unicode" % path
assert isinstance(path, str), "%r is not str" % path
logger.debug('schedule encode of %r', path)
root, _ = os.path.splitext(os.path.basename(path))
outpath = os.path.join(outdir, root + '.' + 'flac')

View File

@@ -23,8 +23,7 @@ Wrap Table of Contents.
"""
import copy
import urllib
import urlparse
from urllib.parse import urlunparse, urlencode
import whipper
@@ -66,7 +65,7 @@ class Track:
:vartype isrc: str
:cvar cdtext: dictionary of CD Text information;
:any:`see CDTEXT_KEYS`
:vartype cdtext: str -> unicode
:vartype cdtext: str
:cvar pre_emphasis: whether track is pre-emphasised
:vartype pre_emphasis: bool
"""
@@ -91,10 +90,10 @@ class Track:
def index(self, number, absolute=None, path=None, relative=None,
counter=None):
"""
:type path: unicode or None
:type path: str or None
"""
if path is not None:
assert isinstance(path, unicode), "%r is not unicode" % path
assert isinstance(path, str), "%r is not str" % path
i = Index(number, absolute, path, relative, counter)
self.indexes[number] = i
@@ -133,7 +132,7 @@ class Index:
"""
:cvar counter: counter for the index source; distinguishes between
the matching FILE lines in .cue files for example
:vartype path: unicode or None
:vartype path: str or None
"""
number = None
absolute = None
@@ -145,7 +144,7 @@ class Index:
counter=None):
if path is not None:
assert isinstance(path, unicode), "%r is not unicode" % path
assert isinstance(path, str), "%r is not str" % path
self.number = number
self.absolute = absolute
@@ -158,7 +157,7 @@ class Index:
self.number, self.absolute, self.path, self.relative, self.counter)
class Table(object):
class Table:
"""
I represent a table of indexes on a CD.
@@ -221,7 +220,10 @@ class Table(object):
# if on a session border, subtract the session leadin
thisTrack = self.tracks[number - 1]
nextTrack = self.tracks[number]
if nextTrack.session > thisTrack.session:
# The session attribute of a track is None by default (session 1)
# with value > 1 if the track is in another session. Py3 doesn't
# allow NoneType comparisons so we compare against 1 in that case
if int(nextTrack.session or 1) > int(thisTrack.session or 1):
gap = self._getSessionGap(nextTrack.session)
end -= gap
@@ -286,7 +288,7 @@ class Table(object):
offset = self.getTrackStart(track.number) + delta
offsets.append(offset)
debug.append(str(offset))
seconds = offset / common.FRAMES_PER_SECOND
seconds = offset // common.FRAMES_PER_SECOND
n += self._cddbSum(seconds)
# the 'real' leadout, not offset by 150 frames
@@ -297,8 +299,8 @@ class Table(object):
# FIXME: we can't replace these calculations with the getFrameLength
# call because the start and leadout in the algorithm get rounded
# before making the difference
startSeconds = self.getTrackStart(1) / common.FRAMES_PER_SECOND
leadoutSeconds = leadout / common.FRAMES_PER_SECOND
startSeconds = self.getTrackStart(1) // common.FRAMES_PER_SECOND
leadoutSeconds = leadout // common.FRAMES_PER_SECOND
t = leadoutSeconds - startSeconds
# durationFrames = self.getFrameLength(data=True)
# duration = durationFrames / common.FRAMES_PER_SECOND
@@ -348,12 +350,12 @@ class Table(object):
sha = sha1()
# number of first track
sha.update("%02X" % values[0])
sha.update(("%02X" % values[0]).encode())
# number of last track
sha.update("%02X" % values[1])
sha.update(("%02X" % values[1]).encode())
sha.update("%08X" % values[2])
sha.update(("%08X" % values[2]).encode())
# offsets of tracks
for i in range(1, 100):
@@ -361,7 +363,7 @@ class Table(object):
offset = values[2 + i]
except IndexError:
offset = 0
sha.update("%08X" % offset)
sha.update(("%08X" % offset).encode())
digest = sha.digest()
assert len(digest) == 20, \
@@ -372,10 +374,10 @@ class Table(object):
# (Rob) used ., _, and -
# base64 altchars specify replacements for + and /
result = base64.b64encode(digest, '._')
result = base64.b64encode(digest, b'._').decode()
# now replace =
result = "-".join(result.split("="))
result = result.replace("=", "-")
assert len(result) == 28, \
"Result should be 28 characters, not %d" % len(result)
@@ -389,13 +391,13 @@ class Table(object):
discid = self.getMusicBrainzDiscId()
values = self._getMusicBrainzValues()
query = urllib.urlencode({
'id': discid,
'toc': ' '.join([str(v) for v in values]),
'tracks': self.getAudioTracks(),
})
query = urlencode([
('toc', ' '.join([str(v) for v in values])),
('tracks', self.getAudioTracks()),
('id', discid),
])
return urlparse.urlunparse((
return urlunparse((
'https', host, '/cdtoc/attach', '', query, ''))
def getFrameLength(self, data=False):
@@ -477,7 +479,7 @@ class Table(object):
Dump our internal representation to a .cue file content.
:rtype: unicode
:rtype: str
"""
logger.debug('generating .cue for cuePath %r', cuePath)

View File

@@ -25,7 +25,6 @@ The .toc file format is described in the man page of cdrdao
"""
import re
import codecs
from whipper.common import common
from whipper.image import table
@@ -134,13 +133,13 @@ class Sources:
return self._sources[-1][1]
class TocFile(object):
class TocFile:
def __init__(self, path):
"""
:type path: unicode
:type path: str
"""
assert isinstance(path, unicode), "%r is not unicode" % path
assert isinstance(path, str), "%r is not str" % path
self._path = path
self._messages = []
self.table = table.Table()
@@ -189,9 +188,9 @@ class TocFile(object):
# the first track's INDEX 1 can only be gotten from the .toc
# file once the first pregap is calculated; so we add INDEX 1
# at the end of each parsed TRACK record
handle = codecs.open(self._path, "r", "utf-8")
for number, line in enumerate(handle.readlines()):
with open(self._path) as f:
content = f.readlines()
for number, line in enumerate(content):
line = line.rstrip()
# look for CDTEXT stuff in either header or tracks
@@ -202,7 +201,7 @@ class TocFile(object):
# usually, value is encoded with octal escapes and in latin-1
# FIXME: other encodings are possible, does cdrdao handle
# them ?
value = value.decode('string-escape').decode('latin-1')
value = value.encode().decode('unicode_escape')
if key in table.CDTEXT_FIELDS:
# FIXME: consider ISRC separate for now, but this
# is a limitation of our parser approach
@@ -412,7 +411,7 @@ class TocFile(object):
"""
Translate the .toc's FILE to an existing path.
:type path: unicode
:type path: str
"""
return common.getRealPath(self._path, path)
@@ -424,12 +423,12 @@ class File:
def __init__(self, path, start, length):
"""
:type path: unicode
:type path: str
:type start: int
:param start: starting point for the track in this file, in frames
:param length: length for the track in this file, in frames
"""
assert isinstance(path, unicode), "%r is not unicode" % path
assert isinstance(path, str), "%r is not str" % path
self.path = path
self.start = start

View File

@@ -2,4 +2,4 @@ import accuraterip
def accuraterip_checksum(f, track_number, total_tracks):
return accuraterip.compute(f.encode('utf-8'), track_number, total_tracks)
return accuraterip.compute(f, track_number, total_tracks)

View File

@@ -220,7 +220,7 @@ class ReadTrackTask(task.Task):
Read the given track.
:param path: where to store the ripped track
:type path: unicode
:type path: str
:param table: table of contents of CD
:type table: table.Table
:param start: first frame to rip
@@ -236,7 +236,7 @@ class ReadTrackTask(task.Task):
:param what: a string representing what's being read; e.g. Track
:type what: str
"""
assert isinstance(path, unicode), "%r is not unicode" % path
assert isinstance(path, str), "%r is not str" % path
self.path = path
self._table = table
@@ -314,7 +314,7 @@ class ReadTrackTask(task.Task):
self.schedule(0.01, self._read, runner)
return
self._buffer += ret
self._buffer += ret.decode()
# parse buffer into lines if possible, and parse them
if "\n" in self._buffer:
@@ -452,8 +452,7 @@ class ReadVerifyTrackTask(task.MultiSeparateTask):
logger.debug('read and verify with taglist %r', taglist)
# FIXME: choose a dir on the same disk/dir as the final path
fd, tmppath = tempfile.mkstemp(suffix='.whipper.wav')
tmppath = unicode(tmppath)
os.fchmod(fd, 0644)
os.fchmod(fd, 0o644)
os.close(fd)
self._tmpwavpath = tmppath
@@ -472,13 +471,13 @@ class ReadVerifyTrackTask(task.MultiSeparateTask):
# encode to the final path + '.part'
try:
tmpoutpath = path + u'.part'
tmpoutpath = path + '.part'
open(tmpoutpath, 'wb').close()
except IOError as e:
if errno.ENAMETOOLONG != e.errno:
raise
path = common.truncate_filename(common.shrinkPath(path))
tmpoutpath = common.truncate_filename(path + u'.part')
tmpoutpath = common.truncate_filename(path + '.part')
open(tmpoutpath, 'wb').close()
self._tmppath = tmpoutpath
self.path = path
@@ -597,7 +596,7 @@ class AnalyzeTask(ctask.PopenTask):
def done(self):
if self.cwd:
shutil.rmtree(self.cwd)
output = "".join(self._output)
output = "".join(o.decode() for o in self._output)
m = _OK_RE.search(output)
self.defeatsCache = bool(m)

View File

@@ -84,7 +84,7 @@ class ReadTOCTask(task.Task):
self._parser = ProgressParser()
self.fd, self.tocfile = tempfile.mkstemp(
suffix=u'.cdrdao.read-toc.whipper.task')
suffix='.cdrdao.read-toc.whipper.task')
def start(self, runner):
task.Task.start(self, runner)
@@ -112,7 +112,7 @@ class ReadTOCTask(task.Task):
return
self.schedule(0.01, self._read, runner)
return
self._buffer += ret
self._buffer += ret.decode()
# parse buffer into lines if possible, and parse them
if "\n" in self._buffer:
@@ -151,8 +151,7 @@ class ReadTOCTask(task.Task):
t_comp = os.path.abspath(self.toc_path).split(os.sep)
t_dirn = os.sep.join(t_comp[:-1])
# If the output path doesn't exist, make it recursively
if not os.path.isdir(t_dirn):
os.makedirs(t_dirn)
os.makedirs(t_dirn, exist_ok=True)
t_dst = truncate_filename(
os.path.join(t_dirn, t_comp[-1] + '.toc'))
shutil.copy(self.tocfile, os.path.join(t_dirn, t_dst))
@@ -168,7 +167,7 @@ def DetectCdr(device):
cmd = [CDRDAO, 'disk-info', '-v1', '--device', device]
logger.debug("executing %r", cmd)
p = Popen(cmd, stdout=PIPE, stderr=PIPE)
return 'CD-R medium : n/a' not in p.stdout.read()
return 'CD-R medium : n/a' not in p.stdout.read().decode()
def version():

View File

@@ -21,11 +21,11 @@ class AudioLengthTask(ctask.PopenTask):
def __init__(self, path):
"""
:type path: unicode
:type path: str
"""
assert isinstance(path, unicode), "%r is not unicode" % path
assert isinstance(path, str), "%r is not str" % path
self.logName = os.path.basename(path).encode('utf-8')
self.logName = os.path.basename(path)
self.command = [SOXI, '-s', path]
@@ -47,4 +47,4 @@ class AudioLengthTask(ctask.PopenTask):
def done(self):
if self._error:
logger.warning("soxi reported on stderr: %s", "".join(self._error))
self.length = int("".join(self._output))
self.length = int("".join(o.decode() for o in self._output))

View File

@@ -119,7 +119,7 @@ class RipResult:
return None
class Logger(object):
class Logger:
"""
I log the result of a rip.
"""
@@ -140,7 +140,7 @@ class Logger(object):
# A setuptools-like entry point
class EntryPoint(object):
class EntryPoint:
name = 'whipper'
@staticmethod

View File

@@ -70,7 +70,8 @@ class TestCase(unittest.TestCase):
version so we can use it in comparisons.
"""
cuefile = os.path.join(os.path.dirname(__file__), name)
ret = open(cuefile).read().decode('utf-8')
with open(cuefile) as f:
ret = f.read()
ret = re.sub(
'REM COMMENT "whipper.*',
'REM COMMENT "whipper %s"' % whipper.__version__,
@@ -83,7 +84,7 @@ class UnicodeTestMixin:
# A helper mixin to skip tests if we're not in a UTF-8 locale
try:
os.stat(u'whipper.test.B\xeate Noire.empty')
os.stat('whipper.test.B\xeate Noire.empty')
except UnicodeEncodeError:
skip = 'No UTF-8 locale'
except OSError:

View File

@@ -1,5 +1,5 @@
# vi:si:et:sw=4:sts=4:ts=4:set fileencoding=utf-8
u"""Tests for whipper.command.mblookup"""
"""Tests for whipper.command.mblookup"""
import os
import pickle
@@ -9,22 +9,22 @@ from whipper.command import mblookup
class MBLookupTestCase(unittest.TestCase):
u"""Test cases for whipper.command.mblookup.MBLookup"""
"""Test cases for whipper.command.mblookup.MBLookup"""
@staticmethod
def _mock_musicbrainz(discid, country=None, record=False):
u"""Mock function for whipper.common.mbngs.musicbrainz function."""
filename = u"whipper.discid.{}.pickle".format(discid)
"""Mock function for whipper.common.mbngs.musicbrainz function."""
filename = "whipper.discid.{}.pickle".format(discid)
path = os.path.join(os.path.dirname(__file__), filename)
with open(path) as p:
with open(path, "rb") as p:
return pickle.load(p)
def testMissingReleaseType(self):
u"""Test that lookup for release without a type set doesn't fail."""
"""Test that lookup for release without a type set doesn't fail."""
# Using: Gustafsson, Österberg & Cowle - What's Up? 8 (disc 4)
# https://musicbrainz.org/release/d8e6153a-2c47-4804-9d73-0aac1081c3b1
mblookup.musicbrainz = self._mock_musicbrainz
discid = u"xu338_M8WukSRi0J.KTlDoflB8Y-"
discid = "xu338_M8WukSRi0J.KTlDoflB8Y-"
# https://musicbrainz.org/cdtoc/xu338_M8WukSRi0J.KTlDoflB8Y-
lookup = mblookup.MBLookup([discid], u'whipper mblookup', None)
lookup = mblookup.MBLookup([discid], 'whipper mblookup', None)
lookup.do()

View File

@@ -2,7 +2,7 @@
# vi:si:et:sw=4:sts=4:ts=4
import sys
from StringIO import StringIO
from io import StringIO
from os import chmod, makedirs
from os.path import dirname, exists, join
from shutil import copy, rmtree
@@ -21,9 +21,8 @@ class TestAccurateRipResponse(TestCase):
@classmethod
def setUpClass(cls):
cls.path = 'c/1/2/dBAR-002-0000f21c-00027ef8-05021002.bin'
cls.entry = _split_responses(
open(join(dirname(__file__), cls.path[6:])).read()
)
with open(join(dirname(__file__), cls.path[6:]), 'rb') as f:
cls.entry = _split_responses(f.read())
cls.other_path = '4/8/2/dBAR-011-0010e284-009228a3-9809ff0b.bin'
def setUp(self):
@@ -100,9 +99,8 @@ class TestVerifyResult(TestCase):
@classmethod
def setUpClass(cls):
path = 'c/1/2/dBAR-002-0000f21c-00027ef8-05021002.bin'
cls.responses = _split_responses(
open(join(dirname(__file__), path[6:])).read()
)
with open(join(dirname(__file__), path[6:]), 'rb') as f:
cls.responses = _split_responses(f.read())
cls.checksums = {
'v1': ['284fc705', '9cc1f32e'],
'v2': ['dc77f9ab', 'dd97d2c3'],

View File

@@ -12,7 +12,7 @@ from whipper.test import common as tcommon
class ShrinkTestCase(tcommon.TestCase):
def testSufjan(self):
path = (u'whipper/Sufjan Stevens - Illinois/02. Sufjan Stevens - '
path = ('whipper/Sufjan Stevens - Illinois/02. Sufjan Stevens - '
'The Black Hawk War, or, How to Demolish an Entire '
'Civilization and Still Feel Good About Yourself in the '
'Morning, or, We Apologize for the Inconvenience but '
@@ -52,7 +52,7 @@ class GetRelativePathTestCase(tcommon.TestCase):
class GetRealPathTestCase(tcommon.TestCase):
def testRealWithBackslash(self):
fd, path = tempfile.mkstemp(suffix=u'back\\slash.flac')
fd, path = tempfile.mkstemp(suffix='back\\slash.flac')
refPath = os.path.join(os.path.dirname(path), 'fake.cue')
self.assertEqual(common.getRealPath(refPath, path), path)

View File

@@ -12,7 +12,7 @@ from whipper.test import common as tcommon
class ConfigTestCase(tcommon.TestCase):
def setUp(self):
fd, self._path = tempfile.mkstemp(suffix=u'.whipper.test.config')
fd, self._path = tempfile.mkstemp(suffix='.whipper.test.config')
os.close(fd)
self._config = config.Config(self._path)

View File

@@ -18,7 +18,7 @@ class MetadataTestCase(unittest.TestCase):
filename = 'whipper.release.c56ff16e-1d81-47de-926f-ba22891bd2bd.json'
path = os.path.join(os.path.dirname(__file__), filename)
handle = open(path, "rb")
response = json.loads(handle.read())
response = json.loads(handle.read().decode('utf-8'))
handle.close()
discid = "b.yqPuCBdsV5hrzDvYrw52iK_jE-"
@@ -31,16 +31,16 @@ class MetadataTestCase(unittest.TestCase):
filename = 'whipper.release.a76714e0-32b1-4ed4-b28e-f86d99642193.json'
path = os.path.join(os.path.dirname(__file__), filename)
handle = open(path, "rb")
response = json.loads(handle.read())
response = json.loads(handle.read().decode('utf-8'))
handle.close()
discid = "f7XO36a7n1LCCskkCiulReWbwZA-"
metadata = mbngs._getMetadata(response['release'], discid)
self.assertEqual(metadata.artist, u'Various Artists')
self.assertEqual(metadata.release, u'2001-10-15')
self.assertEqual(metadata.artist, 'Various Artists')
self.assertEqual(metadata.release, '2001-10-15')
self.assertEqual(metadata.mbidArtist,
[u'89ad4ac3-39f7-470e-963a-56509c546377'])
['89ad4ac3-39f7-470e-963a-56509c546377'])
self.assertEqual(len(metadata.tracks), 18)
@@ -48,43 +48,43 @@ class MetadataTestCase(unittest.TestCase):
self.assertEqual(track16.artist, 'Tom Jones & Stereophonics')
self.assertEqual(track16.mbidArtist, [
u'57c6f649-6cde-48a7-8114-2a200247601a',
u'0bfba3d3-6a04-4779-bb0a-df07df5b0558',
'57c6f649-6cde-48a7-8114-2a200247601a',
'0bfba3d3-6a04-4779-bb0a-df07df5b0558',
])
self.assertEqual(track16.sortName,
u'Jones, Tom & Stereophonics')
'Jones, Tom & Stereophonics')
def testBalladOfTheBrokenSeas(self):
# various artists disc
filename = 'whipper.release.e32ae79a-336e-4d33-945c-8c5e8206dbd3.json'
path = os.path.join(os.path.dirname(__file__), filename)
handle = open(path, "rb")
response = json.loads(handle.read())
response = json.loads(handle.read().decode('utf-8'))
handle.close()
discid = "xAq8L4ELMW14.6wI6tt7QAcxiDI-"
metadata = mbngs._getMetadata(response['release'], discid)
self.assertEqual(metadata.artist, u'Isobel Campbell & Mark Lanegan')
self.assertEqual(metadata.artist, 'Isobel Campbell & Mark Lanegan')
self.assertEqual(metadata.sortName,
u'Campbell, Isobel & Lanegan, Mark')
self.assertEqual(metadata.release, u'2006-01-30')
'Campbell, Isobel & Lanegan, Mark')
self.assertEqual(metadata.release, '2006-01-30')
self.assertEqual(metadata.mbidArtist, [
u'd51f3a15-12a2-41a0-acfa-33b5eae71164',
u'a9126556-f555-4920-9617-6e013f8228a7',
'd51f3a15-12a2-41a0-acfa-33b5eae71164',
'a9126556-f555-4920-9617-6e013f8228a7',
])
self.assertEqual(len(metadata.tracks), 12)
track12 = metadata.tracks[11]
self.assertEqual(track12.artist, u'Isobel Campbell & Mark Lanegan')
self.assertEqual(track12.artist, 'Isobel Campbell & Mark Lanegan')
self.assertEqual(track12.sortName,
u'Campbell, Isobel'
'Campbell, Isobel'
' & Lanegan, Mark')
self.assertEqual(track12.mbidArtist, [
u'd51f3a15-12a2-41a0-acfa-33b5eae71164',
u'a9126556-f555-4920-9617-6e013f8228a7',
'd51f3a15-12a2-41a0-acfa-33b5eae71164',
'a9126556-f555-4920-9617-6e013f8228a7',
])
def testMalaInCuba(self):
@@ -93,29 +93,29 @@ class MetadataTestCase(unittest.TestCase):
filename = 'whipper.release.61c6fd9b-18f8-4a45-963a-ba3c5d990cae.json'
path = os.path.join(os.path.dirname(__file__), filename)
handle = open(path, "rb")
response = json.loads(handle.read())
response = json.loads(handle.read().decode('utf-8'))
handle.close()
discid = "u0aKVpO.59JBy6eQRX2vYcoqQZ0-"
metadata = mbngs._getMetadata(response['release'], discid)
self.assertEqual(metadata.artist, u'Mala')
self.assertEqual(metadata.sortName, u'Mala')
self.assertEqual(metadata.release, u'2012-09-17')
self.assertEqual(metadata.artist, 'Mala')
self.assertEqual(metadata.sortName, 'Mala')
self.assertEqual(metadata.release, '2012-09-17')
self.assertEqual(metadata.mbidArtist,
[u'09f221eb-c97e-4da5-ac22-d7ab7c555bbb'])
['09f221eb-c97e-4da5-ac22-d7ab7c555bbb'])
self.assertEqual(len(metadata.tracks), 14)
track6 = metadata.tracks[5]
self.assertEqual(track6.artist, u'Mala feat. Dreiser & Sexto Sentido')
self.assertEqual(track6.artist, 'Mala feat. Dreiser & Sexto Sentido')
self.assertEqual(track6.sortName,
u'Mala feat. Dreiser & Sexto Sentido')
'Mala feat. Dreiser & Sexto Sentido')
self.assertEqual(track6.mbidArtist, [
u'09f221eb-c97e-4da5-ac22-d7ab7c555bbb',
u'ec07a209-55ff-4084-bc41-9d4d1764e075',
u'f626b92e-07b1-4a19-ad13-c09d690db66c',
'09f221eb-c97e-4da5-ac22-d7ab7c555bbb',
'ec07a209-55ff-4084-bc41-9d4d1764e075',
'f626b92e-07b1-4a19-ad13-c09d690db66c',
])
def testUnknownArtist(self):
@@ -130,33 +130,33 @@ class MetadataTestCase(unittest.TestCase):
filename = 'whipper.release.8478d4da-0cda-4e46-ae8c-1eeacfa5cf37.json'
path = os.path.join(os.path.dirname(__file__), filename)
handle = open(path, "rb")
response = json.loads(handle.read())
response = json.loads(handle.read().decode('utf-8'))
handle.close()
discid = "RhrwgVb0hZNkabQCw1dZIhdbMFg-"
metadata = mbngs._getMetadata(response['release'], discid)
self.assertEqual(metadata.artist, u'CunninLynguists')
self.assertEqual(metadata.release, u'2003')
self.assertEqual(metadata.artist, 'CunninLynguists')
self.assertEqual(metadata.release, '2003')
self.assertEqual(metadata.mbidArtist,
[u'69c4cc43-8163-41c5-ac81-30946d27bb69'])
['69c4cc43-8163-41c5-ac81-30946d27bb69'])
self.assertEqual(len(metadata.tracks), 30)
track8 = metadata.tracks[7]
self.assertEqual(track8.artist, u'???')
self.assertEqual(track8.sortName, u'[unknown]')
self.assertEqual(track8.artist, '???')
self.assertEqual(track8.sortName, '[unknown]')
self.assertEqual(track8.mbidArtist,
[u'125ec42a-7229-4250-afc5-e057484327fe'])
['125ec42a-7229-4250-afc5-e057484327fe'])
track9 = metadata.tracks[8]
self.assertEqual(track9.artist, u'CunninLynguists feat. Tonedeff')
self.assertEqual(track9.artist, 'CunninLynguists feat. Tonedeff')
self.assertEqual(track9.sortName,
u'CunninLynguists feat. Tonedeff')
'CunninLynguists feat. Tonedeff')
self.assertEqual(track9.mbidArtist, [
u'69c4cc43-8163-41c5-ac81-30946d27bb69',
u'b3869d83-9fb5-4eac-b5ca-2d155fcbee12'
'69c4cc43-8163-41c5-ac81-30946d27bb69',
'b3869d83-9fb5-4eac-b5ca-2d155fcbee12'
])
def testNenaAndKimWildSingle(self):
@@ -167,45 +167,45 @@ class MetadataTestCase(unittest.TestCase):
filename = 'whipper.release.f484a9fc-db21-4106-9408-bcd105c90047.json'
path = os.path.join(os.path.dirname(__file__), filename)
handle = open(path, "rb")
response = json.loads(handle.read())
response = json.loads(handle.read().decode('utf-8'))
handle.close()
discid = "X2c2IQ5vUy5x6Jh7Xi_DGHtA1X8-"
metadata = mbngs._getMetadata(response['release'], discid)
self.assertEqual(metadata.artist, u'Nena & Kim Wilde')
self.assertEqual(metadata.release, u'2003-05-19')
self.assertEqual(metadata.artist, 'Nena & Kim Wilde')
self.assertEqual(metadata.release, '2003-05-19')
self.assertEqual(metadata.mbidArtist, [
u'38bfaa7f-ee98-48cb-acd0-946d7aeecd76',
u'4b462375-c508-432a-8c88-ceeec38b16ae',
'38bfaa7f-ee98-48cb-acd0-946d7aeecd76',
'4b462375-c508-432a-8c88-ceeec38b16ae',
])
self.assertEqual(len(metadata.tracks), 4)
track1 = metadata.tracks[0]
self.assertEqual(track1.artist, u'Nena & Kim Wilde')
self.assertEqual(track1.sortName, u'Nena & Wilde, Kim')
self.assertEqual(track1.artist, 'Nena & Kim Wilde')
self.assertEqual(track1.sortName, 'Nena & Wilde, Kim')
self.assertEqual(track1.mbidArtist, [
u'38bfaa7f-ee98-48cb-acd0-946d7aeecd76',
u'4b462375-c508-432a-8c88-ceeec38b16ae',
'38bfaa7f-ee98-48cb-acd0-946d7aeecd76',
'4b462375-c508-432a-8c88-ceeec38b16ae',
])
self.assertEqual(track1.mbid,
u'1cc96e78-28ed-3820-b0b6-614c35b121ac')
'1cc96e78-28ed-3820-b0b6-614c35b121ac')
self.assertEqual(track1.mbidRecording,
u'fde5622c-ce23-4ebb-975d-51d4a926f901')
'fde5622c-ce23-4ebb-975d-51d4a926f901')
track2 = metadata.tracks[1]
self.assertEqual(track2.artist, u'Nena & Kim Wilde')
self.assertEqual(track2.sortName, u'Nena & Wilde, Kim')
self.assertEqual(track2.artist, 'Nena & Kim Wilde')
self.assertEqual(track2.sortName, 'Nena & Wilde, Kim')
self.assertEqual(track2.mbidArtist, [
u'38bfaa7f-ee98-48cb-acd0-946d7aeecd76',
u'4b462375-c508-432a-8c88-ceeec38b16ae',
'38bfaa7f-ee98-48cb-acd0-946d7aeecd76',
'4b462375-c508-432a-8c88-ceeec38b16ae',
])
self.assertEqual(track2.mbid,
u'f16db4bf-9a34-3d5a-a975-c9375ab7a2ca')
'f16db4bf-9a34-3d5a-a975-c9375ab7a2ca')
self.assertEqual(track2.mbidRecording,
u'5f19758e-7421-4c71-a599-9a9575d8e1b0')
'5f19758e-7421-4c71-a599-9a9575d8e1b0')
def testMissingReleaseGroupType(self):
"""Check that whipper doesn't break if there's no type."""
@@ -214,7 +214,7 @@ class MetadataTestCase(unittest.TestCase):
filename = 'whipper.release.d8e6153a-2c47-4804-9d73-0aac1081c3b1.json'
path = os.path.join(os.path.dirname(__file__), filename)
handle = open(path, "rb")
response = json.loads(handle.read())
response = json.loads(handle.read().decode('utf-8'))
handle.close()
discid = "xu338_M8WukSRi0J.KTlDoflB8Y-" # disc 4
@@ -228,42 +228,42 @@ class MetadataTestCase(unittest.TestCase):
filename = 'whipper.release.6109ceed-7e21-490b-b5ad-3a66b4e4cfbb.json'
path = os.path.join(os.path.dirname(__file__), filename)
handle = open(path, "rb")
response = json.loads(handle.read())
response = json.loads(handle.read().decode('utf-8'))
handle.close()
discid = "cHW1Uutl_kyWNaLJsLmTGTe4rnE-"
metadata = mbngs._getMetadata(response['release'], discid)
self.assertEqual(metadata.artist, u'David Rovics')
self.assertEqual(metadata.sortName, u'Rovics, David')
self.assertEqual(metadata.artist, 'David Rovics')
self.assertEqual(metadata.sortName, 'Rovics, David')
self.assertFalse(metadata.various)
self.assertIsInstance(metadata.tracks, list)
self.assertEqual(metadata.release, u'2015')
self.assertEqual(metadata.releaseTitle, u'The Other Side')
self.assertEqual(metadata.releaseType, u'Album')
self.assertEqual(metadata.release, '2015')
self.assertEqual(metadata.releaseTitle, 'The Other Side')
self.assertEqual(metadata.releaseType, 'Album')
self.assertEqual(metadata.mbid,
u'6109ceed-7e21-490b-b5ad-3a66b4e4cfbb')
'6109ceed-7e21-490b-b5ad-3a66b4e4cfbb')
self.assertEqual(metadata.mbidReleaseGroup,
u'99850b41-a06e-4fb8-992c-75c191a77803')
'99850b41-a06e-4fb8-992c-75c191a77803')
self.assertEqual(metadata.mbidArtist,
[u'4d56eb9f-13b3-4f05-9db7-50195378d49f'])
['4d56eb9f-13b3-4f05-9db7-50195378d49f'])
self.assertEqual(metadata.url,
u'https://musicbrainz.org/release'
'https://musicbrainz.org/release'
'/6109ceed-7e21-490b-b5ad-3a66b4e4cfbb')
self.assertEqual(metadata.catalogNumber, u'[none]')
self.assertEqual(metadata.barcode, u'700261430249')
self.assertEqual(metadata.catalogNumber, '[none]')
self.assertEqual(metadata.barcode, '700261430249')
self.assertEqual(len(metadata.tracks), 16)
track1 = metadata.tracks[0]
self.assertEqual(track1.artist, u'David Rovics')
self.assertEqual(track1.title, u'Waiting for the Hurricane')
self.assertEqual(track1.artist, 'David Rovics')
self.assertEqual(track1.title, 'Waiting for the Hurricane')
self.assertEqual(track1.duration, 176320)
self.assertEqual(track1.mbid,
u'4116eea3-b9c2-452a-8d63-92f1e585b225')
self.assertEqual(track1.sortName, u'Rovics, David')
'4116eea3-b9c2-452a-8d63-92f1e585b225')
self.assertEqual(track1.sortName, 'Rovics, David')
self.assertEqual(track1.mbidArtist,
[u'4d56eb9f-13b3-4f05-9db7-50195378d49f'])
['4d56eb9f-13b3-4f05-9db7-50195378d49f'])
self.assertEqual(track1.mbidRecording,
u'b191794d-b7c6-4d6f-971e-0a543959b5ad')
'b191794d-b7c6-4d6f-971e-0a543959b5ad')
self.assertEqual(track1.mbidWorks,
[u'90d5be68-0b29-45a3-ba01-c27ad78e3625'])
['90d5be68-0b29-45a3-ba01-c27ad78e3625'])

View File

@@ -12,19 +12,19 @@ class FilterTestCase(common.TestCase):
self._filter = path.PathFilter(special=True)
def testSlash(self):
part = u'A Charm/A Blade'
self.assertEqual(self._filter.filter(part), u'A Charm-A Blade')
part = 'A Charm/A Blade'
self.assertEqual(self._filter.filter(part), 'A Charm-A Blade')
def testFat(self):
part = u'A Word: F**k you?'
self.assertEqual(self._filter.filter(part), u'A Word - F__k you_')
part = 'A Word: F**k you?'
self.assertEqual(self._filter.filter(part), 'A Word - F__k you_')
def testSpecial(self):
part = u'<<< $&*!\' "()`{}[]spaceship>>>'
part = '<<< $&*!\' "()`{}[]spaceship>>>'
self.assertEqual(self._filter.filter(part),
u'___ _____ ________spaceship___')
'___ _____ ________spaceship___')
def testGreatest(self):
part = u'Greatest Ever! Soul: The Definitive Collection'
part = 'Greatest Ever! Soul: The Definitive Collection'
self.assertEqual(self._filter.filter(part),
u'Greatest Ever_ Soul - The Definitive Collection')
'Greatest Ever_ Soul - The Definitive Collection')

View File

@@ -13,10 +13,10 @@ class PathTestCase(unittest.TestCase):
def testStandardTemplateEmpty(self):
prog = program.Program(config.Config())
path = prog.getPath(u'/tmp', DEFAULT_DISC_TEMPLATE,
path = prog.getPath('/tmp', DEFAULT_DISC_TEMPLATE,
'mbdiscid', None)
self.assertEqual(path, (u'/tmp/unknown/Unknown Artist - mbdiscid/'
u'Unknown Artist - mbdiscid'))
self.assertEqual(path, ('/tmp/unknown/Unknown Artist - mbdiscid/'
'Unknown Artist - mbdiscid'))
def testStandardTemplateFilled(self):
prog = program.Program(config.Config())
@@ -24,10 +24,10 @@ class PathTestCase(unittest.TestCase):
md.artist = md.sortName = 'Jeff Buckley'
md.title = 'Grace'
path = prog.getPath(u'/tmp', DEFAULT_DISC_TEMPLATE,
path = prog.getPath('/tmp', DEFAULT_DISC_TEMPLATE,
'mbdiscid', md, 0)
self.assertEqual(path, (u'/tmp/unknown/Jeff Buckley - Grace/'
u'Jeff Buckley - Grace'))
self.assertEqual(path, ('/tmp/unknown/Jeff Buckley - Grace/'
'Jeff Buckley - Grace'))
def testIssue66TemplateFilled(self):
prog = program.Program(config.Config())
@@ -35,6 +35,6 @@ class PathTestCase(unittest.TestCase):
md.artist = md.sortName = 'Jeff Buckley'
md.title = 'Grace'
path = prog.getPath(u'/tmp', u'%A/%d', 'mbdiscid', md, 0)
path = prog.getPath('/tmp', '%A/%d', 'mbdiscid', md, 0)
self.assertEqual(path,
u'/tmp/Jeff Buckley/Grace')
'/tmp/Jeff Buckley/Grace')

View File

@@ -13,7 +13,7 @@ class RenameInFileTestcase(unittest.TestCase):
def setUp(self):
(fd, self._path) = tempfile.mkstemp(suffix='.whipper.renamer.infile')
os.write(fd, 'This is a test\nThis is another\n')
os.write(fd, 'This is a test\nThis is another\n'.encode())
os.close(fd)
def testVerify(self):
@@ -25,7 +25,8 @@ class RenameInFileTestcase(unittest.TestCase):
def testDo(self):
o = renamer.RenameInFile(self._path, 'is is a', 'at was some')
o.do()
output = open(self._path).read()
with open(self._path) as f:
output = f.read()
self.assertEqual(output, 'That was some test\nThat was somenother\n')
os.unlink(self._path)
@@ -34,7 +35,8 @@ class RenameInFileTestcase(unittest.TestCase):
data = o.serialize()
o2 = renamer.RenameInFile.deserialize(data)
o2.do()
output = open(self._path).read()
with open(self._path) as f:
output = f.read()
self.assertEqual(output, 'That was some test\nThat was somenother\n')
os.unlink(self._path)
@@ -43,7 +45,7 @@ class RenameFileTestcase(unittest.TestCase):
def setUp(self):
(fd, self._source) = tempfile.mkstemp(suffix='.whipper.renamer.file')
os.write(fd, 'This is a test\nThis is another\n')
os.write(fd, 'This is a test\nThis is another\n'.encode())
os.close(fd)
(fd, self._destination) = tempfile.mkstemp(
suffix='.whipper.renamer.file')
@@ -66,7 +68,8 @@ class RenameFileTestcase(unittest.TestCase):
def testDo(self):
self._operation.do()
output = open(self._destination).read()
with open(self._destination) as f:
output = f.read()
self.assertEqual(output, 'This is a test\nThis is another\n')
os.unlink(self._destination)
@@ -74,7 +77,8 @@ class RenameFileTestcase(unittest.TestCase):
data = self._operation.serialize()
o = renamer.RenameFile.deserialize(data)
o.do()
output = open(self._destination).read()
with open(self._destination) as f:
output = f.read()
self.assertEqual(output, 'This is a test\nThis is another\n')
os.unlink(self._destination)
@@ -87,7 +91,7 @@ class OperatorTestCase(unittest.TestCase):
(fd, self._source) = tempfile.mkstemp(
suffix='.whipper.renamer.operator')
os.write(fd, 'This is a test\nThis is another\n')
os.write(fd, 'This is a test\nThis is another\n'.encode())
os.close(fd)
(fd, self._destination) = tempfile.mkstemp(
suffix='.whipper.renamer.operator')

View File

@@ -16,7 +16,7 @@ class KingsSingleTestCase(unittest.TestCase):
def setUp(self):
self.cue = cue.CueFile(os.path.join(os.path.dirname(__file__),
u'kings-single.cue'))
'kings-single.cue'))
self.cue.parse()
self.assertEqual(len(self.cue.table.tracks), 11)
@@ -32,7 +32,7 @@ class KingsSeparateTestCase(unittest.TestCase):
def setUp(self):
self.cue = cue.CueFile(os.path.join(os.path.dirname(__file__),
u'kings-separate.cue'))
'kings-separate.cue'))
self.cue.parse()
self.assertEqual(len(self.cue.table.tracks), 11)
@@ -48,7 +48,7 @@ class KanyeMixedTestCase(unittest.TestCase):
def setUp(self):
self.cue = cue.CueFile(os.path.join(os.path.dirname(__file__),
u'kanye.cue'))
'kanye.cue'))
self.cue.parse()
self.assertEqual(len(self.cue.table.tracks), 13)
@@ -61,24 +61,24 @@ class WriteCueFileTestCase(unittest.TestCase):
@staticmethod
def testWrite():
fd, path = tempfile.mkstemp(suffix=u'.whipper.test.cue')
fd, path = tempfile.mkstemp(suffix='.whipper.test.cue')
os.close(fd)
it = table.Table()
t = table.Track(1)
t.index(1, absolute=0, path=u'track01.wav', relative=0, counter=1)
t.index(1, absolute=0, path='track01.wav', relative=0, counter=1)
it.tracks.append(t)
t = table.Track(2)
t.index(0, absolute=1000, path=u'track01.wav',
t.index(0, absolute=1000, path='track01.wav',
relative=1000, counter=1)
t.index(1, absolute=2000, path=u'track02.wav', relative=0, counter=2)
t.index(1, absolute=2000, path='track02.wav', relative=0, counter=2)
it.tracks.append(t)
it.absolutize()
it.leadout = 3000
common.diffStrings(u"""REM DISCID 0C002802
common.diffStrings("""REM DISCID 0C002802
REM COMMENT "whipper %s"
FILE "track01.wav" WAVE
TRACK 01 AUDIO

View File

@@ -14,7 +14,7 @@ from whipper.test import common
class CureTestCase(common.TestCase):
def setUp(self):
self.path = os.path.join(os.path.dirname(__file__), u'cure.toc')
self.path = os.path.join(os.path.dirname(__file__), 'cure.toc')
self.toc = toc.TocFile(self.path)
self.toc.parse()
self.assertEqual(len(self.toc.table.tracks), 13)
@@ -93,8 +93,8 @@ class CureTestCase(common.TestCase):
'3/c/4/dBAR-013-0019d4c3-00fe8924-b90c650d.bin')
def testGetRealPath(self):
self.assertRaises(KeyError, self.toc.getRealPath, u'track01.wav')
(fd, path) = tempfile.mkstemp(suffix=u'.whipper.test.wav')
self.assertRaises(KeyError, self.toc.getRealPath, 'track01.wav')
(fd, path) = tempfile.mkstemp(suffix='.whipper.test.wav')
self.assertEqual(self.toc.getRealPath(path), path)
winpath = path.replace('/', '\\')
@@ -108,7 +108,7 @@ class CureTestCase(common.TestCase):
class BlocTestCase(common.TestCase):
def setUp(self):
self.path = os.path.join(os.path.dirname(__file__), u'bloc.toc')
self.path = os.path.join(os.path.dirname(__file__), 'bloc.toc')
self.toc = toc.TocFile(self.path)
self.toc.parse()
self.assertEqual(len(self.toc.table.tracks), 13)
@@ -173,7 +173,7 @@ class BlocTestCase(common.TestCase):
class BreedersTestCase(common.TestCase):
def setUp(self):
self.path = os.path.join(os.path.dirname(__file__), u'breeders.toc')
self.path = os.path.join(os.path.dirname(__file__), 'breeders.toc')
self.toc = toc.TocFile(self.path)
self.toc.parse()
self.assertEqual(len(self.toc.table.tracks), 13)
@@ -200,7 +200,7 @@ class BreedersTestCase(common.TestCase):
class LadyhawkeTestCase(common.TestCase):
def setUp(self):
self.path = os.path.join(os.path.dirname(__file__), u'ladyhawke.toc')
self.path = os.path.join(os.path.dirname(__file__), 'ladyhawke.toc')
self.toc = toc.TocFile(self.path)
self.toc.parse()
self.assertEqual(len(self.toc.table.tracks), 13)
@@ -237,13 +237,13 @@ class CapitalMergeTestCase(common.TestCase):
def setUp(self):
self.toc1 = toc.TocFile(os.path.join(os.path.dirname(__file__),
u'capital.1.toc'))
'capital.1.toc'))
self.toc1.parse()
self.assertEqual(len(self.toc1.table.tracks), 11)
self.assertTrue(self.toc1.table.tracks[-1].audio)
self.toc2 = toc.TocFile(os.path.join(os.path.dirname(__file__),
u'capital.2.toc'))
'capital.2.toc'))
self.toc2.parse()
self.assertEqual(len(self.toc2.table.tracks), 1)
self.assertFalse(self.toc2.table.tracks[-1].audio)
@@ -278,8 +278,8 @@ class UnicodeTestCase(common.TestCase, common.UnicodeTestMixin):
# we copy the normal non-utf8 filename to a utf-8 filename
# in this test because builds with LANG=C fail if we include
# utf-8 filenames in the dist
path = u'Jos\xe9Gonz\xe1lez.toc'
self._performer = u'Jos\xe9 Gonz\xe1lez'
path = 'Jos\xe9Gonz\xe1lez.toc'
self._performer = 'Jos\xe9 Gonz\xe1lez'
source = os.path.join(os.path.dirname(__file__), 'jose.toc')
(fd, self.dest) = tempfile.mkstemp(suffix=path)
os.close(fd)
@@ -311,7 +311,7 @@ class UnicodeTestCase(common.TestCase, common.UnicodeTestMixin):
class TOTBLTestCase(common.TestCase):
def setUp(self):
self.path = os.path.join(os.path.dirname(__file__), u'totbl.fast.toc')
self.path = os.path.join(os.path.dirname(__file__), 'totbl.fast.toc')
self.toc = toc.TocFile(self.path)
self.toc.parse()
self.assertEqual(len(self.toc.table.tracks), 11)
@@ -324,7 +324,7 @@ class GentlemenTestCase(common.TestCase):
def setUp(self):
self.path = os.path.join(os.path.dirname(__file__),
u'gentlemen.fast.toc')
'gentlemen.fast.toc')
self.toc = toc.TocFile(self.path)
self.toc.parse()
self.assertEquals(len(self.toc.table.tracks), 11)
@@ -341,7 +341,7 @@ class StrokesTestCase(common.TestCase):
def setUp(self):
self.path = os.path.join(os.path.dirname(__file__),
u'strokes-someday.toc')
'strokes-someday.toc')
self.toc = toc.TocFile(self.path)
self.toc.parse()
self.assertEqual(len(self.toc.table.tracks), 1)
@@ -358,13 +358,12 @@ class StrokesTestCase(common.TestCase):
self.assertEqual(i1.relative, 0)
self.assertEqual(i1.absolute, 1)
self.assertEqual(i1.counter, 1)
self.assertEqual(i1.path, u'data.wav')
self.assertEqual(i1.path, 'data.wav')
cue = self._filterCue(self.toc.table.cue())
ref = self._filterCue(
open(os.path.join(
os.path.dirname(__file__),
'strokes-someday.eac.cue')).read()).decode('utf-8')
with open(os.path.join(os.path.dirname(__file__),
'strokes-someday.eac.cue')) as f:
ref = self._filterCue(f.read())
common.diffStrings(ref, cue)
@staticmethod
@@ -400,7 +399,7 @@ class StrokesTestCase(common.TestCase):
class SurferRosaTestCase(common.TestCase):
def setUp(self):
self.path = os.path.join(os.path.dirname(__file__), u'surferrosa.toc')
self.path = os.path.join(os.path.dirname(__file__), 'surferrosa.toc')
self.toc = toc.TocFile(self.path)
self.toc.parse()
self.assertEqual(len(self.toc.table.tracks), 21)

View File

@@ -8,7 +8,7 @@ from whipper.extern.task import task
from whipper.program.soxi import AudioLengthTask
from whipper.test import common as tcommon
base_track_file = os.path.join(os.path.dirname(__file__), u'track.flac')
base_track_file = os.path.join(os.path.dirname(__file__), 'track.flac')
base_track_length = 10 * common.SAMPLES_PER_FRAME
@@ -27,7 +27,8 @@ class AudioLengthPathTestCase(tcommon.TestCase):
def _testSuffix(self, suffix):
fd, path = tempfile.mkstemp(suffix=suffix)
with os.fdopen(fd, "wb") as temptrack:
temptrack.write(open(base_track_file, "rb").read())
with open(base_track_file, "rb") as f:
temptrack.write(f.read())
t = AudioLengthTask(path)
runner = task.SyncRunner()
@@ -39,26 +40,18 @@ class AudioLengthPathTestCase(tcommon.TestCase):
class NormalAudioLengthPathTestCase(AudioLengthPathTestCase):
def testSingleQuote(self):
self._testSuffix(u"whipper.test.Guns 'N Roses.flac")
self._testSuffix("whipper.test.Guns 'N Roses.flac")
def testDoubleQuote(self):
# This test makes sure we can checksum files with double quote in
# their name
self._testSuffix(u'whipper.test.12" edit.flac')
class UnicodeAudioLengthPathTestCase(AudioLengthPathTestCase,
tcommon.UnicodeTestMixin):
def testUnicodePath(self):
# this test makes sure we can checksum a unicode path
self._testSuffix(u'whipper.test.B\xeate Noire.empty.flac')
self._testSuffix('whipper.test.12" edit.flac')
class AbsentFileAudioLengthPathTestCase(AudioLengthPathTestCase):
def testAbsentFile(self):
tempdir = tempfile.mkdtemp()
path = os.path.join(tempdir, u"nonexistent.flac")
path = os.path.join(tempdir, "nonexistent.flac")
t = AudioLengthTask(path)
runner = task.SyncRunner()

View File

@@ -131,20 +131,23 @@ class LoggerTestCase(unittest.TestCase):
logger = WhipperLogger()
actual = logger.log(ripResult)
actualLines = actual.splitlines()
expectedLines = open(
os.path.join(self.path, 'test_result_logger.log'), 'r'
).read().splitlines()
with open(os.path.join(self.path,
'test_result_logger.log'), 'r') as f:
expectedLines = f.read().splitlines()
# do not test on version line, date line, or SHA-256 hash line
self.assertListEqual(actualLines[2:-1], expectedLines[2:-1])
self.assertRegexpMatches(
# RegEX updated to support all the 4 cases of the versioning scheme:
# https://github.com/pypa/setuptools_scm/#default-versioning-scheme
self.assertRegex(
actualLines[0],
re.compile((
r'Log created by: whipper '
r'[\d]+\.[\d]+.[\d]+\.dev[\w\.\+]+ \(internal logger\)'
r'[\d]+\.[\d]+\.[\d]+(\+d\d{8}|\.dev[\w\.\+]+)? '
r'\(internal logger\)'
))
)
self.assertRegexpMatches(
self.assertRegex(
actualLines[1],
re.compile((
r'Log creation date: '
@@ -163,7 +166,8 @@ class LoggerTestCase(unittest.TestCase):
Dumper=ruamel.yaml.RoundTripDumper
)
)
log_body = "\n".join(actualLines[:-1]).encode()
self.assertEqual(
parsedLog['SHA-256 hash'],
hashlib.sha256("\n".join(actualLines[:-1])).hexdigest().upper()
hashlib.sha256(log_body).hexdigest().upper()
)