Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: Add example for reading/writing vorbiscomment images #200

Closed
lazka opened this issue Oct 27, 2014 · 15 comments
Closed

docs: Add example for reading/writing vorbiscomment images #200

lazka opened this issue Oct 27, 2014 · 15 comments
Labels

Comments

@lazka
Copy link
Member

@lazka lazka commented Oct 27, 2014

Originally reported by: scribbled_pixels (Bitbucket: scribbled_pixels, GitHub: Unknown)


According to this specification there's now a standard for embedding Cover art in Ogg Vorbis files. As far as I understood it, this is exactly the same format that FLAC uses, so they could share the Picture class.


@lazka

This comment has been minimized.

Copy link
Member Author

@lazka lazka commented Oct 27, 2014

Original comment by Christoph Reiter (Bitbucket: lazka, GitHub: lazka):


The page doesn't say standard or specification, but I guess we can add an example to the docs:

#!python

for value in tag.get("metadata_block_picture", []):
    yield flac.Picture(base64.b64decode(value))
@lazka

This comment has been minimized.

Copy link
Member Author

@lazka lazka commented Oct 27, 2014

Original comment by scribbled_pixels (Bitbucket: scribbled_pixels, GitHub: Unknown):


Yes, but it's by the creators of Ogg / FLAC Vorbis and it says "This is the preferred and recommended way of embedding cover art within VorbisComments." (it also mentions a deprecated way of doing it), so I think it can be seen as a standard and we should support it. I just added coverart with the "METADATA_BLOCK_PICTURE" tag by using this script from SuperUser and VLC shows it.

@lazka

This comment has been minimized.

Copy link
Member Author

@lazka lazka commented Oct 28, 2014

Original comment by Christoph Reiter (Bitbucket: lazka, GitHub: lazka):


docs: Add example for reading/writing images in vorbiscomment tags (Fixes issue #200)

@lazka

This comment has been minimized.

Copy link
Member Author

@lazka lazka commented Oct 28, 2014

Original comment by Christoph Reiter (Bitbucket: lazka, GitHub: lazka):


See https://mutagen.readthedocs.org/en/latest/tutorial.html#vorbiscomment

@lazka

This comment has been minimized.

Copy link
Member Author

@lazka lazka commented Oct 28, 2014

Original comment by scribbled_pixels (Bitbucket: scribbled_pixels, GitHub: Unknown):


@lazka Thanks, but did you read my reply? Using 'coverart' or 'coverartmime' is deprecated (by the developers of FLAC / Ogg Vorbis)

@lazka

This comment has been minimized.

Copy link
Member Author

@lazka lazka commented Oct 28, 2014

Original comment by Christoph Reiter (Bitbucket: lazka, GitHub: lazka):


Yes, thanks. Note that I only added an example for reading. Deprecated doesn't mean you shouldn't read them..

@lazka

This comment has been minimized.

Copy link
Member Author

@lazka lazka commented Oct 28, 2014

Original comment by scribbled_pixels (Bitbucket: scribbled_pixels, GitHub: Unknown):


@lazka Sorry, you're right, but shouldn't there be a report for adding direct support (via add_picture) for pictures in Ogg Vorbis files then?

Just a wild guess, but would it make sense to create an ogg package (__init__.py which contains ogg.py + Picture class), add the following files to it and remove the ogg prefix from all these codecs? (Would break backwards compatibility though)

  • ogg.py
  • oggflac.py
  • oggopus.py
  • oggspeex.py
  • oggtheora.py
  • oggvorbis.py
  • _vorbis.py
  • flac.py

And maybe all the codecs could be added to a codecs package?

@lazka

This comment has been minimized.

Copy link
Member Author

@lazka lazka commented Oct 28, 2014

Original comment by Christoph Reiter (Bitbucket: lazka, GitHub: lazka):


How values are encoded isn't part of vorbiscomment or ogg. You can write a wrapper with a more structured interface (images/dates/ratings..), but I'd rather see this work being done in a format agnostic interface, see issue #186.

something like:

#!python

pic = Pic.from_path("foo.jpeg", type=PicType.COVER, description="foo")
tags.add_picture(pic)

(that's about what we have in Quod Libet right now)

@lazka

This comment has been minimized.

Copy link
Member Author

@lazka lazka commented Oct 28, 2014

Original comment by scribbled_pixels (Bitbucket: scribbled_pixels, GitHub: Unknown):


1.
@lazka What do you mean with "(that's about what we have in Quod Libet right now)" / what does it refer to?


2.

#!python


pic = Pic.from_path("foo.jpeg", type=PicType.COVER, description="foo")
tags.add_picture(pic)

That would be awesome! Although I guess type=PicType.COVER could be a default value and description could be None if it mustn't be provided.


3.
I think that the format-agnostic common tagging interface should be build on a solid foundation, so that it only needs to care about abstracting the different formats and I'm not sure if it should need to know exactly how to add an image to an Ogg Vorbis file. It also seems a bit inconsistent, because Ogg FLAC supports it (add_picture), and they're basically (as far as I understand) using the same mechanism / specification.

What would need doing for adding pictures to Ogg Vorbis besides adding a add_picture method? If it isn't too much I might be willing to help out.

@lazka

This comment has been minimized.

Copy link
Member Author

@lazka lazka commented Oct 31, 2014

Original comment by Samuel Messner (Bitbucket: obskyr, GitHub: obskyr):


Oh man, thank you so much for adding the examples. Impeccable timing, just as I was trying to implement cover art support in a thing using Mutagen.

There could probably be room to add a coverart tag to the Easy classes to make that easier in general. Perhaps expand the Easy concept onto other formats, too, which would mean adding cover art to Vorbis comments would be trivial. That'd at least be a fairly uniform and user-friendly solution.

Lot more of that discussion in issue #186, as mentioned.

@lazka

This comment has been minimized.

Copy link
Member Author

@lazka lazka commented Nov 2, 2014

Original comment by scribbled_pixels (Bitbucket: scribbled_pixels, GitHub: Unknown):


@lazka How can I save the picture to a file with the examples? (the image could also be a PNG, which I actually prefer, because it's lossless)

@lazka

This comment has been minimized.

Copy link
Member Author

@lazka lazka commented Nov 2, 2014

Original comment by Christoph Reiter (Bitbucket: lazka, GitHub: lazka):


See 71b47f63720

I use the following in quodlibet to get the file extension (depends on pygobject/gdk-pixbuf):

#!python

def get_extensions(mime_type):
    """A possibly empty list of extensions e.g. ["jpeg", jpg"]"""

    from gi.repository import GdkPixbuf

    for format_ in GdkPixbuf.Pixbuf.get_formats():
        if mime_type in format_.get_mime_types():
            return format_.get_extensions()
    return []
@lazka

This comment has been minimized.

Copy link
Member Author

@lazka lazka commented Nov 2, 2014

Original comment by scribbled_pixels (Bitbucket: scribbled_pixels, GitHub: Unknown):


@lazka Thank you :), but why not use mimetypes if it is in the standard library? Just wondering.

So this is what the code looks like.. I'd have no problem if you would include it in the documentation (or library) when it's solid enough.

#!python

import base64
import mimetypes
import os

from mutagen.oggvorbis import OggVorbis
from mutagen.flac import Picture, error as FLACError
import PIL # sudo pip3.4 install pillow


mimetypes.init()

mode_to_bpp = {'1':1, 'L':8, 'P':8, 'RGB':24, 'RGBA':32, 'CMYK':32, 'YCbCr':24, 'I':32, 'F':32}



def extract_pictures_from_ogg(path, image_directory, prefix='image_'):
    file_ = OggVorbis(path)

    b64_pictures = file_.get("metadata_block_picture", [])
    for n, b64_data in enumerate(b64_pictures):
        try:
            data = base64.b64decode(b64_data)
        except (TypeError, ValueError):
            continue

        try:
            picture = Picture(data)
        except FLACError:
            continue

        extension = mimetypes.guess_extension(picture.mime)

        image_filename = '{}{}{}'.format(prefix, n, extension)
        image_path = os.path.join(image_directory, image_filename)
        with open(image_path, "wb") as h:
            h.write(picture.data)


def add_picture_to_ogg(path, image_path, description="", type_=17):
    file_ = OggVorbis(path)

    with open(image_path, "rb") as h:
        data = h.read()

    im = PIL.Image.open(image_path)
    width, height = im.size

    picture = Picture()
    picture.data = data
    picture.type = type_
    picture.desc = description
    picture.mime = mimetypes.guess_type(image_path)[0]
    picture.width = width
    picture.height = height
    picture.depth = mode_to_bpp[im.mode]

    picture_data = picture.write()
    encoded_data = base64.b64encode(picture_data)

    file_["metadata_block_picture"] = [encoded_data]
    file_.save()

My problem is now that I get the following exception when trying to add an image (either using the function above or the example in the documentation):

#!python

Traceback (most recent call last):
  File "<pyshell#32>", line 23, in <module>
    file_.save()
  File "/usr/local/lib/python3.4/dist-packages/mutagen/ogg.py", line 500, in save
    self.tags._inject(fileobj)
  File "/usr/local/lib/python3.4/dist-packages/mutagen/oggvorbis.py", line 114, in _inject
    packets[0] = b"\x03vorbis" + self.write()
  File "/usr/local/lib/python3.4/dist-packages/mutagen/_vorbis.py", line 189, in write
    self.validate()
  File "/usr/local/lib/python3.4/dist-packages/mutagen/_vorbis.py", line 163, in validate
    raise ValueError
ValueError
@lazka

This comment has been minimized.

Copy link
Member Author

@lazka lazka commented Nov 2, 2014

Original comment by Christoph Reiter (Bitbucket: lazka, GitHub: lazka):


I forgot about Python3, sorry. Try rev 97b330ccc0a74f

I didn't know about mimetypes, thanks :)

@lazka

This comment has been minimized.

Copy link
Member Author

@lazka lazka commented Nov 2, 2014

Original comment by scribbled_pixels (Bitbucket: scribbled_pixels, GitHub: Unknown):


Nice! Thanks! Now it works almost like Pic.from_path("foo.jpeg", type=PicType.COVER, description="foo"):

#!python

import base64
import mimetypes
import os

from mutagen.oggvorbis import OggVorbis
from mutagen.flac import Picture, error as FLACError
import PIL # sudo pip3.4 install pillow


class PictureType(object):
    other = 0
    png_file_icon_32x32 = 1
    other_file_icon = 2
    front_cover = 3
    back_cover = 4
    leaflet_page = 5
    media = 6 # (e.g. label side of CD)
    lead_artist_or_lead_performer_or_soloist = 7
    artist_or_performer = 8
    conductor = 9
    band_or_orchestra = 10
    composer = 11
    lyricist_or_text_writer = 12
    recording_location = 13
    during_recording = 14
    during_performance = 15
    movie_or_video_screen_capture = 16
    a_bright_coloured_fish = 17
    illustration = 18
    band_or_artist_logotype = 19
    publisher_or_studio_logotype = 20



mimetypes.init()

mode_to_bpp = {'1':1, 'L':8, 'P':8, 'RGB':24, 'RGBA':32, 'CMYK':32, 'YCbCr':24, 'I':32, 'F':32}



def extract_pictures_from_ogg(path, image_directory, prefix='image_'):
    file_ = OggVorbis(path)

    b64_pictures = file_.get("metadata_block_picture", [])
    for n, b64_data in enumerate(b64_pictures):
        try:
            data = base64.b64decode(b64_data)
        except (TypeError, ValueError):
            continue

        try:
            picture = Picture(data)
        except FLACError:
            continue

        extension = mimetypes.guess_extension(picture.mime)

        image_filename = '{}{}{}'.format(prefix, n, extension)
        image_path = os.path.join(image_directory, image_filename)
        with open(image_path, "wb") as h:
            h.write(picture.data)


def add_picture_to_ogg(path, image_path, description="", type_=PictureType.front_cover):
    file_ = OggVorbis(path)

    with open(image_path, "rb") as h:
        data = h.read()

    im = PIL.Image.open(image_path)
    width, height = im.size

    picture = Picture()
    picture.data = data
    picture.type = type_
    picture.desc = description
    picture.mime = mimetypes.guess_type(image_path)[0]
    picture.width = width
    picture.height = height
    picture.depth = mode_to_bpp[im.mode]

    picture_data = picture.write()
    encoded_data = base64.b64encode(picture_data)
    vcomment_value = encoded_data.decode("ascii")

    file_["metadata_block_picture"] = [vcomment_value]
    file_.save()

I tested for Python 3.4, but it should work in Python 2.7 just as well.

@lazka What do you think about including it in the library or documentation? (I guess the PictureType class, which is based on this could actually be included in the file next to Picture.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
1 participant
You can’t perform that action at this time.