tinytag icon indicating copy to clipboard operation
tinytag copied to clipboard

Support BytesIo objects for tagging

Open Khailz opened this issue 4 years ago • 1 comments
trafficstars

It would be awesome to support BytesIO or any other IO types with TinyTag. This way it can be used in some scenarios where files are packaged and do not have a normal path.

Khailz avatar Aug 19 '21 11:08 Khailz

Have you tried using the parser classes directly? If you know what kind of file type you're handling, you should be able to use:

from tinytag import MP3
mp3_info = MP3(my_bytes_io)
mp3_info.load(tags=True, duration=True)

I agree that this is pretty ugly API wise, so we can discuss how we could make this more accessible, but I think this should work.

devsnd avatar Mar 12 '22 15:03 devsnd

I got this to work by writing my BytesIO object into a temporary file and then reading that file into TinyTag. The temporary file automatically gets deleted as soon as the with statement is finished.

Assuming data contains a BytesIO object:

import tempfile

with tempfile.NamedTemporaryFile() as tmp:
    tmp.write(data.getvalue())
    tmp.seek(0)

    tag = TinyTag.get(tmp.name)

Here is a full example showing how it works in practice:

Full example
import tempfile
import requests
import io
from tinytag import TinyTag

def download_file(url: str) -> io.BytesIO:
    """
    Downloads the file from the given URL and returns it as a BytesIO object
    Args:
        url: The url to run from

    Returns:
        An object containing the file
    """

    try:
        response = requests.get(url, stream=True)
        response.raise_for_status()
    except requests.exceptions.RequestException as e:
        raise Exception(f"Could not run file from {url}: {e}")

    file = io.BytesIO()
    for chunk in response.iter_content(chunk_size=8192):
        file.write(chunk)
    file.seek(0)

    return file


def read_bytes_io_as_tiny_tag(data: io.BytesIO) -> TinyTag:
    """
    Reads the given BytesIO object as a file and returns it
    Args:
        data: The BytesIO object to read

    Returns:
        The file
    """
    with tempfile.NamedTemporaryFile() as tmp:
        tmp.write(data.getvalue())
        tmp.seek(0)

        tag = TinyTag.get(tmp.name)

        return tag


def run():
    url = "https://example.com/file.mp3"
    data = download_file(url)
    tag = read_bytes_io_as_tiny_tag(data=data)

    print('This track is by %s.' % tag.artist)
    print('It is %f seconds long.' % tag.duration)

AllenEllis avatar May 06 '23 07:05 AllenEllis

Tested on Windows, the code above won't run if you don't set delete = False in tempfile.NamedTemporaryFile()

I don't know why but this should work ` import tempfile

with tempfile.NamedTemporaryFile(delete = False, suffix = ".mp3") as tmp:
    tmp.write(data.getvalue())
    tmp.seek(0)

    tag = TinyTag.get(tmp.name)

` Do you have any solution for this? Deleting temp file manually seems not a good idea :(

thinhhja2001 avatar May 28 '23 05:05 thinhhja2001

Can you check if this branch works well for your needs? https://github.com/mathiascode/tinytag/tree/bytesio

You can provide a file-like object like this:

tag = TinyTag.get(file_obj=file_obj)

mathiascode avatar Jun 02 '23 20:06 mathiascode