diff --git a/lib/mutagen/__init__.py b/lib/mutagen/__init__.py
index 6e2a48b0..5205a98f 100644
--- a/lib/mutagen/__init__.py
+++ b/lib/mutagen/__init__.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2005 Michael Urman
#
# This program is free software; you can redistribute it and/or modify
@@ -23,7 +22,7 @@ from mutagen._util import MutagenError
from mutagen._file import FileType, StreamInfo, File
from mutagen._tags import Tags, Metadata, PaddingInfo
-version = (1, 45, 1)
+version = (1, 47, 1)
"""Version tuple."""
version_string = ".".join(map(str, version))
diff --git a/lib/mutagen/_constants.py b/lib/mutagen/_constants.py
index 5c1c1a10..77282315 100644
--- a/lib/mutagen/_constants.py
+++ b/lib/mutagen/_constants.py
@@ -1,4 +1,3 @@
-# -*- 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
diff --git a/lib/mutagen/_file.py b/lib/mutagen/_file.py
index 1c367ffc..edf9e0cc 100644
--- a/lib/mutagen/_file.py
+++ b/lib/mutagen/_file.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2005 Michael Urman
#
# This program is free software; you can redistribute it and/or modify
@@ -7,6 +6,7 @@
# (at your option) any later version.
import warnings
+from typing import List
from mutagen._util import DictMixin, loadfile
@@ -82,9 +82,9 @@ class FileType(DictMixin):
if self.tags is None:
raise KeyError(key)
else:
- del(self.tags[key])
+ del self.tags[key]
- def keys(self):
+ def keys(self) -> list:
"""Return a list of keys in the metadata tag.
If the file has no tags at all, an empty list is returned.
@@ -131,12 +131,13 @@ class FileType(DictMixin):
if self.tags is not None:
return self.tags.save(filething, **kwargs)
- def pprint(self):
+ def pprint(self) -> str:
"""
Returns:
text: stream information and comment key=value pairs.
"""
+ assert self.info is not None
stream = "%s (%s)" % (self.info.pprint(), self.mime[0])
try:
tags = self.tags.pprint()
@@ -145,7 +146,7 @@ class FileType(DictMixin):
else:
return stream + ((tags and "\n" + tags) or "")
- def add_tags(self):
+ def add_tags(self) -> None:
"""Adds new tags to the file.
Raises:
@@ -156,7 +157,7 @@ class FileType(DictMixin):
raise NotImplementedError
@property
- def mime(self):
+ def mime(self) -> List[str]:
"""A list of mime types (:class:`mutagen.text`)"""
mimes = []
@@ -167,7 +168,7 @@ class FileType(DictMixin):
return mimes
@staticmethod
- def score(filename, fileobj, header):
+ def score(filename, fileobj, header) -> int:
"""Returns a score for how likely the file can be parsed by this type.
Args:
@@ -195,7 +196,7 @@ class StreamInfo(object):
__module__ = "mutagen"
- def pprint(self):
+ def pprint(self) -> str:
"""
Returns:
text: Print stream information
diff --git a/lib/mutagen/_iff.py b/lib/mutagen/_iff.py
index 4aae2466..cdc6e3d8 100644
--- a/lib/mutagen/_iff.py
+++ b/lib/mutagen/_iff.py
@@ -1,8 +1,7 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2014 Evan Purkhiser
# 2014 Ben Ockmore
# 2017 Borewit
-# 2019-2020 Philipp Wolfer
+# 2019-2021 Philipp Wolfer
#
# 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
@@ -38,7 +37,7 @@ class EmptyChunk(InvalidChunk):
pass
-def is_valid_chunk_id(id):
+def is_valid_chunk_id(id: str) -> bool:
""" is_valid_chunk_id(FOURCC)
Arguments:
@@ -57,7 +56,7 @@ def is_valid_chunk_id(id):
# Assert FOURCC formatted valid
-def assert_valid_chunk_id(id):
+def assert_valid_chunk_id(id: str) -> None:
if not is_valid_chunk_id(id):
raise ValueError("IFF chunk ID must be four ASCII characters.")
@@ -127,13 +126,13 @@ class IffChunk(object):
% (type(self).__name__, self.id, self.offset, self.size,
self.data_offset, self.data_size))
- def read(self):
+ def read(self) -> bytes:
"""Read the chunks data"""
self._fileobj.seek(self.data_offset)
return self._fileobj.read(self.data_size)
- def write(self, data):
+ def write(self, data: bytes) -> None:
"""Write the chunk data"""
if len(data) > self.data_size:
@@ -147,7 +146,7 @@ class IffChunk(object):
self._fileobj.seek(self.data_offset + self.data_size)
self._fileobj.write(b'\x00' * padding)
- def delete(self):
+ def delete(self) -> None:
"""Removes the chunk from the file"""
delete_bytes(self._fileobj, self.size, self.offset)
@@ -173,23 +172,36 @@ class IffChunk(object):
self.size = self.HEADER_SIZE + self.data_size + self.padding()
assert self.size % 2 == 0
- def resize(self, new_data_size):
+ def resize(self, new_data_size: int) -> None:
"""Resize the file and update the chunk sizes"""
+ old_size = self._get_actual_data_size()
padding = new_data_size % 2
- resize_bytes(self._fileobj, self.data_size + self.padding(),
+ resize_bytes(self._fileobj, old_size,
new_data_size + padding, self.data_offset)
size_diff = new_data_size - self.data_size
self._update_size(size_diff)
self._fileobj.flush()
- def padding(self):
+ def padding(self) -> int:
"""Returns the number of padding bytes (0 or 1).
IFF chunks are required to be a even number in total length. If
data_size is odd a padding byte will be added at the end.
"""
return self.data_size % 2
+ def _get_actual_data_size(self) -> int:
+ """Returns the data size that is actually possible.
+ Some files have chunks that are truncated and their reported size
+ would be outside of the file's actual size."""
+ fileobj = self._fileobj
+ fileobj.seek(0, 2)
+ file_size = fileobj.tell()
+
+ expected_size = self.data_size + self.padding()
+ max_size_possible = file_size - self.data_offset
+ return min(expected_size, max_size_possible)
+
class IffContainerChunkMixin():
"""A IFF chunk containing other chunks.
@@ -250,7 +262,7 @@ class IffContainerChunkMixin():
if not is_valid_chunk_id(id_):
raise KeyError("Invalid IFF key.")
- next_offset = self.offset + self.size
+ next_offset = self.data_offset + self._get_actual_data_size()
size = self.HEADER_SIZE
data_size = 0
if data:
diff --git a/lib/mutagen/_riff.py b/lib/mutagen/_riff.py
index 3afe0623..f3f23f6a 100644
--- a/lib/mutagen/_riff.py
+++ b/lib/mutagen/_riff.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2017 Borewit
# Copyright (C) 2019-2020 Philipp Wolfer
#
diff --git a/lib/mutagen/_senf/README.rst b/lib/mutagen/_senf/README.rst
deleted file mode 100644
index 49ff9281..00000000
--- a/lib/mutagen/_senf/README.rst
+++ /dev/null
@@ -1 +0,0 @@
-Don't change things here, this is a copy of https://github.com/lazka/senf
diff --git a/lib/mutagen/_senf/__init__.py b/lib/mutagen/_senf/__init__.py
deleted file mode 100644
index 4c1abe01..00000000
--- a/lib/mutagen/_senf/__init__.py
+++ /dev/null
@@ -1,85 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright 2016 Christoph Reiter
-#
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and associated documentation files (the
-# "Software"), to deal in the Software without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject to
-# the following conditions:
-#
-# The above copyright notice and this permission notice shall be included
-# in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-from ._fsnative import fsnative, path2fsn, fsn2text, fsn2bytes, \
- bytes2fsn, uri2fsn, fsn2uri, text2fsn, fsn2norm
-from ._print import print_, input_, supports_ansi_escape_codes
-from ._stdlib import sep, pathsep, curdir, pardir, altsep, extsep, devnull, \
- defpath, getcwd, expanduser, expandvars
-from ._argv import argv
-from ._environ import environ, getenv, unsetenv, putenv
-from ._temp import mkstemp, gettempdir, gettempprefix, mkdtemp
-
-
-fsnative, print_, getcwd, getenv, unsetenv, putenv, environ, expandvars, \
- path2fsn, fsn2text, fsn2bytes, bytes2fsn, uri2fsn, fsn2uri, mkstemp, \
- gettempdir, gettempprefix, mkdtemp, input_, expanduser, text2fsn, \
- supports_ansi_escape_codes, fsn2norm
-
-
-version = (1, 4, 2)
-"""Tuple[`int`, `int`, `int`]: The version tuple (major, minor, micro)"""
-
-
-version_string = ".".join(map(str, version))
-"""`str`: A version string"""
-
-
-argv = argv
-"""List[`fsnative`]: Like `sys.argv` but contains unicode under
-Windows + Python 2
-"""
-
-
-sep = sep
-"""`fsnative`: Like `os.sep` but a `fsnative`"""
-
-
-pathsep = pathsep
-"""`fsnative`: Like `os.pathsep` but a `fsnative`"""
-
-
-curdir = curdir
-"""`fsnative`: Like `os.curdir` but a `fsnative`"""
-
-
-pardir = pardir
-"""`fsnative`: Like `os.pardir` but a fsnative"""
-
-
-altsep = altsep
-"""`fsnative` or `None`: Like `os.altsep` but a `fsnative` or `None`"""
-
-
-extsep = extsep
-"""`fsnative`: Like `os.extsep` but a `fsnative`"""
-
-
-devnull = devnull
-"""`fsnative`: Like `os.devnull` but a `fsnative`"""
-
-
-defpath = defpath
-"""`fsnative`: Like `os.defpath` but a `fsnative`"""
-
-
-__all__ = []
diff --git a/lib/mutagen/_senf/__init__.pyi b/lib/mutagen/_senf/__init__.pyi
deleted file mode 100644
index 649a8755..00000000
--- a/lib/mutagen/_senf/__init__.pyi
+++ /dev/null
@@ -1,104 +0,0 @@
-import sys
-import os
-
-from typing import Text, Union, Any, Optional, Tuple, List, Dict
-
-if sys.version_info[0] == 2:
- _pathlike = Union[Text, bytes]
-else:
- _pathlike = Union[Text, bytes, 'os.PathLike[Any]']
-_uri = Union[Text, str]
-
-if sys.version_info[0] == 2:
- if sys.platform == "win32":
- _base = Text
- else:
- _base = bytes
-else:
- _base = Text
-
-class fsnative(_base):
- def __init__(self, object: Text=u"") -> None:
- ...
-
-_fsnative = Union[fsnative, _base]
-
-if sys.platform == "win32":
- _bytes_default_encoding = str
-else:
- _bytes_default_encoding = Optional[str]
-
-def path2fsn(path: _pathlike) -> _fsnative:
- ...
-
-def fsn2text(path: _fsnative, strict: bool=False) -> Text:
- ...
-
-def text2fsn(text: Text) -> _fsnative:
- ...
-
-def fsn2bytes(path: _fsnative, encoding: _bytes_default_encoding="utf-8") -> bytes:
- ...
-
-def bytes2fsn(data: bytes, encoding: _bytes_default_encoding="utf-8") -> _fsnative:
- ...
-
-def uri2fsn(uri: _uri) -> _fsnative:
- ...
-
-def fsn2uri(path: _fsnative) -> Text:
- ...
-
-def fsn2norm(path: _fsnative) -> _fsnative:
- ...
-
-sep: _fsnative
-pathsep: _fsnative
-curdir: _fsnative
-pardir: _fsnative
-altsep: _fsnative
-extsep: _fsnative
-devnull: _fsnative
-defpath: _fsnative
-
-def getcwd() -> _fsnative:
- ...
-
-def getenv(key: _pathlike, value: Optional[_fsnative]=None) -> Optional[_fsnative]:
- ...
-
-def putenv(key: _pathlike, value: _pathlike):
- ...
-
-def unsetenv(key: _pathlike) -> None:
- ...
-
-def supports_ansi_escape_codes(fd: int) -> bool:
- ...
-
-def expandvars(path: _pathlike) -> _fsnative:
- ...
-
-def expanduser(path: _pathlike) -> _fsnative:
- ...
-
-environ: Dict[_fsnative,_fsnative]
-argv: List[_fsnative]
-
-def gettempdir() -> _fsnative:
- pass
-
-def mkstemp(suffix: Optional[_pathlike]=None, prefix: Optional[_pathlike]=None, dir: Optional[_pathlike]=None, text: bool=False) -> Tuple[int, _fsnative]:
- ...
-
-def mkdtemp(suffix: Optional[_pathlike]=None, prefix: Optional[_pathlike]=None, dir: Optional[_pathlike]=None) -> _fsnative:
- ...
-
-version_string: str
-
-version: Tuple[int, int, int]
-
-print_ = print
-
-def input_(prompt: Any=None) -> _fsnative:
- ...
diff --git a/lib/mutagen/_senf/_argv.py b/lib/mutagen/_senf/_argv.py
deleted file mode 100644
index ff5f9abd..00000000
--- a/lib/mutagen/_senf/_argv.py
+++ /dev/null
@@ -1,120 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright 2016 Christoph Reiter
-#
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and associated documentation files (the
-# "Software"), to deal in the Software without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject to
-# the following conditions:
-#
-# The above copyright notice and this permission notice shall be included
-# in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-import sys
-import ctypes
-try:
- from collections import abc
-except ImportError:
- import collections as abc # type: ignore
-from functools import total_ordering
-
-from ._compat import PY2, string_types
-from ._fsnative import is_win, _fsn2legacy, path2fsn
-from . import _winapi as winapi
-
-
-def _get_win_argv():
- """Returns a unicode argv under Windows and standard sys.argv otherwise
-
- Returns:
- List[`fsnative`]
- """
-
- assert is_win
-
- argc = ctypes.c_int()
- try:
- argv = winapi.CommandLineToArgvW(
- winapi.GetCommandLineW(), ctypes.byref(argc))
- except WindowsError:
- return []
-
- if not argv:
- return []
-
- res = argv[max(0, argc.value - len(sys.argv)):argc.value]
-
- winapi.LocalFree(argv)
-
- return res
-
-
-@total_ordering
-class Argv(abc.MutableSequence):
- """List[`fsnative`]: Like `sys.argv` but contains unicode
- keys and values under Windows + Python 2.
-
- Any changes made will be forwarded to `sys.argv`.
- """
-
- def __init__(self):
- if PY2 and is_win:
- self._argv = _get_win_argv()
- else:
- self._argv = sys.argv
-
- def __getitem__(self, index):
- return self._argv[index]
-
- def __setitem__(self, index, value):
- if isinstance(value, string_types):
- value = path2fsn(value)
-
- self._argv[index] = value
-
- if sys.argv is not self._argv:
- try:
- if isinstance(value, string_types):
- sys.argv[index] = _fsn2legacy(value)
- else:
- sys.argv[index] = [_fsn2legacy(path2fsn(v)) for v in value]
- except IndexError:
- pass
-
- def __delitem__(self, index):
- del self._argv[index]
- try:
- del sys.argv[index]
- except IndexError:
- pass
-
- def __eq__(self, other):
- return self._argv == other
-
- def __lt__(self, other):
- return self._argv < other
-
- def __len__(self):
- return len(self._argv)
-
- def __repr__(self):
- return repr(self._argv)
-
- def insert(self, index, value):
- value = path2fsn(value)
- self._argv.insert(index, value)
- if sys.argv is not self._argv:
- sys.argv.insert(index, _fsn2legacy(value))
-
-
-argv = Argv()
diff --git a/lib/mutagen/_senf/_compat.py b/lib/mutagen/_senf/_compat.py
deleted file mode 100644
index bf1cb304..00000000
--- a/lib/mutagen/_senf/_compat.py
+++ /dev/null
@@ -1,58 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright 2016 Christoph Reiter
-#
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and associated documentation files (the
-# "Software"), to deal in the Software without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject to
-# the following conditions:
-#
-# The above copyright notice and this permission notice shall be included
-# in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-import sys
-
-
-PY2 = sys.version_info[0] == 2
-PY3 = not PY2
-
-
-if PY2:
- from urlparse import urlparse, urlunparse
- urlparse, urlunparse
- from urllib import quote, unquote
- quote, unquote
-
- from StringIO import StringIO
- BytesIO = StringIO
- from io import StringIO as TextIO
- TextIO
-
- string_types = (str, unicode)
- text_type = unicode
-
- iteritems = lambda d: d.iteritems()
-elif PY3:
- from urllib.parse import urlparse, quote, unquote, urlunparse
- urlparse, quote, unquote, urlunparse
-
- from io import StringIO
- StringIO = StringIO
- TextIO = StringIO
- from io import BytesIO
- BytesIO = BytesIO
-
- string_types = (str,)
- text_type = str
-
- iteritems = lambda d: iter(d.items())
diff --git a/lib/mutagen/_senf/_environ.py b/lib/mutagen/_senf/_environ.py
deleted file mode 100644
index 82536935..00000000
--- a/lib/mutagen/_senf/_environ.py
+++ /dev/null
@@ -1,270 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright 2016 Christoph Reiter
-#
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and associated documentation files (the
-# "Software"), to deal in the Software without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject to
-# the following conditions:
-#
-# The above copyright notice and this permission notice shall be included
-# in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-import os
-import ctypes
-try:
- from collections import abc
-except ImportError:
- import collections as abc # type: ignore
-
-from ._compat import text_type, PY2
-from ._fsnative import path2fsn, is_win, _fsn2legacy, fsnative
-from . import _winapi as winapi
-
-
-def get_windows_env_var(key):
- """Get an env var.
-
- Raises:
- WindowsError
- """
-
- if not isinstance(key, text_type):
- raise TypeError("%r not of type %r" % (key, text_type))
-
- buf = ctypes.create_unicode_buffer(32767)
-
- stored = winapi.GetEnvironmentVariableW(key, buf, 32767)
- if stored == 0:
- raise ctypes.WinError()
- return buf[:stored]
-
-
-def set_windows_env_var(key, value):
- """Set an env var.
-
- Raises:
- WindowsError
- """
-
- if not isinstance(key, text_type):
- raise TypeError("%r not of type %r" % (key, text_type))
-
- if not isinstance(value, text_type):
- raise TypeError("%r not of type %r" % (value, text_type))
-
- status = winapi.SetEnvironmentVariableW(key, value)
- if status == 0:
- raise ctypes.WinError()
-
-
-def del_windows_env_var(key):
- """Delete an env var.
-
- Raises:
- WindowsError
- """
-
- if not isinstance(key, text_type):
- raise TypeError("%r not of type %r" % (key, text_type))
-
- status = winapi.SetEnvironmentVariableW(key, None)
- if status == 0:
- raise ctypes.WinError()
-
-
-def read_windows_environ():
- """Returns a unicode dict of the Windows environment.
-
- Raises:
- WindowsEnvironError
- """
-
- res = winapi.GetEnvironmentStringsW()
- if not res:
- raise ctypes.WinError()
-
- res = ctypes.cast(res, ctypes.POINTER(ctypes.c_wchar))
-
- done = []
- current = u""
- i = 0
- while 1:
- c = res[i]
- i += 1
- if c == u"\x00":
- if not current:
- break
- done.append(current)
- current = u""
- continue
- current += c
-
- dict_ = {}
- for entry in done:
- try:
- key, value = entry.split(u"=", 1)
- except ValueError:
- continue
- key = _norm_key(key)
- dict_[key] = value
-
- status = winapi.FreeEnvironmentStringsW(res)
- if status == 0:
- raise ctypes.WinError()
-
- return dict_
-
-
-def _norm_key(key):
- assert isinstance(key, fsnative)
- if is_win:
- key = key.upper()
- return key
-
-
-class Environ(abc.MutableMapping):
- """Dict[`fsnative`, `fsnative`]: Like `os.environ` but contains unicode
- keys and values under Windows + Python 2.
-
- Any changes made will be forwarded to `os.environ`.
- """
-
- def __init__(self):
- if is_win and PY2:
- try:
- env = read_windows_environ()
- except WindowsError:
- env = {}
- else:
- env = os.environ
- self._env = env
-
- def __getitem__(self, key):
- key = _norm_key(path2fsn(key))
- return self._env[key]
-
- def __setitem__(self, key, value):
- key = _norm_key(path2fsn(key))
- value = path2fsn(value)
-
- if is_win and PY2:
- # this calls putenv, so do it first and replace later
- try:
- os.environ[_fsn2legacy(key)] = _fsn2legacy(value)
- except OSError:
- raise ValueError
-
- try:
- set_windows_env_var(key, value)
- except WindowsError:
- # py3+win fails for invalid keys. try to do the same
- raise ValueError
- try:
- self._env[key] = value
- except OSError:
- raise ValueError
-
- def __delitem__(self, key):
- key = _norm_key(path2fsn(key))
-
- if is_win and PY2:
- try:
- del_windows_env_var(key)
- except WindowsError:
- pass
-
- try:
- del os.environ[_fsn2legacy(key)]
- except KeyError:
- pass
-
- del self._env[key]
-
- def __iter__(self):
- return iter(self._env)
-
- def __len__(self):
- return len(self._env)
-
- def __repr__(self):
- return repr(self._env)
-
- def copy(self):
- return self._env.copy()
-
-
-environ = Environ()
-
-
-def getenv(key, value=None):
- """Like `os.getenv` but returns unicode under Windows + Python 2
-
- Args:
- key (pathlike): The env var to get
- value (object): The value to return if the env var does not exist
- Returns:
- `fsnative` or `object`:
- The env var or the passed value if it doesn't exist
- """
-
- key = path2fsn(key)
- if is_win and PY2:
- return environ.get(key, value)
- return os.getenv(key, value)
-
-
-def unsetenv(key):
- """Like `os.unsetenv` but takes unicode under Windows + Python 2
-
- Args:
- key (pathlike): The env var to unset
- """
-
- key = path2fsn(key)
- if is_win:
- # python 3 has no unsetenv under Windows -> use our ctypes one as well
- try:
- del_windows_env_var(key)
- except WindowsError:
- pass
- else:
- os.unsetenv(key)
-
-
-def putenv(key, value):
- """Like `os.putenv` but takes unicode under Windows + Python 2
-
- Args:
- key (pathlike): The env var to get
- value (pathlike): The value to set
- Raises:
- ValueError
- """
-
- key = path2fsn(key)
- value = path2fsn(value)
-
- if is_win and PY2:
- try:
- set_windows_env_var(key, value)
- except WindowsError:
- # py3 + win fails here
- raise ValueError
- else:
- try:
- os.putenv(key, value)
- except OSError:
- # win + py3 raise here for invalid keys which is probably a bug.
- # ValueError seems better
- raise ValueError
diff --git a/lib/mutagen/_senf/_fsnative.py b/lib/mutagen/_senf/_fsnative.py
deleted file mode 100644
index 7cc4ddc1..00000000
--- a/lib/mutagen/_senf/_fsnative.py
+++ /dev/null
@@ -1,625 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright 2016 Christoph Reiter
-#
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and associated documentation files (the
-# "Software"), to deal in the Software without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject to
-# the following conditions:
-#
-# The above copyright notice and this permission notice shall be included
-# in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-import os
-import sys
-import ctypes
-import codecs
-
-from . import _winapi as winapi
-from ._compat import text_type, PY3, PY2, urlparse, quote, unquote, urlunparse
-
-
-is_win = os.name == "nt"
-is_unix = not is_win
-is_darwin = sys.platform == "darwin"
-
-_surrogatepass = "strict" if PY2 else "surrogatepass"
-
-
-def _normalize_codec(codec, _cache={}):
- """Raises LookupError"""
-
- try:
- return _cache[codec]
- except KeyError:
- _cache[codec] = codecs.lookup(codec).name
- return _cache[codec]
-
-
-def _swap_bytes(data):
- """swaps bytes for 16 bit, leaves remaining trailing bytes alone"""
-
- a, b = data[1::2], data[::2]
- data = bytearray().join(bytearray(x) for x in zip(a, b))
- if len(b) > len(a):
- data += b[-1:]
- return bytes(data)
-
-
-def _decode_surrogatepass(data, codec):
- """Like data.decode(codec, 'surrogatepass') but makes utf-16-le/be work
- on Python 2.
-
- https://bugs.python.org/issue27971
-
- Raises UnicodeDecodeError, LookupError
- """
-
- try:
- return data.decode(codec, _surrogatepass)
- except UnicodeDecodeError:
- if PY2:
- if _normalize_codec(codec) == "utf-16-be":
- data = _swap_bytes(data)
- codec = "utf-16-le"
- if _normalize_codec(codec) == "utf-16-le":
- buffer_ = ctypes.create_string_buffer(data + b"\x00\x00")
- value = ctypes.wstring_at(buffer_, len(data) // 2)
- if value.encode("utf-16-le", _surrogatepass) != data:
- raise
- return value
- else:
- raise
- else:
- raise
-
-
-def _merge_surrogates(text):
- """Returns a copy of the text with all surrogate pairs merged"""
-
- return _decode_surrogatepass(
- text.encode("utf-16-le", _surrogatepass),
- "utf-16-le")
-
-
-def fsn2norm(path):
- """
- Args:
- path (fsnative): The path to normalize
- Returns:
- `fsnative`
-
- Normalizes an fsnative path.
-
- The same underlying path can have multiple representations as fsnative
- (due to surrogate pairs and variable length encodings). When concatenating
- fsnative the result might be different than concatenating the serialized
- form and then deserializing it.
-
- This returns the normalized form i.e. the form which os.listdir() would
- return. This is useful when you alter fsnative but require that the same
- underlying path always maps to the same fsnative value.
-
- All functions like :func:`bytes2fsn`, :func:`fsnative`, :func:`text2fsn`
- and :func:`path2fsn` always return a normalized path, independent of their
- input.
- """
-
- native = _fsn2native(path)
-
- if is_win:
- return _merge_surrogates(native)
- elif PY3:
- return bytes2fsn(native, None)
- else:
- return path
-
-
-def _fsn2legacy(path):
- """Takes a fsnative path and returns a path that can be put into os.environ
- or sys.argv. Might result in a mangled path on Python2 + Windows.
- Can't fail.
-
- Args:
- path (fsnative)
- Returns:
- str
- """
-
- if PY2 and is_win:
- return path.encode(_encoding, "replace")
- return path
-
-
-def _fsnative(text):
- if not isinstance(text, text_type):
- raise TypeError("%r needs to be a text type (%r)" % (text, text_type))
-
- if is_unix:
- # First we go to bytes so we can be sure we have a valid source.
- # Theoretically we should fail here in case we have a non-unicode
- # encoding. But this would make everything complicated and there is
- # no good way to handle a failure from the user side. Instead
- # fall back to utf-8 which is the most likely the right choice in
- # a mis-configured environment
- encoding = _encoding
- try:
- path = text.encode(encoding, _surrogatepass)
- except UnicodeEncodeError:
- path = text.encode("utf-8", _surrogatepass)
-
- if b"\x00" in path:
- path = path.replace(b"\x00", fsn2bytes(_fsnative(u"\uFFFD"), None))
-
- if PY3:
- return path.decode(_encoding, "surrogateescape")
- return path
- else:
- if u"\x00" in text:
- text = text.replace(u"\x00", u"\uFFFD")
- text = fsn2norm(text)
- return text
-
-
-def _create_fsnative(type_):
- # a bit of magic to make fsnative(u"foo") and isinstance(path, fsnative)
- # work
-
- class meta(type):
-
- def __instancecheck__(self, instance):
- return _typecheck_fsnative(instance)
-
- def __subclasscheck__(self, subclass):
- return issubclass(subclass, type_)
-
- class impl(object):
- """fsnative(text=u"")
-
- Args:
- text (text): The text to convert to a path
- Returns:
- fsnative: The new path.
- Raises:
- TypeError: In case something other then `text` has been passed
-
- This type is a virtual base class for the real path type.
- Instantiating it returns an instance of the real path type and it
- overrides instance and subclass checks so that `isinstance` and
- `issubclass` checks work:
-
- ::
-
- isinstance(fsnative(u"foo"), fsnative) == True
- issubclass(type(fsnative(u"foo")), fsnative) == True
-
- The real returned type is:
-
- - **Python 2 + Windows:** :obj:`python:unicode`, with ``surrogates``,
- without ``null``
- - **Python 2 + Unix:** :obj:`python:str`, without ``null``
- - **Python 3 + Windows:** :obj:`python3:str`, with ``surrogates``,
- without ``null``
- - **Python 3 + Unix:** :obj:`python3:str`, with ``surrogates``, without
- ``null``, without code points not encodable with the locale encoding
-
- Constructing a `fsnative` can't fail.
-
- Passing a `fsnative` to :func:`open` will never lead to `ValueError`
- or `TypeError`.
-
- Any operation on `fsnative` can also use the `str` type, as long as
- the `str` only contains ASCII and no NULL.
- """
-
- def __new__(cls, text=u""):
- return _fsnative(text)
-
- new_type = meta("fsnative", (object,), dict(impl.__dict__))
- new_type.__module__ = "senf"
- return new_type
-
-
-fsnative_type = text_type if is_win or PY3 else bytes
-fsnative = _create_fsnative(fsnative_type)
-
-
-def _typecheck_fsnative(path):
- """
- Args:
- path (object)
- Returns:
- bool: if path is a fsnative
- """
-
- if not isinstance(path, fsnative_type):
- return False
-
- if PY3 or is_win:
- if u"\x00" in path:
- return False
-
- if is_unix:
- try:
- path.encode(_encoding, "surrogateescape")
- except UnicodeEncodeError:
- return False
- elif b"\x00" in path:
- return False
-
- return True
-
-
-def _fsn2native(path):
- """
- Args:
- path (fsnative)
- Returns:
- `text` on Windows, `bytes` on Unix
- Raises:
- TypeError: in case the type is wrong or the ´str` on Py3 + Unix
- can't be converted to `bytes`
-
- This helper allows to validate the type and content of a path.
- To reduce overhead the encoded value for Py3 + Unix is returned so
- it can be reused.
- """
-
- if not isinstance(path, fsnative_type):
- raise TypeError("path needs to be %s, not %s" % (
- fsnative_type.__name__, type(path).__name__))
-
- if is_unix:
- if PY3:
- try:
- path = path.encode(_encoding, "surrogateescape")
- except UnicodeEncodeError:
- # This look more like ValueError, but raising only one error
- # makes things simpler... also one could say str + surrogates
- # is its own type
- raise TypeError(
- "path contained Unicode code points not valid in"
- "the current path encoding. To create a valid "
- "path from Unicode use text2fsn()")
-
- if b"\x00" in path:
- raise TypeError("fsnative can't contain nulls")
- else:
- if u"\x00" in path:
- raise TypeError("fsnative can't contain nulls")
-
- return path
-
-
-def _get_encoding():
- """The encoding used for paths, argv, environ, stdout and stdin"""
-
- encoding = sys.getfilesystemencoding()
- if encoding is None:
- if is_darwin:
- encoding = "utf-8"
- elif is_win:
- encoding = "mbcs"
- else:
- encoding = "ascii"
- encoding = _normalize_codec(encoding)
- return encoding
-
-
-_encoding = _get_encoding()
-
-
-def path2fsn(path):
- """
- Args:
- path (pathlike): The path to convert
- Returns:
- `fsnative`
- Raises:
- TypeError: In case the type can't be converted to a `fsnative`
- ValueError: In case conversion fails
-
- Returns a `fsnative` path for a `pathlike`.
- """
-
- # allow mbcs str on py2+win and bytes on py3
- if PY2:
- if is_win:
- if isinstance(path, bytes):
- path = path.decode(_encoding)
- else:
- if isinstance(path, text_type):
- path = path.encode(_encoding)
- if "\x00" in path:
- raise ValueError("embedded null")
- else:
- path = getattr(os, "fspath", lambda x: x)(path)
- if isinstance(path, bytes):
- if b"\x00" in path:
- raise ValueError("embedded null")
- path = path.decode(_encoding, "surrogateescape")
- elif is_unix and isinstance(path, str):
- # make sure we can encode it and this is not just some random
- # unicode string
- data = path.encode(_encoding, "surrogateescape")
- if b"\x00" in data:
- raise ValueError("embedded null")
- path = fsn2norm(path)
- else:
- if u"\x00" in path:
- raise ValueError("embedded null")
- path = fsn2norm(path)
-
- if not isinstance(path, fsnative_type):
- raise TypeError("path needs to be %s", fsnative_type.__name__)
-
- return path
-
-
-def fsn2text(path, strict=False):
- """
- Args:
- path (fsnative): The path to convert
- strict (bool): Fail in case the conversion is not reversible
- Returns:
- `text`
- Raises:
- TypeError: In case no `fsnative` has been passed
- ValueError: In case ``strict`` was True and the conversion failed
-
- Converts a `fsnative` path to `text`.
-
- Can be used to pass a path to some unicode API, like for example a GUI
- toolkit.
-
- If ``strict`` is True the conversion will fail in case it is not
- reversible. This can be useful for converting program arguments that are
- supposed to be text and erroring out in case they are not.
-
- Encoding with a Unicode encoding will always succeed with the result.
- """
-
- path = _fsn2native(path)
-
- errors = "strict" if strict else "replace"
-
- if is_win:
- return path.encode("utf-16-le", _surrogatepass).decode("utf-16-le",
- errors)
- else:
- return path.decode(_encoding, errors)
-
-
-def text2fsn(text):
- """
- Args:
- text (text): The text to convert
- Returns:
- `fsnative`
- Raises:
- TypeError: In case no `text` has been passed
-
- Takes `text` and converts it to a `fsnative`.
-
- This operation is not reversible and can't fail.
- """
-
- return fsnative(text)
-
-
-def fsn2bytes(path, encoding="utf-8"):
- """
- Args:
- path (fsnative): The path to convert
- encoding (`str`): encoding used for Windows
- Returns:
- `bytes`
- Raises:
- TypeError: If no `fsnative` path is passed
- ValueError: If encoding fails or the encoding is invalid
-
- Converts a `fsnative` path to `bytes`.
-
- The passed *encoding* is only used on platforms where paths are not
- associated with an encoding (Windows for example).
-
- For Windows paths, lone surrogates will be encoded like normal code points
- and surrogate pairs will be merged before encoding. In case of ``utf-8``
- or ``utf-16-le`` this is equal to the `WTF-8 and WTF-16 encoding
- `__.
- """
-
- path = _fsn2native(path)
-
- if is_win:
- if encoding is None:
- raise ValueError("invalid encoding %r" % encoding)
-
- if PY2:
- try:
- return path.encode(encoding)
- except LookupError:
- raise ValueError("invalid encoding %r" % encoding)
- else:
- try:
- return path.encode(encoding)
- except LookupError:
- raise ValueError("invalid encoding %r" % encoding)
- except UnicodeEncodeError:
- # Fallback implementation for text including surrogates
- # merge surrogate codepoints
- if _normalize_codec(encoding).startswith("utf-16"):
- # fast path, utf-16 merges anyway
- return path.encode(encoding, _surrogatepass)
- return _merge_surrogates(path).encode(encoding, _surrogatepass)
- else:
- return path
-
-
-def bytes2fsn(data, encoding="utf-8"):
- """
- Args:
- data (bytes): The data to convert
- encoding (`str`): encoding used for Windows
- Returns:
- `fsnative`
- Raises:
- TypeError: If no `bytes` path is passed
- ValueError: If decoding fails or the encoding is invalid
-
- Turns `bytes` to a `fsnative` path.
-
- The passed *encoding* is only used on platforms where paths are not
- associated with an encoding (Windows for example).
-
- For Windows paths ``WTF-8`` is accepted if ``utf-8`` is used and
- ``WTF-16`` accepted if ``utf-16-le`` is used.
- """
-
- if not isinstance(data, bytes):
- raise TypeError("data needs to be bytes")
-
- if is_win:
- if encoding is None:
- raise ValueError("invalid encoding %r" % encoding)
- try:
- path = _decode_surrogatepass(data, encoding)
- except LookupError:
- raise ValueError("invalid encoding %r" % encoding)
- if u"\x00" in path:
- raise ValueError("contains nulls")
- return path
- else:
- if b"\x00" in data:
- raise ValueError("contains nulls")
- if PY2:
- return data
- else:
- return data.decode(_encoding, "surrogateescape")
-
-
-def uri2fsn(uri):
- """
- Args:
- uri (`text` or :obj:`python:str`): A file URI
- Returns:
- `fsnative`
- Raises:
- TypeError: In case an invalid type is passed
- ValueError: In case the URI isn't a valid file URI
-
- Takes a file URI and returns a `fsnative` path
- """
-
- if PY2:
- if isinstance(uri, text_type):
- uri = uri.encode("utf-8")
- if not isinstance(uri, bytes):
- raise TypeError("uri needs to be ascii str or unicode")
- else:
- if not isinstance(uri, str):
- raise TypeError("uri needs to be str")
-
- parsed = urlparse(uri)
- scheme = parsed.scheme
- netloc = parsed.netloc
- path = parsed.path
-
- if scheme != "file":
- raise ValueError("Not a file URI: %r" % uri)
-
- if not path:
- raise ValueError("Invalid file URI: %r" % uri)
-
- uri = urlunparse(parsed)[7:]
-
- if is_win:
- try:
- drive, rest = uri.split(":", 1)
- except ValueError:
- path = ""
- rest = uri.replace("/", "\\")
- else:
- path = drive[-1] + ":"
- rest = rest.replace("/", "\\")
- if PY2:
- path += unquote(rest)
- else:
- path += unquote(rest, encoding="utf-8", errors="surrogatepass")
- if netloc:
- path = "\\\\" + path
- if PY2:
- path = path.decode("utf-8")
- if u"\x00" in path:
- raise ValueError("embedded null")
- return path
- else:
- if PY2:
- path = unquote(uri)
- else:
- path = unquote(uri, encoding=_encoding, errors="surrogateescape")
- if "\x00" in path:
- raise ValueError("embedded null")
- return path
-
-
-def fsn2uri(path):
- """
- Args:
- path (fsnative): The path to convert to an URI
- Returns:
- `text`: An ASCII only URI
- Raises:
- TypeError: If no `fsnative` was passed
- ValueError: If the path can't be converted
-
- Takes a `fsnative` path and returns a file URI.
-
- On Windows non-ASCII characters will be encoded using utf-8 and then
- percent encoded.
- """
-
- path = _fsn2native(path)
-
- def _quote_path(path):
- # RFC 2396
- path = quote(path, "/:@&=+$,")
- if PY2:
- path = path.decode("ascii")
- return path
-
- if is_win:
- buf = ctypes.create_unicode_buffer(winapi.INTERNET_MAX_URL_LENGTH)
- length = winapi.DWORD(winapi.INTERNET_MAX_URL_LENGTH)
- flags = 0
- try:
- winapi.UrlCreateFromPathW(path, buf, ctypes.byref(length), flags)
- except WindowsError as e:
- raise ValueError(e)
- uri = buf[:length.value]
- # https://bitbucket.org/pypy/pypy/issues/3133
- uri = _merge_surrogates(uri)
-
- # For some reason UrlCreateFromPathW escapes some chars outside of
- # ASCII and some not. Unquote and re-quote with utf-8.
- if PY3:
- # latin-1 maps code points directly to bytes, which is what we want
- uri = unquote(uri, "latin-1")
- else:
- # Python 2 does what we want by default
- uri = unquote(uri)
-
- return _quote_path(uri.encode("utf-8", _surrogatepass))
-
- else:
- return u"file://" + _quote_path(path)
diff --git a/lib/mutagen/_senf/_print.py b/lib/mutagen/_senf/_print.py
deleted file mode 100644
index 63c50fa5..00000000
--- a/lib/mutagen/_senf/_print.py
+++ /dev/null
@@ -1,424 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright 2016 Christoph Reiter
-#
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and associated documentation files (the
-# "Software"), to deal in the Software without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject to
-# the following conditions:
-#
-# The above copyright notice and this permission notice shall be included
-# in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-import sys
-import os
-import ctypes
-import re
-
-from ._fsnative import _encoding, is_win, is_unix, _surrogatepass, bytes2fsn
-from ._compat import text_type, PY2, PY3
-from ._winansi import AnsiState, ansi_split
-from . import _winapi as winapi
-
-
-def print_(*objects, **kwargs):
- """print_(*objects, sep=None, end=None, file=None, flush=False)
-
- Args:
- objects (object): zero or more objects to print
- sep (str): Object separator to use, defaults to ``" "``
- end (str): Trailing string to use, defaults to ``"\\n"``.
- If end is ``"\\n"`` then `os.linesep` is used.
- file (object): A file-like object, defaults to `sys.stdout`
- flush (bool): If the file stream should be flushed
- Raises:
- EnvironmentError
-
- Like print(), but:
-
- * Supports printing filenames under Unix + Python 3 and Windows + Python 2
- * Emulates ANSI escape sequence support under Windows
- * Never fails due to encoding/decoding errors. Tries hard to get everything
- on screen as is, but will fall back to "?" if all fails.
-
- This does not conflict with ``colorama``, but will not use it on Windows.
- """
-
- sep = kwargs.get("sep")
- sep = sep if sep is not None else " "
- end = kwargs.get("end")
- end = end if end is not None else "\n"
- file = kwargs.get("file")
- file = file if file is not None else sys.stdout
- flush = bool(kwargs.get("flush", False))
-
- if is_win:
- _print_windows(objects, sep, end, file, flush)
- else:
- _print_unix(objects, sep, end, file, flush)
-
-
-def _print_unix(objects, sep, end, file, flush):
- """A print_() implementation which writes bytes"""
-
- encoding = _encoding
-
- if isinstance(sep, text_type):
- sep = sep.encode(encoding, "replace")
- if not isinstance(sep, bytes):
- raise TypeError
-
- if isinstance(end, text_type):
- end = end.encode(encoding, "replace")
- if not isinstance(end, bytes):
- raise TypeError
-
- if end == b"\n":
- end = os.linesep
- if PY3:
- end = end.encode("ascii")
-
- parts = []
- for obj in objects:
- if not isinstance(obj, text_type) and not isinstance(obj, bytes):
- obj = text_type(obj)
- if isinstance(obj, text_type):
- if PY2:
- obj = obj.encode(encoding, "replace")
- else:
- try:
- obj = obj.encode(encoding, "surrogateescape")
- except UnicodeEncodeError:
- obj = obj.encode(encoding, "replace")
- assert isinstance(obj, bytes)
- parts.append(obj)
-
- data = sep.join(parts) + end
- assert isinstance(data, bytes)
-
- file = getattr(file, "buffer", file)
-
- try:
- file.write(data)
- except TypeError:
- if PY3:
- # For StringIO, first try with surrogates
- surr_data = data.decode(encoding, "surrogateescape")
- try:
- file.write(surr_data)
- except (TypeError, ValueError):
- file.write(data.decode(encoding, "replace"))
- else:
- # for file like objects with don't support bytes
- file.write(data.decode(encoding, "replace"))
-
- if flush:
- file.flush()
-
-
-ansi_state = AnsiState()
-
-
-def _print_windows(objects, sep, end, file, flush):
- """The windows implementation of print_()"""
-
- h = winapi.INVALID_HANDLE_VALUE
-
- try:
- fileno = file.fileno()
- except (EnvironmentError, AttributeError):
- pass
- else:
- if fileno == 1:
- h = winapi.GetStdHandle(winapi.STD_OUTPUT_HANDLE)
- elif fileno == 2:
- h = winapi.GetStdHandle(winapi.STD_ERROR_HANDLE)
-
- encoding = _encoding
-
- parts = []
- for obj in objects:
- if isinstance(obj, bytes):
- obj = obj.decode(encoding, "replace")
- if not isinstance(obj, text_type):
- obj = text_type(obj)
- parts.append(obj)
-
- if isinstance(sep, bytes):
- sep = sep.decode(encoding, "replace")
- if not isinstance(sep, text_type):
- raise TypeError
-
- if isinstance(end, bytes):
- end = end.decode(encoding, "replace")
- if not isinstance(end, text_type):
- raise TypeError
-
- if end == u"\n":
- end = os.linesep
-
- text = sep.join(parts) + end
- assert isinstance(text, text_type)
-
- is_console = True
- if h == winapi.INVALID_HANDLE_VALUE:
- is_console = False
- else:
- # get the default value
- info = winapi.CONSOLE_SCREEN_BUFFER_INFO()
- if not winapi.GetConsoleScreenBufferInfo(h, ctypes.byref(info)):
- is_console = False
-
- if is_console:
- # make sure we flush before we apply any console attributes
- file.flush()
-
- # try to force a utf-8 code page, use the output CP if that fails
- cp = winapi.GetConsoleOutputCP()
- try:
- encoding = "utf-8"
- if winapi.SetConsoleOutputCP(65001) == 0:
- encoding = None
-
- for is_ansi, part in ansi_split(text):
- if is_ansi:
- ansi_state.apply(h, part)
- else:
- if encoding is not None:
- data = part.encode(encoding, _surrogatepass)
- else:
- data = _encode_codepage(cp, part)
- os.write(fileno, data)
- finally:
- # reset the code page to what we had before
- winapi.SetConsoleOutputCP(cp)
- else:
- # try writing bytes first, so in case of Python 2 StringIO we get
- # the same type on all platforms
- try:
- file.write(text.encode("utf-8", _surrogatepass))
- except (TypeError, ValueError):
- file.write(text)
-
- if flush:
- file.flush()
-
-
-def _readline_windows():
- """Raises OSError"""
-
- try:
- fileno = sys.stdin.fileno()
- except (EnvironmentError, AttributeError):
- fileno = -1
-
- # In case stdin is replaced, read from that
- if fileno != 0:
- return _readline_windows_fallback()
-
- h = winapi.GetStdHandle(winapi.STD_INPUT_HANDLE)
- if h == winapi.INVALID_HANDLE_VALUE:
- return _readline_windows_fallback()
-
- buf_size = 1024
- buf = ctypes.create_string_buffer(buf_size * ctypes.sizeof(winapi.WCHAR))
- read = winapi.DWORD()
-
- text = u""
- while True:
- if winapi.ReadConsoleW(
- h, buf, buf_size, ctypes.byref(read), None) == 0:
- if not text:
- return _readline_windows_fallback()
- raise ctypes.WinError()
- data = buf[:read.value * ctypes.sizeof(winapi.WCHAR)]
- text += data.decode("utf-16-le", _surrogatepass)
- if text.endswith(u"\r\n"):
- return text[:-2]
-
-
-def _decode_codepage(codepage, data):
- """
- Args:
- codepage (int)
- data (bytes)
- Returns:
- `text`
-
- Decodes data using the given codepage. If some data can't be decoded
- using the codepage it will not fail.
- """
-
- assert isinstance(data, bytes)
-
- if not data:
- return u""
-
- # get the required buffer length first
- length = winapi.MultiByteToWideChar(codepage, 0, data, len(data), None, 0)
- if length == 0:
- raise ctypes.WinError()
-
- # now decode
- buf = ctypes.create_unicode_buffer(length)
- length = winapi.MultiByteToWideChar(
- codepage, 0, data, len(data), buf, length)
- if length == 0:
- raise ctypes.WinError()
-
- return buf[:]
-
-
-def _encode_codepage(codepage, text):
- """
- Args:
- codepage (int)
- text (text)
- Returns:
- `bytes`
-
- Encode text using the given code page. Will not fail if a char
- can't be encoded using that codepage.
- """
-
- assert isinstance(text, text_type)
-
- if not text:
- return b""
-
- size = (len(text.encode("utf-16-le", _surrogatepass)) //
- ctypes.sizeof(winapi.WCHAR))
-
- # get the required buffer size
- length = winapi.WideCharToMultiByte(
- codepage, 0, text, size, None, 0, None, None)
- if length == 0:
- raise ctypes.WinError()
-
- # decode to the buffer
- buf = ctypes.create_string_buffer(length)
- length = winapi.WideCharToMultiByte(
- codepage, 0, text, size, buf, length, None, None)
- if length == 0:
- raise ctypes.WinError()
- return buf[:length]
-
-
-def _readline_windows_fallback():
- # In case reading from the console failed (maybe we get piped data)
- # we assume the input was generated according to the output encoding.
- # Got any better ideas?
- assert is_win
- cp = winapi.GetConsoleOutputCP()
- data = getattr(sys.stdin, "buffer", sys.stdin).readline().rstrip(b"\r\n")
- return _decode_codepage(cp, data)
-
-
-def _readline_default():
- assert is_unix
- data = getattr(sys.stdin, "buffer", sys.stdin).readline().rstrip(b"\r\n")
- if PY3:
- return data.decode(_encoding, "surrogateescape")
- else:
- return data
-
-
-def _readline():
- if is_win:
- return _readline_windows()
- else:
- return _readline_default()
-
-
-def input_(prompt=None):
- """
- Args:
- prompt (object): Prints the passed object to stdout without
- adding a trailing newline
- Returns:
- `fsnative`
- Raises:
- EnvironmentError
-
- Like :func:`python3:input` but returns a `fsnative` and allows printing
- filenames as prompt to stdout.
-
- Use :func:`fsn2text` on the result if you just want to deal with text.
- """
-
- if prompt is not None:
- print_(prompt, end="")
-
- return _readline()
-
-
-def _get_file_name_for_handle(handle):
- """(Windows only) Returns a file name for a file handle.
-
- Args:
- handle (winapi.HANDLE)
- Returns:
- `text` or `None` if no file name could be retrieved.
- """
-
- assert is_win
- assert handle != winapi.INVALID_HANDLE_VALUE
-
- size = winapi.FILE_NAME_INFO.FileName.offset + \
- winapi.MAX_PATH * ctypes.sizeof(winapi.WCHAR)
- buf = ctypes.create_string_buffer(size)
-
- if winapi.GetFileInformationByHandleEx is None:
- # Windows XP
- return None
-
- status = winapi.GetFileInformationByHandleEx(
- handle, winapi.FileNameInfo, buf, size)
- if status == 0:
- return None
-
- name_info = ctypes.cast(
- buf, ctypes.POINTER(winapi.FILE_NAME_INFO)).contents
- offset = winapi.FILE_NAME_INFO.FileName.offset
- data = buf[offset:offset + name_info.FileNameLength]
- return bytes2fsn(data, "utf-16-le")
-
-
-def supports_ansi_escape_codes(fd):
- """Returns whether the output device is capable of interpreting ANSI escape
- codes when :func:`print_` is used.
-
- Args:
- fd (int): file descriptor (e.g. ``sys.stdout.fileno()``)
- Returns:
- `bool`
- """
-
- if os.isatty(fd):
- return True
-
- if not is_win:
- return False
-
- # Check for cygwin/msys terminal
- handle = winapi._get_osfhandle(fd)
- if handle == winapi.INVALID_HANDLE_VALUE:
- return False
-
- if winapi.GetFileType(handle) != winapi.FILE_TYPE_PIPE:
- return False
-
- file_name = _get_file_name_for_handle(handle)
- match = re.match(
- "^\\\\(cygwin|msys)-[a-z0-9]+-pty[0-9]+-(from|to)-master$", file_name)
- return match is not None
diff --git a/lib/mutagen/_senf/_stdlib.py b/lib/mutagen/_senf/_stdlib.py
deleted file mode 100644
index f3193d33..00000000
--- a/lib/mutagen/_senf/_stdlib.py
+++ /dev/null
@@ -1,154 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright 2016 Christoph Reiter
-#
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and associated documentation files (the
-# "Software"), to deal in the Software without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject to
-# the following conditions:
-#
-# The above copyright notice and this permission notice shall be included
-# in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-import re
-import os
-
-from ._fsnative import path2fsn, fsnative, is_win
-from ._compat import PY2
-from ._environ import environ
-
-
-sep = path2fsn(os.sep)
-pathsep = path2fsn(os.pathsep)
-curdir = path2fsn(os.curdir)
-pardir = path2fsn(os.pardir)
-altsep = path2fsn(os.altsep) if os.altsep is not None else None
-extsep = path2fsn(os.extsep)
-devnull = path2fsn(os.devnull)
-defpath = path2fsn(os.defpath)
-
-
-def getcwd():
- """Like `os.getcwd` but returns a `fsnative` path
-
- Returns:
- `fsnative`
- """
-
- if is_win and PY2:
- return os.getcwdu()
- return os.getcwd()
-
-
-def _get_userdir(user=None):
- """Returns the user dir or None"""
-
- if user is not None and not isinstance(user, fsnative):
- raise TypeError
-
- if is_win:
- if "HOME" in environ:
- path = environ["HOME"]
- elif "USERPROFILE" in environ:
- path = environ["USERPROFILE"]
- elif "HOMEPATH" in environ and "HOMEDRIVE" in environ:
- path = os.path.join(environ["HOMEDRIVE"], environ["HOMEPATH"])
- else:
- return
-
- if user is None:
- return path
- else:
- return os.path.join(os.path.dirname(path), user)
- else:
- import pwd
-
- if user is None:
- if "HOME" in environ:
- return environ["HOME"]
- else:
- try:
- return path2fsn(pwd.getpwuid(os.getuid()).pw_dir)
- except KeyError:
- return
- else:
- try:
- return path2fsn(pwd.getpwnam(user).pw_dir)
- except KeyError:
- return
-
-
-def expanduser(path):
- """
- Args:
- path (pathlike): A path to expand
- Returns:
- `fsnative`
-
- Like :func:`python:os.path.expanduser` but supports unicode home
- directories under Windows + Python 2 and always returns a `fsnative`.
- """
-
- path = path2fsn(path)
-
- if path == "~":
- return _get_userdir()
- elif path.startswith("~" + sep) or (
- altsep is not None and path.startswith("~" + altsep)):
- userdir = _get_userdir()
- if userdir is None:
- return path
- return userdir + path[1:]
- elif path.startswith("~"):
- sep_index = path.find(sep)
- if altsep is not None:
- alt_index = path.find(altsep)
- if alt_index != -1 and alt_index < sep_index:
- sep_index = alt_index
-
- if sep_index == -1:
- user = path[1:]
- rest = ""
- else:
- user = path[1:sep_index]
- rest = path[sep_index:]
-
- userdir = _get_userdir(user)
- if userdir is not None:
- return userdir + rest
- else:
- return path
- else:
- return path
-
-
-def expandvars(path):
- """
- Args:
- path (pathlike): A path to expand
- Returns:
- `fsnative`
-
- Like :func:`python:os.path.expandvars` but supports unicode under Windows
- + Python 2 and always returns a `fsnative`.
- """
-
- path = path2fsn(path)
-
- def repl_func(match):
- return environ.get(match.group(1), match.group(0))
-
- path = re.compile(r"\$(\w+)", flags=re.UNICODE).sub(repl_func, path)
- if os.name == "nt":
- path = re.sub(r"%([^%]+)%", repl_func, path)
- return re.sub(r"\$\{([^\}]+)\}", repl_func, path)
diff --git a/lib/mutagen/_senf/_temp.py b/lib/mutagen/_senf/_temp.py
deleted file mode 100644
index d29b7217..00000000
--- a/lib/mutagen/_senf/_temp.py
+++ /dev/null
@@ -1,96 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright 2016 Christoph Reiter
-#
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and associated documentation files (the
-# "Software"), to deal in the Software without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject to
-# the following conditions:
-#
-# The above copyright notice and this permission notice shall be included
-# in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-import tempfile
-
-from ._fsnative import path2fsn, fsnative
-
-
-def gettempdir():
- """
- Returns:
- `fsnative`
-
- Like :func:`python3:tempfile.gettempdir`, but always returns a `fsnative`
- path
- """
-
- # FIXME: I don't want to reimplement all that logic, reading env vars etc.
- # At least for the default it works.
- return path2fsn(tempfile.gettempdir())
-
-
-def gettempprefix():
- """
- Returns:
- `fsnative`
-
- Like :func:`python3:tempfile.gettempprefix`, but always returns a
- `fsnative` path
- """
-
- return path2fsn(tempfile.gettempprefix())
-
-
-def mkstemp(suffix=None, prefix=None, dir=None, text=False):
- """
- Args:
- suffix (`pathlike` or `None`): suffix or `None` to use the default
- prefix (`pathlike` or `None`): prefix or `None` to use the default
- dir (`pathlike` or `None`): temp dir or `None` to use the default
- text (bool): if the file should be opened in text mode
- Returns:
- Tuple[`int`, `fsnative`]:
- A tuple containing the file descriptor and the file path
- Raises:
- EnvironmentError
-
- Like :func:`python3:tempfile.mkstemp` but always returns a `fsnative`
- path.
- """
-
- suffix = fsnative() if suffix is None else path2fsn(suffix)
- prefix = gettempprefix() if prefix is None else path2fsn(prefix)
- dir = gettempdir() if dir is None else path2fsn(dir)
-
- return tempfile.mkstemp(suffix, prefix, dir, text)
-
-
-def mkdtemp(suffix=None, prefix=None, dir=None):
- """
- Args:
- suffix (`pathlike` or `None`): suffix or `None` to use the default
- prefix (`pathlike` or `None`): prefix or `None` to use the default
- dir (`pathlike` or `None`): temp dir or `None` to use the default
- Returns:
- `fsnative`: A path to a directory
- Raises:
- EnvironmentError
-
- Like :func:`python3:tempfile.mkstemp` but always returns a `fsnative` path.
- """
-
- suffix = fsnative() if suffix is None else path2fsn(suffix)
- prefix = gettempprefix() if prefix is None else path2fsn(prefix)
- dir = gettempdir() if dir is None else path2fsn(dir)
-
- return tempfile.mkdtemp(suffix, prefix, dir)
diff --git a/lib/mutagen/_senf/_winansi.py b/lib/mutagen/_senf/_winansi.py
deleted file mode 100644
index fbbc1c22..00000000
--- a/lib/mutagen/_senf/_winansi.py
+++ /dev/null
@@ -1,319 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright 2016 Christoph Reiter
-#
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and associated documentation files (the
-# "Software"), to deal in the Software without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject to
-# the following conditions:
-#
-# The above copyright notice and this permission notice shall be included
-# in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-import ctypes
-import re
-import atexit
-
-from . import _winapi as winapi
-
-
-def ansi_parse(code):
- """Returns command, (args)"""
-
- return code[-1:], tuple([int(v or "0") for v in code[2:-1].split(";")])
-
-
-def ansi_split(text, _re=re.compile(u"(\x1b\\[(\\d*;?)*\\S)")):
- """Yields (is_ansi, text)"""
-
- for part in _re.split(text):
- if part:
- yield (bool(_re.match(part)), part)
-
-
-class AnsiCommand(object):
- TEXT = "m"
-
- MOVE_UP = "A"
- MOVE_DOWN = "B"
- MOVE_FORWARD = "C"
- MOVE_BACKWARD = "D"
-
- SET_POS = "H"
- SET_POS_ALT = "f"
-
- SAVE_POS = "s"
- RESTORE_POS = "u"
-
-
-class TextAction(object):
- RESET_ALL = 0
-
- SET_BOLD = 1
- SET_DIM = 2
- SET_ITALIC = 3
- SET_UNDERLINE = 4
- SET_BLINK = 5
- SET_BLINK_FAST = 6
- SET_REVERSE = 7
- SET_HIDDEN = 8
-
- RESET_BOLD = 21
- RESET_DIM = 22
- RESET_ITALIC = 23
- RESET_UNDERLINE = 24
- RESET_BLINK = 25
- RESET_BLINK_FAST = 26
- RESET_REVERSE = 27
- RESET_HIDDEN = 28
-
- FG_BLACK = 30
- FG_RED = 31
- FG_GREEN = 32
- FG_YELLOW = 33
- FG_BLUE = 34
- FG_MAGENTA = 35
- FG_CYAN = 36
- FG_WHITE = 37
-
- FG_DEFAULT = 39
-
- FG_LIGHT_BLACK = 90
- FG_LIGHT_RED = 91
- FG_LIGHT_GREEN = 92
- FG_LIGHT_YELLOW = 93
- FG_LIGHT_BLUE = 94
- FG_LIGHT_MAGENTA = 95
- FG_LIGHT_CYAN = 96
- FG_LIGHT_WHITE = 97
-
- BG_BLACK = 40
- BG_RED = 41
- BG_GREEN = 42
- BG_YELLOW = 43
- BG_BLUE = 44
- BG_MAGENTA = 45
- BG_CYAN = 46
- BG_WHITE = 47
-
- BG_DEFAULT = 49
-
- BG_LIGHT_BLACK = 100
- BG_LIGHT_RED = 101
- BG_LIGHT_GREEN = 102
- BG_LIGHT_YELLOW = 103
- BG_LIGHT_BLUE = 104
- BG_LIGHT_MAGENTA = 105
- BG_LIGHT_CYAN = 106
- BG_LIGHT_WHITE = 107
-
-
-class AnsiState(object):
-
- def __init__(self):
- self.default_attrs = None
-
- self.bold = False
- self.bg_light = False
- self.fg_light = False
-
- self.saved_pos = (0, 0)
-
- def do_text_action(self, attrs, action):
- # In case the external state has changed, apply it it to ours.
- # Mostly the first time this is called.
- if attrs & winapi.FOREGROUND_INTENSITY and not self.fg_light \
- and not self.bold:
- self.fg_light = True
- if attrs & winapi.BACKGROUND_INTENSITY and not self.bg_light:
- self.bg_light = True
-
- dark_fg = {
- TextAction.FG_BLACK: 0,
- TextAction.FG_RED: winapi.FOREGROUND_RED,
- TextAction.FG_GREEN: winapi.FOREGROUND_GREEN,
- TextAction.FG_YELLOW:
- winapi.FOREGROUND_GREEN | winapi.FOREGROUND_RED,
- TextAction.FG_BLUE: winapi.FOREGROUND_BLUE,
- TextAction.FG_MAGENTA: winapi.FOREGROUND_BLUE |
- winapi.FOREGROUND_RED,
- TextAction.FG_CYAN:
- winapi.FOREGROUND_BLUE | winapi.FOREGROUND_GREEN,
- TextAction.FG_WHITE:
- winapi.FOREGROUND_BLUE | winapi.FOREGROUND_GREEN |
- winapi.FOREGROUND_RED,
- }
-
- dark_bg = {
- TextAction.BG_BLACK: 0,
- TextAction.BG_RED: winapi.BACKGROUND_RED,
- TextAction.BG_GREEN: winapi.BACKGROUND_GREEN,
- TextAction.BG_YELLOW:
- winapi.BACKGROUND_GREEN | winapi.BACKGROUND_RED,
- TextAction.BG_BLUE: winapi.BACKGROUND_BLUE,
- TextAction.BG_MAGENTA:
- winapi.BACKGROUND_BLUE | winapi.BACKGROUND_RED,
- TextAction.BG_CYAN:
- winapi.BACKGROUND_BLUE | winapi.BACKGROUND_GREEN,
- TextAction.BG_WHITE:
- winapi.BACKGROUND_BLUE | winapi.BACKGROUND_GREEN |
- winapi.BACKGROUND_RED,
- }
-
- light_fg = {
- TextAction.FG_LIGHT_BLACK: 0,
- TextAction.FG_LIGHT_RED: winapi.FOREGROUND_RED,
- TextAction.FG_LIGHT_GREEN: winapi.FOREGROUND_GREEN,
- TextAction.FG_LIGHT_YELLOW:
- winapi.FOREGROUND_GREEN | winapi.FOREGROUND_RED,
- TextAction.FG_LIGHT_BLUE: winapi.FOREGROUND_BLUE,
- TextAction.FG_LIGHT_MAGENTA:
- winapi.FOREGROUND_BLUE | winapi.FOREGROUND_RED,
- TextAction.FG_LIGHT_CYAN:
- winapi.FOREGROUND_BLUE | winapi.FOREGROUND_GREEN,
- TextAction.FG_LIGHT_WHITE:
- winapi.FOREGROUND_BLUE | winapi.FOREGROUND_GREEN |
- winapi.FOREGROUND_RED,
- }
-
- light_bg = {
- TextAction.BG_LIGHT_BLACK: 0,
- TextAction.BG_LIGHT_RED: winapi.BACKGROUND_RED,
- TextAction.BG_LIGHT_GREEN: winapi.BACKGROUND_GREEN,
- TextAction.BG_LIGHT_YELLOW:
- winapi.BACKGROUND_GREEN | winapi.BACKGROUND_RED,
- TextAction.BG_LIGHT_BLUE: winapi.BACKGROUND_BLUE,
- TextAction.BG_LIGHT_MAGENTA:
- winapi.BACKGROUND_BLUE | winapi.BACKGROUND_RED,
- TextAction.BG_LIGHT_CYAN:
- winapi.BACKGROUND_BLUE | winapi.BACKGROUND_GREEN,
- TextAction.BG_LIGHT_WHITE:
- winapi.BACKGROUND_BLUE | winapi.BACKGROUND_GREEN |
- winapi.BACKGROUND_RED,
- }
-
- if action == TextAction.RESET_ALL:
- attrs = self.default_attrs
- self.bold = self.fg_light = self.bg_light = False
- elif action == TextAction.SET_BOLD:
- self.bold = True
- elif action == TextAction.RESET_BOLD:
- self.bold = False
- elif action == TextAction.SET_DIM:
- self.bold = False
- elif action == TextAction.SET_REVERSE:
- attrs |= winapi.COMMON_LVB_REVERSE_VIDEO
- elif action == TextAction.RESET_REVERSE:
- attrs &= ~winapi.COMMON_LVB_REVERSE_VIDEO
- elif action == TextAction.SET_UNDERLINE:
- attrs |= winapi.COMMON_LVB_UNDERSCORE
- elif action == TextAction.RESET_UNDERLINE:
- attrs &= ~winapi.COMMON_LVB_UNDERSCORE
- elif action == TextAction.FG_DEFAULT:
- attrs = (attrs & ~0xF) | (self.default_attrs & 0xF)
- self.fg_light = False
- elif action == TextAction.BG_DEFAULT:
- attrs = (attrs & ~0xF0) | (self.default_attrs & 0xF0)
- self.bg_light = False
- elif action in dark_fg:
- attrs = (attrs & ~0xF) | dark_fg[action]
- self.fg_light = False
- elif action in dark_bg:
- attrs = (attrs & ~0xF0) | dark_bg[action]
- self.bg_light = False
- elif action in light_fg:
- attrs = (attrs & ~0xF) | light_fg[action]
- self.fg_light = True
- elif action in light_bg:
- attrs = (attrs & ~0xF0) | light_bg[action]
- self.bg_light = True
-
- if self.fg_light or self.bold:
- attrs |= winapi.FOREGROUND_INTENSITY
- else:
- attrs &= ~winapi.FOREGROUND_INTENSITY
-
- if self.bg_light:
- attrs |= winapi.BACKGROUND_INTENSITY
- else:
- attrs &= ~winapi.BACKGROUND_INTENSITY
-
- return attrs
-
- def apply(self, handle, code):
- buffer_info = winapi.CONSOLE_SCREEN_BUFFER_INFO()
- if not winapi.GetConsoleScreenBufferInfo(handle,
- ctypes.byref(buffer_info)):
- return
-
- attrs = buffer_info.wAttributes
-
- # We take the first attrs we see as default
- if self.default_attrs is None:
- self.default_attrs = attrs
- # Make sure that like with linux terminals the program doesn't
- # affect the prompt after it exits
- atexit.register(
- winapi.SetConsoleTextAttribute, handle, self.default_attrs)
-
- cmd, args = ansi_parse(code)
- if cmd == AnsiCommand.TEXT:
- for action in args:
- attrs = self.do_text_action(attrs, action)
- winapi.SetConsoleTextAttribute(handle, attrs)
- elif cmd in (AnsiCommand.MOVE_UP, AnsiCommand.MOVE_DOWN,
- AnsiCommand.MOVE_FORWARD, AnsiCommand.MOVE_BACKWARD):
-
- coord = buffer_info.dwCursorPosition
- x, y = coord.X, coord.Y
-
- amount = max(args[0], 1)
-
- if cmd == AnsiCommand.MOVE_UP:
- y -= amount
- elif cmd == AnsiCommand.MOVE_DOWN:
- y += amount
- elif cmd == AnsiCommand.MOVE_FORWARD:
- x += amount
- elif cmd == AnsiCommand.MOVE_BACKWARD:
- x -= amount
-
- x = max(x, 0)
- y = max(y, 0)
- winapi.SetConsoleCursorPosition(handle, winapi.COORD(x, y))
- elif cmd in (AnsiCommand.SET_POS, AnsiCommand.SET_POS_ALT):
- args = list(args)
- while len(args) < 2:
- args.append(0)
- x, y = args[:2]
-
- win_rect = buffer_info.srWindow
- x += win_rect.Left - 1
- y += win_rect.Top - 1
-
- x = max(x, 0)
- y = max(y, 0)
- winapi.SetConsoleCursorPosition(handle, winapi.COORD(x, y))
- elif cmd == AnsiCommand.SAVE_POS:
- win_rect = buffer_info.srWindow
- coord = buffer_info.dwCursorPosition
- x, y = coord.X, coord.Y
- x -= win_rect.Left
- y -= win_rect.Top
- self.saved_pos = (x, y)
- elif cmd == AnsiCommand.RESTORE_POS:
- win_rect = buffer_info.srWindow
- x, y = self.saved_pos
- x += win_rect.Left
- y += win_rect.Top
- winapi.SetConsoleCursorPosition(handle, winapi.COORD(x, y))
diff --git a/lib/mutagen/_senf/_winapi.py b/lib/mutagen/_senf/_winapi.py
deleted file mode 100644
index 6a63ea35..00000000
--- a/lib/mutagen/_senf/_winapi.py
+++ /dev/null
@@ -1,221 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright 2016 Christoph Reiter
-#
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and associated documentation files (the
-# "Software"), to deal in the Software without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject to
-# the following conditions:
-#
-# The above copyright notice and this permission notice shall be included
-# in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-import sys
-import ctypes
-
-if sys.platform == 'win32':
- from ctypes import WinDLL, CDLL, wintypes
-
- shell32 = WinDLL("shell32")
- kernel32 = WinDLL("kernel32")
- shlwapi = WinDLL("shlwapi")
- msvcrt = CDLL("msvcrt")
-
- GetCommandLineW = kernel32.GetCommandLineW
- GetCommandLineW.argtypes = []
- GetCommandLineW.restype = wintypes.LPCWSTR
-
- CommandLineToArgvW = shell32.CommandLineToArgvW
- CommandLineToArgvW.argtypes = [
- wintypes.LPCWSTR, ctypes.POINTER(ctypes.c_int)]
- CommandLineToArgvW.restype = ctypes.POINTER(wintypes.LPWSTR)
-
- LocalFree = kernel32.LocalFree
- LocalFree.argtypes = [wintypes.HLOCAL]
- LocalFree.restype = wintypes.HLOCAL
-
- # https://msdn.microsoft.com/en-us/library/windows/desktop/aa383751.aspx
- LPCTSTR = ctypes.c_wchar_p
- LPWSTR = wintypes.LPWSTR
- LPCWSTR = ctypes.c_wchar_p
- LPTSTR = LPWSTR
- PCWSTR = ctypes.c_wchar_p
- PCTSTR = PCWSTR
- PWSTR = ctypes.c_wchar_p
- PTSTR = PWSTR
- LPVOID = wintypes.LPVOID
- WCHAR = wintypes.WCHAR
- LPSTR = ctypes.c_char_p
-
- BOOL = wintypes.BOOL
- LPBOOL = ctypes.POINTER(BOOL)
- UINT = wintypes.UINT
- WORD = wintypes.WORD
- DWORD = wintypes.DWORD
- SHORT = wintypes.SHORT
- HANDLE = wintypes.HANDLE
- ULONG = wintypes.ULONG
- LPCSTR = wintypes.LPCSTR
-
- STD_INPUT_HANDLE = DWORD(-10)
- STD_OUTPUT_HANDLE = DWORD(-11)
- STD_ERROR_HANDLE = DWORD(-12)
-
- INVALID_HANDLE_VALUE = wintypes.HANDLE(-1).value
-
- INTERNET_MAX_SCHEME_LENGTH = 32
- INTERNET_MAX_PATH_LENGTH = 2048
- INTERNET_MAX_URL_LENGTH = (
- INTERNET_MAX_SCHEME_LENGTH + len("://") + INTERNET_MAX_PATH_LENGTH)
-
- FOREGROUND_BLUE = 0x0001
- FOREGROUND_GREEN = 0x0002
- FOREGROUND_RED = 0x0004
- FOREGROUND_INTENSITY = 0x0008
-
- BACKGROUND_BLUE = 0x0010
- BACKGROUND_GREEN = 0x0020
- BACKGROUND_RED = 0x0040
- BACKGROUND_INTENSITY = 0x0080
-
- COMMON_LVB_REVERSE_VIDEO = 0x4000
- COMMON_LVB_UNDERSCORE = 0x8000
-
- UrlCreateFromPathW = shlwapi.UrlCreateFromPathW
- UrlCreateFromPathW.argtypes = [
- PCTSTR, PTSTR, ctypes.POINTER(DWORD), DWORD]
- UrlCreateFromPathW.restype = ctypes.HRESULT
-
- SetEnvironmentVariableW = kernel32.SetEnvironmentVariableW
- SetEnvironmentVariableW.argtypes = [LPCTSTR, LPCTSTR]
- SetEnvironmentVariableW.restype = wintypes.BOOL
-
- GetEnvironmentVariableW = kernel32.GetEnvironmentVariableW
- GetEnvironmentVariableW.argtypes = [LPCTSTR, LPTSTR, DWORD]
- GetEnvironmentVariableW.restype = DWORD
-
- GetEnvironmentStringsW = kernel32.GetEnvironmentStringsW
- GetEnvironmentStringsW.argtypes = []
- GetEnvironmentStringsW.restype = ctypes.c_void_p
-
- FreeEnvironmentStringsW = kernel32.FreeEnvironmentStringsW
- FreeEnvironmentStringsW.argtypes = [ctypes.c_void_p]
- FreeEnvironmentStringsW.restype = ctypes.c_bool
-
- GetStdHandle = kernel32.GetStdHandle
- GetStdHandle.argtypes = [DWORD]
- GetStdHandle.restype = HANDLE
-
- class COORD(ctypes.Structure):
-
- _fields_ = [
- ("X", SHORT),
- ("Y", SHORT),
- ]
-
- class SMALL_RECT(ctypes.Structure):
-
- _fields_ = [
- ("Left", SHORT),
- ("Top", SHORT),
- ("Right", SHORT),
- ("Bottom", SHORT),
- ]
-
- class CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
-
- _fields_ = [
- ("dwSize", COORD),
- ("dwCursorPosition", COORD),
- ("wAttributes", WORD),
- ("srWindow", SMALL_RECT),
- ("dwMaximumWindowSize", COORD),
- ]
-
- GetConsoleScreenBufferInfo = kernel32.GetConsoleScreenBufferInfo
- GetConsoleScreenBufferInfo.argtypes = [
- HANDLE, ctypes.POINTER(CONSOLE_SCREEN_BUFFER_INFO)]
- GetConsoleScreenBufferInfo.restype = BOOL
-
- GetConsoleOutputCP = kernel32.GetConsoleOutputCP
- GetConsoleOutputCP.argtypes = []
- GetConsoleOutputCP.restype = UINT
-
- SetConsoleOutputCP = kernel32.SetConsoleOutputCP
- SetConsoleOutputCP.argtypes = [UINT]
- SetConsoleOutputCP.restype = BOOL
-
- GetConsoleCP = kernel32.GetConsoleCP
- GetConsoleCP.argtypes = []
- GetConsoleCP.restype = UINT
-
- SetConsoleCP = kernel32.SetConsoleCP
- SetConsoleCP.argtypes = [UINT]
- SetConsoleCP.restype = BOOL
-
- SetConsoleTextAttribute = kernel32.SetConsoleTextAttribute
- SetConsoleTextAttribute.argtypes = [HANDLE, WORD]
- SetConsoleTextAttribute.restype = BOOL
-
- SetConsoleCursorPosition = kernel32.SetConsoleCursorPosition
- SetConsoleCursorPosition.argtypes = [HANDLE, COORD]
- SetConsoleCursorPosition.restype = BOOL
-
- ReadConsoleW = kernel32.ReadConsoleW
- ReadConsoleW.argtypes = [
- HANDLE, LPVOID, DWORD, ctypes.POINTER(DWORD), LPVOID]
- ReadConsoleW.restype = BOOL
-
- MultiByteToWideChar = kernel32.MultiByteToWideChar
- MultiByteToWideChar.argtypes = [
- UINT, DWORD, LPCSTR, ctypes.c_int, LPWSTR, ctypes.c_int]
- MultiByteToWideChar.restype = ctypes.c_int
-
- WideCharToMultiByte = kernel32.WideCharToMultiByte
- WideCharToMultiByte.argtypes = [
- UINT, DWORD, LPCWSTR, ctypes.c_int, LPSTR, ctypes.c_int,
- LPCSTR, LPBOOL]
- WideCharToMultiByte.restype = ctypes.c_int
-
- MoveFileW = kernel32.MoveFileW
- MoveFileW.argtypes = [LPCTSTR, LPCTSTR]
- MoveFileW.restype = BOOL
-
- GetFileInformationByHandleEx = None
- if hasattr(kernel32, "GetFileInformationByHandleEx"):
- GetFileInformationByHandleEx = kernel32.GetFileInformationByHandleEx
- GetFileInformationByHandleEx.argtypes = [
- HANDLE, ctypes.c_int, ctypes.c_void_p, DWORD]
- GetFileInformationByHandleEx.restype = BOOL
- else:
- # Windows XP
- pass
-
- MAX_PATH = 260
- FileNameInfo = 2
-
- class FILE_NAME_INFO(ctypes.Structure):
- _fields_ = [
- ("FileNameLength", DWORD),
- ("FileName", WCHAR),
- ]
-
- _get_osfhandle = msvcrt._get_osfhandle
- _get_osfhandle.argtypes = [ctypes.c_int]
- _get_osfhandle.restype = HANDLE
-
- GetFileType = kernel32.GetFileType
- GetFileType.argtypes = [HANDLE]
- GetFileType.restype = DWORD
-
- FILE_TYPE_PIPE = 0x0003
diff --git a/lib/mutagen/_tags.py b/lib/mutagen/_tags.py
index b64caa9a..a236cf82 100644
--- a/lib/mutagen/_tags.py
+++ b/lib/mutagen/_tags.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2005 Michael Urman
#
# This program is free software; you can redistribute it and/or modify
@@ -37,11 +36,11 @@ class PaddingInfo(object):
size (`int`): The amount of data following the padding
"""
- def __init__(self, padding, size):
+ def __init__(self, padding: int, size: int):
self.padding = padding
self.size = size
- def get_default_padding(self):
+ def get_default_padding(self) -> int:
"""The default implementation which tries to select a reasonable
amount of padding and which might change in future versions.
diff --git a/lib/mutagen/_tools/__init__.py b/lib/mutagen/_tools/__init__.py
index 3e6b1556..94b5bb2f 100644
--- a/lib/mutagen/_tools/__init__.py
+++ b/lib/mutagen/_tools/__init__.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright 2016 Christoph Reiter
#
# This program is free software; you can redistribute it and/or modify
diff --git a/lib/mutagen/_tools/_util.py b/lib/mutagen/_tools/_util.py
index a31de654..513f3989 100644
--- a/lib/mutagen/_tools/_util.py
+++ b/lib/mutagen/_tools/_util.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright 2015 Christoph Reiter
#
# This program is free software; you can redistribute it and/or modify
@@ -11,7 +10,6 @@ import signal
import contextlib
import optparse
-from mutagen._senf import print_
from mutagen._util import iterbytes
@@ -88,8 +86,4 @@ class SignalHandler(object):
raise SystemExit("Aborted...")
-class OptionParser(optparse.OptionParser):
- """OptionParser subclass which supports printing Unicode under Windows"""
-
- def print_help(self, file=None):
- print_(self.format_help(), file=file)
+OptionParser = optparse.OptionParser
diff --git a/lib/mutagen/_tools/mid3cp.py b/lib/mutagen/_tools/mid3cp.py
index 995c7040..b48d285c 100644
--- a/lib/mutagen/_tools/mid3cp.py
+++ b/lib/mutagen/_tools/mid3cp.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright 2014 Marcus Sundman
#
# This program is free software; you can redistribute it and/or modify
@@ -15,7 +14,6 @@ import os.path
import mutagen
import mutagen.id3
-from mutagen._senf import print_, argv
from ._util import SignalHandler, OptionParser
@@ -24,11 +22,6 @@ VERSION = (0, 1)
_sig = SignalHandler()
-def printerr(*args, **kwargs):
- kwargs.setdefault("file", sys.stderr)
- print_(*args, **kwargs)
-
-
class ID3OptionParser(OptionParser):
def __init__(self):
mutagen_version = mutagen.version_string
@@ -51,15 +44,15 @@ def copy(src, dst, merge, write_v1=True, excluded_tags=None, verbose=False):
try:
id3 = mutagen.id3.ID3(src, translate=False)
except mutagen.id3.ID3NoHeaderError:
- print_(u"No ID3 header found in ", src, file=sys.stderr)
+ print(u"No ID3 header found in ", src, file=sys.stderr)
return 1
except Exception as err:
- print_(str(err), file=sys.stderr)
+ print(str(err), file=sys.stderr)
return 1
if verbose:
- print_(u"File", src, u"contains:", file=sys.stderr)
- print_(id3.pprint(), file=sys.stderr)
+ print(u"File", src, u"contains:", file=sys.stderr)
+ print(id3.pprint(), file=sys.stderr)
for tag in excluded_tags:
id3.delall(tag)
@@ -71,7 +64,7 @@ def copy(src, dst, merge, write_v1=True, excluded_tags=None, verbose=False):
# no need to merge
pass
except Exception as err:
- print_(str(err), file=sys.stderr)
+ print(str(err), file=sys.stderr)
return 1
else:
for frame in id3.values():
@@ -90,12 +83,12 @@ def copy(src, dst, merge, write_v1=True, excluded_tags=None, verbose=False):
try:
id3.save(dst, v1=(2 if write_v1 else 0), v2_version=v2_version)
except Exception as err:
- print_(u"Error saving", dst, u":\n%s" % str(err),
- file=sys.stderr)
+ print(u"Error saving", dst, u":\n%s" % str(err),
+ file=sys.stderr)
return 1
else:
if verbose:
- print_(u"Successfully saved", dst, file=sys.stderr)
+ print(u"Successfully saved", dst, file=sys.stderr)
return 0
@@ -119,12 +112,12 @@ def main(argv):
(src, dst) = args
if not os.path.isfile(src):
- print_(u"File not found:", src, file=sys.stderr)
+ print(u"File not found:", src, file=sys.stderr)
parser.print_help(file=sys.stderr)
return 1
if not os.path.isfile(dst):
- printerr(u"File not found:", dst, file=sys.stderr)
+ print(u"File not found:", dst, file=sys.stderr)
parser.print_help(file=sys.stderr)
return 1
@@ -138,4 +131,4 @@ def main(argv):
def entry_point():
_sig.init()
- return main(argv)
+ return main(sys.argv)
diff --git a/lib/mutagen/_tools/mid3iconv.py b/lib/mutagen/_tools/mid3iconv.py
index 5cc42f40..b0669acf 100644
--- a/lib/mutagen/_tools/mid3iconv.py
+++ b/lib/mutagen/_tools/mid3iconv.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright 2006 Emfox Zhou
#
# This program is free software; you can redistribute it and/or modify
@@ -7,7 +6,7 @@
# (at your option) any later version.
"""
-ID3iconv is a Java based ID3 encoding convertor, here's the Python version.
+ID3iconv is a Java based ID3 encoding converter, here's the Python version.
"""
import sys
@@ -15,7 +14,6 @@ import locale
import mutagen
import mutagen.id3
-from mutagen._senf import argv, print_, fsnative
from ._util import SignalHandler, OptionParser
@@ -74,7 +72,7 @@ def update(options, filenames):
for filename in filenames:
with _sig.block():
if verbose != "quiet":
- print_(u"Updating", filename)
+ print(u"Updating", filename)
if has_id3v1(filename) and not noupdate and force_v1:
mutagen.id3.delete(filename, False, True)
@@ -83,10 +81,10 @@ def update(options, filenames):
id3 = mutagen.id3.ID3(filename)
except mutagen.id3.ID3NoHeaderError:
if verbose != "quiet":
- print_(u"No ID3 header found; skipping...")
+ print(u"No ID3 header found; skipping...")
continue
except Exception as err:
- print_(str(err), file=sys.stderr)
+ print(str(err), file=sys.stderr)
continue
for tag in filter(lambda t: t.startswith(("T", "COMM")), id3):
@@ -110,7 +108,7 @@ def update(options, filenames):
frame.encoding = 1
if verbose == "debug":
- print_(id3.pprint())
+ print(id3.pprint())
if not noupdate:
if remove_v1:
@@ -153,9 +151,9 @@ def main(argv):
for i, arg in enumerate(argv):
if arg == "-v1":
- argv[i] = fsnative(u"--force-v1")
+ argv[i] = "--force-v1"
elif arg == "-removev1":
- argv[i] = fsnative(u"--remove-v1")
+ argv[i] = "--remove-v1"
(options, args) = parser.parse_args(argv[1:])
@@ -167,4 +165,4 @@ def main(argv):
def entry_point():
_sig.init()
- return main(argv)
+ return main(sys.argv)
diff --git a/lib/mutagen/_tools/mid3v2.py b/lib/mutagen/_tools/mid3v2.py
index db78514c..b6949b76 100644
--- a/lib/mutagen/_tools/mid3v2.py
+++ b/lib/mutagen/_tools/mid3v2.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright 2005 Joe Wreschnig
#
# This program is free software; you can redistribute it and/or modify
@@ -8,6 +7,7 @@
"""Pretend to be /usr/bin/id3v2 from id3lib, sort of."""
+import os
import sys
import codecs
import mimetypes
@@ -18,8 +18,6 @@ from optparse import SUPPRESS_HELP
import mutagen
import mutagen.id3
from mutagen.id3 import Encoding, PictureType
-from mutagen._senf import fsnative, print_, argv, fsn2text, fsn2bytes, \
- bytes2fsn
from ._util import split_escape, SignalHandler, OptionParser
@@ -57,7 +55,7 @@ Any editing operation will cause the ID3 tag to be upgraded to ID3v2.4.
def list_frames(option, opt, value, parser):
items = mutagen.id3.Frames.items()
for name, frame in sorted(items):
- print_(u" --%s %s" % (name, frame.__doc__.split("\n")[0]))
+ print(u" --%s %s" % (name, frame.__doc__.split("\n")[0]))
raise SystemExit
@@ -65,13 +63,13 @@ def list_frames_2_2(option, opt, value, parser):
items = mutagen.id3.Frames_2_2.items()
items.sort()
for name, frame in items:
- print_(u" --%s %s" % (name, frame.__doc__.split("\n")[0]))
+ print(u" --%s %s" % (name, frame.__doc__.split("\n")[0]))
raise SystemExit
def list_genres(option, opt, value, parser):
for i, genre in enumerate(mutagen.id3.TCON.GENRES):
- print_(u"%3d: %s" % (i, genre))
+ print(u"%3d: %s" % (i, genre))
raise SystemExit
@@ -79,7 +77,7 @@ def delete_tags(filenames, v1, v2):
for filename in filenames:
with _sig.block():
if verbose:
- print_(u"deleting ID3 tag info in", filename, file=sys.stderr)
+ print(u"deleting ID3 tag info in", filename, file=sys.stderr)
mutagen.id3.delete(filename, v1, v2)
@@ -88,22 +86,22 @@ def delete_frames(deletes, filenames):
try:
deletes = frame_from_fsnative(deletes)
except ValueError as err:
- print_(str(err), file=sys.stderr)
+ print(str(err), file=sys.stderr)
frames = deletes.split(",")
for filename in filenames:
with _sig.block():
if verbose:
- print_(u"deleting %s from" % deletes, filename,
- file=sys.stderr)
+ print("deleting %s from" % deletes, filename,
+ file=sys.stderr)
try:
id3 = mutagen.id3.ID3(filename)
except mutagen.id3.ID3NoHeaderError:
if verbose:
- print_(u"No ID3 header found; skipping.", file=sys.stderr)
+ print(u"No ID3 header found; skipping.", file=sys.stderr)
except Exception as err:
- print_(str(err), file=sys.stderr)
+ print(str(err), file=sys.stderr)
raise SystemExit(1)
else:
for frame in frames:
@@ -116,10 +114,8 @@ def frame_from_fsnative(arg):
or raises ValueError.
"""
- assert isinstance(arg, fsnative)
-
- text = fsn2text(arg, strict=True)
- return text.encode("ascii").decode("ascii")
+ assert isinstance(arg, str)
+ return arg.encode("ascii").decode("ascii")
def value_from_fsnative(arg, escape):
@@ -127,23 +123,23 @@ def value_from_fsnative(arg, escape):
surrogate escapes or raises ValueError.
"""
- assert isinstance(arg, fsnative)
+ assert isinstance(arg, str)
if escape:
- bytes_ = fsn2bytes(arg)
+ bytes_ = os.fsencode(arg)
# With py3.7 this has started to warn for invalid escapes, but we
# don't control the input so ignore it.
with warnings.catch_warnings():
warnings.simplefilter("ignore")
bytes_ = codecs.escape_decode(bytes_)[0]
- arg = bytes2fsn(bytes_)
+ arg = os.fsdecode(bytes_)
- text = fsn2text(arg, strict=True)
+ text = arg.encode("utf-8").decode("utf-8")
return text
def error(*args):
- print_(*args, file=sys.stderr)
+ print(*args, file=sys.stderr)
raise SystemExit(1)
@@ -165,7 +161,7 @@ def write_files(edits, filenames, escape):
try:
frame = frame_from_fsnative(frame)
except ValueError as err:
- print_(str(err), file=sys.stderr)
+ print(str(err), file=sys.stderr)
assert isinstance(frame, str)
@@ -203,16 +199,16 @@ def write_files(edits, filenames, escape):
for filename in filenames:
with _sig.block():
if verbose:
- print_(u"Writing", filename, file=sys.stderr)
+ print(u"Writing", filename, file=sys.stderr)
try:
id3 = mutagen.id3.ID3(filename)
except mutagen.id3.ID3NoHeaderError:
if verbose:
- print_(u"No ID3 header found; creating a new tag",
+ print(u"No ID3 header found; creating a new tag",
file=sys.stderr)
id3 = mutagen.id3.ID3()
except Exception as err:
- print_(str(err), file=sys.stderr)
+ print(str(err), file=sys.stderr)
continue
for (frame, vlist) in edits.items():
if frame == "POPM":
@@ -336,31 +332,31 @@ def write_files(edits, filenames, escape):
def list_tags(filenames):
for filename in filenames:
- print_("IDv2 tag info for", filename)
+ print("IDv2 tag info for", filename)
try:
id3 = mutagen.id3.ID3(filename, translate=False)
except mutagen.id3.ID3NoHeaderError:
- print_(u"No ID3 header found; skipping.")
+ print(u"No ID3 header found; skipping.")
except Exception as err:
- print_(str(err), file=sys.stderr)
+ print(str(err), file=sys.stderr)
raise SystemExit(1)
else:
- print_(id3.pprint())
+ print(id3.pprint())
def list_tags_raw(filenames):
for filename in filenames:
- print_("Raw IDv2 tag info for", filename)
+ print("Raw IDv2 tag info for", filename)
try:
id3 = mutagen.id3.ID3(filename, translate=False)
except mutagen.id3.ID3NoHeaderError:
- print_(u"No ID3 header found; skipping.")
+ print(u"No ID3 header found; skipping.")
except Exception as err:
- print_(str(err), file=sys.stderr)
+ print(str(err), file=sys.stderr)
raise SystemExit(1)
else:
for frame in id3.values():
- print_(str(repr(frame)))
+ print(str(repr(frame)))
def main(argv):
@@ -409,43 +405,43 @@ def main(argv):
parser.add_option(
"-a", "--artist", metavar='"ARTIST"', action="callback",
help="Set the artist information", type="string",
- callback=lambda *args: args[3].edits.append((fsnative(u"--TPE1"),
+ callback=lambda *args: args[3].edits.append(("--TPE1",
args[2])))
parser.add_option(
"-A", "--album", metavar='"ALBUM"', action="callback",
help="Set the album title information", type="string",
- callback=lambda *args: args[3].edits.append((fsnative(u"--TALB"),
+ callback=lambda *args: args[3].edits.append(("--TALB",
args[2])))
parser.add_option(
"-t", "--song", metavar='"SONG"', action="callback",
help="Set the song title information", type="string",
- callback=lambda *args: args[3].edits.append((fsnative(u"--TIT2"),
+ callback=lambda *args: args[3].edits.append(("--TIT2",
args[2])))
parser.add_option(
"-c", "--comment", metavar='"DESCRIPTION":"COMMENT":"LANGUAGE"',
action="callback", help="Set the comment information", type="string",
- callback=lambda *args: args[3].edits.append((fsnative(u"--COMM"),
+ callback=lambda *args: args[3].edits.append(("--COMM",
args[2])))
parser.add_option(
"-p", "--picture",
metavar='"FILENAME":"DESCRIPTION":"IMAGE-TYPE":"MIME-TYPE"',
action="callback", help="Set the picture", type="string",
- callback=lambda *args: args[3].edits.append((fsnative(u"--APIC"),
+ callback=lambda *args: args[3].edits.append(("--APIC",
args[2])))
parser.add_option(
"-g", "--genre", metavar='"GENRE"', action="callback",
help="Set the genre or genre number", type="string",
- callback=lambda *args: args[3].edits.append((fsnative(u"--TCON"),
+ callback=lambda *args: args[3].edits.append(("--TCON",
args[2])))
parser.add_option(
"-y", "--year", "--date", metavar='YYYY[-MM-DD]', action="callback",
help="Set the year/date", type="string",
- callback=lambda *args: args[3].edits.append((fsnative(u"--TDRC"),
+ callback=lambda *args: args[3].edits.append(("--TDRC",
args[2])))
parser.add_option(
"-T", "--track", metavar='"num/num"', action="callback",
help="Set the track number/(optional) total tracks", type="string",
- callback=lambda *args: args[3].edits.append((fsnative(u"--TRCK"),
+ callback=lambda *args: args[3].edits.append(("--TRCK",
args[2])))
for key, frame in mutagen.id3.Frames.items():
@@ -485,4 +481,4 @@ def main(argv):
def entry_point():
_sig.init()
- return main(argv)
+ return main(sys.argv)
diff --git a/lib/mutagen/_tools/moggsplit.py b/lib/mutagen/_tools/moggsplit.py
index 710f0dfe..de382060 100644
--- a/lib/mutagen/_tools/moggsplit.py
+++ b/lib/mutagen/_tools/moggsplit.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright 2006 Joe Wreschnig
#
# This program is free software; you can redistribute it and/or modify
@@ -9,9 +8,9 @@
"""Split a multiplex/chained Ogg file into its component parts."""
import os
+import sys
import mutagen.ogg
-from mutagen._senf import argv
from ._util import SignalHandler, OptionParser
@@ -72,4 +71,4 @@ def main(argv):
def entry_point():
_sig.init()
- return main(argv)
+ return main(sys.argv)
diff --git a/lib/mutagen/_tools/mutagen_inspect.py b/lib/mutagen/_tools/mutagen_inspect.py
index 0ab15f2a..fac529a9 100644
--- a/lib/mutagen/_tools/mutagen_inspect.py
+++ b/lib/mutagen/_tools/mutagen_inspect.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright 2005 Joe Wreschnig
#
# This program is free software; you can redistribute it and/or modify
@@ -8,7 +7,7 @@
"""Full tag list for any given file."""
-from mutagen._senf import print_, argv
+import sys
from ._util import SignalHandler, OptionParser
@@ -19,7 +18,7 @@ _sig = SignalHandler()
def main(argv):
from mutagen import File
- parser = OptionParser()
+ parser = OptionParser(usage="usage: %prog [options] FILE [FILE...]")
parser.add_option("--no-flac", help="Compatibility; does nothing.")
parser.add_option("--no-mp3", help="Compatibility; does nothing.")
parser.add_option("--no-apev2", help="Compatibility; does nothing.")
@@ -29,16 +28,16 @@ def main(argv):
raise SystemExit(parser.print_help() or 1)
for filename in args:
- print_(u"--", filename)
+ print(u"--", filename)
try:
- print_(u"-", File(filename).pprint())
+ print(u"-", File(filename).pprint())
except AttributeError:
- print_(u"- Unknown file type")
+ print(u"- Unknown file type")
except Exception as err:
- print_(str(err))
- print_(u"")
+ print(str(err))
+ print(u"")
def entry_point():
_sig.init()
- return main(argv)
+ return main(sys.argv)
diff --git a/lib/mutagen/_tools/mutagen_pony.py b/lib/mutagen/_tools/mutagen_pony.py
index e4a496c7..30ee5502 100644
--- a/lib/mutagen/_tools/mutagen_pony.py
+++ b/lib/mutagen/_tools/mutagen_pony.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright 2005 Joe Wreschnig, Michael Urman
#
# This program is free software; you can redistribute it and/or modify
@@ -10,8 +9,6 @@ import os
import sys
import traceback
-from mutagen._senf import print_, argv
-
from ._util import SignalHandler
@@ -76,14 +73,14 @@ class Report(object):
else:
strings.append("\nNo errors!")
- return("\n".join(strings))
+ return "\n".join(strings)
def check_dir(path):
from mutagen.mp3 import MP3
rep = Report(path)
- print_(u"Scanning", path)
+ print(u"Scanning", path)
for path, dirs, files in os.walk(path):
files.sort()
for fn in files:
@@ -100,12 +97,12 @@ def check_dir(path):
else:
rep.success(mp3.tags)
- print_(str(rep))
+ print(str(rep))
def main(argv):
if len(argv) == 1:
- print_(u"Usage:", argv[0], u"directory ...")
+ print(u"Usage:", argv[0], u"directory ...")
else:
for path in argv[1:]:
check_dir(path)
@@ -113,4 +110,4 @@ def main(argv):
def entry_point():
SignalHandler().init()
- return main(argv)
+ return main(sys.argv)
diff --git a/lib/mutagen/_util.py b/lib/mutagen/_util.py
index 7dbb57d9..b99c7c78 100644
--- a/lib/mutagen/_util.py
+++ b/lib/mutagen/_util.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2006 Joe Wreschnig
#
# This program is free software; you can redistribute it and/or modify
@@ -18,6 +17,7 @@ import codecs
import errno
import decimal
from io import BytesIO
+from typing import Tuple, List
from collections import namedtuple
from contextlib import contextmanager
@@ -25,11 +25,11 @@ from functools import wraps
from fnmatch import fnmatchcase
-_DEFAULT_BUFFER_SIZE = 2 ** 18
+_DEFAULT_BUFFER_SIZE = 2 ** 20
def endswith(text, end):
- # usefull for paths which can be both, str and bytes
+ # useful for paths which can be both, str and bytes
if isinstance(text, str):
if not isinstance(end, str):
end = end.decode("ascii")
@@ -51,7 +51,7 @@ def iterbytes(b):
return (bytes([v]) for v in b)
-def intround(value):
+def intround(value: float) -> int:
"""Given a float returns a rounded int. Should give the same result on
both Py2/3
"""
@@ -60,7 +60,7 @@ def intround(value):
value).to_integral_value(decimal.ROUND_HALF_EVEN))
-def is_fileobj(fileobj):
+def is_fileobj(fileobj) -> bool:
"""Returns:
bool: if an argument passed ot mutagen should be treated as a
file object
@@ -478,7 +478,7 @@ class DictMixin(object):
return args[0]
else:
raise
- del(self[key])
+ del self[key]
return value
def popitem(self):
@@ -540,7 +540,7 @@ class DictProxy(DictMixin):
self.__dict[key] = value
def __delitem__(self, key):
- del(self.__dict[key])
+ del self.__dict[key]
def keys(self):
return self.__dict.keys()
@@ -615,7 +615,7 @@ class cdata(object):
_fill_cdata(cdata)
-def get_size(fileobj):
+def get_size(fileobj) -> int:
"""Returns the size of the file.
The position when passed in will be preserved if no error occurs.
@@ -635,7 +635,7 @@ def get_size(fileobj):
fileobj.seek(old_pos, 0)
-def read_full(fileobj, size):
+def read_full(fileobj, size: int) -> None:
"""Like fileobj.read but raises IOError if not all requested data is
returned.
@@ -658,7 +658,7 @@ def read_full(fileobj, size):
return data
-def seek_end(fileobj, offset):
+def seek_end(fileobj, offset: int) -> None:
"""Like fileobj.seek(-offset, 2), but will not try to go beyond the start
Needed since file objects from BytesIO will not raise IOError and
@@ -683,7 +683,7 @@ def seek_end(fileobj, offset):
fileobj.seek(-offset, 2)
-def resize_file(fobj, diff, BUFFER_SIZE=_DEFAULT_BUFFER_SIZE):
+def resize_file(fobj, diff: int, BUFFER_SIZE: int = _DEFAULT_BUFFER_SIZE) -> None:
"""Resize a file by `diff`.
New space will be filled with zeros.
@@ -720,7 +720,8 @@ def resize_file(fobj, diff, BUFFER_SIZE=_DEFAULT_BUFFER_SIZE):
raise
-def move_bytes(fobj, dest, src, count, BUFFER_SIZE=_DEFAULT_BUFFER_SIZE):
+def move_bytes(fobj, dest: int, src: int, count: int,
+ BUFFER_SIZE: int = _DEFAULT_BUFFER_SIZE) -> None:
"""Moves data around using read()/write().
Args:
@@ -763,7 +764,8 @@ def move_bytes(fobj, dest, src, count, BUFFER_SIZE=_DEFAULT_BUFFER_SIZE):
fobj.flush()
-def insert_bytes(fobj, size, offset, BUFFER_SIZE=_DEFAULT_BUFFER_SIZE):
+def insert_bytes(fobj, size: int, offset: int,
+ BUFFER_SIZE: int = _DEFAULT_BUFFER_SIZE) -> None:
"""Insert size bytes of empty space starting at offset.
fobj must be an open file object, open rb+ or
@@ -791,7 +793,8 @@ def insert_bytes(fobj, size, offset, BUFFER_SIZE=_DEFAULT_BUFFER_SIZE):
move_bytes(fobj, offset + size, offset, movesize, BUFFER_SIZE)
-def delete_bytes(fobj, size, offset, BUFFER_SIZE=_DEFAULT_BUFFER_SIZE):
+def delete_bytes(fobj, size: int, offset: int,
+ BUFFER_SIZE: int = _DEFAULT_BUFFER_SIZE) -> None:
"""Delete size bytes of empty space starting at offset.
fobj must be an open file object, open rb+ or
@@ -819,7 +822,7 @@ def delete_bytes(fobj, size, offset, BUFFER_SIZE=_DEFAULT_BUFFER_SIZE):
resize_file(fobj, -size, BUFFER_SIZE)
-def resize_bytes(fobj, old_size, new_size, offset):
+def resize_bytes(fobj, old_size: int, new_size: int, offset: int) -> None:
"""Resize an area in a file adding and deleting at the end of it.
Does nothing if no resizing is needed.
@@ -865,7 +868,8 @@ def dict_match(d, key, default=None):
return default
-def encode_endian(text, encoding, errors="strict", le=True):
+def encode_endian(text: str, encoding: str,
+ errors: str = "strict", le: bool = True) -> bytes:
"""Like text.encode(encoding) but always returns little endian/big endian
BOMs instead of the system one.
@@ -897,7 +901,8 @@ def encode_endian(text, encoding, errors="strict", le=True):
return text.encode(encoding, errors)
-def decode_terminated(data, encoding, strict=True):
+def decode_terminated(data: bytes, encoding: str,
+ strict: bool = True) -> Tuple[str, bytes]:
"""Returns the decoded data until the first NULL terminator
and all data after it.
@@ -937,7 +942,7 @@ def decode_terminated(data, encoding, strict=True):
# slow path
decoder = codec_info.incrementaldecoder()
- r = []
+ r: List[str] = []
for i, b in enumerate(iterbytes(data)):
c = decoder.decode(b)
if c == u"\x00":
@@ -963,7 +968,7 @@ class BitReader(object):
self._bits = 0
self._pos = fileobj.tell()
- def bits(self, count):
+ def bits(self, count: int) -> int:
"""Reads `count` bits and returns an uint, MSB read first.
May raise BitReaderError if not enough data could be read or
@@ -988,7 +993,7 @@ class BitReader(object):
assert self._bits < 8
return value
- def bytes(self, count):
+ def bytes(self, count: int) -> bytes:
"""Returns a bytearray of length `count`. Works unaligned."""
if count < 0:
@@ -1003,7 +1008,7 @@ class BitReader(object):
return bytes(bytearray(self.bits(8) for _ in range(count)))
- def skip(self, count):
+ def skip(self, count: int) -> None:
"""Skip `count` bits.
Might raise BitReaderError if there wasn't enough data to skip,
@@ -1022,12 +1027,12 @@ class BitReader(object):
count -= n_bytes * 8
self.bits(count)
- def get_position(self):
+ def get_position(self) -> int:
"""Returns the amount of bits read or skipped so far"""
return (self._fileobj.tell() - self._pos) * 8 - self._bits
- def align(self):
+ def align(self) -> int:
"""Align to the next byte, returns the amount of bits skipped"""
bits = self._bits
@@ -1035,7 +1040,7 @@ class BitReader(object):
self._bits = 0
return bits
- def is_aligned(self):
+ def is_aligned(self) -> bool:
"""If we are currently aligned to bytes and nothing is buffered"""
return self._bits == 0
diff --git a/lib/mutagen/_vorbis.py b/lib/mutagen/_vorbis.py
index f71144d3..1168d180 100644
--- a/lib/mutagen/_vorbis.py
+++ b/lib/mutagen/_vorbis.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2005-2006 Joe Wreschnig
# 2013 Christoph Reiter
#
@@ -23,7 +22,7 @@ import mutagen
from mutagen._util import DictMixin, cdata, MutagenError, reraise
-def is_valid_key(key):
+def is_valid_key(key: str) -> bool:
"""Return true if a string is a valid Vorbis comment key.
Valid Vorbis comment keys are printable ASCII between 0x20 (space)
@@ -159,7 +158,7 @@ class VComment(mutagen.Tags, list):
return True
- def clear(self):
+ def clear(self) -> None:
"""Clear all keys from the comment."""
for i in list(self):
@@ -197,7 +196,7 @@ class VComment(mutagen.Tags, list):
f.write(b"\x01")
return f.getvalue()
- def pprint(self):
+ def pprint(self) -> str:
def _decode(value):
if not isinstance(value, str):
@@ -208,7 +207,7 @@ class VComment(mutagen.Tags, list):
return u"\n".join(tags)
-class VCommentDict(VComment, DictMixin):
+class VCommentDict(VComment, DictMixin): # type: ignore
"""A VComment that looks like a dictionary.
This object differs from a dictionary in two ways. First,
@@ -290,7 +289,7 @@ class VCommentDict(VComment, DictMixin):
if not isinstance(values, list):
values = [values]
try:
- del(self[key])
+ del self[key]
except KeyError:
pass
diff --git a/lib/mutagen/aac.py b/lib/mutagen/aac.py
index 30162d03..274b20df 100644
--- a/lib/mutagen/aac.py
+++ b/lib/mutagen/aac.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2014 Christoph Reiter
#
# This program is free software; you can redistribute it and/or modify
diff --git a/lib/mutagen/ac3.py b/lib/mutagen/ac3.py
index 467434e5..d31408db 100644
--- a/lib/mutagen/ac3.py
+++ b/lib/mutagen/ac3.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2019 Philipp Wolfer
#
# This program is free software; you can redistribute it and/or modify
@@ -220,7 +219,7 @@ class AC3Info(StreamInfo):
if timecod2e:
r.skip(14) # Time Code Second Half
if r.bits(1): # Additional Bit Stream Information Exists
- addbsil = r.bit(6) # Additional Bit Stream Information Length
+ addbsil = r.bits(6) # Additional Bit Stream Information Length
r.skip((addbsil + 1) * 8)
@staticmethod
@@ -271,7 +270,7 @@ class AC3Info(StreamInfo):
if r.bits(1): # blkid
r.skip(6) # frmsizecod
if r.bits(1): # Additional Bit Stream Information Exists
- addbsil = r.bit(6) # Additional Bit Stream Information Length
+ addbsil = r.bits(6) # Additional Bit Stream Information Length
r.skip((addbsil + 1) * 8)
@staticmethod
diff --git a/lib/mutagen/aiff.py b/lib/mutagen/aiff.py
index 7c0015d1..74d2f03d 100644
--- a/lib/mutagen/aiff.py
+++ b/lib/mutagen/aiff.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2014 Evan Purkhiser
# 2014 Ben Ockmore
# 2019-2020 Philipp Wolfer
diff --git a/lib/mutagen/apev2.py b/lib/mutagen/apev2.py
index 13ca256d..4c57d94d 100644
--- a/lib/mutagen/apev2.py
+++ b/lib/mutagen/apev2.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2005 Joe Wreschnig
#
# This program is free software; you can redistribute it and/or modify
@@ -41,6 +40,7 @@ from mutagen._util import DictMixin, cdata, delete_bytes, total_ordering, \
def is_valid_apev2_key(key):
+ # https://wiki.hydrogenaud.io/index.php?title=APE_key
if not isinstance(key, str):
raise TypeError("APEv2 key must be str")
@@ -252,8 +252,8 @@ class _CIDictProxy(DictMixin):
def __delitem__(self, key):
lower = key.lower()
- del(self.__casemap[lower])
- del(self.__dict[lower])
+ del self.__casemap[lower]
+ del self.__dict[lower]
def keys(self):
return [self.__casemap.get(key, key) for key in self.__dict.keys()]
@@ -327,6 +327,10 @@ class APEv2(_CIDictProxy, Metadata):
key = key.decode("ascii")
except UnicodeError as err:
reraise(APEBadItemError, err, sys.exc_info()[2])
+
+ if not is_valid_apev2_key(key):
+ raise APEBadItemError("%r is not a valid APEv2 key" % key)
+
value = fileobj.read(size)
if len(value) != size:
raise APEBadItemError
@@ -517,7 +521,7 @@ def APEValue(value, kind):
class _APEValue(object):
- kind = None
+ kind: int
value = None
def __init__(self, value, kind=None):
diff --git a/lib/mutagen/asf/__init__.py b/lib/mutagen/asf/__init__.py
index 8b64e2a2..a756a691 100644
--- a/lib/mutagen/asf/__init__.py
+++ b/lib/mutagen/asf/__init__.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2005-2006 Joe Wreschnig
# Copyright (C) 2006-2007 Lukas Lalinsky
#
@@ -74,7 +73,7 @@ class ASFInfo(StreamInfo):
return s
-class ASFTags(list, DictMixin, Tags):
+class ASFTags(list, DictMixin, Tags): # type: ignore
"""ASFTags()
Dictionary containing ASF attributes.
@@ -148,7 +147,7 @@ class ASFTags(list, DictMixin, Tags):
to_append.append((key, value))
try:
- del(self[key])
+ del self[key]
except KeyError:
pass
diff --git a/lib/mutagen/asf/_attrs.py b/lib/mutagen/asf/_attrs.py
index 823ea2fb..0417d9db 100644
--- a/lib/mutagen/asf/_attrs.py
+++ b/lib/mutagen/asf/_attrs.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2005-2006 Joe Wreschnig
# Copyright (C) 2006-2007 Lukas Lalinsky
#
@@ -9,6 +8,7 @@
import sys
import struct
+from typing import Dict, Type
from mutagen._util import total_ordering, reraise
@@ -18,9 +18,9 @@ from ._util import ASFError
class ASFBaseAttribute(object):
"""Generic attribute."""
- TYPE = None
+ TYPE: int
- _TYPES = {}
+ _TYPES: "Dict[int, Type[ASFBaseAttribute]]" = {}
value = None
"""The Python value of this attribute (type depends on the class)"""
diff --git a/lib/mutagen/asf/_objects.py b/lib/mutagen/asf/_objects.py
index 0560b116..df95bace 100644
--- a/lib/mutagen/asf/_objects.py
+++ b/lib/mutagen/asf/_objects.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2005-2006 Joe Wreschnig
# Copyright (C) 2006-2007 Lukas Lalinsky
#
@@ -8,6 +7,7 @@
# (at your option) any later version.
import struct
+from typing import Dict, Type
from mutagen._util import cdata, get_size
from mutagen._tags import PaddingInfo
@@ -19,8 +19,8 @@ from ._attrs import ASFBaseAttribute, ASFUnicodeAttribute
class BaseObject(object):
"""Base ASF object."""
- GUID = None
- _TYPES = {}
+ GUID: bytes
+ _TYPES: "Dict[bytes, Type[BaseObject]]" = {}
def __init__(self):
self.objects = []
diff --git a/lib/mutagen/asf/_util.py b/lib/mutagen/asf/_util.py
index 45ade0e2..6ff0c992 100644
--- a/lib/mutagen/asf/_util.py
+++ b/lib/mutagen/asf/_util.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2005-2006 Joe Wreschnig
# Copyright (C) 2006-2007 Lukas Lalinsky
#
@@ -8,6 +7,7 @@
# (at your option) any later version.
import struct
+from typing import List
from mutagen._util import MutagenError
@@ -24,7 +24,7 @@ class ASFHeaderError(error):
pass
-def guid2bytes(s):
+def guid2bytes(s: str) -> bytes:
"""Converts a GUID to the serialized bytes representation"""
assert isinstance(s, str)
@@ -38,13 +38,13 @@ def guid2bytes(s):
])
-def bytes2guid(s):
+def bytes2guid(s: bytes) -> str:
"""Converts a serialized GUID to a text GUID"""
assert isinstance(s, bytes)
u = struct.unpack
- v = []
+ v: List[int] = []
v.extend(u("HQ", s[8:10] + b"\x00\x00" + s[10:]))
return "%08X-%04X-%04X-%04X-%012X" % tuple(v)
@@ -285,7 +285,7 @@ CODECS = {
0xA10C: u"Media Foundation Spectrum Analyzer Output",
0xA10D: u"GSM 6.10 (Full-Rate) Speech",
0xA10E: u"GSM 6.20 (Half-Rate) Speech",
- 0xA10F: u"GSM 6.60 (Enchanced Full-Rate) Speech",
+ 0xA10F: u"GSM 6.60 (Enhanced Full-Rate) Speech",
0xA110: u"GSM 6.90 (Adaptive Multi-Rate) Speech",
0xA111: u"GSM Adaptive Multi-Rate WideBand Speech",
0xA112: u"Polycom G.722",
diff --git a/lib/mutagen/dsdiff.py b/lib/mutagen/dsdiff.py
index 50f7a5cc..c4292b7c 100644
--- a/lib/mutagen/dsdiff.py
+++ b/lib/mutagen/dsdiff.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2020 Philipp Wolfer
#
# This program is free software; you can redistribute it and/or modify
diff --git a/lib/mutagen/dsf.py b/lib/mutagen/dsf.py
index 8686efeb..121a01ba 100644
--- a/lib/mutagen/dsf.py
+++ b/lib/mutagen/dsf.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2017 Boris Pruessmann
#
# This program is free software; you can redistribute it and/or modify
diff --git a/lib/mutagen/easyid3.py b/lib/mutagen/easyid3.py
index 08a2fef4..510b9222 100644
--- a/lib/mutagen/easyid3.py
+++ b/lib/mutagen/easyid3.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2006 Joe Wreschnig
#
# This program is free software; you can redistribute it and/or modify
@@ -12,6 +11,8 @@ EasyID3 is a wrapper around mutagen.id3.ID3 to make ID3 tags appear
more like Vorbis or APEv2 tags.
"""
+from typing import Callable, Dict
+
import mutagen.id3
from mutagen import Metadata
@@ -64,10 +65,10 @@ class EasyID3(DictMixin, Metadata):
"""
- Set = {}
- Get = {}
- Delete = {}
- List = {}
+ Set: Dict[str, Callable] = {}
+ Get: Dict[str, Callable] = {}
+ Delete: Dict[str, Callable] = {}
+ List: Dict[str, Callable] = {}
# For compatibility.
valid_keys = Get
@@ -129,7 +130,7 @@ class EasyID3(DictMixin, Metadata):
frame.text = value
def deleter(id3, key):
- del(id3[frameid])
+ del id3[frameid]
cls.RegisterKey(key, getter, setter, deleter)
@@ -159,7 +160,7 @@ class EasyID3(DictMixin, Metadata):
id3.add(mutagen.id3.TXXX(encoding=enc, text=value, desc=desc))
def deleter(id3, key):
- del(id3[frameid])
+ del id3[frameid]
cls.RegisterKey(key, getter, setter, deleter)
@@ -269,7 +270,7 @@ def genre_set(id3, key, value):
def genre_delete(id3, key):
- del(id3["TCON"])
+ del id3["TCON"]
def date_get(id3, key):
@@ -281,7 +282,7 @@ def date_set(id3, key, value):
def date_delete(id3, key):
- del(id3["TDRC"])
+ del id3["TDRC"]
def original_date_get(id3, key):
@@ -293,7 +294,7 @@ def original_date_set(id3, key, value):
def original_date_delete(id3, key):
- del(id3["TDOR"])
+ del id3["TDOR"]
def performer_get(id3, key):
@@ -338,7 +339,7 @@ def performer_delete(id3, key):
elif people:
mcl.people = people
else:
- del(id3["TMCL"])
+ del id3["TMCL"]
def performer_list(id3, key):
@@ -368,7 +369,7 @@ def musicbrainz_trackid_set(id3, key, value):
def musicbrainz_trackid_delete(id3, key):
- del(id3["UFID:http://musicbrainz.org"])
+ del id3["UFID:http://musicbrainz.org"]
def website_get(id3, key):
@@ -420,7 +421,7 @@ def gain_delete(id3, key):
if frame.peak:
frame.gain = 0.0
else:
- del(id3["RVA2:" + key[11:-5]])
+ del id3["RVA2:" + key[11:-5]]
def peak_get(id3, key):
@@ -456,7 +457,7 @@ def peak_delete(id3, key):
if frame.gain:
frame.peak = 0.0
else:
- del(id3["RVA2:" + key[11:-5]])
+ del id3["RVA2:" + key[11:-5]]
def peakgain_list(id3, key):
@@ -477,6 +478,7 @@ for frameid, key in {
"TLEN": "length",
"TMED": "media",
"TMOO": "mood",
+ "TIT1": "grouping",
"TIT2": "title",
"TIT3": "version",
"TPE1": "artist",
@@ -553,4 +555,4 @@ class EasyID3FileType(ID3FileType):
tags (`EasyID3`)
"""
- ID3 = EasyID3
+ ID3 = EasyID3 # type: ignore
diff --git a/lib/mutagen/easymp4.py b/lib/mutagen/easymp4.py
index 54d9ab5b..f56faccc 100644
--- a/lib/mutagen/easymp4.py
+++ b/lib/mutagen/easymp4.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2009 Joe Wreschnig
#
# This program is free software; you can redistribute it and/or modify
@@ -6,6 +5,8 @@
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
+from typing import Dict, Callable
+
from mutagen import Tags
from mutagen._util import DictMixin, dict_match
from mutagen.mp4 import MP4, MP4Tags, error, delete
@@ -31,10 +32,10 @@ class EasyMP4Tags(DictMixin, Tags):
MP4, not EasyMP4.
"""
- Set = {}
- Get = {}
- Delete = {}
- List = {}
+ Set: Dict[str, Callable] = {}
+ Get: Dict[str, Callable] = {}
+ Delete: Dict[str, Callable] = {}
+ List: Dict[str, Callable] = {}
def __init__(self, *args, **kwargs):
self.__mp4 = MP4Tags(*args, **kwargs)
@@ -95,7 +96,7 @@ class EasyMP4Tags(DictMixin, Tags):
tags[atomid] = value
def deleter(tags, key):
- del(tags[atomid])
+ del tags[atomid]
cls.RegisterKey(key, getter, setter, deleter)
@@ -112,7 +113,7 @@ class EasyMP4Tags(DictMixin, Tags):
tags[atomid] = [clamp(v) for v in map(int, value)]
def deleter(tags, key):
- del(tags[atomid])
+ del tags[atomid]
cls.RegisterKey(key, getter, setter, deleter)
@@ -143,7 +144,7 @@ class EasyMP4Tags(DictMixin, Tags):
tags[atomid] = data
def deleter(tags, key):
- del(tags[atomid])
+ del tags[atomid]
cls.RegisterKey(key, getter, setter, deleter)
@@ -172,7 +173,7 @@ class EasyMP4Tags(DictMixin, Tags):
tags[atomid] = encoded
def deleter(tags, key):
- del(tags[atomid])
+ del tags[atomid]
cls.RegisterKey(key, getter, setter, deleter)
@@ -276,7 +277,7 @@ class EasyMP4(MP4):
tags (`EasyMP4Tags`)
"""
- MP4Tags = EasyMP4Tags
+ MP4Tags = EasyMP4Tags # type: ignore
Get = EasyMP4Tags.Get
Set = EasyMP4Tags.Set
diff --git a/lib/mutagen/flac.py b/lib/mutagen/flac.py
index 58a12d07..bde374b4 100644
--- a/lib/mutagen/flac.py
+++ b/lib/mutagen/flac.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2005 Joe Wreschnig
#
# This program is free software; you can redistribute it and/or modify
@@ -281,7 +280,7 @@ class SeekPoint(tuple):
"""
def __new__(cls, first_sample, byte_offset, num_samples):
- return super(cls, SeekPoint).__new__(
+ return super(SeekPoint, cls).__new__(
cls, (first_sample, byte_offset, num_samples))
def __getnewargs__(self):
@@ -374,7 +373,7 @@ class CueSheetTrackIndex(tuple):
"""
def __new__(cls, index_number, index_offset):
- return super(cls, CueSheetTrackIndex).__new__(
+ return super(CueSheetTrackIndex, cls).__new__(
cls, (index_number, index_offset))
index_number = property(lambda self: self[0])
@@ -388,7 +387,7 @@ class CueSheetTrack(object):
For CD-DA, track_numbers must be 1-99, or 170 for the
lead-out. Track_numbers must be unique within a cue sheet. There
- must be atleast one index in every track except the lead-out track
+ must be at least one index in every track except the lead-out track
which must have none.
Attributes:
@@ -520,7 +519,7 @@ class CueSheet(MetadataBlock):
track_flags |= 0x40
track_packed = struct.pack(
self.__CUESHEET_TRACK_FORMAT, track.start_offset,
- track.track_number, track.isrc, track_flags,
+ track.track_number, track.isrc or b"\0", track_flags,
len(track.indexes))
f.write(track_packed)
for index in track.indexes:
@@ -688,7 +687,6 @@ class FLAC(mutagen.FileType):
_mimes = ["audio/flac", "audio/x-flac", "application/x-flac"]
- info = None
tags = None
METADATA_BLOCKS = [StreamInfo, Padding, None, SeekTable, VCFLACDict,
@@ -714,7 +712,7 @@ class FLAC(mutagen.FileType):
if block_type._distrust_size:
# Some jackass is writing broken Metadata block length
# for Vorbis comment blocks, and the FLAC reference
- # implementaton can parse them (mostly by accident),
+ # implementation can parse them (mostly by accident),
# so we have to too. Instead of parsing the size
# given, parse an actual Vorbis comment, leaving
# fileobj in the right position.
@@ -798,7 +796,7 @@ class FLAC(mutagen.FileType):
pass
try:
- self.metadata_blocks[0].length
+ self.info.length
except (AttributeError, IndexError):
raise FLACNoHeaderError("Stream info block not found")
@@ -812,7 +810,11 @@ class FLAC(mutagen.FileType):
@property
def info(self):
- return self.metadata_blocks[0]
+ streaminfo_blocks = [
+ block for block in self.metadata_blocks
+ if block.code == StreamInfo.code
+ ]
+ return streaminfo_blocks[0]
def add_picture(self, picture):
"""Add a new picture to the file.
@@ -844,6 +846,15 @@ class FLAC(mutagen.FileType):
If no filename is given, the one most recently loaded is used.
"""
+ # add new cuesheet and seektable
+ if self.cuesheet and self.cuesheet not in self.metadata_blocks:
+ if not isinstance(self.cuesheet, CueSheet):
+ raise ValueError("Invalid cuesheet object type!")
+ self.metadata_blocks.append(self.cuesheet)
+ if self.seektable and self.seektable not in self.metadata_blocks:
+ if not isinstance(self.seektable, SeekTable):
+ raise ValueError("Invalid seektable object type!")
+ self.metadata_blocks.append(self.seektable)
self._save(filething, self.metadata_blocks, deleteid3, padding)
diff --git a/lib/mutagen/id3/__init__.py b/lib/mutagen/id3/__init__.py
index adebd7a3..b70638bd 100644
--- a/lib/mutagen/id3/__init__.py
+++ b/lib/mutagen/id3/__init__.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2005 Michael Urman
# 2006 Lukas Lalinsky
# 2013 Christoph Reiter
diff --git a/lib/mutagen/id3/_file.py b/lib/mutagen/id3/_file.py
index 0ec11bd4..95c460f2 100644
--- a/lib/mutagen/id3/_file.py
+++ b/lib/mutagen/id3/_file.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2005 Michael Urman
# 2006 Lukas Lalinsky
# 2013 Christoph Reiter
@@ -167,7 +166,10 @@ class ID3(ID3Tags, mutagen.Metadata):
if known_frames is not None:
self._header._known_frames = known_frames
- data = read_full(fileobj, self.size - 10)
+ size = self.size - 10
+ if self.f_extended:
+ size -= 4 + len(self._header._extdata)
+ data = read_full(fileobj, size)
remaining_data = self._read(self._header, data)
self._padding = len(remaining_data)
@@ -232,7 +234,7 @@ class ID3(ID3Tags, mutagen.Metadata):
if 0, ID3v1 tags will be removed.
if 1, ID3v1 tags will be updated but not added.
if 2, ID3v1 tags will be created and/or updated
- v2 (int):
+ v2_version (int):
version of ID3v2 tags (3 or 4).
v23_sep (text):
the separator used to join multiple text values
diff --git a/lib/mutagen/id3/_frames.py b/lib/mutagen/id3/_frames.py
index 7b778b1c..bee693bd 100644
--- a/lib/mutagen/id3/_frames.py
+++ b/lib/mutagen/id3/_frames.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2005 Michael Urman
#
# This program is free software; you can redistribute it and/or modify
@@ -8,6 +7,7 @@
import zlib
from struct import unpack
+from typing import Sequence
from ._util import ID3JunkFrameError, ID3EncryptionUnsupportedError, unsynch, \
ID3SaveConfig, error
@@ -17,7 +17,7 @@ from ._specs import BinaryDataSpec, StringSpec, Latin1TextSpec, \
VolumeAdjustmentSpec, ChannelSpec, MultiSpec, SynchronizedTextSpec, \
KeyEventSpec, TimeStampSpec, EncodedNumericPartTextSpec, \
EncodedNumericTextSpec, SpecError, PictureTypeSpec, ID3FramesSpec, \
- Latin1TextListSpec, CTOCFlagsSpec, FrameIDSpec, RVASpec
+ Latin1TextListSpec, CTOCFlagsSpec, FrameIDSpec, RVASpec, Spec
def _bytes2key(b):
@@ -49,8 +49,8 @@ class Frame(object):
FLAG24_UNSYNCH = 0x0002
FLAG24_DATALEN = 0x0001
- _framespec = []
- _optionalspec = []
+ _framespec: Sequence[Spec] = []
+ _optionalspec: Sequence[Spec] = []
def __init__(self, *args, **kwargs):
if len(args) == 1 and len(kwargs) == 0 and \
@@ -291,7 +291,7 @@ class Frame(object):
frame._readData(header, data)
return frame
- def __hash__(self):
+ def __hash__(self: object):
raise TypeError("Frame objects are unhashable")
@@ -514,7 +514,7 @@ class UrlFrame(Frame):
ASCII.
"""
- _framespec = [
+ _framespec: Sequence[Spec] = [
Latin1TextSpec('url'),
]
@@ -568,7 +568,7 @@ class TCON(TextFrame):
genre_re = re.compile(r"((?:\((?P[0-9]+|RX|CR)\))*)(?P.+)?")
for value in self.text:
# 255 possible entries in id3v1
- if value.isdigit() and int(value) < 256:
+ if value.isdecimal() and int(value) < 256:
try:
genres.append(self.GENRES[int(value)])
except IndexError:
@@ -832,7 +832,7 @@ class TSOC(TextFrame):
class TSOP(TextFrame):
- "Perfomer Sort Order key"
+ "Performer Sort Order key"
class TSOT(TextFrame):
@@ -1110,7 +1110,7 @@ class SYLT(Frame):
class COMM(TextFrame):
"""User comment.
- User comment frames have a descrption, like TXXX, and also a three
+ User comment frames have a description, like TXXX, and also a three
letter ISO language code in the 'lang' attribute.
"""
@@ -1895,7 +1895,7 @@ class TS2(TSO2):
class TSP(TSOP):
- "Perfomer Sort Order key"
+ "Performer Sort Order key"
class TSC(TSOC):
@@ -1931,7 +1931,7 @@ class TOT(TOAL):
class TOA(TOPE):
- "Original Artist/Perfomer"
+ "Original Artist/Performer"
class TOL(TOLY):
@@ -1995,7 +1995,7 @@ class STC(SYTC):
class ULT(USLT):
- "Unsychronised lyrics/text transcription"
+ "Unsynchronised lyrics/text transcription"
class SLT(SYLT):
diff --git a/lib/mutagen/id3/_id3v1.py b/lib/mutagen/id3/_id3v1.py
index 13138e75..4e1ca05f 100644
--- a/lib/mutagen/id3/_id3v1.py
+++ b/lib/mutagen/id3/_id3v1.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2005 Michael Urman
# 2006 Lukas Lalinsky
# 2013 Christoph Reiter
diff --git a/lib/mutagen/id3/_specs.py b/lib/mutagen/id3/_specs.py
index 99d42206..44c19106 100644
--- a/lib/mutagen/id3/_specs.py
+++ b/lib/mutagen/id3/_specs.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2005 Michael Urman
#
# This program is free software; you can redistribute it and/or modify
@@ -541,7 +540,7 @@ class MultiSpec(Spec):
spec = self.specs[0]
# Merge single text spec multispecs only.
- # (TimeStampSpec beeing the exception, but it's not a valid v2.3 frame)
+ # (TimeStampSpec being the exception, but it's not a valid v2.3 frame)
if not isinstance(spec, EncodedTextSpec) or \
isinstance(spec, TimeStampSpec):
return value
diff --git a/lib/mutagen/id3/_tags.py b/lib/mutagen/id3/_tags.py
index 1774fd3c..48c741a3 100644
--- a/lib/mutagen/id3/_tags.py
+++ b/lib/mutagen/id3/_tags.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright 2005 Michael Urman
# Copyright 2016 Christoph Reiter
#
@@ -109,6 +108,9 @@ class ID3Header(object):
# excludes itself."
extsize = struct.unpack('>L', extsize_data)[0]
+ if extsize < 0:
+ raise error("invalid extended header size")
+
self._extdata = read_full(fileobj, extsize)
@@ -254,12 +256,12 @@ class ID3Tags(DictProxy, Tags):
"""
if key in self:
- del(self[key])
+ del self[key]
else:
key = key + ":"
for k in list(self.keys()):
if k.startswith(key):
- del(self[k])
+ del self[k]
def pprint(self):
"""
@@ -367,17 +369,20 @@ class ID3Tags(DictProxy, Tags):
# TDAT, TYER, and TIME have been turned into TDRC.
timestamps = []
old_frames = [self.pop(n, []) for n in ["TYER", "TDAT", "TIME"]]
- for y, d, t in zip_longest(*old_frames, fillvalue=u""):
- ym = re.match(r"([0-9]+)\Z", y)
- dm = re.match(r"([0-9]{2})([0-9]{2})\Z", d)
- tm = re.match(r"([0-9]{2})([0-9]{2})\Z", t)
+ for tyer, tdat, time in zip_longest(*old_frames, fillvalue=""):
+ ym = re.match(r"([0-9]{4})(-[0-9]{2}-[0-9]{2})?\Z", tyer)
+ dm = re.match(r"([0-9]{2})([0-9]{2})\Z", tdat)
+ tm = re.match(r"([0-9]{2})([0-9]{2})\Z", time)
timestamp = ""
if ym:
- timestamp += u"%s" % ym.groups()
+ (year, month_day) = ym.groups()
+ timestamp += "%s" % year
if dm:
- timestamp += u"-%s-%s" % dm.groups()[::-1]
+ month_day = "-%s-%s" % dm.groups()[::-1]
+ if month_day:
+ timestamp += month_day
if tm:
- timestamp += u"T%s:%s:00" % tm.groups()
+ timestamp += "T%s:%s:00" % tm.groups()
if timestamp:
timestamps.append(timestamp)
if timestamps and "TDRC" not in self:
@@ -402,7 +407,7 @@ class ID3Tags(DictProxy, Tags):
# should have been removed already.
for key in ["RVAD", "EQUA", "TRDA", "TSIZ", "TDAT", "TIME"]:
if key in self:
- del(self[key])
+ del self[key]
# Recurse into chapters
for f in self.getall("CHAP"):
@@ -466,7 +471,7 @@ class ID3Tags(DictProxy, Tags):
for key in v24_frames:
if key in self:
- del(self[key])
+ del self[key]
# Recurse into chapters
for f in self.getall("CHAP"):
diff --git a/lib/mutagen/id3/_util.py b/lib/mutagen/id3/_util.py
index 76932b26..699c5ded 100644
--- a/lib/mutagen/id3/_util.py
+++ b/lib/mutagen/id3/_util.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2005 Michael Urman
# 2013 Christoph Reiter
# 2014 Ben Ockmore
@@ -8,6 +7,8 @@
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
+from typing import Union
+
from mutagen._util import MutagenError
@@ -45,7 +46,7 @@ class ID3JunkFrameError(error):
class unsynch(object):
@staticmethod
- def decode(value):
+ def decode(value: bytes) -> bytes:
fragments = bytearray(value).split(b'\xff')
if len(fragments) > 1 and not fragments[-1]:
raise ValueError('string ended unsafe')
@@ -60,7 +61,7 @@ class unsynch(object):
return bytes(bytearray(b'\xff').join(fragments))
@staticmethod
- def encode(value):
+ def encode(value: bytes) -> bytes:
fragments = bytearray(value).split(b'\xff')
for f in fragments[1:]:
if (not f) or (f[0] >= 0xE0) or (f[0] == 0x00):
@@ -74,7 +75,8 @@ class _BitPaddedMixin(object):
return self.to_str(self, self.bits, self.bigendian, width, minwidth)
@staticmethod
- def to_str(value, bits=7, bigendian=True, width=4, minwidth=4):
+ def to_str(value: int, bits: int = 7, bigendian: bool = True,
+ width: int = 4, minwidth: int = 4) -> bytes:
mask = (1 << bits) - 1
if width != -1:
@@ -102,7 +104,7 @@ class _BitPaddedMixin(object):
return bytes(bytes_)
@staticmethod
- def has_valid_padding(value, bits=7):
+ def has_valid_padding(value: Union[int, bytes], bits: int = 7) -> bool:
"""Whether the padding bits are all zero"""
assert bits <= 8
diff --git a/lib/mutagen/m4a.py b/lib/mutagen/m4a.py
index c7583f8e..eea2136b 100644
--- a/lib/mutagen/m4a.py
+++ b/lib/mutagen/m4a.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright 2006 Joe Wreschnig
#
# This program is free software; you can redistribute it and/or modify
diff --git a/lib/mutagen/monkeysaudio.py b/lib/mutagen/monkeysaudio.py
index 6d5d021c..30cf78c0 100644
--- a/lib/mutagen/monkeysaudio.py
+++ b/lib/mutagen/monkeysaudio.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2006 Lukas Lalinsky
#
# This program is free software; you can redistribute it and/or modify
diff --git a/lib/mutagen/mp3/__init__.py b/lib/mutagen/mp3/__init__.py
index 70a3891a..fa7b41d3 100644
--- a/lib/mutagen/mp3/__init__.py
+++ b/lib/mutagen/mp3/__init__.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2006 Joe Wreschnig
#
# This program is free software; you can redistribute it and/or modify
@@ -81,16 +80,16 @@ class MPEGFrame(object):
# Map (version, layer) tuples to bitrates.
__BITRATE = {
- (1, 1): [0, 32, 64, 96, 128, 160, 192, 224,
- 256, 288, 320, 352, 384, 416, 448],
- (1, 2): [0, 32, 48, 56, 64, 80, 96, 112, 128,
- 160, 192, 224, 256, 320, 384],
- (1, 3): [0, 32, 40, 48, 56, 64, 80, 96, 112,
- 128, 160, 192, 224, 256, 320],
- (2, 1): [0, 32, 48, 56, 64, 80, 96, 112, 128,
- 144, 160, 176, 192, 224, 256],
- (2, 2): [0, 8, 16, 24, 32, 40, 48, 56, 64,
- 80, 96, 112, 128, 144, 160],
+ (1., 1): [0, 32, 64, 96, 128, 160, 192, 224,
+ 256, 288, 320, 352, 384, 416, 448],
+ (1., 2): [0, 32, 48, 56, 64, 80, 96, 112, 128,
+ 160, 192, 224, 256, 320, 384],
+ (1., 3): [0, 32, 40, 48, 56, 64, 80, 96, 112,
+ 128, 160, 192, 224, 256, 320],
+ (2., 1): [0, 32, 48, 56, 64, 80, 96, 112, 128,
+ 144, 160, 176, 192, 224, 256],
+ (2., 2): [0, 8, 16, 24, 32, 40, 48, 56, 64,
+ 80, 96, 112, 128, 144, 160],
}
__BITRATE[(2, 3)] = __BITRATE[(2, 2)]
@@ -456,7 +455,11 @@ class MP3(ID3FileType):
def score(filename, fileobj, header_data):
filename = filename.lower()
- return (header_data.startswith(b"ID3") * 2 +
+ return ((header_data.startswith(b"ID3") or
+ header_data.startswith(b'\xFF\xF2') or
+ header_data.startswith(b'\xFF\xF3') or
+ header_data.startswith(b'\xFF\xFA') or
+ header_data.startswith(b'\xFF\xFB')) * 2 +
endswith(filename, b".mp3") +
endswith(filename, b".mp2") + endswith(filename, b".mpg") +
endswith(filename, b".mpeg"))
@@ -479,4 +482,4 @@ class EasyMP3(MP3):
"""
from mutagen.easyid3 import EasyID3 as ID3
- ID3 = ID3
+ ID3 = ID3 # type: ignore
diff --git a/lib/mutagen/mp3/_util.py b/lib/mutagen/mp3/_util.py
index 96d897d6..b67ab6aa 100644
--- a/lib/mutagen/mp3/_util.py
+++ b/lib/mutagen/mp3/_util.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright 2015 Christoph Reiter
#
# This program is free software; you can redistribute it and/or modify
@@ -14,6 +13,7 @@ http://wiki.hydrogenaud.io/index.php?title=MP3
from __future__ import division
from functools import partial
from io import BytesIO
+from typing import List
from mutagen._util import cdata, BitReader, iterbytes
@@ -356,7 +356,7 @@ class XingHeader(object):
bytes = -1
"""Number of bytes, -1 if unknown"""
- toc = []
+ toc: List[int] = []
"""List of 100 file offsets in percent encoded as 0-255. E.g. entry
50 contains the file offset in percent at 50% play time.
Empty if unknown.
@@ -474,7 +474,7 @@ class VBRIHeader(object):
toc_frames = 0
"""Number of frames per table entry"""
- toc = []
+ toc: List[int] = []
"""TOC"""
def __init__(self, fileobj):
diff --git a/lib/mutagen/mp4/__init__.py b/lib/mutagen/mp4/__init__.py
index 8231d65e..9e1757ec 100644
--- a/lib/mutagen/mp4/__init__.py
+++ b/lib/mutagen/mp4/__init__.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2006 Joe Wreschnig
#
# This program is free software; you can redistribute it and/or modify
@@ -248,7 +247,7 @@ def _item_sort_key(key, value):
last = len(order)
# If there's no key-based way to distinguish, order by length.
# If there's still no way, go by string comparison on the
- # values, so we at least have something determinstic.
+ # values, so we at least have something deterministic.
return (order.get(key[:4], last), len(repr(value)), repr(value))
@@ -365,8 +364,7 @@ class MP4Tags(DictProxy, Tags):
self.__parse_text(atom, data, implicit=False)
except MP4MetadataError:
# parsing failed, save them so we can write them back
- key = _name2key(atom.name)
- self._failed_atoms.setdefault(key, []).append(data)
+ self._failed_atoms.setdefault(_name2key(atom.name), []).append(data)
def __setitem__(self, key, value):
if not isinstance(key, str):
@@ -521,10 +519,13 @@ class MP4Tags(DictProxy, Tags):
fileobj.seek(atom.offset + 12)
data = fileobj.read(atom.length - 12)
fmt = fmt % cdata.uint_be(data[:4])
- offsets = struct.unpack(fmt, data[4:])
- offsets = [o + (0, delta)[offset < o] for o in offsets]
- fileobj.seek(atom.offset + 16)
- fileobj.write(struct.pack(fmt, *offsets))
+ try:
+ offsets = struct.unpack(fmt, data[4:])
+ offsets = [o + (0, delta)[offset < o] for o in offsets]
+ fileobj.seek(atom.offset + 16)
+ fileobj.write(struct.pack(fmt, *offsets))
+ except struct.error:
+ raise MP4MetadataError("wrong offset inside %r" % atom.name)
def __update_tfhd(self, fileobj, atom, delta, offset):
if atom.offset > offset:
diff --git a/lib/mutagen/mp4/_as_entry.py b/lib/mutagen/mp4/_as_entry.py
index 9ee60c44..e5013c42 100644
--- a/lib/mutagen/mp4/_as_entry.py
+++ b/lib/mutagen/mp4/_as_entry.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2014 Christoph Reiter
#
# This program is free software; you can redistribute it and/or modify
@@ -204,7 +203,7 @@ class DescriptorError(Exception):
class BaseDescriptor(object):
- TAG = None
+ TAG: int
@classmethod
def _parse_desc_length_file(cls, fileobj):
diff --git a/lib/mutagen/mp4/_atom.py b/lib/mutagen/mp4/_atom.py
index e755d39b..e1bf1535 100644
--- a/lib/mutagen/mp4/_atom.py
+++ b/lib/mutagen/mp4/_atom.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2006 Joe Wreschnig
#
# This program is free software; you can redistribute it and/or modify
diff --git a/lib/mutagen/mp4/_util.py b/lib/mutagen/mp4/_util.py
index 43d81c82..b8e208a1 100644
--- a/lib/mutagen/mp4/_util.py
+++ b/lib/mutagen/mp4/_util.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2014 Christoph Reiter
#
# This program is free software; you can redistribute it and/or modify
diff --git a/lib/mutagen/musepack.py b/lib/mutagen/musepack.py
index 200c5f7a..944de6d5 100644
--- a/lib/mutagen/musepack.py
+++ b/lib/mutagen/musepack.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2006 Lukas Lalinsky
# Copyright (C) 2012 Christoph Reiter
#
diff --git a/lib/mutagen/ogg.py b/lib/mutagen/ogg.py
index 86ac5c2e..263c939d 100644
--- a/lib/mutagen/ogg.py
+++ b/lib/mutagen/ogg.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2006 Joe Wreschnig
#
# This program is free software; you can redistribute it and/or modify
@@ -20,10 +19,13 @@ import struct
import sys
import zlib
from io import BytesIO
+from typing import Type
from mutagen import FileType
from mutagen._util import cdata, resize_bytes, MutagenError, loadfile, \
seek_end, bchr, reraise
+from mutagen._file import StreamInfo
+from mutagen._tags import Tags
class error(MutagenError):
@@ -165,7 +167,7 @@ class OggPage(object):
return data
@property
- def size(self):
+ def size(self) -> int:
"""Total frame size."""
size = 27 # Initial header size
@@ -211,7 +213,7 @@ class OggPage(object):
to logical stream 'serial'. Other pages will be ignored.
fileobj must point to the start of a valid Ogg page; any
- occuring after it and part of the specified logical stream
+ occurring after it and part of the specified logical stream
will be numbered. No adjustment will be made to the data in
the pages nor the granule position; only the page number, and
so also the CRC.
@@ -427,7 +429,7 @@ class OggPage(object):
new_data_end = offset + data_size
offset_adjust += (data_size - old_page.size)
- # Finally, if there's any discrepency in length, we need to
+ # Finally, if there's any discrepancy in length, we need to
# renumber the pages for the logical stream.
if len(old_pages) != len(new_pages):
fileobj.seek(new_data_end, 0)
@@ -508,9 +510,9 @@ class OggFileType(FileType):
filething (filething)
"""
- _Info = None
- _Tags = None
- _Error = None
+ _Info: Type[StreamInfo]
+ _Tags: Type[Tags]
+ _Error: Type[error]
_mimes = ["application/ogg", "application/x-ogg"]
@loadfile()
diff --git a/lib/mutagen/oggflac.py b/lib/mutagen/oggflac.py
index fe17a28f..9a4ce3e7 100644
--- a/lib/mutagen/oggflac.py
+++ b/lib/mutagen/oggflac.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2006 Joe Wreschnig
#
# This program is free software; you can redistribute it and/or modify
diff --git a/lib/mutagen/oggopus.py b/lib/mutagen/oggopus.py
index e6ca9a81..486b5853 100644
--- a/lib/mutagen/oggopus.py
+++ b/lib/mutagen/oggopus.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2012, 2013 Christoph Reiter
#
# This program is free software; you can redistribute it and/or modify
diff --git a/lib/mutagen/oggspeex.py b/lib/mutagen/oggspeex.py
index de02a449..a3edb247 100644
--- a/lib/mutagen/oggspeex.py
+++ b/lib/mutagen/oggspeex.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright 2006 Joe Wreschnig
#
# This program is free software; you can redistribute it and/or modify
diff --git a/lib/mutagen/oggtheora.py b/lib/mutagen/oggtheora.py
index 796cec72..51f0a451 100644
--- a/lib/mutagen/oggtheora.py
+++ b/lib/mutagen/oggtheora.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright 2006 Joe Wreschnig
#
# This program is free software; you can redistribute it and/or modify
diff --git a/lib/mutagen/oggvorbis.py b/lib/mutagen/oggvorbis.py
index d73f3192..c1c90732 100644
--- a/lib/mutagen/oggvorbis.py
+++ b/lib/mutagen/oggvorbis.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright 2006 Joe Wreschnig
#
# This program is free software; you can redistribute it and/or modify
diff --git a/lib/mutagen/optimfrog.py b/lib/mutagen/optimfrog.py
index 46453d8d..dca03e11 100644
--- a/lib/mutagen/optimfrog.py
+++ b/lib/mutagen/optimfrog.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2006 Lukas Lalinsky
#
# This program is free software; you can redistribute it and/or modify
diff --git a/lib/mutagen/_senf/py.typed b/lib/mutagen/py.typed
similarity index 100%
rename from lib/mutagen/_senf/py.typed
rename to lib/mutagen/py.typed
diff --git a/lib/mutagen/smf.py b/lib/mutagen/smf.py
index 2921ef52..7aa8b466 100644
--- a/lib/mutagen/smf.py
+++ b/lib/mutagen/smf.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright 2015 Christoph Reiter
#
# This program is free software; you can redistribute it and/or modify
@@ -9,6 +8,7 @@
"""Standard MIDI File (SMF)"""
import struct
+from typing import Tuple
from mutagen import StreamInfo, MutagenError
from mutagen._file import FileType
@@ -19,7 +19,7 @@ class SMFError(MutagenError):
pass
-def _var_int(data, offset=0):
+def _var_int(data: bytearray, offset: int = 0) -> Tuple[int, int]:
val = 0
while 1:
try:
@@ -33,12 +33,12 @@ def _var_int(data, offset=0):
def _read_track(chunk):
- """Retuns a list of midi events and tempo change events"""
+ """Returns a list of midi events and tempo change events"""
TEMPO, MIDI = range(2)
# Deviations: The running status should be reset on non midi events, but
- # some files contain meta events inbetween.
+ # some files contain meta events in between.
# TODO: Offset and time signature are not considered.
tempos = []
diff --git a/lib/mutagen/tak.py b/lib/mutagen/tak.py
index 424bd881..2c457c97 100644
--- a/lib/mutagen/tak.py
+++ b/lib/mutagen/tak.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2008 Lukáš Lalinský
# Copyright (C) 2019 Philipp Wolfer
#
@@ -163,6 +162,7 @@ class TAKInfo(StreamInfo):
raise TAKHeaderError("not a TAK file")
bitreader = _LSBBitReader(fileobj)
+ found_stream_info = False
while True:
type = TAKMetadata(bitreader.bits(7))
bitreader.skip(1) # Unused
@@ -174,12 +174,16 @@ class TAKInfo(StreamInfo):
break
elif type == TAKMetadata.STREAM_INFO:
self._parse_stream_info(bitreader, size)
+ found_stream_info = True
elif type == TAKMetadata.ENCODER_INFO:
self._parse_encoder_info(bitreader, data_size)
assert bitreader.is_aligned()
fileobj.seek(pos + size)
+ if not found_stream_info:
+ raise TAKHeaderError("missing stream info")
+
if self.sample_rate > 0:
self.length = self.number_of_samples / float(self.sample_rate)
diff --git a/lib/mutagen/trueaudio.py b/lib/mutagen/trueaudio.py
index 3251ce47..805a83b0 100644
--- a/lib/mutagen/trueaudio.py
+++ b/lib/mutagen/trueaudio.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2006 Joe Wreschnig
#
# This program is free software; you can redistribute it and/or modify
@@ -10,7 +9,7 @@
True Audio is a lossless format designed for real-time encoding and
decoding. This module is based on the documentation at
-http://www.true-audio.com/TTA_Lossless_Audio_Codec\\_-_Format_Description
+http://tausoft.org/wiki/True_Audio_Codec_Format
True Audio files use ID3 tags.
"""
@@ -48,9 +47,11 @@ class TrueAudioInfo(StreamInfo):
header = fileobj.read(18)
if len(header) != 18 or not header.startswith(b"TTA"):
raise TrueAudioHeaderError("TTA header not found")
- self.sample_rate = cdata.int_le(header[10:14])
+ self.sample_rate = cdata.uint_le(header[10:14])
samples = cdata.uint_le(header[14:18])
- self.length = float(samples) / self.sample_rate
+ self.length = 0.0
+ if self.sample_rate != 0:
+ self.length = float(samples) / self.sample_rate
def pprint(self):
return u"True Audio, %.2f seconds, %d Hz." % (
@@ -98,4 +99,4 @@ class EasyTrueAudio(TrueAudio):
"""
from mutagen.easyid3 import EasyID3 as ID3
- ID3 = ID3
+ ID3 = ID3 # type: ignore
diff --git a/lib/mutagen/wave.py b/lib/mutagen/wave.py
index 5acf01cc..6391a5d5 100644
--- a/lib/mutagen/wave.py
+++ b/lib/mutagen/wave.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2017 Borewit
# Copyright (C) 2019-2020 Philipp Wolfer
#
@@ -86,10 +85,10 @@ class WaveStreamInfo(StreamInfo):
# RIFF: http://soundfile.sapp.org/doc/WaveFormat/
# Python struct.unpack:
# https://docs.python.org/2/library/struct.html#byte-order-size-and-alignment
- info = struct.unpack('