def test_write_delete_cover(self, writeable_track, writeable_track_name): if not writeable_track.has_cover: return if writeable_track.ext in ['aac', 'mp4']: from mutagen.mp4 import MP4Cover newcover = CoverImage(None, None, 'image/jpeg', MP4Cover(random_str())) else: newcover = CoverImage(3, 'cover', 'image/jpeg', bytes(random_str())) tr = track.Track(writeable_track_name) assert tr.get_tag_raw('cover') is None assert tr.get_tag_disk('cover') is not None # Can we delete a cover? tr.set_tag_disk('cover', None) assert tr.write_tags() is not False self.verify_tags_exist(tr, writeable_track, deleted='cover') # Can we write a new cover? tr.set_tag_disk('cover', newcover) assert tr.get_tag_raw('cover') is None assert tr.get_tag_disk('cover') == [newcover] # reading the tags shouldn't change anything, since we're reading from disk assert tr.read_tags() is not False assert tr.get_tag_raw('cover') is None assert tr.get_tag_disk('cover') == [newcover] self.verify_tags_exist(tr, writeable_track)
def __init__(self, all_button=True): Gtk.Box.__init__(self) self.init_template() self.parent_row = None self.all_func = None self.update_func = None # Prevents the update function from being called, make # sure you do that manually after the batch update self.batch_update = False self.pixbuf = None self.info = CoverImage(None, None, None, None) self.default_type = 3 self.mime_info = { 'image/jpeg': { # Title for display 'title': _('JPEG image'), # Type and options for GDK Pixbuf saving 'type': 'jpeg', 'options': { 'quality': '90' }, }, 'image/png': { 'title': _('PNG image'), 'type': 'png', 'options': {} }, 'image/': { 'title': _('Image'), # Store unknown images as JPEG 'type': 'jpeg', 'options': { 'quality': '90' }, }, # TODO: Handle linked images '-->': { 'title': _('Linked image') }, } self.button.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY) self.button.drag_dest_add_uri_targets() self.type_selection.connect('scroll-event', dummy_scroll_handler) self.all_button = None if all_button: self.all_button = AllButton(self) self.pack_start(self.all_button, False, False, 0)
def __init__(self, all_button=True): Gtk.Box.__init__(self) self.init_template() self.parent_row = None self.all_func = None self.update_func = None # Prevents the update function from being called, make # sure you do that manually after the batch update self.batch_update = False self.pixbuf = None self.info = CoverImage(None, None, None, None) self.default_type = 3 self.mime_info = { 'image/jpeg': { # Title for display 'title': _('JPEG image'), # Type and options for GDK Pixbuf saving 'type': 'jpeg', 'options': {'quality': '90'}, }, 'image/png': {'title': _('PNG image'), 'type': 'png', 'options': {}}, 'image/': { 'title': _('Image'), # Store unknown images as JPEG 'type': 'jpeg', 'options': {'quality': '90'}, }, # TODO: Handle linked images '-->': {'title': _('Linked image')}, } self.button.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY) self.button.drag_dest_add_uri_targets() self.type_selection.connect('scroll-event', dummy_scroll_handler) self.all_button = None if all_button: self.all_button = AllButton(self) self.pack_start(self.all_button, False, False, 0)
class TagImageField(Gtk.Box): __gtype_name__ = 'TagImageField' ( button, image, type_model, description_entry, type_selection, info_label, ) = GtkTemplate.Child.widgets(6) def __init__(self, all_button=True): Gtk.Box.__init__(self) self.init_template() self.parent_row = None self.all_func = None self.update_func = None # Prevents the update function from being called, make # sure you do that manually after the batch update self.batch_update = False self.pixbuf = None self.info = CoverImage(None, None, None, None) self.default_type = 3 self.mime_info = { 'image/jpeg': { # Title for display 'title': _('JPEG image'), # Type and options for GDK Pixbuf saving 'type': 'jpeg', 'options': { 'quality': '90' }, }, 'image/png': { 'title': _('PNG image'), 'type': 'png', 'options': {} }, 'image/': { 'title': _('Image'), # Store unknown images as JPEG 'type': 'jpeg', 'options': { 'quality': '90' }, }, # TODO: Handle linked images '-->': { 'title': _('Linked image') }, } self.button.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY) self.button.drag_dest_add_uri_targets() self.type_selection.connect('scroll-event', dummy_scroll_handler) self.all_button = None if all_button: self.all_button = AllButton(self) self.pack_start(self.all_button, False, False, 0) def grab_focus(self): """ Gives focus to the internal widget """ self.image.grab_focus() def register_parent_row(self, parent_row): self.parent_row = parent_row def register_update_func(self, func): self.update_func = func def register_all_func(self, function): self.all_func = function def set_value(self, val, all_vals=None, doupdate=True): if doupdate: if val: loader = GdkPixbuf.PixbufLoader() try: loader.write(val.data) loader.close() except GLib.GError: pass else: self.batch_update = True self.set_pixbuf(loader.get_pixbuf(), val.mime) # some file types do not support multiple cover types if val.type is not None: self.type_selection.set_active(val.type) self.type_selection.set_sensitive(True) else: self.type_selection.set_active(-1) self.type_selection.set_sensitive(False) if val.desc is not None: self.description_entry.set_text(val.desc) self.description_entry.set_sensitive(True) else: self.description_entry.set_text('') self.description_entry.set_sensitive(False) self.batch_update = False else: self.batch_update = True self.set_pixbuf(None) self.type_selection.set_active(-1) self.type_selection.set_sensitive(False) self.description_entry.set_text('') self.description_entry.set_sensitive(False) self.batch_update = False self.call_update_func() if None not in (all_vals, self.all_button): self.all_button.set_active(all(val == v for v in all_vals)) def get_value(self): if not self.pixbuf: return None mime = self.mime_info[self.info.mime] # Retrieve proper image data writer = io.BytesIO() def gdk_pixbuf_save_func(buf, count, user_data): if writer.write(buf) == count: return True return False # workaround for some undocumented changes in GdkPixbuf API # see https://bugzilla.gnome.org/show_bug.cgi?id=670372 # see https://github.com/mypaint/mypaint/issues/236 try: save_to_callback_function = self.pixbuf.save_to_callbackv except AttributeError: save_to_callback_function = self.pixbuf.save_to_callback save_to_callback_function( gdk_pixbuf_save_func, None, mime['type'], list(mime['options'].keys()), # must be a sequence (list) list(mime['options'].values()), # must be a sequence (list) ) # Move to the beginning of the buffer to allow read operations writer.seek(0) return self.info._replace(data=writer.read()) def call_update_func(self): """ Wrapper around the update function """ if not self.update_func or self.batch_update: return self.update_func(self, self.parent_row.tag, self.parent_row.multi_id, self.get_value) def set_pixbuf(self, pixbuf, mime=None): """ Updates the displayed cover image and info values """ self.pixbuf = pixbuf if pixbuf is None: self.image.set_from_icon_name('list-add', Gtk.IconSize.DIALOG) self.info_label.set_markup('') else: self.image.set_from_pixbuf( pixbuf.scale_simple(100, 100, GdkPixbuf.InterpType.BILINEAR)) width, height = pixbuf.get_width(), pixbuf.get_height() if mime is None: # TRANSLATORS: do not translate 'width' and 'height' markup = _('{width}x{height} pixels').format(width=width, height=height) else: # TRANSLATORS: do not translate 'format', 'width', and 'height' markup = _('{format} ({width}x{height} pixels)').format( format=self.mime_info.get( mime, self.mime_info['image/'])['title'], width=width, height=height, ) self.info_label.set_markup(markup) self.info = self.info._replace(mime=mime) def _on_button_clicked(self, button): """ Allows setting the cover image using a file selection dialog """ dialog = dialogs.FileOperationDialog( title=_('Select image to set as cover'), parent=self.get_toplevel(), buttons=( Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OK, Gtk.ResponseType.OK, ), ) dialog.set_select_multiple(False) filefilter = Gtk.FileFilter() # Not using Gtk.FileFilter.add_pixbuf_formats since # not all image formats are supported in tags filefilter.set_name(_('Supported image formats')) filefilter.add_pattern('*.[jJ][pP][gG]') filefilter.add_pattern('*.[jJ][pP][eE][gG]') filefilter.add_pattern('*.[pP][nN][gG]') dialog.add_filter(filefilter) if dialog.run() == Gtk.ResponseType.OK: filename = dialog.get_filename() try: pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename) info = GdkPixbuf.Pixbuf.get_file_info(filename)[0] except TypeError: pass else: self.batch_update = True self.set_pixbuf(pixbuf, info.get_mime_types()[0]) self.type_selection.set_active(self.default_type) self.type_selection.set_sensitive(True) self.description_entry.set_text( os.path.basename(filename).rsplit('.', 1)[0]) self.description_entry.set_sensitive(True) self.batch_update = False self.call_update_func() dialog.destroy() def _on_button_drag_data_received(self, widget, context, x, y, selection, info, time): """ Allows setting the cover image via drag and drop """ if selection.target.name() == 'text/uri-list': filename = Gio.File.new_for_uri(selection.get_uris()[0]).get_path() try: pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename) info = GdkPixbuf.Pixbuf.get_file_info(filename)[0] except TypeError: pass else: self.batch_update = True self.set_pixbuf(pixbuf, info['mime_types'][0]) self.type_selection.set_active(self.default_type) self.description_entry.set_sensitive(True) self.description_entry.set_text( os.path.basename(filename).rsplit('.', 1)[0]) self.description_entry.set_sensitive(True) self.batch_update = False self.call_update_func() def _on_type_selection_changed(self, combobox): """ Notifies about changes in the cover type """ self.info = self.info._replace( type=self.type_model[combobox.get_active()][0]) self.call_update_func() def _on_description_entry_changed(self, entry): """ Notifies about changes in the cover description """ self.info = self.info._replace(desc=entry.get_text()) self.call_update_func()
class TagImageField(Gtk.Box): __gtype_name__ = 'TagImageField' ( button, image, type_model, description_entry, type_selection, info_label, ) = GtkTemplate.Child.widgets(6) def __init__(self, all_button=True): Gtk.Box.__init__(self) self.init_template() self.parent_row = None self.all_func = None self.update_func = None # Prevents the update function from being called, make # sure you do that manually after the batch update self.batch_update = False self.pixbuf = None self.info = CoverImage(None, None, None, None) self.default_type = 3 self.mime_info = { 'image/jpeg': { # Title for display 'title': _('JPEG image'), # Type and options for GDK Pixbuf saving 'type': 'jpeg', 'options': {'quality': '90'}, }, 'image/png': {'title': _('PNG image'), 'type': 'png', 'options': {}}, 'image/': { 'title': _('Image'), # Store unknown images as JPEG 'type': 'jpeg', 'options': {'quality': '90'}, }, # TODO: Handle linked images '-->': {'title': _('Linked image')}, } self.button.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY) self.button.drag_dest_add_uri_targets() self.type_selection.connect('scroll-event', dummy_scroll_handler) self.all_button = None if all_button: self.all_button = AllButton(self) self.pack_start(self.all_button, False, False, 0) def grab_focus(self): """ Gives focus to the internal widget """ self.image.grab_focus() def register_parent_row(self, parent_row): self.parent_row = parent_row def register_update_func(self, func): self.update_func = func def register_all_func(self, function): self.all_func = function def set_value(self, val, all_vals=None, doupdate=True): if doupdate: if val: loader = GdkPixbuf.PixbufLoader() try: loader.write(val.data) loader.close() except GLib.GError: pass else: self.batch_update = True self.set_pixbuf(loader.get_pixbuf(), val.mime) # some file types do not support multiple cover types if val.type is not None: self.type_selection.set_active(val.type) self.type_selection.set_sensitive(True) else: self.type_selection.set_active(-1) self.type_selection.set_sensitive(False) if val.desc is not None: self.description_entry.set_text(val.desc) self.description_entry.set_sensitive(True) else: self.description_entry.set_text('') self.description_entry.set_sensitive(False) self.batch_update = False else: self.batch_update = True self.set_pixbuf(None) self.type_selection.set_active(-1) self.type_selection.set_sensitive(False) self.description_entry.set_text('') self.description_entry.set_sensitive(False) self.batch_update = False self.call_update_func() if None not in (all_vals, self.all_button): self.all_button.set_active(all(val == v for v in all_vals)) def get_value(self): if not self.pixbuf: return None mime = self.mime_info[self.info.mime] # Retrieve proper image data writer = io.BytesIO() def gdk_pixbuf_save_func(buf, count, user_data): if writer.write(buf) == count: return True return False # workaround for some undocumented changes in GdkPixbuf API # see https://bugzilla.gnome.org/show_bug.cgi?id=670372 # see https://github.com/mypaint/mypaint/issues/236 try: save_to_callback_function = self.pixbuf.save_to_callbackv except AttributeError: save_to_callback_function = self.pixbuf.save_to_callback save_to_callback_function( gdk_pixbuf_save_func, None, mime['type'], mime['options'].keys(), mime['options'].values(), ) # Move to the beginning of the buffer to allow read operations writer.seek(0) return self.info._replace(data=writer.read()) def call_update_func(self): """ Wrapper around the update function """ if not self.update_func or self.batch_update: return self.update_func( self, self.parent_row.tag, self.parent_row.multi_id, self.get_value ) def set_pixbuf(self, pixbuf, mime=None): """ Updates the displayed cover image and info values """ self.pixbuf = pixbuf if pixbuf is None: self.image.set_from_icon_name('list-add', Gtk.IconSize.DIALOG) self.info_label.set_markup('') else: self.image.set_from_pixbuf( pixbuf.scale_simple(100, 100, GdkPixbuf.InterpType.BILINEAR) ) width, height = pixbuf.get_width(), pixbuf.get_height() if mime is None: # TRANSLATORS: do not translate 'width' and 'height' markup = _('{width}x{height} pixels').format(width=width, height=height) else: # TRANSLATORS: do not translate 'format', 'width', and 'height' markup = _('{format} ({width}x{height} pixels)').format( format=self.mime_info.get(mime, self.mime_info['image/'])['title'], width=width, height=height, ) self.info_label.set_markup(markup) self.info = self.info._replace(mime=mime) def _on_button_clicked(self, button): """ Allows setting the cover image using a file selection dialog """ dialog = dialogs.FileOperationDialog( title=_('Select image to set as cover'), parent=self.get_toplevel(), buttons=( Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OK, Gtk.ResponseType.OK, ), ) dialog.set_select_multiple(False) filefilter = Gtk.FileFilter() # Not using Gtk.FileFilter.add_pixbuf_formats since # not all image formats are supported in tags filefilter.set_name(_('Supported image formats')) filefilter.add_pattern('*.[jJ][pP][gG]') filefilter.add_pattern('*.[jJ][pP][eE][gG]') filefilter.add_pattern('*.[pP][nN][gG]') dialog.add_filter(filefilter) if dialog.run() == Gtk.ResponseType.OK: filename = dialog.get_filename() try: pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename) info = GdkPixbuf.Pixbuf.get_file_info(filename)[0] except TypeError: pass else: self.batch_update = True self.set_pixbuf(pixbuf, info.get_mime_types()[0]) self.type_selection.set_active(self.default_type) self.type_selection.set_sensitive(True) self.description_entry.set_text( os.path.basename(filename).rsplit('.', 1)[0] ) self.description_entry.set_sensitive(True) self.batch_update = False self.call_update_func() dialog.destroy() def _on_button_drag_data_received( self, widget, context, x, y, selection, info, time ): """ Allows setting the cover image via drag and drop """ if selection.target.name() == 'text/uri-list': filename = Gio.File.new_for_uri(selection.get_uris()[0]).get_path() try: pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename) info = GdkPixbuf.Pixbuf.get_file_info(filename)[0] except TypeError: pass else: self.batch_update = True self.set_pixbuf(pixbuf, info['mime_types'][0]) self.type_selection.set_active(self.default_type) self.description_entry.set_sensitive(True) self.description_entry.set_text( os.path.basename(filename).rsplit('.', 1)[0] ) self.description_entry.set_sensitive(True) self.batch_update = False self.call_update_func() def _on_type_selection_changed(self, combobox): """ Notifies about changes in the cover type """ self.info = self.info._replace(type=self.type_model[combobox.get_active()][0]) self.call_update_func() def _on_description_entry_changed(self, entry): """ Notifies about changes in the cover description """ self.info = self.info._replace(desc=entry.get_text()) self.call_update_func()