mutagen icon indicating copy to clipboard operation
mutagen copied to clipboard

No io.BytesIO support in mutagen.File function

Open helltraitor opened this issue 1 year ago • 3 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) https://github.com/quodlibet/mutagen/blob/30f373fa6d56e1afa17d48a0c6f3e111267fbd32/mutagen/_util.py#L62-L69

_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) https://github.com/quodlibet/mutagen/blob/30f373fa6d56e1afa17d48a0c6f3e111267fbd32/mutagen/_util.py#L219-L223

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

helltraitor avatar Jul 18 '22 22:07 helltraitor

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...

lazka avatar Jul 19 '22 06:07 lazka

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

helltraitor avatar Jul 19 '22 10:07 helltraitor

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

lazka avatar Jul 24 '22 09:07 lazka