diff -Nru mutagen-1.36/debian/changelog mutagen-1.38/debian/changelog --- mutagen-1.36/debian/changelog 2017-08-07 21:57:03.000000000 +0000 +++ mutagen-1.38/debian/changelog 2017-08-08 01:53:42.000000000 +0000 @@ -1,8 +1,12 @@ -mutagen (1.36-1ubuntu1) artful; urgency=medium +mutagen (1.38-1) unstable; urgency=medium - * Switch Build-Depends and intersphinx location to Python 3.6 from 3.5. + * Update watch file. + * New upstream release. + * Use Python 3.6 docs instead of 3.5 (closes: #871426). + * Run wrap-and-sort -t -s. + * Bump Standards-Version to 4.0.0 (no changes). - -- Michael Hudson-Doyle Tue, 08 Aug 2017 09:56:29 +1200 + -- Tristan Seligmann Tue, 08 Aug 2017 03:53:42 +0200 mutagen (1.36-1) unstable; urgency=medium diff -Nru mutagen-1.36/debian/control mutagen-1.38/debian/control --- mutagen-1.36/debian/control 2017-08-07 21:57:14.000000000 +0000 +++ mutagen-1.38/debian/control 2017-08-08 01:53:42.000000000 +0000 @@ -1,29 +1,30 @@ Source: mutagen Section: python Priority: optional -Maintainer: Ubuntu Developers -XSBC-Original-Maintainer: Tristan Seligmann -Uploaders: Debian Python Modules Team , - Simon Chopin -Build-Depends: debhelper (>= 9), - dh-python, - faad, - flac, - libc-bin (>= 2.13), - oggz-tools, - pypy, - pypy-pytest, - python-all (>= 2.6.6-3~), - python-docutils, - python-pytest, - python-sphinx (>= 1.0.7+dfsg), - python-sphinx-rtd-theme, - python2.7-doc, - python3-all, - python3-pytest, - python3.6-doc, - vorbis-tools -Standards-Version: 3.9.8 +Maintainer: Tristan Seligmann +Uploaders: + Debian Python Modules Team , + Simon Chopin , +Build-Depends: + debhelper (>= 9), + dh-python, + faad, + flac, + libc-bin (>= 2.13), + oggz-tools, + pypy, + pypy-pytest, + python-all (>= 2.6.6-3~), + python-docutils, + python-pytest, + python-sphinx (>= 1.0.7+dfsg), + python-sphinx-rtd-theme, + python2.7-doc, + python3-all, + python3-pytest, + python3.6-doc, + vorbis-tools, +Standards-Version: 4.0.0 X-Python-Version: >= 2.6 Vcs-Git: https://anonscm.debian.org/git/python-modules/packages/mutagen.git Vcs-Browser: https://anonscm.debian.org/cgit/python-modules/packages/mutagen.git diff -Nru mutagen-1.36/debian/.git-dpm mutagen-1.38/debian/.git-dpm --- mutagen-1.36/debian/.git-dpm 2016-12-26 12:18:19.000000000 +0000 +++ mutagen-1.38/debian/.git-dpm 2017-08-08 01:53:42.000000000 +0000 @@ -1,11 +1,11 @@ # see git-dpm(1) from git-dpm package -3d58aa32f4baae54f63b8d866a6e7a390f1baaab -3d58aa32f4baae54f63b8d866a6e7a390f1baaab -5d35b4f7cd0a01ec8e6d160fa9bbf076e2dbae6d -5d35b4f7cd0a01ec8e6d160fa9bbf076e2dbae6d -mutagen_1.36.orig.tar.gz -1c9e7f1eed007bcf4706d315399ea2431a99b1c6 -888238 +26b59ced744c49052c7940437ff6f1f6667ef56f +26b59ced744c49052c7940437ff6f1f6667ef56f +d68e483fbb42ae4ea1cd174e4169629d961cdb68 +d68e483fbb42ae4ea1cd174e4169629d961cdb68 +mutagen_1.38.orig.tar.gz +bc37d508c5c1513e3118c669da85552478629387 +913243 debianTag="debian/%e%v" patchedTag="patched/%e%v" upstreamTag="upstream/%e%u" diff -Nru mutagen-1.36/debian/patches/use-rtd-package mutagen-1.38/debian/patches/use-rtd-package --- mutagen-1.36/debian/patches/use-rtd-package 2016-12-26 12:18:19.000000000 +0000 +++ mutagen-1.38/debian/patches/use-rtd-package 2017-08-08 01:53:42.000000000 +0000 @@ -1,4 +1,4 @@ -From 59af603923dba63a9976f92208d9ae042d8539e7 Mon Sep 17 00:00:00 2001 +From dfab641acb417e131fd37702cb70cd99621d20af Mon Sep 17 00:00:00 2001 From: Tristan Seligmann Date: Thu, 8 Oct 2015 09:57:12 -0700 Subject: Use the Debian package of the sphinx-rtd theme @@ -7,11 +7,11 @@ Last-Update: 2014-12-08 Patch-Name: use-rtd-package --- - docs/conf.py | 3 +++ - 1 file changed, 3 insertions(+) + docs/conf.py | 1 + + 1 file changed, 1 insertion(+) diff --git a/docs/conf.py b/docs/conf.py -index 40faa34..1647baa 100644 +index 4bc64da..23d719a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -2,6 +2,7 @@ @@ -22,12 +22,3 @@ dir_ = os.path.dirname(os.path.realpath(__file__)) sys.path.insert(0, dir_) -@@ -29,6 +30,8 @@ exclude_patterns = ['_build'] - bug_url_template = "https://github.com/quodlibet/mutagen/issues/%s" - pr_url_template = "https://github.com/quodlibet/mutagen/pull/%s" - bbpr_url_template = "https://bitbucket.org/lazka/mutagen/pull-requests/%s" -+html_theme = "sphinx_rtd_theme" -+html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] - - autodoc_member_order = "bysource" - default_role = "obj" diff -Nru mutagen-1.36/debian/patches/use-system-inventory mutagen-1.38/debian/patches/use-system-inventory --- mutagen-1.36/debian/patches/use-system-inventory 2017-08-07 21:49:27.000000000 +0000 +++ mutagen-1.38/debian/patches/use-system-inventory 2017-08-08 01:53:42.000000000 +0000 @@ -1,4 +1,4 @@ -From 3d58aa32f4baae54f63b8d866a6e7a390f1baaab Mon Sep 17 00:00:00 2001 +From 26b59ced744c49052c7940437ff6f1f6667ef56f Mon Sep 17 00:00:00 2001 From: Tristan Seligmann Date: Thu, 8 Oct 2015 09:57:13 -0700 Subject: Use the system copy of the Python documentation inventory @@ -10,11 +10,11 @@ 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py -index 1647baa..c627d7d 100644 +index 23d719a..863a421 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,8 +17,8 @@ extensions = [ - 'ext', + 'sphinx.ext.extlinks', ] intersphinx_mapping = { - 'python': ('https://docs.python.org/2.7', None), diff -Nru mutagen-1.36/debian/watch mutagen-1.38/debian/watch --- mutagen-1.36/debian/watch 2016-12-26 12:18:19.000000000 +0000 +++ mutagen-1.38/debian/watch 2017-08-08 01:53:42.000000000 +0000 @@ -1,4 +1,4 @@ -version=3 -opts="pgpsigurlmangle=s/$/.sig/" \ - https://bitbucket.org/lazka/mutagen/downloads/ \ - mutagen-([0-9.]+).tar.gz +version=4 +opts=filenamemangle=s/.+\/mutagen-?(\d\S+)\.tar\.gz/mutagen-$1\.tar\.gz/,\ +pgpsigurlmangle=s/$/.sig/ \ + https://github.com/quodlibet/mutagen/releases .*/mutagen-(\d\S+)\.tar\.gz diff -Nru mutagen-1.36/docs/api/dsf.rst mutagen-1.38/docs/api/dsf.rst --- mutagen-1.36/docs/api/dsf.rst 1970-01-01 00:00:00.000000000 +0000 +++ mutagen-1.38/docs/api/dsf.rst 2017-05-25 13:49:20.000000000 +0000 @@ -0,0 +1,11 @@ +DSF +--- + +.. automodule:: mutagen.dsf + +.. autoclass:: mutagen.dsf.DSF(filething) + :show-inheritance: + :members: + +.. autoclass:: mutagen.dsf.DSFInfo() + :members: diff -Nru mutagen-1.36/docs/api/id3_frames.rst mutagen-1.38/docs/api/id3_frames.rst --- mutagen-1.36/docs/api/id3_frames.rst 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/docs/api/id3_frames.rst 2017-05-25 13:49:20.000000000 +0000 @@ -50,7 +50,7 @@ ---------------- -.. autoclass:: mutagen.id3.AENC(owner=u'', preview_start=0, preview_length=0) +.. autoclass:: mutagen.id3.AENC(owner=u'', preview_start=0, preview_length=0, data='') :show-inheritance: :members: @@ -105,7 +105,12 @@ :members: -.. autoclass:: mutagen.id3.GRID(owner=u'', group=128) +.. autoclass:: mutagen.id3.GRID(owner=u'', group=128, data='') + :show-inheritance: + :members: + + +.. autoclass:: mutagen.id3.GRP1(encoding=, text=[]) :show-inheritance: :members: @@ -115,7 +120,7 @@ :members: -.. autoclass:: mutagen.id3.LINK(frameid='XXXX', url=u'') +.. autoclass:: mutagen.id3.LINK(frameid='XXXX', url=u'', data='') :show-inheritance: :members: @@ -129,6 +134,16 @@ :show-inheritance: :members: + +.. autoclass:: mutagen.id3.MVIN(encoding=, text=[]) + :show-inheritance: + :members: + + +.. autoclass:: mutagen.id3.MVNM(encoding=, text=[]) + :show-inheritance: + :members: + .. autoclass:: mutagen.id3.OWNE(encoding=, price=u'', date='19700101', seller=u'') :show-inheritance: diff -Nru mutagen-1.36/docs/api/index.rst mutagen-1.38/docs/api/index.rst --- mutagen-1.36/docs/api/index.rst 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/docs/api/index.rst 2017-05-25 13:49:20.000000000 +0000 @@ -8,6 +8,7 @@ aiff ape asf + dsf flac id3 monkeysaudio diff -Nru mutagen-1.36/docs/conf.py mutagen-1.38/docs/conf.py --- mutagen-1.36/docs/conf.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/docs/conf.py 2017-05-25 14:08:07.000000000 +0000 @@ -13,7 +13,7 @@ 'sphinx.ext.autodoc', 'sphinx.ext.napoleon', 'sphinx.ext.intersphinx', - 'ext', + 'sphinx.ext.extlinks', ] intersphinx_mapping = { 'python': ('https://docs.python.org/2.7', None), @@ -26,9 +26,14 @@ u'Christoph Reiter, Ben Ockmore & others' html_title = project exclude_patterns = ['_build'] -bug_url_template = "https://github.com/quodlibet/mutagen/issues/%s" -pr_url_template = "https://github.com/quodlibet/mutagen/pull/%s" -bbpr_url_template = "https://bitbucket.org/lazka/mutagen/pull-requests/%s" + +extlinks = { + 'bug': ('https://github.com/quodlibet/mutagen/issues/%s', '#'), + 'pr': ('https://github.com/quodlibet/mutagen/pull/%s', '#pr'), + 'commit': ('https://github.com/quodlibet/mutagen/commit/%s', '#'), + 'user': ('https://github.com/%s', ''), +} + autodoc_member_order = "bysource" default_role = "obj" @@ -38,3 +43,13 @@ html_theme_options = { "display_version": False, } + +html_context = { + 'extra_css_files': [ + '_static/extra.css', + ], +} + +html_static_path = [ + "extra.css", +] diff -Nru mutagen-1.36/docs/ext.py mutagen-1.38/docs/ext.py --- mutagen-1.36/docs/ext.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/docs/ext.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- - -from docutils import nodes - - -def bug_role(name, rawtext, text, lineno, inliner, *args, **kwargs): - app = inliner.document.settings.env.app - url_tmpl = app.config.bug_url_template or "missing/%s" - node = nodes.reference( - rawtext, - "[%s]" % text, - refuri=url_tmpl % text) - return [node], [] - - -def pr_role(name, rawtext, text, lineno, inliner, *args, **kwargs): - app = inliner.document.settings.env.app - if name == "pr": - url_tmpl = app.config.pr_url_template - else: - url_tmpl = app.config.bbpr_url_template - url_tmpl = url_tmpl or "missing/%s" - node = nodes.reference( - rawtext, - "[%s-%s]" % (name, text), - refuri=url_tmpl % text) - return [node], [] - - -def setup(app): - app.add_role('bug', bug_role) - app.add_config_value('bug_url_template', None, 'env') - app.add_role('bb-pr', pr_role) - app.add_role('pr', pr_role) - app.add_config_value('pr_url_template', None, 'env') - app.add_config_value('bbpr_url_template', None, 'env') diff -Nru mutagen-1.36/docs/extra.css mutagen-1.38/docs/extra.css --- mutagen-1.36/docs/extra.css 1970-01-01 00:00:00.000000000 +0000 +++ mutagen-1.38/docs/extra.css 2017-05-25 13:49:20.000000000 +0000 @@ -0,0 +1,32 @@ +.wy-side-nav-search { + background-color: initial; +} + +.wy-nav-side, .wy-nav-top { + background-color: #24292E; +} + +.wy-side-nav-search input[type="text"] { + border-color: transparent; +} + +.wy-nav-content { + margin: initial; +} + +.rst-content div[role=navigation], footer { + font-size: 0.85em; + color: #999; +} + +.rst-content div[role=navigation] hr { + margin-top: 6px; +} + +footer hr { + margin-bottom: 6px; +} + +.rst-footer-buttons { + display: none; +} diff -Nru mutagen-1.36/docs/id3_frames_gen.py mutagen-1.38/docs/id3_frames_gen.py --- mutagen-1.36/docs/id3_frames_gen.py 2016-11-01 19:04:58.000000000 +0000 +++ mutagen-1.38/docs/id3_frames_gen.py 2017-05-25 13:49:20.000000000 +0000 @@ -3,8 +3,9 @@ # Copyright 2013 Christoph Reiter # # This program is free software; you can redistribute it and/or modify -# it under the terms of version 2 of the GNU General Public License as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. """ ./id3_frames_gen.py > api/id3_frames.rst diff -Nru mutagen-1.36/docs/index.rst mutagen-1.38/docs/index.rst --- mutagen-1.36/docs/index.rst 2016-11-02 14:34:17.000000000 +0000 +++ mutagen-1.38/docs/index.rst 2017-05-25 13:57:23.000000000 +0000 @@ -2,6 +2,8 @@ :align: center :width: 400px +| + .. toctree:: :hidden: :titlesonly: @@ -16,6 +18,7 @@ .. title:: Overview .. include:: ../README.rst + :start-after: | ---- @@ -40,9 +43,10 @@ Where do I get it? ------------------ -Mutagen is hosted on `GitHub `_. The -`download page `_ will have the -latest version or check out the git repository:: +Mutagen is hosted on `GitHub `_. The +`download page `_ or `PyPI +`_ will have the latest version or check out +the git repository:: $ git clone https://github.com/quodlibet/mutagen.git diff -Nru mutagen-1.36/docs/user/examples/fileobj-gio.py mutagen-1.38/docs/user/examples/fileobj-gio.py --- mutagen-1.36/docs/user/examples/fileobj-gio.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/docs/user/examples/fileobj-gio.py 2017-05-25 13:49:20.000000000 +0000 @@ -3,8 +3,9 @@ # Copyright 2016 Christoph Reiter # # This program is free software; you can redistribute it and/or modify -# it under the terms of version 2 of the GNU General Public License as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. import mutagen diff -Nru mutagen-1.36/docs/user/id3.rst mutagen-1.38/docs/user/id3.rst --- mutagen-1.36/docs/user/id3.rst 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/docs/user/id3.rst 2017-05-25 13:49:20.000000000 +0000 @@ -1,3 +1,5 @@ +.. currentmodule:: mutagen.id3 + === ID3 === @@ -17,6 +19,58 @@ ID3v2 tags are stored, by reading the the details of the ID3v2 standard at http://id3.org/id3v2.4.0-structure. +ID3 Dict Interface +^^^^^^^^^^^^^^^^^^ + +.. code:: pycon + + >>> mutagen.File("01. On The Road Again.mp3").keys() + [u'TXXX:replaygain_album_peak', u'RVA2:track', u'APIC:picture', + u'UFID:http://musicbrainz.org', 'TDRC', u'TXXX:replaygain_track_peak', + 'TIT2', u'RVA2:album', u'TXXX:replaygain_track_gain', + u'TXXX:MusicBrainz Album Id', 'TRCK', 'TPE1', 'TALB', + u'TXXX:MusicBrainz Album Artist Id', u'TXXX:replaygain_album_gain'] + >>> + + +On the first look the key format in the ID3 dict seem a bit confusing, this is +because they are the frame hashes of the corresponding dict values +(:obj:`Frame.HashKey`). For example the ID3 specification states that there +can't be two APIC frames with the same description, so the frame hash contains +the description and adding a new frame with the same description will replace +the old one. Only the first four letters always represent the frame type name. + +In many cases you don't care about the hash and just want to look up all +frames of one type. For this use the :meth:`ID3Tags.getall` method: + +.. code:: pycon + + >>> for frame in mutagen.File("01. On The Road Again.mp3").tags.getall("TXXX"): + ... frame + ... + TXXX(encoding=, desc=u'replaygain_album_peak', text=[u'1.00000000047']) + TXXX(encoding=, desc=u'replaygain_track_peak', text=[u'1.00000000047']) + TXXX(encoding=, desc=u'replaygain_track_gain', text=[u'-7.429688 dB']) + TXXX(encoding=, desc=u'MusicBrainz Album Id', text=[u'be6fb9b0-5073-4633-aefa-c559554f28e5']) + TXXX(encoding=, desc=u'MusicBrainz Album Artist Id', text=[u'815a0279-558c-4522-ac3b-6a1e259e95b5']) + TXXX(encoding=, desc=u'replaygain_album_gain', text=[u'-7.429688 dB']) + +For adding new frames you can use the :meth:`ID3Tags.add` method, which will +use the frame hash as key. For example the ID3 spec only allows one TALB +frame, so passing a TALB frame to add() will replace the old frame: + +.. code:: pycon + + >>> tags.getall("TALB") + [TALB(encoding=, text=[u'The Very Best of Canned Heat'])] + >>> tags.add(TALB(text=[u"new value"])) + >>> tags.getall("TALB") + [TALB(encoding=, text=[u'new value'])] + >>> + +There is also a corresponding :meth:`ID3Tags.delall` method for deleting all +frames of one type. + ID3 Versions ^^^^^^^^^^^^ diff -Nru mutagen-1.36/MANIFEST.in mutagen-1.38/MANIFEST.in --- mutagen-1.36/MANIFEST.in 2016-11-01 18:57:53.000000000 +0000 +++ mutagen-1.38/MANIFEST.in 2017-05-25 13:49:20.000000000 +0000 @@ -8,5 +8,5 @@ include tests/*.py include man/*.1 recursive-include mutagen README.rst -recursive-include docs *.py Makefile *.rst *.png *.svg *.ico +recursive-include docs *.py Makefile *.rst *.png *.svg *.ico *.css prune docs/_build diff -Nru mutagen-1.36/mutagen/aac.py mutagen-1.38/mutagen/aac.py --- mutagen-1.36/mutagen/aac.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/mutagen/aac.py 2017-05-25 13:49:20.000000000 +0000 @@ -2,8 +2,9 @@ # Copyright (C) 2014 Christoph Reiter # # This program is free software; you can redistribute it and/or modify -# it under the terms of version 2 of the GNU General Public License as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. """ * ADTS - Audio Data Transport Stream diff -Nru mutagen-1.36/mutagen/aiff.py mutagen-1.38/mutagen/aiff.py --- mutagen-1.36/mutagen/aiff.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/mutagen/aiff.py 2017-05-25 13:49:20.000000000 +0000 @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- - # Copyright (C) 2014 Evan Purkhiser # 2014 Ben Ockmore # # This program is free software; you can redistribute it and/or modify -# it under the terms of version 2 of the GNU General Public License as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. """AIFF audio stream information and tags.""" diff -Nru mutagen-1.36/mutagen/apev2.py mutagen-1.38/mutagen/apev2.py --- mutagen-1.36/mutagen/apev2.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/mutagen/apev2.py 2017-05-25 13:49:20.000000000 +0000 @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- - # Copyright (C) 2005 Joe Wreschnig # # This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. """APEv2 reading and writing. diff -Nru mutagen-1.36/mutagen/asf/_attrs.py mutagen-1.38/mutagen/asf/_attrs.py --- mutagen-1.36/mutagen/asf/_attrs.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/mutagen/asf/_attrs.py 2017-05-25 13:49:20.000000000 +0000 @@ -3,8 +3,9 @@ # Copyright (C) 2006-2007 Lukas Lalinsky # # This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. import sys import struct diff -Nru mutagen-1.36/mutagen/asf/__init__.py mutagen-1.38/mutagen/asf/__init__.py --- mutagen-1.36/mutagen/asf/__init__.py 2016-11-09 13:50:52.000000000 +0000 +++ mutagen-1.38/mutagen/asf/__init__.py 2017-05-25 13:49:20.000000000 +0000 @@ -3,8 +3,9 @@ # Copyright (C) 2006-2007 Lukas Lalinsky # # This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. """Read and write ASF (Window Media Audio) files.""" diff -Nru mutagen-1.36/mutagen/asf/_objects.py mutagen-1.38/mutagen/asf/_objects.py --- mutagen-1.36/mutagen/asf/_objects.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/mutagen/asf/_objects.py 2017-05-25 13:49:20.000000000 +0000 @@ -3,8 +3,9 @@ # Copyright (C) 2006-2007 Lukas Lalinsky # # This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. import struct diff -Nru mutagen-1.36/mutagen/asf/_util.py mutagen-1.38/mutagen/asf/_util.py --- mutagen-1.36/mutagen/asf/_util.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/mutagen/asf/_util.py 2017-05-25 13:49:20.000000000 +0000 @@ -3,8 +3,9 @@ # Copyright (C) 2006-2007 Lukas Lalinsky # # This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. import struct diff -Nru mutagen-1.36/mutagen/_compat.py mutagen-1.38/mutagen/_compat.py --- mutagen-1.36/mutagen/_compat.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/mutagen/_compat.py 2017-05-25 13:49:20.000000000 +0000 @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- - # Copyright (C) 2013 Christoph Reiter # # This program is free software; you can redistribute it and/or modify -# it under the terms of version 2 of the GNU General Public License as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. import sys diff -Nru mutagen-1.36/mutagen/_constants.py mutagen-1.38/mutagen/_constants.py --- mutagen-1.36/mutagen/_constants.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/mutagen/_constants.py 2017-05-25 13:49:20.000000000 +0000 @@ -1,4 +1,9 @@ # -*- coding: utf-8 -*- +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. """Constants used by Mutagen.""" diff -Nru mutagen-1.36/mutagen/dsf.py mutagen-1.38/mutagen/dsf.py --- mutagen-1.36/mutagen/dsf.py 1970-01-01 00:00:00.000000000 +0000 +++ mutagen-1.38/mutagen/dsf.py 2017-05-25 13:49:20.000000000 +0000 @@ -0,0 +1,358 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2017 Boris Pruessmann +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +"""Read and write DSF audio stream information and tags.""" + + +import sys +import struct + +from ._compat import cBytesIO, reraise, endswith + +from mutagen import FileType, StreamInfo +from mutagen._util import cdata, MutagenError, loadfile, convert_error +from mutagen.id3 import ID3 +from mutagen.id3._util import ID3NoHeaderError, error as ID3Error + + +__all__ = ["DSF", "Open", "delete"] + + +class error(MutagenError): + pass + + +class DSFChunk(object): + """A generic chunk of a DSFFile.""" + + chunk_offset = 0 + chunk_header = " " + chunk_size = -1 + + def __init__(self, fileobj, create=False): + self.fileobj = fileobj + + if not create: + self.chunk_offset = fileobj.tell() + self.load() + + def load(self): + raise NotImplementedError + + def write(self): + raise NotImplementedError + + +class DSDChunk(DSFChunk): + """Represents the first chunk of a DSF file""" + + CHUNK_SIZE = 28 + + total_size = 0 + offset_metdata_chunk = 0 + + def __init__(self, fileobj, create=False): + super(DSDChunk, self).__init__(fileobj, create) + + if create: + self.chunk_header = b"DSD " + self.chunk_size = DSDChunk.CHUNK_SIZE + + def load(self): + data = self.fileobj.read(DSDChunk.CHUNK_SIZE) + if len(data) != DSDChunk.CHUNK_SIZE: + raise error("DSF chunk truncated") + + self.chunk_header = data[0:4] + if self.chunk_header != b"DSD ": + raise error("DSF dsd header not found") + + self.chunk_size = cdata.ulonglong_le(data[4:12]) + if self.chunk_size != DSDChunk.CHUNK_SIZE: + raise error("DSF dsd header size mismatch") + + self.total_size = cdata.ulonglong_le(data[12:20]) + self.offset_metdata_chunk = cdata.ulonglong_le(data[20:28]) + + def write(self): + f = cBytesIO() + f.write(self.chunk_header) + f.write(struct.pack("I", time)) return b"".join(data) diff -Nru mutagen-1.36/mutagen/id3/_tags.py mutagen-1.38/mutagen/id3/_tags.py --- mutagen-1.36/mutagen/id3/_tags.py 2016-12-22 14:04:52.000000000 +0000 +++ mutagen-1.38/mutagen/id3/_tags.py 2017-05-25 13:49:20.000000000 +0000 @@ -3,8 +3,9 @@ # Copyright 2016 Christoph Reiter # # This program is free software; you can redistribute it and/or modify -# it under the terms of version 2 of the GNU General Public License as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. import struct diff -Nru mutagen-1.36/mutagen/id3/_util.py mutagen-1.38/mutagen/id3/_util.py --- mutagen-1.36/mutagen/id3/_util.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/mutagen/id3/_util.py 2017-05-25 13:49:20.000000000 +0000 @@ -1,12 +1,12 @@ # -*- coding: utf-8 -*- - # Copyright (C) 2005 Michael Urman # 2013 Christoph Reiter # 2014 Ben Ockmore # # This program is free software; you can redistribute it and/or modify -# it under the terms of version 2 of the GNU General Public License as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. from mutagen._compat import long_, integer_types, PY3 from mutagen._util import MutagenError diff -Nru mutagen-1.36/mutagen/__init__.py mutagen-1.38/mutagen/__init__.py --- mutagen-1.36/mutagen/__init__.py 2016-12-22 19:18:34.000000000 +0000 +++ mutagen-1.38/mutagen/__init__.py 2017-06-01 16:06:11.000000000 +0000 @@ -1,11 +1,10 @@ # -*- coding: utf-8 -*- - # Copyright (C) 2005 Michael Urman # # This program is free software; you can redistribute it and/or modify -# it under the terms of version 2 of the GNU General Public License as -# published by the Free Software Foundation. - +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. """Mutagen aims to be an all purpose multimedia tagging library. @@ -24,7 +23,7 @@ from mutagen._file import FileType, StreamInfo, File from mutagen._tags import Tags, Metadata, PaddingInfo -version = (1, 36) +version = (1, 38) """Version tuple.""" version_string = ".".join(map(str, version)) diff -Nru mutagen-1.36/mutagen/m4a.py mutagen-1.38/mutagen/m4a.py --- mutagen-1.36/mutagen/m4a.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/mutagen/m4a.py 2017-05-25 13:49:20.000000000 +0000 @@ -2,8 +2,9 @@ # Copyright 2006 Joe Wreschnig # # This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. """ since 1.9: mutagen.m4a is deprecated; use mutagen.mp4 instead. diff -Nru mutagen-1.36/mutagen/monkeysaudio.py mutagen-1.38/mutagen/monkeysaudio.py --- mutagen-1.36/mutagen/monkeysaudio.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/mutagen/monkeysaudio.py 2017-05-25 13:49:20.000000000 +0000 @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- - # Copyright (C) 2006 Lukas Lalinsky # # This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. """Monkey's Audio streams with APEv2 tags. diff -Nru mutagen-1.36/mutagen/mp3/__init__.py mutagen-1.38/mutagen/mp3/__init__.py --- mutagen-1.36/mutagen/mp3/__init__.py 2016-09-27 11:43:47.000000000 +0000 +++ mutagen-1.38/mutagen/mp3/__init__.py 2017-05-25 13:49:20.000000000 +0000 @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- - # Copyright (C) 2006 Joe Wreschnig # # This program is free software; you can redistribute it and/or modify -# it under the terms of version 2 of the GNU General Public License as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. """MPEG audio stream information and tags.""" @@ -68,7 +68,7 @@ return BitrateMode.CBR # older lame and non-lame with some variant of vbr - if xing.vbr_scale != -1 or xing.lame_version: + if xing.vbr_scale != -1 or xing.lame_version_desc: return BitrateMode.VBR return BitrateMode.UNKNOWN @@ -183,16 +183,21 @@ lame = xing.lame_header self.sketchy = False self.bitrate_mode = _guess_xing_bitrate_mode(xing) + self.encoder_settings = xing.get_encoder_settings() if xing.frames != -1: samples = frame_size * xing.frames if lame is not None: samples -= lame.encoder_delay_start samples -= lame.encoder_padding_end + if samples < 0: + # older lame versions wrote bogus delay/padding for short + # files with low bitrate + samples = 0 self.length = float(samples) / self.sample_rate - if xing.bytes != -1 and self.length: - self.bitrate = int((xing.bytes * 8) / self.length) - if xing.lame_version: - self.encoder_info = u"LAME %s" % xing.lame_version + if xing.bytes != -1 and self.length: + self.bitrate = int((xing.bytes * 8) / self.length) + if xing.lame_version_desc: + self.encoder_info = u"LAME %s" % xing.lame_version_desc if lame is not None: self.track_gain = lame.track_gain_adjustment self.track_peak = lame.track_peak @@ -298,6 +303,9 @@ possibly version. In case a lame tag is present this will start with ``"LAME "``, if unknown it is empty, otherwise the text format is undefined. + encoder_settings (`mutagen.text`): a string containing a guess about + the settings used for encoding. The format is undefined and + depends on the encoder. bitrate_mode (`BitrateMode`): a :class:`BitrateMode` track_gain (`float` or `None`): replaygain track gain (89db) or None track_peak (`float` or `None`): replaygain track peak or None @@ -316,6 +324,7 @@ sketchy = False encoder_info = u"" + encoder_settings = u"" bitrate_mode = BitrateMode.UNKNOWN track_gain = track_peak = album_gain = album_peak = None @@ -405,6 +414,8 @@ info = u"CBR?" if self.encoder_info: info += ", %s" % self.encoder_info + if self.encoder_settings: + info += ", %s" % self.encoder_settings s = u"MPEG %s layer %d, %d bps (%s), %s Hz, %d chn, %.2f seconds" % ( self.version, self.layer, self.bitrate, info, self.sample_rate, self.channels, self.length) diff -Nru mutagen-1.36/mutagen/mp3/_util.py mutagen-1.38/mutagen/mp3/_util.py --- mutagen-1.36/mutagen/mp3/_util.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/mutagen/mp3/_util.py 2017-05-25 13:49:20.000000000 +0000 @@ -2,8 +2,9 @@ # Copyright 2015 Christoph Reiter # # This program is free software; you can redistribute it and/or modify -# it under the terms of version 2 of the GNU General Public License as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. """ http://www.codeproject.com/Articles/8295/MPEG-Audio-Frame-Header @@ -173,6 +174,97 @@ self.header_crc = r.bits(16) assert r.is_aligned() + def guess_settings(self, major, minor): + """Gives a guess about the encoder settings used. Returns an empty + string if unknown. + + The guess is mostly correct in case the file was encoded with + the default options (-V --preset --alt-preset --abr -b etc) and no + other fancy options. + + Args: + major (int) + minor (int) + Returns: + text + """ + + version = major, minor + + if self.vbr_method == 2: + if version in ((3, 90), (3, 91), (3, 92)) and self.encoding_flags: + if self.bitrate < 255: + return u"--alt-preset %d" % self.bitrate + else: + return u"--alt-preset %d+" % self.bitrate + if self.preset_used != 0: + return u"--preset %d" % self.preset_used + elif self.bitrate < 255: + return u"--abr %d" % self.bitrate + else: + return u"--abr %d+" % self.bitrate + elif self.vbr_method == 1: + if self.preset_used == 0: + if self.bitrate < 255: + return u"-b %d" % self.bitrate + else: + return u"-b 255+" + elif self.preset_used == 1003: + return u"--preset insane" + return u"-b %d" % self.preset_used + elif version in ((3, 90), (3, 91), (3, 92)): + preset_key = (self.vbr_quality, self.quality, self.vbr_method, + self.lowpass_filter, self.ath_type) + if preset_key == (1, 2, 4, 19500, 3): + return u"--preset r3mix" + if preset_key == (2, 2, 3, 19000, 4): + return u"--alt-preset standard" + if preset_key == (2, 2, 3, 19500, 2): + return u"--alt-preset extreme" + + if self.vbr_method == 3: + return u"-V %s" % self.vbr_quality + elif self.vbr_method in (4, 5): + return u"-V %s --vbr-new" % self.vbr_quality + elif version in ((3, 93), (3, 94), (3, 95), (3, 96), (3, 97)): + if self.preset_used == 1001: + return u"--preset standard" + elif self.preset_used == 1002: + return u"--preset extreme" + elif self.preset_used == 1004: + return u"--preset fast standard" + elif self.preset_used == 1005: + return u"--preset fast extreme" + elif self.preset_used == 1006: + return u"--preset medium" + elif self.preset_used == 1007: + return u"--preset fast medium" + + if self.vbr_method == 3: + return u"-V %s" % self.vbr_quality + elif self.vbr_method in (4, 5): + return u"-V %s --vbr-new" % self.vbr_quality + elif version == (3, 98): + if self.vbr_method == 3: + return u"-V %s --vbr-old" % self.vbr_quality + elif self.vbr_method in (4, 5): + return u"-V %s" % self.vbr_quality + elif version >= (3, 99): + if self.vbr_method == 3: + return u"-V %s --vbr-old" % self.vbr_quality + elif self.vbr_method in (4, 5): + p = self.vbr_quality + adjust_key = (p, self.bitrate, self.lowpass_filter) + # https://sourceforge.net/p/lame/bugs/455/ + p = { + (5, 32, 0): 7, + (5, 8, 0): 8, + (6, 8, 0): 9, + }.get(adjust_key, p) + return u"-V %s" % p + + return u"" + @classmethod def parse_version(cls, fileobj): """Returns a version string and True if a LAMEHeader follows. @@ -211,9 +303,9 @@ if (major, minor) < (3, 90) or ( (major, minor) == (3, 90) and data[-11:-10] == b"("): flag = data.strip(b"\x00").rstrip().decode("ascii") - return u"%d.%d%s" % (major, minor, flag), False + return (major, minor), u"%d.%d%s" % (major, minor, flag), False - if len(data) <= 11: + if len(data) < 11: raise LAMEError("Invalid version: too long") flag = data[:-11].rstrip(b"\x00") @@ -239,7 +331,8 @@ # extended header, seek back to 9 bytes for the caller fileobj.seek(-11, 1) - return u"%d.%d%s%s" % (major, minor, patch, flag_string), True + return (major, minor), \ + u"%d.%d%s%s" % (major, minor, patch, flag_string), True class XingHeaderError(Exception): @@ -273,7 +366,10 @@ lame_header = None """A LAMEHeader instance or None""" - lame_version = u"" + lame_version = (0, 0) + """The LAME version as two element tuple (major, minor)""" + + lame_version_desc = u"" """The version of the LAME encoder e.g. '3.99.0'. Empty if unknown""" is_info = False @@ -318,12 +414,20 @@ self.vbr_scale = cdata.uint32_be(data) try: - self.lame_version, has_header = LAMEHeader.parse_version(fileobj) + self.lame_version, self.lame_version_desc, has_header = \ + LAMEHeader.parse_version(fileobj) if has_header: self.lame_header = LAMEHeader(self, fileobj) except LAMEError: pass + def get_encoder_settings(self): + """Returns the guessed encoder settings""" + + if self.lame_header is None: + return u"" + return self.lame_header.guess_settings(*self.lame_version) + @classmethod def get_offset(cls, info): """Calculate the offset to the Xing header from the start of the diff -Nru mutagen-1.36/mutagen/mp4/_as_entry.py mutagen-1.38/mutagen/mp4/_as_entry.py --- mutagen-1.36/mutagen/mp4/_as_entry.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/mutagen/mp4/_as_entry.py 2017-05-25 13:49:20.000000000 +0000 @@ -2,8 +2,9 @@ # Copyright (C) 2014 Christoph Reiter # # This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. from mutagen._compat import cBytesIO, xrange from mutagen.aac import ProgramConfigElement diff -Nru mutagen-1.36/mutagen/mp4/_atom.py mutagen-1.38/mutagen/mp4/_atom.py --- mutagen-1.36/mutagen/mp4/_atom.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/mutagen/mp4/_atom.py 2017-05-25 13:49:20.000000000 +0000 @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- - # Copyright (C) 2006 Joe Wreschnig # # This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. import struct diff -Nru mutagen-1.36/mutagen/mp4/__init__.py mutagen-1.38/mutagen/mp4/__init__.py --- mutagen-1.36/mutagen/mp4/__init__.py 2016-12-15 06:07:56.000000000 +0000 +++ mutagen-1.38/mutagen/mp4/__init__.py 2017-06-01 15:42:11.000000000 +0000 @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- - # Copyright (C) 2006 Joe Wreschnig # # This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. """Read and write MPEG-4 audio files with iTunes metadata. @@ -290,6 +290,8 @@ * 'soco' -- composer sort order * 'sosn' -- show sort order * 'tvsh' -- show name + * '\\xa9wrk' -- work + * '\\xa9mvn' -- movement Boolean values: @@ -302,9 +304,21 @@ * 'trkn' -- track number, total tracks * 'disk' -- disc number, total discs + Integer values: + + * 'tmpo' -- tempo/BPM + * '\\xa9mvc' -- Movement Count + * '\\xa9mvi' -- Movement Index + * 'shwm' -- work/movement + * 'stik' -- Media Kind + * 'rtng' -- Content Rating + * 'tves' -- TV Episode + * 'tvsn' -- TV Season + * 'plID', 'cnID', 'geID', 'atID', 'sfID', 'cmID', 'akID' -- Various iTunes + Internal IDs + Others: - * 'tmpo' -- tempo/BPM, 16 bit int * 'covr' -- cover artwork, list of MP4Cover objects (which are tagged strs) * 'gnre' -- ID3v1 genre. Not supported, use '\\xa9gen' instead. @@ -369,10 +383,12 @@ atom_name = _key2name(key)[:4] if atom_name in self.__atoms: render_func = self.__atoms[atom_name][1] + render_args = self.__atoms[atom_name][2:] else: render_func = type(self).__render_text + render_args = [] - return render_func(self, key, value) + return render_func(self, key, value, *render_args) @convert_error(IOError, error) @loadfile(writable=True) @@ -664,30 +680,59 @@ key = _name2key(b"\xa9gen") self.__add(key, values) - def __parse_tempo(self, atom, data): + def __parse_integer(self, atom, data): values = [] for version, flags, data in self.__parse_data(atom, data): - # version = 0, flags = 0 or 21 - if len(data) != 2: - raise MP4MetadataValueError("invalid tempo") - values.append(cdata.ushort_be(data)) + if version != 0: + raise MP4MetadataValueError("unsupported version") + if flags not in (AtomDataType.IMPLICIT, AtomDataType.INTEGER): + raise MP4MetadataValueError("unsupported type") + + if len(data) == 1: + value = cdata.int8(data) + elif len(data) == 2: + value = cdata.int16_be(data) + elif len(data) == 3: + value = cdata.int32_be(data + b"\x00") >> 8 + elif len(data) == 4: + value = cdata.int32_be(data) + elif len(data) == 8: + value = cdata.int64_be(data) + else: + raise MP4MetadataValueError( + "invalid value size %d" % len(data)) + values.append(value) + key = _name2key(atom.name) self.__add(key, values) - def __render_tempo(self, key, value): + def __render_integer(self, key, value, min_bytes): + assert min_bytes in (1, 2, 4, 8) + + data_list = [] try: - if len(value) == 0: - return self.__render_data(key, 0, AtomDataType.INTEGER, b"") + for v in value: + # We default to the int size of the usual values written + # by itunes for compatibility. + if cdata.int8_min <= v <= cdata.int8_max and min_bytes <= 1: + data = cdata.to_int8(v) + if cdata.int16_min <= v <= cdata.int16_max and min_bytes <= 2: + data = cdata.to_int16_be(v) + elif cdata.int32_min <= v <= cdata.int32_max and \ + min_bytes <= 4: + data = cdata.to_int32_be(v) + elif cdata.int64_min <= v <= cdata.int64_max and \ + min_bytes <= 8: + data = cdata.to_int64_be(v) + else: + raise MP4MetadataValueError( + "value out of range: %r" % value) + data_list.append(data) - if (min(value) < 0) or (max(value) >= 2 ** 16): - raise MP4MetadataValueError( - "invalid 16 bit integers: %r" % value) - except TypeError: - raise MP4MetadataValueError( - "tmpo must be a list of 16 bit integers") + except (TypeError, ValueError, cdata.error) as e: + raise MP4MetadataValueError(e) - values = [cdata.to_ushort_be(v) for v in value] - return self.__render_data(key, 0, AtomDataType.INTEGER, values) + return self.__render_data(key, 0, AtomDataType.INTEGER, data_list) def __parse_bool(self, atom, data): for version, flags, data in self.__parse_data(atom, data): @@ -789,10 +834,24 @@ b"trkn": (__parse_pair, __render_pair), b"disk": (__parse_pair, __render_pair_no_trailing), b"gnre": (__parse_genre, None), - b"tmpo": (__parse_tempo, __render_tempo), + b"plID": (__parse_integer, __render_integer, 8), + b"cnID": (__parse_integer, __render_integer, 4), + b"geID": (__parse_integer, __render_integer, 4), + b"atID": (__parse_integer, __render_integer, 4), + b"sfID": (__parse_integer, __render_integer, 4), + b"cmID": (__parse_integer, __render_integer, 4), + b"akID": (__parse_integer, __render_integer, 1), + b"tvsn": (__parse_integer, __render_integer, 4), + b"tves": (__parse_integer, __render_integer, 4), + b"tmpo": (__parse_integer, __render_integer, 2), + b"\xa9mvi": (__parse_integer, __render_integer, 2), + b"\xa9mvc": (__parse_integer, __render_integer, 2), b"cpil": (__parse_bool, __render_bool), b"pgap": (__parse_bool, __render_bool), b"pcst": (__parse_bool, __render_bool), + b"shwm": (__parse_integer, __render_integer, 1), + b"stik": (__parse_integer, __render_integer, 1), + b"rtng": (__parse_integer, __render_integer, 1), b"covr": (__parse_cover, __render_cover), b"purl": (__parse_text, __render_text), b"egid": (__parse_text, __render_text), diff -Nru mutagen-1.36/mutagen/mp4/_util.py mutagen-1.38/mutagen/mp4/_util.py --- mutagen-1.36/mutagen/mp4/_util.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/mutagen/mp4/_util.py 2017-05-25 13:49:20.000000000 +0000 @@ -2,8 +2,9 @@ # Copyright (C) 2014 Christoph Reiter # # This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. from mutagen._util import cdata diff -Nru mutagen-1.36/mutagen/musepack.py mutagen-1.38/mutagen/musepack.py --- mutagen-1.36/mutagen/musepack.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/mutagen/musepack.py 2017-05-25 13:49:20.000000000 +0000 @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- - # Copyright (C) 2006 Lukas Lalinsky # Copyright (C) 2012 Christoph Reiter # # This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. """Musepack audio streams with APEv2 tags. diff -Nru mutagen-1.36/mutagen/oggflac.py mutagen-1.38/mutagen/oggflac.py --- mutagen-1.36/mutagen/oggflac.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/mutagen/oggflac.py 2017-06-01 12:43:24.000000000 +0000 @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- - # Copyright (C) 2006 Joe Wreschnig # # This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. """Read and write Ogg FLAC comments. @@ -79,7 +79,9 @@ def _post_tags(self, fileobj): if self.length: return - page = OggPage.find_last(fileobj, self.serial) + page = OggPage.find_last(fileobj, self.serial, finishing=True) + if page is None: + raise OggFLACHeaderError self.length = page.position / float(self.sample_rate) def pprint(self): diff -Nru mutagen-1.36/mutagen/oggopus.py mutagen-1.38/mutagen/oggopus.py --- mutagen-1.36/mutagen/oggopus.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/mutagen/oggopus.py 2017-06-01 12:43:24.000000000 +0000 @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- - # Copyright (C) 2012, 2013 Christoph Reiter # # This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. """Read and write Ogg Opus comments. @@ -69,7 +69,7 @@ raise OggOpusHeaderError("version %r unsupported" % major) def _post_tags(self, fileobj): - page = OggPage.find_last(fileobj, self.serial) + page = OggPage.find_last(fileobj, self.serial, finishing=True) if page is None: raise OggOpusHeaderError self.length = (page.position - self.__pre_skip) / float(48000) diff -Nru mutagen-1.36/mutagen/ogg.py mutagen-1.38/mutagen/ogg.py --- mutagen-1.36/mutagen/ogg.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/mutagen/ogg.py 2017-06-01 12:43:24.000000000 +0000 @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- - # Copyright (C) 2006 Joe Wreschnig # # This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. """Read and write Ogg bitstreams and pages. @@ -434,7 +434,7 @@ cls.renumber(fileobj, serial, sequence) @staticmethod - def find_last(fileobj, serial): + def find_last(fileobj, serial, finishing=False): """Find the last page of the stream 'serial'. If the file is not multiplexed this function is fast. If it is, @@ -443,6 +443,10 @@ This finds the last page in the actual file object, or the last page in the stream (with eos set), whichever comes first. + If finishing is True it returns the last page which contains a packet + finishing on it. If there exist pages but none with finishing packets + returns None. + Returns None in case no page with the serial exists. Raises error in case this isn't a valid ogg stream. Raises IOError. @@ -457,13 +461,17 @@ except ValueError: raise error("unable to find final Ogg header") bytesobj = cBytesIO(data[index:]) + + def is_valid(page): + return not finishing or page.position != -1 + best_page = None try: page = OggPage(bytesobj) except error: pass else: - if page.serial == serial: + if page.serial == serial and is_valid(page): if page.last: return page else: @@ -475,12 +483,14 @@ fileobj.seek(0) try: page = OggPage(fileobj) - while not page.last: + while True: + if page.serial == serial: + if is_valid(page): + best_page = page + if page.last: + break page = OggPage(fileobj) - while page.serial != serial: - page = OggPage(fileobj) - best_page = page - return page + return best_page except error: return best_page except EOFError: diff -Nru mutagen-1.36/mutagen/oggspeex.py mutagen-1.38/mutagen/oggspeex.py --- mutagen-1.36/mutagen/oggspeex.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/mutagen/oggspeex.py 2017-06-01 12:43:24.000000000 +0000 @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- - # Copyright 2006 Joe Wreschnig # # This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. """Read and write Ogg Speex comments. @@ -64,7 +64,7 @@ self.serial = page.serial def _post_tags(self, fileobj): - page = OggPage.find_last(fileobj, self.serial) + page = OggPage.find_last(fileobj, self.serial, finishing=True) if page is None: raise OggSpeexHeaderError self.length = page.position / float(self.sample_rate) diff -Nru mutagen-1.36/mutagen/oggtheora.py mutagen-1.38/mutagen/oggtheora.py --- mutagen-1.36/mutagen/oggtheora.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/mutagen/oggtheora.py 2017-06-01 12:43:24.000000000 +0000 @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- - # Copyright 2006 Joe Wreschnig # # This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. """Read and write Ogg Theora comments. @@ -67,7 +67,7 @@ self.serial = page.serial def _post_tags(self, fileobj): - page = OggPage.find_last(fileobj, self.serial) + page = OggPage.find_last(fileobj, self.serial, finishing=True) if page is None: raise OggTheoraHeaderError position = page.position diff -Nru mutagen-1.36/mutagen/oggvorbis.py mutagen-1.38/mutagen/oggvorbis.py --- mutagen-1.36/mutagen/oggvorbis.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/mutagen/oggvorbis.py 2017-06-01 12:43:24.000000000 +0000 @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- - # Copyright 2006 Joe Wreschnig # # This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. """Read and write Ogg Vorbis comments. @@ -83,7 +83,7 @@ def _post_tags(self, fileobj): """Raises ogg.error""" - page = OggPage.find_last(fileobj, self.serial) + page = OggPage.find_last(fileobj, self.serial, finishing=True) if page is None: raise OggVorbisHeaderError self.length = page.position / float(self.sample_rate) diff -Nru mutagen-1.36/mutagen/optimfrog.py mutagen-1.38/mutagen/optimfrog.py --- mutagen-1.36/mutagen/optimfrog.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/mutagen/optimfrog.py 2017-05-25 13:49:20.000000000 +0000 @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- - # Copyright (C) 2006 Lukas Lalinsky # # This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. """OptimFROG audio streams with APEv2 tags. diff -Nru mutagen-1.36/mutagen/smf.py mutagen-1.38/mutagen/smf.py --- mutagen-1.36/mutagen/smf.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/mutagen/smf.py 2017-05-25 13:49:20.000000000 +0000 @@ -2,8 +2,9 @@ # Copyright 2015 Christoph Reiter # # This program is free software; you can redistribute it and/or modify -# it under the terms of version 2 of the GNU General Public License as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. """Standard MIDI File (SMF)""" diff -Nru mutagen-1.36/mutagen/_tags.py mutagen-1.38/mutagen/_tags.py --- mutagen-1.36/mutagen/_tags.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/mutagen/_tags.py 2017-05-25 13:49:20.000000000 +0000 @@ -2,8 +2,9 @@ # Copyright (C) 2005 Michael Urman # # This program is free software; you can redistribute it and/or modify -# it under the terms of version 2 of the GNU General Public License as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. from ._util import loadfile diff -Nru mutagen-1.36/mutagen/_tools/__init__.py mutagen-1.38/mutagen/_tools/__init__.py --- mutagen-1.36/mutagen/_tools/__init__.py 2016-11-01 19:32:36.000000000 +0000 +++ mutagen-1.38/mutagen/_tools/__init__.py 2017-05-25 13:49:20.000000000 +0000 @@ -2,5 +2,6 @@ # Copyright 2016 Christoph Reiter # # This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. diff -Nru mutagen-1.36/mutagen/_tools/mid3cp.py mutagen-1.38/mutagen/_tools/mid3cp.py --- mutagen-1.36/mutagen/_tools/mid3cp.py 2016-12-15 05:56:14.000000000 +0000 +++ mutagen-1.38/mutagen/_tools/mid3cp.py 2017-05-25 13:49:20.000000000 +0000 @@ -1,9 +1,10 @@ # -*- coding: utf-8 -*- # Copyright 2014 Marcus Sundman - +# # This program is free software; you can redistribute it and/or modify -# it under the terms of version 2 of the GNU General Public License as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. """A program replicating the functionality of id3lib's id3cp, using mutagen for tag loading and saving. diff -Nru mutagen-1.36/mutagen/_tools/mid3iconv.py mutagen-1.38/mutagen/_tools/mid3iconv.py --- mutagen-1.36/mutagen/_tools/mid3iconv.py 2016-11-01 20:27:27.000000000 +0000 +++ mutagen-1.38/mutagen/_tools/mid3iconv.py 2017-05-25 13:49:20.000000000 +0000 @@ -1,10 +1,14 @@ # -*- coding: utf-8 -*- -# ID3iconv is a Java based ID3 encoding convertor, here's the Python version. # Copyright 2006 Emfox Zhou # # This program is free software; you can redistribute it and/or modify -# it under the terms of version 2 of the GNU General Public License as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +""" +ID3iconv is a Java based ID3 encoding convertor, here's the Python version. +""" import sys import locale diff -Nru mutagen-1.36/mutagen/_tools/mid3v2.py mutagen-1.38/mutagen/_tools/mid3v2.py --- mutagen-1.36/mutagen/_tools/mid3v2.py 2016-12-06 09:40:16.000000000 +0000 +++ mutagen-1.38/mutagen/_tools/mid3v2.py 2017-05-25 13:49:20.000000000 +0000 @@ -1,10 +1,12 @@ # -*- coding: utf-8 -*- -# Pretend to be /usr/bin/id3v2 from id3lib, sort of. # Copyright 2005 Joe Wreschnig # # This program is free software; you can redistribute it and/or modify -# it under the terms of version 2 of the GNU General Public License as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +"""Pretend to be /usr/bin/id3v2 from id3lib, sort of.""" import sys import codecs diff -Nru mutagen-1.36/mutagen/_tools/moggsplit.py mutagen-1.38/mutagen/_tools/moggsplit.py --- mutagen-1.36/mutagen/_tools/moggsplit.py 2016-11-01 20:34:59.000000000 +0000 +++ mutagen-1.38/mutagen/_tools/moggsplit.py 2017-05-25 13:49:20.000000000 +0000 @@ -1,10 +1,12 @@ # -*- coding: utf-8 -*- -# Split a multiplex/chained Ogg file into its component parts. # Copyright 2006 Joe Wreschnig # # This program is free software; you can redistribute it and/or modify -# it under the terms of version 2 of the GNU General Public License as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +"""Split a multiplex/chained Ogg file into its component parts.""" import os diff -Nru mutagen-1.36/mutagen/_tools/mutagen_inspect.py mutagen-1.38/mutagen/_tools/mutagen_inspect.py --- mutagen-1.36/mutagen/_tools/mutagen_inspect.py 2016-11-01 20:27:36.000000000 +0000 +++ mutagen-1.38/mutagen/_tools/mutagen_inspect.py 2017-05-25 13:49:20.000000000 +0000 @@ -1,10 +1,12 @@ # -*- coding: utf-8 -*- -# Full tag list for any given file. # Copyright 2005 Joe Wreschnig # # This program is free software; you can redistribute it and/or modify -# it under the terms of version 2 of the GNU General Public License as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +"""Full tag list for any given file.""" from mutagen._senf import print_, argv from mutagen._compat import text_type diff -Nru mutagen-1.36/mutagen/_tools/mutagen_pony.py mutagen-1.38/mutagen/_tools/mutagen_pony.py --- mutagen-1.36/mutagen/_tools/mutagen_pony.py 2016-11-01 20:35:18.000000000 +0000 +++ mutagen-1.38/mutagen/_tools/mutagen_pony.py 2017-05-25 13:49:20.000000000 +0000 @@ -2,8 +2,9 @@ # Copyright 2005 Joe Wreschnig, Michael Urman # # This program is free software; you can redistribute it and/or modify -# it under the terms of version 2 of the GNU General Public License as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. import os import sys diff -Nru mutagen-1.36/mutagen/_tools/_util.py mutagen-1.38/mutagen/_tools/_util.py --- mutagen-1.36/mutagen/_tools/_util.py 2016-11-01 20:28:17.000000000 +0000 +++ mutagen-1.38/mutagen/_tools/_util.py 2017-05-25 13:49:20.000000000 +0000 @@ -2,8 +2,9 @@ # Copyright 2015 Christoph Reiter # # This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. import os import signal diff -Nru mutagen-1.36/mutagen/trueaudio.py mutagen-1.38/mutagen/trueaudio.py --- mutagen-1.36/mutagen/trueaudio.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/mutagen/trueaudio.py 2017-05-25 13:49:20.000000000 +0000 @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- - # Copyright (C) 2006 Joe Wreschnig # # This program is free software; you can redistribute it and/or modify -# it under the terms of version 2 of the GNU General Public License as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. """True Audio audio stream information and tags. diff -Nru mutagen-1.36/mutagen/_util.py mutagen-1.38/mutagen/_util.py --- mutagen-1.36/mutagen/_util.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/mutagen/_util.py 2017-05-25 13:49:20.000000000 +0000 @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- - # Copyright (C) 2006 Joe Wreschnig # # This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. """Utility classes for Mutagen. @@ -16,7 +16,13 @@ import struct import codecs import errno -import mmap + +try: + import mmap +except ImportError: + # Google App Engine has no mmap: + # https://github.com/quodlibet/mutagen/issues/286 + mmap = None from collections import namedtuple from contextlib import contextmanager @@ -527,6 +533,19 @@ if s.size == 1: esuffix = "" bits = str(s.size * 8) + + if unsigned: + max_ = 2 ** (s.size * 8) - 1 + min_ = 0 + else: + max_ = 2 ** (s.size * 8 - 1) - 1 + min_ = - 2 ** (s.size * 8 - 1) + + funcs["%s%s_min" % (prefix, name)] = min_ + funcs["%s%s_max" % (prefix, name)] = max_ + funcs["%sint%s_min" % (prefix, bits)] = min_ + funcs["%sint%s_max" % (prefix, bits)] = max_ + funcs["%s%s%s" % (prefix, name, esuffix)] = unpack funcs["%sint%s%s" % (prefix, bits, esuffix)] = unpack funcs["%s%s%s_from" % (prefix, name, esuffix)] = unpack_from @@ -644,6 +663,8 @@ ValueError: In case invalid parameters were given """ + assert mmap is not None, "no mmap support" + if dest < 0 or src < 0 or count < 0: raise ValueError("Invalid parameters") @@ -789,9 +810,12 @@ resize_file(fobj, size, BUFFER_SIZE) - try: - mmap_move(fobj, offset + size, offset, movesize) - except mmap.error: + if mmap is not None: + try: + mmap_move(fobj, offset + size, offset, movesize) + except mmap.error: + fallback_move(fobj, offset + size, offset, movesize, BUFFER_SIZE) + else: fallback_move(fobj, offset + size, offset, movesize, BUFFER_SIZE) @@ -820,9 +844,12 @@ if movesize < 0: raise ValueError - try: - mmap_move(fobj, offset, offset + size, movesize) - except mmap.error: + if mmap is not None: + try: + mmap_move(fobj, offset, offset + size, movesize) + except mmap.error: + fallback_move(fobj, offset, offset + size, movesize, BUFFER_SIZE) + else: fallback_move(fobj, offset, offset + size, movesize, BUFFER_SIZE) resize_file(fobj, -size, BUFFER_SIZE) @@ -874,6 +901,38 @@ return default +def encode_endian(text, encoding, errors="strict", le=True): + """Like text.encode(encoding) but always returns little endian/big endian + BOMs instead of the system one. + + Args: + text (text) + encoding (str) + errors (str) + le (boolean): if little endian + Returns: + bytes + Raises: + UnicodeEncodeError + LookupError + """ + + encoding = codecs.lookup(encoding).name + + if encoding == "utf-16": + if le: + return codecs.BOM_UTF16_LE + text.encode("utf-16-le", errors) + else: + return codecs.BOM_UTF16_BE + text.encode("utf-16-be", errors) + elif encoding == "utf-32": + if le: + return codecs.BOM_UTF32_LE + text.encode("utf-32-le", errors) + else: + return codecs.BOM_UTF32_BE + text.encode("utf-32-be", errors) + else: + return text.encode(encoding, errors) + + def decode_terminated(data, encoding, strict=True): """Returns the decoded data until the first NULL terminator and all data after it. diff -Nru mutagen-1.36/mutagen/_vorbis.py mutagen-1.38/mutagen/_vorbis.py --- mutagen-1.36/mutagen/_vorbis.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/mutagen/_vorbis.py 2017-05-25 13:49:20.000000000 +0000 @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- - # Copyright (C) 2005-2006 Joe Wreschnig # 2013 Christoph Reiter # # This program is free software; you can redistribute it and/or modify -# it under the terms of version 2 of the GNU General Public License as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. """Read and write Vorbis comment data. @@ -167,7 +167,7 @@ try: value.decode("utf-8") - except: + except Exception: raise ValueError("%r is not a valid value" % value) return True diff -Nru mutagen-1.36/mutagen/wavpack.py mutagen-1.38/mutagen/wavpack.py --- mutagen-1.36/mutagen/wavpack.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/mutagen/wavpack.py 2017-05-25 13:49:20.000000000 +0000 @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- - # Copyright 2006 Joe Wreschnig # 2014 Christoph Reiter # # This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. """WavPack reading and writing. diff -Nru mutagen-1.36/NEWS mutagen-1.38/NEWS --- mutagen-1.36/NEWS 2016-12-22 17:21:21.000000000 +0000 +++ mutagen-1.38/NEWS 2017-06-01 16:02:37.000000000 +0000 @@ -1,3 +1,53 @@ +1.38 - 2017-06-01 +----------------- + +* Note: New release tarballs are now hosted on github: + https://github.com/quodlibet/mutagen/releases +* ID3: + + * Add iTunes grouping frame `id3.GRP1` :bug:`304` + * Fix exposing text frames where the text can't be encoded with the + reported encoding due to merging of frames :bug:`307` + +* OGG: Fix wrong StreamInfo.length (small negative value) for all + ogg based formats in rare cases. :bug:`308` + + +1.37 - 2017.02.24 +----------------- + +* Relicense "GPLv2" → "GPLv2 or later" :bug:`291` +* DSF: add `mutagen.dsf` module for DSF (DSD Stream File) support + :pr:`283` (Boris Pruessmann) +* MP3: Add `mp3.MPEGInfo.encoder_settings` containing a guess of the encoder + settings used, for example ``"-V2"`` for LAME :bug:`66` +* ID3: add iTunes movement related frames `id3.MVIN` and `id3.MVNM` +* MP4: support ``©mvi``, ``©mvc``, ``shwm``, ``stik``, ``rtng``, ``tves``, + ``tvsn``, ``plID``, ``cnID``, ``geID``, ``atID``, ``sfID``, ``cmID``, + ``akID`` :bug:`130` + + +1.36.3 - 2017.02.24 +------------------- + +* MP3: fix error with xing frames without a frame count :bug:`292` + + +1.36.2 - 2017.01.25 +------------------- + +* ID3: Always write little endian utf-16 with BOM. + Fixes tests on big endian machines :pr:`289` + + +1.36.1 - 2017.01.22 +------------------- + +* Support GAE runtime :bug:`286` +* FLAC: Fix crash when loading files with zero samples :bug:`287` +* MP3: Handle broken lame tags written by older lame versions + + 1.36 - 2016.12.22 ----------------- @@ -97,8 +147,8 @@ * FLAC: add ``audio/flac`` mime type. :bug:`235` * ASF: Fixed crash when object size is longer than the header and file length (Ben Ockmore) -* ID3: Validate attributes set after frame creation :bb-pr:`8` - (Daniel Plachotich) +* ID3: Validate attributes set after frame creation :commit:`69368c31e00` + (:user:`Daniel Plachotich `) * MP4: validate values in ``__setitem__`` so things don't fail in save() :bug:`236` * tests: Fix SynchronizedTextSpec test on big-endian machines :bug:`247` @@ -207,7 +257,8 @@ * MP4: * New ``MP4Info.codec`` for identifying the contained audio codec - e.g. ``"mp4a"``, ``"alac"``, ``"mp4a.40.2"``, ``"ac-3"`` etc. :bb-pr:`6` + e.g. ``"mp4a"``, ``"alac"``, ``"mp4a.40.2"``, ``"ac-3"`` etc. + :commit:`b2f22b81c77` * New ``MP4Info.codec_description``: name of the audio codec e.g. ``"ALAC"``, ``"AAC LC"``, ``"AC-3"`` @@ -228,7 +279,7 @@ * MP4: * Parse channels/sample_rate/bits_per_sample/bitrate for ALAC files - :bug:`199` :bb-pr:`5` (Adrian Sampson, Christoph Reiter) + :bug:`199` :commit:`192cfcaf14` (Adrian Sampson, Christoph Reiter) * ASF: @@ -243,7 +294,7 @@ * docs: - * New logo :bb-pr:`4` (Samuel Messner) + * New logo :commit:`b728fa75` (:user:`Samuel Messner `) * Add examples for handling cover art in vorbiscomment :bug:`200` * Add examples for id3v2.3 diff -Nru mutagen-1.36/PKG-INFO mutagen-1.38/PKG-INFO --- mutagen-1.36/PKG-INFO 2016-12-22 19:21:34.000000000 +0000 +++ mutagen-1.38/PKG-INFO 2017-06-01 16:07:47.000000000 +0000 @@ -1,12 +1,18 @@ Metadata-Version: 1.1 Name: mutagen -Version: 1.36 +Version: 1.38 Summary: read and write audio tags for many formats Home-page: https://github.com/quodlibet/mutagen Author: Michael Urman Author-email: quod-libet-development@groups.google.com License: GNU GPL v2 -Description: Mutagen is a Python module to handle audio metadata. It supports ASF, FLAC, +Description: .. image:: https://cdn.rawgit.com/quodlibet/mutagen/master/docs/images/logo.svg + :align: center + :width: 400px + + | + + Mutagen is a Python module to handle audio metadata. It supports ASF, FLAC, MP4, Monkey's Audio, MP3, Musepack, Ogg Opus, Ogg FLAC, Ogg Speex, Ogg Theora, Ogg Vorbis, True Audio, WavPack, OptimFROG, and AIFF audio files. All versions of ID3v2 are supported, and all standard ID3v2.4 frames are parsed. @@ -15,7 +21,8 @@ manipulate Ogg streams on an individual packet/page level. Mutagen works with Python 2.7, 3.3+ (CPython and PyPy) on Linux, Windows and - macOS, and has no dependencies outside the Python standard library. + macOS, and has no dependencies outside the Python standard library. Mutagen + is licensed under the GPL version 2 or later. For more information visit https://mutagen.readthedocs.org diff -Nru mutagen-1.36/README.rst mutagen-1.38/README.rst --- mutagen-1.36/README.rst 2016-11-02 14:39:50.000000000 +0000 +++ mutagen-1.38/README.rst 2017-05-25 13:49:20.000000000 +0000 @@ -1,3 +1,9 @@ +.. image:: https://cdn.rawgit.com/quodlibet/mutagen/master/docs/images/logo.svg + :align: center + :width: 400px + +| + Mutagen is a Python module to handle audio metadata. It supports ASF, FLAC, MP4, Monkey's Audio, MP3, Musepack, Ogg Opus, Ogg FLAC, Ogg Speex, Ogg Theora, Ogg Vorbis, True Audio, WavPack, OptimFROG, and AIFF audio files. All @@ -7,7 +13,8 @@ manipulate Ogg streams on an individual packet/page level. Mutagen works with Python 2.7, 3.3+ (CPython and PyPy) on Linux, Windows and -macOS, and has no dependencies outside the Python standard library. +macOS, and has no dependencies outside the Python standard library. Mutagen +is licensed under the GPL version 2 or later. For more information visit https://mutagen.readthedocs.org diff -Nru mutagen-1.36/setup.py mutagen-1.38/setup.py --- mutagen-1.36/setup.py 2016-11-09 14:00:53.000000000 +0000 +++ mutagen-1.38/setup.py 2017-05-25 13:49:20.000000000 +0000 @@ -3,8 +3,9 @@ # Copyright 2005-2009,2011 Joe Wreschnig # # This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. import glob import os Binary files /tmp/tmpiVl8In/2RoOKrX71O/mutagen-1.36/tests/data/2822400-1ch-0s-silence.dsf and /tmp/tmpiVl8In/eRFVO6DBBA/mutagen-1.38/tests/data/2822400-1ch-0s-silence.dsf differ Binary files /tmp/tmpiVl8In/2RoOKrX71O/mutagen-1.36/tests/data/5644800-2ch-s01-silence.dsf and /tmp/tmpiVl8In/eRFVO6DBBA/mutagen-1.38/tests/data/5644800-2ch-s01-silence.dsf differ Binary files /tmp/tmpiVl8In/2RoOKrX71O/mutagen-1.36/tests/data/audacious-trailing-id32-apev2.mp3 and /tmp/tmpiVl8In/eRFVO6DBBA/mutagen-1.38/tests/data/audacious-trailing-id32-apev2.mp3 differ Binary files /tmp/tmpiVl8In/2RoOKrX71O/mutagen-1.36/tests/data/audacious-trailing-id32-id31.mp3 and /tmp/tmpiVl8In/eRFVO6DBBA/mutagen-1.38/tests/data/audacious-trailing-id32-id31.mp3 differ Binary files /tmp/tmpiVl8In/2RoOKrX71O/mutagen-1.36/tests/data/lame397v9short.mp3 and /tmp/tmpiVl8In/eRFVO6DBBA/mutagen-1.38/tests/data/lame397v9short.mp3 differ Binary files /tmp/tmpiVl8In/2RoOKrX71O/mutagen-1.36/tests/data/with-id3.dsf and /tmp/tmpiVl8In/eRFVO6DBBA/mutagen-1.38/tests/data/with-id3.dsf differ Binary files /tmp/tmpiVl8In/2RoOKrX71O/mutagen-1.36/tests/data/without-id3.dsf and /tmp/tmpiVl8In/eRFVO6DBBA/mutagen-1.38/tests/data/without-id3.dsf differ diff -Nru mutagen-1.36/tests/__init__.py mutagen-1.38/tests/__init__.py --- mutagen-1.36/tests/__init__.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/tests/__init__.py 2017-05-25 13:49:20.000000000 +0000 @@ -2,8 +2,10 @@ import re import os +import sys import warnings import shutil +import contextlib from unittest import TestCase as BaseTestCase try: @@ -11,7 +13,7 @@ except ImportError: raise SystemExit("pytest missing: sudo apt-get install python-pytest") -from mutagen._compat import PY3 +from mutagen._compat import PY3, StringIO from mutagen._senf import text2fsn, fsn2text, path2fsn, mkstemp, fsnative @@ -49,6 +51,28 @@ return filename +@contextlib.contextmanager +def capture_output(): + """ + with capture_output() as (stdout, stderr): + some_action() + print stdout.getvalue(), stderr.getvalue() + """ + + err = StringIO() + out = StringIO() + old_err = sys.stderr + old_out = sys.stdout + sys.stderr = err + sys.stdout = out + + try: + yield (out, err) + finally: + sys.stderr = old_err + sys.stdout = old_out + + class TestCase(BaseTestCase): def failUnlessRaisesRegexp(self, exc, re_, fun, *args, **kwargs): diff -Nru mutagen-1.36/tests/quality/__init__.py mutagen-1.38/tests/quality/__init__.py --- mutagen-1.36/tests/quality/__init__.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/tests/quality/__init__.py 2017-05-25 13:49:20.000000000 +0000 @@ -2,5 +2,6 @@ # Copyright 2014 Christoph Reiter # # This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. diff -Nru mutagen-1.36/tests/quality/test_pep8.py mutagen-1.38/tests/quality/test_pep8.py --- mutagen-1.36/tests/quality/test_pep8.py 2016-11-09 14:00:48.000000000 +0000 +++ mutagen-1.38/tests/quality/test_pep8.py 2017-05-25 13:49:20.000000000 +0000 @@ -2,57 +2,38 @@ # Copyright 2013,2014 Christoph Reiter # # This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation - -import os -import subprocess +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. import pytest import mutagen import tests -from tests import TestCase +from tests import TestCase, capture_output + +try: + import pep8 as pycodestyle +except ImportError: + try: + import pycodestyle + except ImportError: + pycodestyle = None @pytest.mark.quality class TPEP8(TestCase): IGNORE = ["E128", "W601", "E402", "E731", "W503", "E741", "E305"] - def _run(self, path, ignore=None): - if ignore is None: - ignore = [] - ignore += self.IGNORE - - p = subprocess.Popen( - ["pep8", "--ignore=" + ",".join(ignore), path], - stderr=subprocess.PIPE, stdout=subprocess.PIPE) - - class Future(object): - - def __init__(self, p): - self.p = p - - def result(self): - result = self.p.communicate() - if self.p.returncode != 0: - return result - - return Future(p) - def test_all(self): paths = [mutagen.__path__[0], tests.__path__[0]] - futures = [] - for path in paths: - assert os.path.exists(path) - futures.append(self._run(path)) - errors = [] - for future in futures: - status = future.result() - if status is not None: - errors.append(status[0].decode("utf-8")) + for path in paths: + style = pycodestyle.StyleGuide(ignore=self.IGNORE) + with capture_output() as (o, e): + style.input_dir(path) + errors.extend(o.getvalue().splitlines()) if errors: raise Exception("\n".join(errors)) diff -Nru mutagen-1.36/tests/quality/test_pyflakes.py mutagen-1.38/tests/quality/test_pyflakes.py --- mutagen-1.36/tests/quality/test_pyflakes.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/tests/quality/test_pyflakes.py 2017-05-25 13:49:20.000000000 +0000 @@ -2,8 +2,9 @@ # Copyright 2013,2014 Christoph Reiter # # This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. import os import re diff -Nru mutagen-1.36/tests/test_dsf.py mutagen-1.38/tests/test_dsf.py --- mutagen-1.36/tests/test_dsf.py 1970-01-01 00:00:00.000000000 +0000 +++ mutagen-1.38/tests/test_dsf.py 2017-05-25 13:49:20.000000000 +0000 @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- + +import os + +from mutagen.dsf import DSF, DSFFile, delete +from mutagen.dsf import error as DSFError + +from tests import TestCase, DATA_DIR, get_temp_copy + + +class TDSF(TestCase): + silence_1 = os.path.join(DATA_DIR, '2822400-1ch-0s-silence.dsf') + silence_2 = os.path.join(DATA_DIR, '5644800-2ch-s01-silence.dsf') + + has_tags = os.path.join(DATA_DIR, 'with-id3.dsf') + no_tags = os.path.join(DATA_DIR, 'without-id3.dsf') + + def setUp(self): + self.filename_1 = get_temp_copy(self.has_tags) + self.filename_2 = get_temp_copy(self.no_tags) + + self.dsf_tmp_id3 = DSF(self.filename_1) + self.dsf_tmp_no_id3 = DSF(self.filename_2) + + self.dsf_1 = DSF(self.silence_1) + self.dsf_2 = DSF(self.silence_2) + + def test_channels(self): + self.failUnlessEqual(self.dsf_1.info.channels, 1) + self.failUnlessEqual(self.dsf_2.info.channels, 2) + + def test_length(self): + self.failUnlessEqual(self.dsf_1.info.length, 0) + self.failUnlessEqual(self.dsf_2.info.length, 0.01) + + def test_sampling_frequency(self): + self.failUnlessEqual(self.dsf_1.info.sample_rate, 2822400) + self.failUnlessEqual(self.dsf_2.info.sample_rate, 5644800) + + def test_bits_per_sample(self): + self.failUnlessEqual(self.dsf_1.info.bits_per_sample, 1) + + def test_notdsf(self): + self.failUnlessRaises( + DSFError, DSF, os.path.join(DATA_DIR, 'empty.ofr')) + + def test_pprint(self): + self.failUnless(self.dsf_tmp_id3.pprint()) + + def test_delete(self): + self.dsf_tmp_id3.delete() + self.failIf(self.dsf_tmp_id3.tags) + self.failUnless(DSF(self.filename_1).tags is None) + + def test_module_delete(self): + delete(self.filename_1) + self.failUnless(DSF(self.filename_1).tags is None) + + def test_module_double_delete(self): + delete(self.filename_1) + delete(self.filename_1) + + def test_pprint_no_tags(self): + self.dsf_tmp_id3.tags = None + self.failUnless(self.dsf_tmp_id3.pprint()) + + def test_save_no_tags(self): + self.dsf_tmp_id3.tags = None + self.dsf_tmp_id3.save() + self.assertTrue(self.dsf_tmp_id3.tags is None) + + def test_add_tags_already_there(self): + self.failUnless(self.dsf_tmp_id3.tags) + self.failUnlessRaises(Exception, self.dsf_tmp_id3.add_tags) + + def test_mime(self): + self.failUnless("audio/dsf" in self.dsf_tmp_id3.mime) + + def test_loaded_tags(self): + self.failUnless(self.dsf_tmp_id3["TIT2"] == "DSF title") + + def test_roundtrip(self): + self.failUnlessEqual(self.dsf_tmp_id3["TIT2"], ["DSF title"]) + self.dsf_tmp_id3.save() + new = DSF(self.dsf_tmp_id3.filename) + self.failUnlessEqual(new["TIT2"], ["DSF title"]) + + def test_save_tags(self): + from mutagen.id3 import TIT2 + tags = self.dsf_tmp_id3.tags + tags.add(TIT2(encoding=3, text="foobar")) + tags.save() + + new = DSF(self.dsf_tmp_id3.filename) + self.failUnlessEqual(new["TIT2"], ["foobar"]) + + def test_corrupt_tag(self): + with open(self.filename_1, "r+b") as h: + chunk = DSFFile(h).dsd_chunk + h.seek(chunk.offset_metdata_chunk) + h.seek(4, 1) + h.write(b"\xff\xff") + self.assertRaises(DSFError, DSF, self.filename_1) + + def test_padding(self): + DSF(self.filename_1).save() + self.assertEqual(DSF(self.filename_1).tags._padding, 1024) + DSF(self.filename_1).save() + self.assertEqual(DSF(self.filename_1).tags._padding, 1024) + + tags = DSF(self.filename_1) + tags.save(padding=lambda x: 1) + self.assertEqual(DSF(self.filename_1).tags._padding, 1) + + tags = DSF(self.filename_1) + tags.save(padding=lambda x: 100) + self.assertEqual(DSF(self.filename_1).tags._padding, 100) + + tags = DSF(self.filename_1) + self.assertRaises(DSFError, tags.save, padding=lambda x: -1) + + def tearDown(self): + os.unlink(self.filename_1) + os.unlink(self.filename_2) diff -Nru mutagen-1.36/tests/test_encoding.py mutagen-1.38/tests/test_encoding.py --- mutagen-1.36/tests/test_encoding.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/tests/test_encoding.py 2017-05-25 13:49:20.000000000 +0000 @@ -2,8 +2,9 @@ # Copyright 2014 Christoph Reiter # # This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. import os import re diff -Nru mutagen-1.36/tests/test_flac.py mutagen-1.38/tests/test_flac.py --- mutagen-1.36/tests/test_flac.py 2016-12-14 14:22:59.000000000 +0000 +++ mutagen-1.38/tests/test_flac.py 2017-05-25 13:49:20.000000000 +0000 @@ -291,6 +291,15 @@ def tearDown(self): os.unlink(self.NEW) + def test_zero_samples(self): + # write back zero sample count and load again + self.flac.info.total_samples = 0 + self.flac.save() + new = FLAC(self.flac.filename) + assert new.info.total_samples == 0 + assert new.info.bitrate == 0 + assert new.info.length == 0.0 + def test_bitrate(self): assert self.flac.info.bitrate == 101430 old_file_size = os.path.getsize(self.flac.filename) diff -Nru mutagen-1.36/tests/test__id3frames.py mutagen-1.38/tests/test__id3frames.py --- mutagen-1.36/tests/test__id3frames.py 2016-11-20 16:03:25.000000000 +0000 +++ mutagen-1.38/tests/test__id3frames.py 2017-05-25 13:49:20.000000000 +0000 @@ -106,6 +106,9 @@ ['TSSE', b'\x0012345', '12345', '', dict(encoding=0)], ['TSST', b'\x0012345', '12345', '', dict(encoding=0)], ['TYER', b'\x002004', '2004', 2004, dict(encoding=0)], + ['MVNM', b'\x00ab\x00', 'ab', '', dict(encoding=0)], + ['MVIN', b'\x001/3\x00', '1/3', 1, dict(encoding=0)], + ['GRP1', b'\x00ab\x00', 'ab', '', dict(encoding=0)], [ 'TXXX', b'\x00usr\x00a/b\x00c', ['a/b', 'c'], '', dict(encoding=0, desc='usr') @@ -373,6 +376,9 @@ ['TS2', b'\x00ab', 'ab', '', dict(encoding=0)], ['TST', b'\x00ab', 'ab', '', dict(encoding=0)], ['TSP', b'\x00ab', 'ab', '', dict(encoding=0)], + ['MVN', b'\x00ab\x00', 'ab', '', dict(encoding=0)], + ['MVI', b'\x001/3\x00', '1/3', 1, dict(encoding=0)], + ['GP1', b'\x00ab\x00', 'ab', '', dict(encoding=0)], ['WAF', b'http://zzz', 'http://zzz', '', {}], ['WAR', b'http://zzz', 'http://zzz', '', {}], diff -Nru mutagen-1.36/tests/test_id3.py mutagen-1.38/tests/test_id3.py --- mutagen-1.36/tests/test_id3.py 2016-12-07 19:25:15.000000000 +0000 +++ mutagen-1.38/tests/test_id3.py 2017-05-31 09:17:00.000000000 +0000 @@ -8,7 +8,8 @@ from mutagen.id3 import ID3, Frames, ID3UnsupportedVersionError, TIT2, \ CHAP, CTOC, TT1, TCON, COMM, TORY, PIC, MakeID3v1, TRCK, TYER, TDRC, \ TDAT, TIME, LNK, IPLS, TPE1, BinaryFrame, TIT3, POPM, APIC, CRM, \ - TALB, TPE2, TSOT, TDEN, TIPL, ParseID3v1, Encoding, ID3Tags, RVAD + TALB, TPE2, TSOT, TDEN, TIPL, ParseID3v1, Encoding, ID3Tags, RVAD, \ + ID3NoHeaderError from mutagen.id3._util import BitPaddedInt, error as ID3Error from mutagen.id3._tags import determine_bpi, ID3Header, \ save_frame, ID3SaveConfig @@ -371,6 +372,22 @@ for key, value in id3.items(): self.assertEqual(key, value.HashKey) + def test_text_duplicate_frame_different_encoding(self): + id3 = ID3Tags() + frame = TPE2(encoding=Encoding.LATIN1, text=[u"foo"]) + id3._add(frame, False) + assert id3.getall("TPE2")[0].encoding == Encoding.LATIN1 + frame = TPE2(encoding=Encoding.LATIN1, text=[u"bar"]) + id3._add(frame, False) + assert id3.getall("TPE2")[0].encoding == Encoding.LATIN1 + frame = TPE2(encoding=Encoding.UTF8, text=[u"baz\u0400"]) + id3._add(frame, False) + assert id3.getall("TPE2")[0].encoding == Encoding.UTF8 + + frames = id3.getall("TPE2") + assert len(frames) == 1 + assert len(frames[0].text) == 3 + def test_add_CRM(self): id3 = ID3Tags() self.assertRaises(TypeError, id3.add, CRM()) @@ -1052,6 +1069,22 @@ self.failUnlessEqual(tag["TDRC"], "1234") +class TID3Trailing(TestCase): + + def test_audacious_trailing_id3(self): + # https://github.com/quodlibet/mutagen/issues/78 + # tagged with audacious 3.2.4, both are id3v2 at the end despite the + # spec saying it should be before other tags. + # Audacious changed it to write in the beginning with 3.4 or 3.5 + # Now with Audacious 3.7, re-saving the files results in the id3v3 + # tag moved to the front and the id3v1/apev2 tags left as is at the + # end. + path = os.path.join(DATA_DIR, 'audacious-trailing-id32-id31.mp3') + self.assertRaises(ID3NoHeaderError, ID3, path) + path = os.path.join(DATA_DIR, 'audacious-trailing-id32-apev2.mp3') + self.assertRaises(ID3NoHeaderError, ID3, path) + + class TID3Misc(TestCase): def test_main(self): diff -Nru mutagen-1.36/tests/test__id3specs.py mutagen-1.38/tests/test__id3specs.py --- mutagen-1.36/tests/test__id3specs.py 2016-11-20 15:44:51.000000000 +0000 +++ mutagen-1.38/tests/test__id3specs.py 2017-05-25 13:49:20.000000000 +0000 @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- -import sys - from tests import TestCase from mutagen._compat import PY3 @@ -27,12 +25,7 @@ self.assertEqual( s.read(None, f, s.write(None, f, values)), (values, b"")) data = s.write(None, f, [(u"A", 100)]) - if sys.byteorder == 'little': - self.assertEquals( - data, b"\xff\xfeA\x00\x00\x00\x00\x00\x00d") - else: - self.assertEquals( - data, b"\xfe\xff\x00A\x00\x00\x00\x00\x00d") + self.assertEquals(data, b"\xff\xfeA\x00\x00\x00\x00\x00\x00d") # utf-16be f.encoding = 2 diff -Nru mutagen-1.36/tests/test___init__.py mutagen-1.38/tests/test___init__.py --- mutagen-1.36/tests/test___init__.py 2016-11-09 13:50:54.000000000 +0000 +++ mutagen-1.38/tests/test___init__.py 2017-05-25 13:49:20.000000000 +0000 @@ -29,6 +29,7 @@ from mutagen.aiff import AIFF from mutagen.aac import AAC from mutagen.smf import SMF +from mutagen.dsf import DSF from os import devnull @@ -312,6 +313,8 @@ with open(self.filename, "rb") as h: fileobj = cBytesIO(h.read()) self.KIND(fileobj) + # make sure it's not closed + fileobj.read(0) def test_testfileobj(self): with open(self.filename, "rb") as h: @@ -488,6 +491,12 @@ SMF: [ os.path.join(DATA_DIR, "sample.mid"), ], + DSF: [ + os.path.join(DATA_DIR, '2822400-1ch-0s-silence.dsf'), + os.path.join(DATA_DIR, '5644800-2ch-s01-silence.dsf'), + os.path.join(DATA_DIR, 'with-id3.dsf'), + os.path.join(DATA_DIR, 'without-id3.dsf'), + ] } _FILETYPES[ID3FileType] = _FILETYPES[MP3] diff -Nru mutagen-1.36/tests/test_mp3.py mutagen-1.38/tests/test_mp3.py --- mutagen-1.36/tests/test_mp3.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/tests/test_mp3.py 2017-05-25 13:49:20.000000000 +0000 @@ -42,6 +42,7 @@ silence_mpeg25 = os.path.join(DATA_DIR, 'silence-44-s-mpeg25.mp3') lame = os.path.join(DATA_DIR, 'lame.mp3') lame_peak = os.path.join(DATA_DIR, 'lame-peak.mp3') + lame_broken_short = os.path.join(DATA_DIR, 'lame397v9short.mp3') def setUp(self): self.filename = get_temp_copy( @@ -54,6 +55,16 @@ self.mp3_lame = MP3(self.lame) self.mp3_lame_peak = MP3(self.lame_peak) + def test_lame_broken_short(self): + # lame <=3.97 wrote broken files + f = MP3(self.lame_broken_short) + assert f.info.encoder_info == "LAME 3.97.0" + assert f.info.encoder_settings == "-V 9" + assert f.info.length == 0.0 + assert f.info.bitrate == 64000 + assert f.info.bitrate_mode == 2 + assert f.info.sample_rate == 24000 + def test_mode(self): from mutagen.mp3 import JOINTSTEREO self.failUnlessEqual(self.mp3.info.mode, JOINTSTEREO) @@ -233,6 +244,18 @@ fileobj = cBytesIO(b"") self.failUnlessRaises(MP3Error, MPEGInfo, fileobj) + def test_xing_unknown_framecount(self): + frame = ( + b'\xff\xfb\xe4\x0c\x00\x0f\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00Info\x00\x00\x00\x02\x00\xb4V@\x00\xb4R\x80\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + ) + fileobj = cBytesIO(frame) + info = MPEGInfo(fileobj) + assert info.bitrate == 320000 + assert info.length > 0 + class TEasyMP3(TestCase): @@ -330,7 +353,7 @@ def parse(data): data = cBytesIO(data + b"\x00" * (20 - len(data))) - return LAMEHeader.parse_version(data) + return tuple(LAMEHeader.parse_version(data)[1:]) self.assertEqual(parse(b"LAME3.80"), (u"3.80", False)) self.assertEqual(parse(b"LAME3.80 "), (u"3.80", False)) @@ -345,6 +368,7 @@ self.assertEqual(parse(b"L3.99r"), (u"3.99.1+", True)) self.assertEqual(parse(b"LAME3100r"), (u"3.100.1+", True)) self.assertEqual(parse(b"LAME3.90.\x03\xbe\x00"), (u"3.90.0+", True)) + self.assertEqual(parse(b"LAME3.100"), (u"3.100.0+", True)) def test_invalid(self): @@ -354,15 +378,74 @@ self.assertRaises(LAMEError, parse, b"") self.assertRaises(LAMEError, parse, b"LAME") - self.assertRaises(LAMEError, parse, b"LAME3.999") + self.assertRaises(LAMEError, parse, b"LAME3.9999") def test_real(self): with open(os.path.join(DATA_DIR, "lame.mp3"), "rb") as h: h.seek(36, 0) xing = XingHeader(h) - self.assertEqual(xing.lame_version, u"3.99.1+") + self.assertEqual(xing.lame_version_desc, u"3.99.1+") self.assertTrue(xing.lame_header) self.assertEqual(xing.lame_header.track_gain_adjustment, 6.0) + assert xing.get_encoder_settings() == u"-V 2" + + def test_settings(self): + with open(os.path.join(DATA_DIR, "lame.mp3"), "rb") as h: + h.seek(36, 0) + xing = XingHeader(h) + header = xing.lame_header + + def s(major, minor, **kwargs): + old = vars(header) + for key, value in kwargs.items(): + assert hasattr(header, key) + setattr(header, key, value) + r = header.guess_settings(major, minor) + header.__dict__.update(old) + return r + + assert s(3, 99) == "-V 2" + assert s(3, 98) == "-V 2" + assert s(3, 97) == "-V 2 --vbr-new" + assert s(3, 96) == "-V 2 --vbr-new" + assert s(3, 95) == "-V 2 --vbr-new" + assert s(3, 94) == "-V 2 --vbr-new" + assert s(3, 93) == "-V 2 --vbr-new" + assert s(3, 92) == "-V 2 --vbr-new" + assert s(3, 91) == "-V 2 --vbr-new" + assert s(3, 90) == "-V 2 --vbr-new" + assert s(3, 89) == "" + + assert s(3, 91, vbr_method=2) == "--alt-preset 32" + assert s(3, 91, vbr_method=2, bitrate=255) == "--alt-preset 255+" + assert s(3, 99, vbr_method=2, preset_used=128) == "--preset 128" + assert s(3, 99, vbr_method=2, preset_used=0, bitrate=48) == "--abr 48" + assert \ + s(3, 94, vbr_method=2, preset_used=0, bitrate=255) == "--abr 255+" + assert s(3, 99, vbr_method=3) == "-V 2 --vbr-old" + assert s(3, 94, vbr_method=3) == "-V 2" + assert s(3, 99, vbr_method=1, preset_used=1003) == "--preset insane" + assert s(3, 93, vbr_method=3, preset_used=1001) == "--preset standard" + assert s(3, 93, vbr_method=3, preset_used=1002) == "--preset extreme" + assert s(3, 93, vbr_method=3, preset_used=1004) == \ + "--preset fast standard" + assert s(3, 93, vbr_method=3, preset_used=1005) == \ + "--preset fast extreme" + assert s(3, 93, vbr_method=3, preset_used=1006) == "--preset medium" + assert s(3, 93, vbr_method=3, preset_used=1007) == \ + "--preset fast medium" + assert s(3, 92, vbr_method=3) == "-V 2" + assert s(3, 92, vbr_method=1, preset_used=0, bitrate=254) == "-b 254" + assert s(3, 92, vbr_method=1, preset_used=0, bitrate=255) == "-b 255+" + + def skey(major, minor, args): + keys = ["vbr_quality", "quality", "vbr_method", "lowpass_filter", + "ath_type"] + return s(major, minor, **dict(zip(keys, args))) + + assert skey(3, 91, (1, 2, 4, 19500, 3)) == "--preset r3mix" + assert skey(3, 91, (2, 2, 3, 19000, 4)) == "--alt-preset standard" + assert skey(3, 91, (2, 2, 3, 19500, 2)) == "--alt-preset extreme" def test_length(self): mp3 = MP3(os.path.join(DATA_DIR, "lame.mp3")) diff -Nru mutagen-1.36/tests/test_mp4.py mutagen-1.38/tests/test_mp4.py --- mutagen-1.36/tests/test_mp4.py 2016-12-15 06:18:17.000000000 +0000 +++ mutagen-1.38/tests/test_mp4.py 2017-05-25 13:49:20.000000000 +0000 @@ -317,6 +317,16 @@ tags = self.wrap_ilst(data) self.assertFalse(tags) + def test_parse_tmpo(self): + for d, v in [(b"\x01", 1), (b"\x01\x02", 258), + (b"\x01\x02\x03", 66051), (b"\x01\x02\x03\x04", 16909060), + (b"\x01\x02\x03\x04\x05\x06\x07\x08", 72623859790382856)]: + data = Atom.render( + b"data", b"\x00\x00\x00\x15" + b"\x00\x00\x00\x00" + d) + tmpo = Atom.render(b"tmpo", data) + tags = self.wrap_ilst(tmpo) + assert tags["tmpo"][0] == v + def test_write_back_bad_atoms(self): # write a broken atom and try to load it data = Atom.render(b"datA", b"\x00\x00\x00\x01\x00\x00\x00\x00wheeee") @@ -614,12 +624,39 @@ def test_tempo(self): self.set_key('tmpo', [150]) self.set_key('tmpo', []) + self.set_key('tmpo', [0]) + self.set_key('tmpo', [cdata.int16_min]) + self.set_key('tmpo', [cdata.int32_min]) + self.set_key('tmpo', [cdata.int64_min]) + self.set_key('tmpo', [cdata.int16_max]) + self.set_key('tmpo', [cdata.int32_max]) + self.set_key('tmpo', [cdata.int64_max]) + + def test_various_int(self): + keys = [ + "stik", "rtng", "plID", "cnID", "geID", "atID", "sfID", + "cmID", "akID", "tvsn", "tves", + ] + + for key in keys: + self.set_key(key, []) + self.set_key(key, [0]) + self.set_key(key, [1]) + self.set_key(key, [cdata.int64_max]) + + def test_movements(self): + self.set_key('shwm', [1]) + self.set_key('\xa9mvc', [42]) + self.set_key('\xa9mvi', [24]) + self.set_key('\xa9mvn', [u"movement"]) + self.set_key('\xa9wrk', [u"work"]) def test_tempos(self): self.set_key('tmpo', [160, 200], faad=False) def test_tempo_invalid(self): - for badvalue in [[10000000], [-1], 10, "foo"]: + for badvalue in [ + [cdata.int64_max + 1], [cdata.int64_min - 1], 10, "foo"]: self.failUnlessRaises(ValueError, self.set_key, 'tmpo', badvalue) def test_compilation(self): diff -Nru mutagen-1.36/tests/test_ogg.py mutagen-1.38/tests/test_ogg.py --- mutagen-1.36/tests/test_ogg.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/tests/test_ogg.py 2017-06-01 12:43:24.000000000 +0000 @@ -430,6 +430,49 @@ self.failUnlessEqual( OggPage.find_last(data, pages[0].serial), pages[-1]) + def test_find_last_none_finishing(self): + page = OggPage() + page.position = -1 + data = BytesIO(page.write()) + assert OggPage.find_last(data, page.serial, finishing=True) is None + + def test_find_last_none_finishing_mux(self): + page1 = OggPage() + page1.last = True + page1.position = -1 + page2 = OggPage() + page2.serial = page1.serial + 1 + pages = [page1, page2] + data = BytesIO(b"".join([page.write() for page in pages])) + + assert OggPage.find_last(data, page1.serial, finishing=True) is None + assert OggPage.find_last(data, page2.serial, finishing=True) == page2 + + def test_find_last_last_empty(self): + # https://github.com/quodlibet/mutagen/issues/308 + pages = [OggPage() for i in xrange(10)] + for i, page in enumerate(pages): + page.sequence = i + page.position = i + pages[-1].last = True + pages[-1].position = -1 + data = BytesIO(b"".join([page.write() for page in pages])) + page = OggPage.find_last(data, pages[-1].serial, finishing=True) + assert page is not None + assert page.position == 8 + page = OggPage.find_last(data, pages[-1].serial, finishing=False) + assert page is not None + assert page.position == -1 + + def test_find_last_single_muxed(self): + page1 = OggPage() + page1.last = True + page2 = OggPage() + page2.serial = page1.serial + 1 + pages = [page1, page2] + data = BytesIO(b"".join([page.write() for page in pages])) + assert OggPage.find_last(data, page2.serial).serial == page2.serial + def test_find_last_really_last(self): pages = [OggPage() for i in xrange(10)] pages[-1].last = True diff -Nru mutagen-1.36/tests/test_tools_mid3cp.py mutagen-1.38/tests/test_tools_mid3cp.py --- mutagen-1.36/tests/test_tools_mid3cp.py 2016-12-15 05:56:14.000000000 +0000 +++ mutagen-1.38/tests/test_tools_mid3cp.py 2017-05-25 13:49:20.000000000 +0000 @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- - # Copyright 2014 Ben Ockmore - +# # This program is free software; you can redistribute it and/or modify -# it under the terms of version 2 of the GNU General Public License as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. """Tests for mid3cp tool. Since the tool is quite simple, most of the functionality is covered by the mutagen package tests - these simply test diff -Nru mutagen-1.36/tests/test__util.py mutagen-1.38/tests/test__util.py --- mutagen-1.36/tests/test__util.py 2016-09-27 09:51:31.000000000 +0000 +++ mutagen-1.38/tests/test__util.py 2017-05-25 13:49:20.000000000 +0000 @@ -3,7 +3,7 @@ from mutagen._util import DictMixin, cdata, insert_bytes, delete_bytes, \ decode_terminated, dict_match, enum, get_size, BitReader, BitReaderError, \ resize_bytes, seek_end, mmap_move, verify_fileobj, fileobj_name, \ - read_full, flags, resize_file, fallback_move + read_full, flags, resize_file, fallback_move, encode_endian from mutagen._compat import text_type, itervalues, iterkeys, iteritems, PY2, \ cBytesIO, xrange, BytesIO from tests import TestCase, get_temp_empty @@ -143,6 +143,10 @@ self.assertTrue(cdata.char is cdata.int8) self.assertTrue(cdata.to_char is cdata.to_int8) self.assertTrue(cdata.char_from is cdata.int8_from) + assert cdata.int8_max == 2 ** 7 - 1 + assert cdata.int8_min == - 2 ** 7 + assert cdata.int8_max == cdata.char_max + assert cdata.int8_min == cdata.char_min def test_char_from_to(self): self.assertEqual(cdata.to_char(-2), b"\xfe") @@ -158,6 +162,10 @@ self.assertTrue(cdata.uchar is cdata.uint8) self.assertTrue(cdata.to_uchar is cdata.to_uint8) self.assertTrue(cdata.uchar_from is cdata.uint8_from) + assert cdata.uint8_max == 2 ** 8 - 1 + assert cdata.uint8_min == 0 + assert cdata.uint8_max == cdata.uchar_max + assert cdata.uint8_min == cdata.uchar_min def test_short(self): self.failUnlessEqual(cdata.short_le(self.ZERO(2)), 0) @@ -584,6 +592,28 @@ mmap.mmap = self._orig_mmap +class FileHandlingNoMMap(FileHandling): + """Disables mmap and makes sure it raises if it still gets used somehow""" + + def setUp(self): + from mutagen import _util + + def MockMMap2(*args, **kwargs): + assert False + + self._orig_mmap = mmap.mmap + mmap.mmap = MockMMap2 + + _util.mmap = None + + def tearDown(self): + from mutagen import _util + import mmap + + _util.mmap = mmap + mmap.mmap = self._orig_mmap + + class Tdict_match(TestCase): def test_match(self): @@ -720,6 +750,33 @@ self.assertEqual(f.tell(), 1) +class Tencode_endian(TestCase): + + def test_other(self): + assert encode_endian(u"\xe4", "latin-1") == b"\xe4" + assert encode_endian(u"\xe4", "utf-8") == b"\xc3\xa4" + with self.assertRaises(LookupError): + encode_endian(u"", "nopenope") + with self.assertRaises(UnicodeEncodeError): + assert encode_endian(u"\u2714", "latin-1") + assert encode_endian(u"\u2714", "latin-1", "replace") == b"?" + + def test_utf_16(self): + assert encode_endian(u"\xe4", "utf-16", le=True) == b"\xff\xfe\xe4\x00" + assert encode_endian(u"\xe4", "utf-16-le") == b"\xe4\x00" + assert encode_endian( + u"\xe4", "utf-16", le=False) == b"\xfe\xff\x00\xe4" + assert encode_endian(u"\xe4", "utf-16-be") == b"\x00\xe4" + + def test_utf_32(self): + assert encode_endian(u"\xe4", "utf-32", le=True) == \ + b"\xff\xfe\x00\x00\xe4\x00\x00\x00" + assert encode_endian(u"\xe4", "utf-32-le") == b"\xe4\x00\x00\x00" + assert encode_endian( + u"\xe4", "utf-32", le=False) == b"\x00\x00\xfe\xff\x00\x00\x00\xe4" + assert encode_endian(u"\xe4", "utf-32-be") == b"\x00\x00\x00\xe4" + + class Tdecode_terminated(TestCase): def test_all(self): diff -Nru mutagen-1.36/tools/mid3cp mutagen-1.38/tools/mid3cp --- mutagen-1.36/tools/mid3cp 2016-11-09 14:00:53.000000000 +0000 +++ mutagen-1.38/tools/mid3cp 2017-05-25 13:49:20.000000000 +0000 @@ -3,8 +3,9 @@ # Copyright 2016 Christoph Reiter # # This program is free software; you can redistribute it and/or modify -# it under the terms of version 2 of the GNU General Public License as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. import sys diff -Nru mutagen-1.36/tools/mid3iconv mutagen-1.38/tools/mid3iconv --- mutagen-1.36/tools/mid3iconv 2016-11-09 14:00:53.000000000 +0000 +++ mutagen-1.38/tools/mid3iconv 2017-05-25 13:49:20.000000000 +0000 @@ -3,8 +3,9 @@ # Copyright 2016 Christoph Reiter # # This program is free software; you can redistribute it and/or modify -# it under the terms of version 2 of the GNU General Public License as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. import sys diff -Nru mutagen-1.36/tools/mid3v2 mutagen-1.38/tools/mid3v2 --- mutagen-1.36/tools/mid3v2 2016-11-09 14:00:53.000000000 +0000 +++ mutagen-1.38/tools/mid3v2 2017-05-25 13:49:20.000000000 +0000 @@ -3,8 +3,9 @@ # Copyright 2016 Christoph Reiter # # This program is free software; you can redistribute it and/or modify -# it under the terms of version 2 of the GNU General Public License as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. import sys diff -Nru mutagen-1.36/tools/moggsplit mutagen-1.38/tools/moggsplit --- mutagen-1.36/tools/moggsplit 2016-11-09 14:00:53.000000000 +0000 +++ mutagen-1.38/tools/moggsplit 2017-05-25 13:49:20.000000000 +0000 @@ -3,8 +3,9 @@ # Copyright 2016 Christoph Reiter # # This program is free software; you can redistribute it and/or modify -# it under the terms of version 2 of the GNU General Public License as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. import sys diff -Nru mutagen-1.36/tools/mutagen-inspect mutagen-1.38/tools/mutagen-inspect --- mutagen-1.36/tools/mutagen-inspect 2016-11-09 14:00:53.000000000 +0000 +++ mutagen-1.38/tools/mutagen-inspect 2017-05-25 13:49:20.000000000 +0000 @@ -3,8 +3,9 @@ # Copyright 2016 Christoph Reiter # # This program is free software; you can redistribute it and/or modify -# it under the terms of version 2 of the GNU General Public License as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. import sys diff -Nru mutagen-1.36/tools/mutagen-pony mutagen-1.38/tools/mutagen-pony --- mutagen-1.36/tools/mutagen-pony 2016-11-09 14:00:53.000000000 +0000 +++ mutagen-1.38/tools/mutagen-pony 2017-05-25 13:49:20.000000000 +0000 @@ -3,8 +3,9 @@ # Copyright 2016 Christoph Reiter # # This program is free software; you can redistribute it and/or modify -# it under the terms of version 2 of the GNU General Public License as -# published by the Free Software Foundation. +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. import sys