Merge branch 'develop'
This commit is contained in:
19
.github/config.yml
vendored
19
.github/config.yml
vendored
@@ -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
42
.github/stale.yml
vendored
@@ -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
16
.github/workflows/greetings.yml
vendored
Normal 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! 💖'
|
||||
@@ -3,7 +3,7 @@ sudo: required
|
||||
|
||||
language: python
|
||||
python:
|
||||
- "2.7"
|
||||
- "3.5"
|
||||
virtualenv:
|
||||
system_site_packages: false
|
||||
|
||||
|
||||
104
CHANGELOG.md
104
CHANGELOG.md
@@ -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 doesn’t 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)*
|
||||
|
||||
44
COVERAGE
44
COVERAGE
@@ -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%
|
||||
|
||||
76
Dockerfile
76
Dockerfile
@@ -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
|
||||
|
||||
57
README.md
57
README.md
@@ -8,14 +8,12 @@
|
||||
[](https://github.com/whipper-team/whipper/issues)
|
||||
[](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
|
||||
|
||||
[](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
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
2
setup.py
2
setup.py
@@ -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=[
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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']:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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']:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
30
whipper/extern/asyncsub.py
vendored
30
whipper/extern/asyncsub.py
vendored
@@ -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()
|
||||
|
||||
72
whipper/extern/freedb.py
vendored
72
whipper/extern/freedb.py
vendored
@@ -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:
|
||||
|
||||
9
whipper/extern/task/task.py
vendored
9
whipper/extern/task/task.py
vendored
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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'])
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user