Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package mpv-mpris for openSUSE:Factory checked in at 2023-09-20 13:26:53 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/mpv-mpris (Old) and /work/SRC/openSUSE:Factory/.mpv-mpris.new.16627 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "mpv-mpris" Wed Sep 20 13:26:53 2023 rev:6 rq:1111981 version:1.1 Changes: -------- --- /work/SRC/openSUSE:Factory/mpv-mpris/mpv-mpris.changes 2023-04-04 21:26:21.867217593 +0200 +++ /work/SRC/openSUSE:Factory/.mpv-mpris.new.16627/mpv-mpris.changes 2023-09-20 13:28:38.205457047 +0200 @@ -1,0 +2,9 @@ +Sun Sep 17 01:36:42 UTC 2023 - Muhammad Akbar Yanuar Mantari <mantari...@pm.me> + +- Update to 1.1 + * Fix idle -> playing state transition + gh#hoyon/mpv-mpris#90 + * add support for embedded cover art + gh#hoyon/mpv-mpris#94 + +------------------------------------------------------------------- Old: ---- mpv-mpris-1.0.tar.gz New: ---- mpv-mpris-1.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ mpv-mpris.spec ++++++ --- /var/tmp/diff_new_pack.IX6oju/_old 2023-09-20 13:28:39.349498033 +0200 +++ /var/tmp/diff_new_pack.IX6oju/_new 2023-09-20 13:28:39.349498033 +0200 @@ -17,7 +17,7 @@ Name: mpv-mpris -Version: 1.0 +Version: 1.1 Release: 0 Summary: MPRIS plugin for mpv License: MIT ++++++ mpv-mpris-1.0.tar.gz -> mpv-mpris-1.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mpv-mpris-1.0/.github/workflows/build.yml new/mpv-mpris-1.1/.github/workflows/build.yml --- old/mpv-mpris-1.0/.github/workflows/build.yml 2023-03-30 20:21:58.000000000 +0200 +++ new/mpv-mpris-1.1/.github/workflows/build.yml 2023-08-30 19:19:38.000000000 +0200 @@ -12,7 +12,7 @@ - name: Install dependencies run: | sudo apt update - sudo apt install libmpv-dev libglib2.0-dev + sudo apt install libmpv-dev libglib2.0-dev libavformat-dev - name: Checkout uses: actions/checkout@v3 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mpv-mpris-1.0/Makefile new/mpv-mpris-1.1/Makefile --- old/mpv-mpris-1.0/Makefile 2023-03-30 20:21:58.000000000 +0200 +++ new/mpv-mpris-1.1/Makefile 2023-08-30 19:19:38.000000000 +0200 @@ -7,8 +7,8 @@ RM := rm # Base flags, environment CFLAGS / LDFLAGS can be appended. -BASE_CFLAGS = -std=c99 -Wall -Wextra -O2 -pedantic $(shell $(PKG_CONFIG) --cflags gio-2.0 gio-unix-2.0 glib-2.0 mpv) -BASE_LDFLAGS = $(shell $(PKG_CONFIG) --libs gio-2.0 gio-unix-2.0 glib-2.0) +BASE_CFLAGS = -std=c99 -Wall -Wextra -O2 -pedantic $(shell $(PKG_CONFIG) --cflags gio-2.0 gio-unix-2.0 glib-2.0 mpv libavformat) +BASE_LDFLAGS = $(shell $(PKG_CONFIG) --libs gio-2.0 gio-unix-2.0 glib-2.0 libavformat) SCRIPTS_DIR := $(HOME)/.config/mpv/scripts diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mpv-mpris-1.0/README.md new/mpv-mpris-1.1/README.md --- old/mpv-mpris-1.0/README.md 2023-03-30 20:21:58.000000000 +0200 +++ new/mpv-mpris-1.1/README.md 2023-08-30 19:19:38.000000000 +0200 @@ -44,6 +44,7 @@ - mpv development files - glib development files - gio development files + - libavformat development files Building should be as simple as running `make` in the source code directory. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mpv-mpris-1.0/mpris.c new/mpv-mpris-1.1/mpris.c --- old/mpv-mpris-1.0/mpris.c 2023-03-30 20:21:58.000000000 +0200 +++ new/mpv-mpris-1.1/mpris.c 2023-08-30 19:19:38.000000000 +0200 @@ -1,9 +1,12 @@ #include <gio/gio.h> #include <glib-unix.h> #include <mpv/client.h> +#include <libavformat/avformat.h> #include <inttypes.h> #include <string.h> + + static const char *introspection_xml = "<node>\n" " <interface name=\"org.mpris.MediaPlayer2\">\n" @@ -80,6 +83,8 @@ GHashTable *changed_properties; GVariant *metadata; gboolean seek_expected; + gboolean idle; + gboolean paused; } UserData; static const char *STATUS_PLAYING = "Playing"; @@ -230,18 +235,16 @@ static const int art_files_count = sizeof(art_files) / sizeof(art_files[0]); -static void try_put_local_art(mpv_handle *mpv, GVariantDict *dict, char *path) +static gchar* try_get_local_art(mpv_handle *mpv, char *path) { - gchar *dirname = g_path_get_dirname(path); + gchar *dirname = g_path_get_dirname(path), *out = NULL; gboolean found = FALSE; for (int i = 0; i < art_files_count; i++) { gchar *filename = g_build_filename(dirname, art_files[i], NULL); if (g_file_test(filename, G_FILE_TEST_EXISTS)) { - gchar *uri = path_to_uri(mpv, filename); - g_variant_dict_insert(dict, "mpris:artUrl", "s", uri); - g_free(uri); + out = path_to_uri(mpv, filename); found = TRUE; } @@ -253,6 +256,7 @@ } g_free(dirname); + return out; } static const char *youtube_url_pattern = @@ -260,8 +264,9 @@ static GRegex *youtube_url_regex; -static void try_put_youtube_thumbnail(GVariantDict *dict, char *path) +static gchar* try_get_youtube_thumbnail(char *path) { + gchar *out = NULL; if (!youtube_url_regex) { youtube_url_regex = g_regex_new(youtube_url_pattern, 0, 0, NULL); } @@ -271,16 +276,56 @@ if (matched) { gchar *video_id = g_match_info_fetch_named(match_info, "id"); - gchar *thumbnail_url = g_strconcat("https://i1.ytimg.com/vi/", + out = g_strconcat("https://i1.ytimg.com/vi/", video_id, "/hqdefault.jpg", NULL); - g_variant_dict_insert(dict, "mpris:artUrl", "s", thumbnail_url); g_free(video_id); - g_free(thumbnail_url); } g_match_info_free(match_info); + return out; +} + +static gchar* extract_embedded_art(AVFormatContext *context) { + if (avformat_find_stream_info(context, NULL) < 0) { + g_printerr("failed to find stream info"); + return NULL; + } + + AVPacket *packet = NULL; + for (unsigned int i = 0; i < context->nb_streams; i++) { + if (context->streams[i]->disposition & AV_DISPOSITION_ATTACHED_PIC) { + packet = &context->streams[i]->attached_pic; + } + } + if (!packet) { + return NULL; + } + + gchar *data = g_base64_encode(packet->data, packet->size); + gchar *img = g_strconcat("data:image/jpeg;base64,", data, NULL); + + g_free(data); + return img; } +static gchar* try_get_embedded_art(char *path) +{ + gchar *out = NULL; + AVFormatContext *context = NULL; + if (!avformat_open_input(&context, path, NULL, NULL)) { + out = extract_embedded_art(context); + avformat_close_input(&context); + } + + return out; +} + +// cached last file path, owned by mpv +static char *cached_path = NULL; + +// cached last artwork url, owned by glib +static gchar *cached_art_url = NULL; + static void add_metadata_art(mpv_handle *mpv, GVariantDict *dict) { char *path = mpv_get_property_string(mpv, "path"); @@ -289,13 +334,27 @@ return; } - if (g_str_has_prefix(path, "http")) { - try_put_youtube_thumbnail(dict, path); + // mpv may call create_metadata multiple times, so cache to save CPU + if (!cached_path || strcmp(path, cached_path)) { + mpv_free(cached_path); + g_free(cached_art_url); + cached_path = path; + + if (g_str_has_prefix(path, "http")) { + cached_art_url = try_get_youtube_thumbnail(path); + } else { + cached_art_url = try_get_embedded_art(path); + if (!cached_art_url) { + cached_art_url = try_get_local_art(mpv, path); + } + } } else { - try_put_local_art(mpv, dict, path); + mpv_free(path); } - mpv_free(path); + if (cached_art_url) { + g_variant_dict_insert(dict, "mpris:artUrl", "s", cached_art_url); + } } static void add_metadata_content_created(mpv_handle *mpv, GVariantDict *dict) @@ -795,6 +854,18 @@ } } +static GVariant * set_playback_status(UserData *ud) +{ + if (ud->idle) { + ud->status = STATUS_STOPPED; + } else if (ud->paused) { + ud->status = STATUS_PAUSED; + } else { + ud->status = STATUS_PLAYING; + } + return g_variant_new_string(ud->status); +} + static void set_stopped_status(UserData *ud) { const char *prop_name = "PlaybackStatus"; @@ -858,14 +929,14 @@ const char *prop_name = NULL; GVariant *prop_value = NULL; if (g_strcmp0(name, "pause") == 0) { - int *paused = data; - if (*paused) { - ud->status = STATUS_PAUSED; - } else { - ud->status = STATUS_PLAYING; - } + ud->paused = *(int*)data; prop_name = "PlaybackStatus"; - prop_value = g_variant_new_string(ud->status); + prop_value = set_playback_status(ud); + + } else if (g_strcmp0(name, "idle-active") == 0) { + ud->idle = *(int*)data; + prop_name = "PlaybackStatus"; + prop_value = set_playback_status(ud); } else if (g_strcmp0(name, "media-title") == 0 || g_strcmp0(name, "duration") == 0) { @@ -926,6 +997,7 @@ gboolean *status = data; prop_name = "Fullscreen"; prop_value = g_variant_new_boolean(*status); + } if (prop_name) { @@ -956,9 +1028,6 @@ set_stopped_status(ud); g_main_loop_quit(ud->loop); break; - case MPV_EVENT_IDLE: - set_stopped_status(ud); - break; case MPV_EVENT_PROPERTY_CHANGE: { mpv_event_property *prop_event = (mpv_event_property*)event->data; handle_property_change(prop_event->name, prop_event->data, ud); @@ -1016,6 +1085,8 @@ ud.loop_status = LOOP_NONE; ud.changed_properties = g_hash_table_new(g_str_hash, g_str_equal); ud.seek_expected = FALSE; + ud.idle = FALSE; + ud.paused = FALSE; g_main_context_push_thread_default(ctx); ud.bus_id = g_bus_own_name(G_BUS_TYPE_SESSION, @@ -1029,6 +1100,7 @@ // Receive event for property changes mpv_observe_property(mpv, 0, "pause", MPV_FORMAT_FLAG); + mpv_observe_property(mpv, 0, "idle-active", MPV_FORMAT_FLAG); mpv_observe_property(mpv, 0, "media-title", MPV_FORMAT_STRING); mpv_observe_property(mpv, 0, "speed", MPV_FORMAT_DOUBLE); mpv_observe_property(mpv, 0, "volume", MPV_FORMAT_DOUBLE);