diff --git a/src/AudioObject.vala b/src/AudioObject.vala index b80934a9d..1ccf10da3 100644 --- a/src/AudioObject.vala +++ b/src/AudioObject.vala @@ -9,8 +9,88 @@ public class Music.AudioObject : Object { public string artist { get; set; } public string title { get; set; } public int64 duration { get; set; default = 0; } + private static MetadataDiscoverer discoverer = new MetadataDiscoverer (); public AudioObject (string uri) { - Object (uri: uri); + Object ( + uri: uri, + title: uri + ); + } + + construct { + discoverer.request (this); + } + + public void update_metadata (Gst.PbUtils.DiscovererInfo info) { + duration = (int64) info.get_duration (); + + unowned Gst.TagList? tag_list = info.get_tags (); + + string _title; + tag_list.get_string (Gst.Tags.TITLE, out _title); + if (_title != null) { + title = _title; + } + + string _artist; + tag_list.get_string (Gst.Tags.ARTIST, out _artist); + if (_artist != null) { + artist = _artist; + } else if (_title != null) { // Don't set artist for files without tags + artist = _("Unknown"); + } + + var sample = get_cover_sample (tag_list); + if (sample != null) { + var buffer = sample.get_buffer (); + + if (buffer != null) { + texture = Gdk.Texture.for_pixbuf (get_pixbuf_from_buffer (buffer)); + } + } + } + + private Gst.Sample? get_cover_sample (Gst.TagList tag_list) { + Gst.Sample cover_sample = null; + Gst.Sample sample; + for (int i = 0; tag_list.get_sample_index (Gst.Tags.IMAGE, i, out sample); i++) { + var caps = sample.get_caps (); + unowned Gst.Structure caps_struct = caps.get_structure (0); + int image_type = Gst.Tag.ImageType.UNDEFINED; + caps_struct.get_enum ("image-type", typeof (Gst.Tag.ImageType), out image_type); + if (image_type == Gst.Tag.ImageType.UNDEFINED && cover_sample == null) { + cover_sample = sample; + } else if (image_type == Gst.Tag.ImageType.FRONT_COVER) { + return sample; + } + } + + return cover_sample; + } + + private Gdk.Pixbuf? get_pixbuf_from_buffer (Gst.Buffer buffer) { + Gst.MapInfo map_info; + + if (!buffer.map (out map_info, Gst.MapFlags.READ)) { + warning ("Could not map memory buffer"); + return null; + } + + Gdk.Pixbuf pix = null; + + try { + var loader = new Gdk.PixbufLoader (); + + if (loader.write (map_info.data) && loader.close ()) { + pix = loader.get_pixbuf (); + } + } catch (Error err) { + warning ("Error processing image data: %s", err.message); + } + + buffer.unmap (map_info); + + return pix; } } diff --git a/src/MetadataDiscoverer.vala b/src/MetadataDiscoverer.vala new file mode 100644 index 000000000..8b8f46a8b --- /dev/null +++ b/src/MetadataDiscoverer.vala @@ -0,0 +1,49 @@ +[SingleInstance] +public class Music.MetadataDiscoverer : Object { + private Gst.PbUtils.Discoverer discoverer; + private HashTable objects_to_update; + + construct { + try { + discoverer = new Gst.PbUtils.Discoverer ((Gst.ClockTime) (5 * Gst.SECOND)); + discoverer.discovered.connect (relay_metadata); + discoverer.finished.connect (discoverer.stop); + } catch (Error e) { + critical ("Unable to start Gstreamer Discoverer: %s", e.message); + } + objects_to_update = new HashTable (str_hash, str_equal); + } + + public void request (AudioObject audio) { + objects_to_update.insert (audio.uri, audio); + discoverer.start (); + discoverer.discover_uri_async (audio.uri); + } + + private void relay_metadata (Gst.PbUtils.DiscovererInfo info, Error? err) { + string uri = info.get_uri (); + var audio_obj = objects_to_update.get (uri); + objects_to_update.remove (uri); + switch (info.get_result ()) { + case Gst.PbUtils.DiscovererResult.URI_INVALID: + critical ("Couldn't read metadata for '%s': invalid URI.", uri); + return; + case Gst.PbUtils.DiscovererResult.ERROR: + critical ("Couldn't read metadata for '%s': %s", uri, err.message); + return; + case Gst.PbUtils.DiscovererResult.TIMEOUT: + critical ("Couldn't read metadata for '%s': Discovery timed out.", uri); + return; + case Gst.PbUtils.DiscovererResult.BUSY: + critical ("Couldn't read metadata for '%s': Already discovering a file.", uri); + return; + case Gst.PbUtils.DiscovererResult.MISSING_PLUGINS: + critical ("Couldn't read metadata for '%s': Missing plugins.", uri); + return; + default: + break; + } + + audio_obj.update_metadata (info); + } +} diff --git a/src/PlaybackManager.vala b/src/PlaybackManager.vala index 4b33f6039..f2e14e279 100644 --- a/src/PlaybackManager.vala +++ b/src/PlaybackManager.vala @@ -21,7 +21,6 @@ public class Music.PlaybackManager : Object { } private dynamic Gst.Element playbin; - private Gst.PbUtils.Discoverer discoverer; private uint progress_timer = 0; private Settings settings; @@ -46,14 +45,6 @@ public class Music.PlaybackManager : Object { bus.add_watch (0, bus_callback); bus.enable_sync_message_emission (); - try { - discoverer = new Gst.PbUtils.Discoverer ((Gst.ClockTime) (5 * Gst.SECOND)); - discoverer.discovered.connect (update_metadata); - discoverer.finished.connect (discoverer.stop); - } catch (Error e) { - critical ("Unable to start Gstreamer Discoverer: %s", e.message); - } - queue_liststore.items_changed.connect (() => { var shuffle_action_action = (SimpleAction) GLib.Application.get_default ().lookup_action (Application.ACTION_SHUFFLE); has_items = queue_liststore.get_n_items () > 0; @@ -91,22 +82,10 @@ public class Music.PlaybackManager : Object { // Files[] must not contain any null entries public void queue_files (File[] files) { - discoverer.start (); int invalids = 0; foreach (unowned var file in files) { if (file.query_exists () && "audio" in ContentType.guess (file.get_uri (), null, null)) { var audio_object = new AudioObject (file.get_uri ()); - - string? basename = file.get_basename (); - - if (basename != null) { - audio_object.title = basename; - } else { - audio_object.title = audio_object.uri; - } - - discoverer.discover_uri_async (audio_object.uri); - queue_liststore.append (audio_object); } else { invalids++; @@ -162,70 +141,6 @@ public class Music.PlaybackManager : Object { queue_liststore.remove (position); } - private void update_metadata (Gst.PbUtils.DiscovererInfo info, Error? err) { - string uri = info.get_uri (); - switch (info.get_result ()) { - case Gst.PbUtils.DiscovererResult.URI_INVALID: - critical ("Couldn't read metadata for '%s': invalid URI.", uri); - return; - case Gst.PbUtils.DiscovererResult.ERROR: - critical ("Couldn't read metadata for '%s': %s", uri, err.message); - return; - case Gst.PbUtils.DiscovererResult.TIMEOUT: - critical ("Couldn't read metadata for '%s': Discovery timed out.", uri); - return; - case Gst.PbUtils.DiscovererResult.BUSY: - critical ("Couldn't read metadata for '%s': Already discovering a file.", uri); - return; - case Gst.PbUtils.DiscovererResult.MISSING_PLUGINS: - critical ("Couldn't read metadata for '%s': Missing plugins.", uri); - return; - default: - break; - } - - EqualFunc equal_func = (a, b) => { - return ((AudioObject) a).uri == ((AudioObject) b).uri; - }; - - var temp_audio_object = new AudioObject (uri); - - uint position = -1; - queue_liststore.find_with_equal_func (temp_audio_object, equal_func, out position); - - if (position != -1) { - var audio_object = (AudioObject) queue_liststore.get_item (position); - audio_object.duration = (int64) info.get_duration (); - - unowned Gst.TagList? tag_list = info.get_tags (); - - string _title; - tag_list.get_string (Gst.Tags.TITLE, out _title); - if (_title != null) { - audio_object.title = _title; - } - - string _artist; - tag_list.get_string (Gst.Tags.ARTIST, out _artist); - if (_artist != null) { - audio_object.artist = _artist; - } else if (_title != null) { // Don't set artist for files without tags - audio_object.artist = _("Unknown"); - } - - var sample = get_cover_sample (tag_list); - if (sample != null) { - var buffer = sample.get_buffer (); - - if (buffer != null) { - audio_object.texture = Gdk.Texture.for_pixbuf (get_pixbuf_from_buffer (buffer)); - } - } - } else { - critical ("Couldn't find '%s' in queue", uri); - } - } - private bool bus_callback (Gst.Bus bus, Gst.Message message) { switch (message.type) { case Gst.MessageType.EOS: @@ -408,47 +323,4 @@ public class Music.PlaybackManager : Object { previous_action.set_enabled (previous_sensitive); } - - private Gst.Sample? get_cover_sample (Gst.TagList tag_list) { - Gst.Sample cover_sample = null; - Gst.Sample sample; - for (int i = 0; tag_list.get_sample_index (Gst.Tags.IMAGE, i, out sample); i++) { - var caps = sample.get_caps (); - unowned Gst.Structure caps_struct = caps.get_structure (0); - int image_type = Gst.Tag.ImageType.UNDEFINED; - caps_struct.get_enum ("image-type", typeof (Gst.Tag.ImageType), out image_type); - if (image_type == Gst.Tag.ImageType.UNDEFINED && cover_sample == null) { - cover_sample = sample; - } else if (image_type == Gst.Tag.ImageType.FRONT_COVER) { - return sample; - } - } - - return cover_sample; - } - - private Gdk.Pixbuf? get_pixbuf_from_buffer (Gst.Buffer buffer) { - Gst.MapInfo map_info; - - if (!buffer.map (out map_info, Gst.MapFlags.READ)) { - warning ("Could not map memory buffer"); - return null; - } - - Gdk.Pixbuf pix = null; - - try { - var loader = new Gdk.PixbufLoader (); - - if (loader.write (map_info.data) && loader.close ()) { - pix = loader.get_pixbuf (); - } - } catch (Error err) { - warning ("Error processing image data: %s", err.message); - } - - buffer.unmap (map_info); - - return pix; - } } diff --git a/src/meson.build b/src/meson.build index e4b85f439..de5a07916 100644 --- a/src/meson.build +++ b/src/meson.build @@ -3,6 +3,7 @@ sources = [ 'AudioObject.vala', 'MainWindow.vala', 'PlaybackManager.vala', + 'MetadataDiscoverer.vala', 'DBus/MprisPlayer.vala', 'DBus/MprisRoot.vala', 'Views/NowPlayingView.vala',