quodlibet / mutagen

Python module for handling audio metadata

Home Page:https://mutagen.readthedocs.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

No io.BytesIO support in mutagen.File function

helltraitor opened this issue · comments

I need to open the file from BytesIO but mutagen doesn't allow me to use it.

is_fileobj: Broken - doesn't checks anything for real (almost any types is not str or bytes). You can use runtime_checkable protocols for example (or abstract classes from standart library)

mutagen/mutagen/_util.py

Lines 62 to 69 in 30f373f

def is_fileobj(fileobj):
"""Returns:
bool: if an argument passed ot mutagen should be treated as a
file object
"""
return not (isinstance(fileobj, (str, bytes)) or
hasattr(fileobj, "__fspath__"))

_openfile: Useless check - FileThing is just a container but isinstance check deny to use user containers variants. Consider to add special class or make FileThing public (PyCharm warning: Access to a protected member _util of a module) (for now you can just check attributes of filething by hasattr as you do in other places)

mutagen/mutagen/_util.py

Lines 219 to 223 in 30f373f

# to allow stacked context managers, just pass the result through
if isinstance(filething, FileThing):
filename = filething.filename
fileobj = filething.fileobj
filething = None

You know, that's very funny when open is not supported BytesIO but save supports.
For now temporary solution is to use ThingFile from non-public module _util.py

If you now the correct way to use BytesIO in mutagen, let me now.
P.S. Mutagen code is awful, have a nice day :D

import io
import urllib.request
import mutagen

r = urllib.request.urlopen("https://opus-codec.org/static/examples/ehren-paper_lights-96.opus")
f = io.BytesIO(r.read())
print(mutagen.File(f))

P.S. Mutagen code is awful, have a nice day :D

yeah, this was all added years later while trying to not break existing users, so lots of hacks and magic in that area...

Mutagen waits that music will have ID3 tag, but it may not (most common situation for me is mp3/m4a music file without tag header - I create it with my code).
https://github.com/quodlibet/mutagen/blob/master/mutagen/mp3/__init__.py#L458-L461

But here we have only pure BytesIO (with music content) so Kinds have 0 score.
Debugger: results = [(0, 'MP3'), (0, 'TrueAudio'), (0, 'OggTheora'), (0, 'OggSpeex'), (0, 'OggVorbis'), (0, 'OggFLAC'), (0, 'FLAC'), (0, 'AIFF'), (0, 'APEv2File'), (0, 'MP4'), (False, 'ID3FileType'), (0, 'WavPack'), (0, 'Musepack'), (0, 'MonkeysAudio'), (0, 'OptimFROG'), (0, 'ASF'), (0, 'OggOpus'), (0, 'AAC'), (0, 'AC3'), (False, 'SMF'), (0, 'TAK'), (0, 'DSF'), (0, 'DSDIFF'), (0, 'WAVE')]
https://github.com/quodlibet/mutagen/blob/master/mutagen/_file.py#L289-L290

Funny story that I have codec from service (but I cannot be sure that I will cover any cases), so I want to use this codec within BytesIO.

class Descriptor:
    ...
    @contextlib.asynccontextmanager
    async def to_track(self) -> AsyncIterator[mutagen.FileType]:
        async with aiofiles.open(self.__filepath, "br") as file:
            buffer = io.BytesIO(await file.read())
            logging.debug("BUFFER IS READY")

        # if (track := mutagen.File(FileThing(buffer, self.__filepath.name, self.__filepath.name))) is None:
        if (track := mutagen.File(buffer)) is None:
            logging.debug("MUTAGEN ERROR")
            raise RuntimeError
        ...

This is an example from my application (for now I totally rewrite it and public ready pieces in my GitHub). Commented line is solution.
If you have the solution for BytesIO + codec - let me know. Because I don't want to use music formats directly

I see, thanks. So we need some way to pass a filename as a hint for the type selection.