From 024aadfe0a3f3352549753f6b924a45502b23314 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Wed, 1 Jan 2025 15:41:21 +0100 Subject: [PATCH 1/7] [http] Support for extracting artwork url from within StreamUrl field --- src/http.c | 36 ++++++++++++++++++++++++++++++++++++ src/http.h | 8 ++++++++ src/inputs/http.c | 21 +++++++++++++++++++-- 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/src/http.c b/src/http.c index 6a1604d4..efb34080 100644 --- a/src/http.c +++ b/src/http.c @@ -36,6 +36,7 @@ #include #include +#include #include @@ -193,6 +194,41 @@ http_client_request(struct http_client_ctx *ctx, struct http_client_session *ses return 0; } +int +http_form_urldecode(struct keyval *kv, const char *uri) +{ + struct evhttp_uri *ev_uri = NULL; + struct evkeyvalq ev_query = { 0 }; + struct evkeyval *param; + const char *query; + int ret; + + ev_uri = evhttp_uri_parse_with_flags(uri, EVHTTP_URI_NONCONFORMANT); + if (!ev_uri) + return -1; + + query = evhttp_uri_get_query(ev_uri); + if (!query) + goto error; + + ret = evhttp_parse_query_str(query, &ev_query); + if (ret < 0) + goto error; + + // musl libc doesn't have sys/queue.h so don't use TAILQ_FOREACH + for (param = ev_query.tqh_first; param; param = param->next.tqe_next) + keyval_add(kv, param->key, param->value); + + evhttp_uri_free(ev_uri); + evhttp_clear_headers(&ev_query); + return 0; + + error: + evhttp_uri_free(ev_uri); + evhttp_clear_headers(&ev_query); + return -1; +} + char * http_form_urlencode(struct keyval *kv) { diff --git a/src/http.h b/src/http.h index 755f3848..a68c5d97 100644 --- a/src/http.h +++ b/src/http.h @@ -86,6 +86,14 @@ http_client_request(struct http_client_ctx *ctx, struct http_client_session *ses char * http_form_urlencode(struct keyval *kv); +/* The reverse of http_form_urlencode, except takes a full url as input. + * + * @param kv keyval struct allocated by caller where values will be added + * @param url with the query to decode + * @return 0 if ok, otherwise -1 + */ +int +http_form_urldecode(struct keyval *kv, const char *uri); /* Returns a newly allocated string with the first stream in the m3u given in * url. If url is not a m3u, the string will be a copy of url. diff --git a/src/inputs/http.c b/src/inputs/http.c index 5a671b54..a50e48fc 100644 --- a/src/inputs/http.c +++ b/src/inputs/http.c @@ -173,8 +173,9 @@ streamurl_process(struct input_metadata *metadata, const char *url) { struct http_client_ctx client = { 0 }; struct keyval kv = { 0 }; - struct evbuffer *evbuf; + struct evbuffer *evbuf = NULL; const char *content_type; + const char *artwork_url; char *body; int ret; @@ -186,6 +187,21 @@ streamurl_process(struct input_metadata *metadata, const char *url) return -1; } + // If the StreamUrl contains a keyword followed by the actual url, e.g. http://metadata.cdnstream1.com/?yadayada&ALBUM_ART=https%3A%2F%2Fis1-ssl.mzstatic.com%2Fimage%2Fthumb%2FMusic%2F11%2Fcc%2F21%2Fmzi.nepwiuir.jpg + if (streamurl_map[0].words) + { + ret = http_form_urldecode(&kv, url); + if (ret < 0) + return -1; + + artwork_url = keyval_get(&kv, streamurl_map[0].words); + metadata->artwork_url = safe_strdup(artwork_url); + keyval_clear(&kv); + + if (metadata->artwork_url) + goto out; + } + DPRINTF(E_DBG, L_PLAYER, "Downloading StreamUrl resource '%s'\n", url); CHECK_NULL(L_PLAYER, evbuf = evbuffer_new()); @@ -219,7 +235,8 @@ streamurl_process(struct input_metadata *metadata, const char *url) out: keyval_clear(&kv); - evbuffer_free(evbuf); + if (evbuf) + evbuffer_free(evbuf); streamurl_settings_unload(); return ret; } From 158b043f55f5de0553b5fb0963b309ad16308523 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Wed, 1 Jan 2025 19:58:40 +0100 Subject: [PATCH 2/7] [artwork] Don't make online artwork searches if artist/album starts with "unknown" --- src/artwork.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/artwork.c b/src/artwork.c index 697228f7..06285c0d 100644 --- a/src/artwork.c +++ b/src/artwork.c @@ -1166,6 +1166,13 @@ online_source_request_url_make(char *url, size_t url_size, const struct online_s goto error; } + if ((artist && strncasecmp("unknown", artist, strlen("unknown")) == 0) || + (album && strncasecmp("unknown", album, strlen("unknown")) == 0) ) + { + DPRINTF(E_DBG, L_ART, "Skipping online artwork search for unknown artist/album\n"); + goto error; + } + for (i = 0; src->query_parts[i].key; i++) { if (!album && strstr(src->query_parts[i].template, "$ALBUM$")) From dbaeb7bdca694358ab9e3892b08ef70cb95ef2be Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Thu, 2 Jan 2025 19:53:26 +0100 Subject: [PATCH 3/7] [artwork] Fix concurrency issues with online search and artwork retrieval --- src/artwork.c | 94 +++++++++++++++++++++++++++++---------------------- src/http.c | 2 +- 2 files changed, 55 insertions(+), 41 deletions(-) diff --git a/src/artwork.c b/src/artwork.c index 06285c0d..010732c2 100644 --- a/src/artwork.c +++ b/src/artwork.c @@ -71,7 +71,7 @@ // See online_source_is_failing() #define ONLINE_SEARCH_COOLDOWN_TIME 3600 -#define ONLINE_SEARCH_FAILURES_MAX 3 +#define ONLINE_SEARCH_FAILURES_MAX 5 enum artwork_cache { @@ -192,6 +192,8 @@ static const char *cover_extension[] = "jpg", "png", }; +static pthread_mutex_t artwork_cache_stash_mutex = PTHREAD_MUTEX_INITIALIZER; + /* ----------------- DECLARE AND CONFIGURE SOURCE HANDLERS ----------------- */ /* Forward - group handlers */ @@ -457,7 +459,6 @@ artwork_read_byurl(struct evbuffer *evbuf, const char *url) ret = http_client_request(&client, NULL); if (ret < 0) { - DPRINTF(E_LOG, L_ART, "Request to '%s' failed with return value %d\n", url, ret); goto out; } @@ -941,12 +942,18 @@ artwork_get_byurl(struct evbuffer *artwork, const char *url, struct artwork_req_ CHECK_NULL(L_ART, raw = evbuffer_new()); format = ART_E_ERROR; + // Accessing the cache is thread safe, the purpose of the lock is to make the + // artwork stash more effective if we have parallel requests for the same url. + // It will assure that the artwork from the first request is downloaded and + // stashed before processing the next request. + pthread_mutex_lock(&artwork_cache_stash_mutex); ret = cache_artwork_read(raw, url, &format); if (ret < 0) { format = artwork_read_byurl(raw, url); cache_artwork_stash(raw, url, format); } + pthread_mutex_unlock(&artwork_cache_stash_mutex); // If we couldn't read, or we have cached a negative result from the last attempt, we stop now if (format <= 0) @@ -1123,7 +1130,7 @@ online_source_response_parse(char **artwork_url, const struct online_source *src } static int -online_source_request_url_make(char *url, size_t url_size, const struct online_source *src, struct artwork_ctx *ctx) +online_source_search_url_make(char *url, size_t url_size, const struct online_source *src, struct artwork_ctx *ctx) { struct db_queue_item *queue_item; struct keyval query = { 0 }; @@ -1166,8 +1173,8 @@ online_source_request_url_make(char *url, size_t url_size, const struct online_s goto error; } - if ((artist && strncasecmp("unknown", artist, strlen("unknown")) == 0) || - (album && strncasecmp("unknown", album, strlen("unknown")) == 0) ) + if ((artist && strncmp(CFG_NAME_UNKNOWN_ARTIST, artist, strlen(CFG_NAME_UNKNOWN_ARTIST)) == 0) || + (album && strncmp(CFG_NAME_UNKNOWN_ALBUM, album, strlen(CFG_NAME_UNKNOWN_ARTIST)) == 0) ) { DPRINTF(E_DBG, L_ART, "Skipping online artwork search for unknown artist/album\n"); goto error; @@ -1192,14 +1199,17 @@ online_source_request_url_make(char *url, size_t url_size, const struct online_s ret = keyval_add(&query, src->query_parts[i].key, param); if (ret < 0) { - DPRINTF(E_LOG, L_ART, "keyval_add() failed in request_url_make()\n"); + DPRINTF(E_LOG, L_ART, "keyval_add() failed in online_source_request_url_make()\n"); goto error; } } encoded_query = http_form_urlencode(&query); if (!encoded_query) - goto error; + { + DPRINTF(E_WARN, L_ART, "URL encoding for online artwork search failed\n"); + goto error; + } snprintf(url, url_size, "%s?%s", src->search_endpoint, src->search_param); if (safe_snreplace(url, url_size, "$QUERY$", encoded_query) < 0) @@ -1227,18 +1237,13 @@ online_source_search_check_last(char **last_artwork_url, const struct online_sou struct online_search_history *history = src->search_history; bool is_same; - pthread_mutex_lock(&history->mutex); - is_same = (hash == history->last_hash) && (max_w == history->last_max_w) && (max_h == history->last_max_h); - // Copy this to the caller while we have the lock anyway if (is_same) *last_artwork_url = safe_strdup(history->last_artwork_url); - pthread_mutex_unlock(&history->mutex); - return is_same ? 0 : -1; } @@ -1248,8 +1253,6 @@ online_source_is_failing(const struct online_source *src, int id) struct online_search_history *history = src->search_history; bool is_failing; - pthread_mutex_lock(&history->mutex); - // If the last request was more than ONLINE_SEARCH_COOLDOWN_TIME ago we will always try again if (time(NULL) > history->last_timestamp + ONLINE_SEARCH_COOLDOWN_TIME) is_failing = false; @@ -1266,22 +1269,20 @@ online_source_is_failing(const struct online_source *src, int id) else is_failing = true; - pthread_mutex_unlock(&history->mutex); - return is_failing; } static void -online_source_history_update(const struct online_source *src, int id, uint32_t request_hash, int response_code, const char *artwork_url) +online_source_history_update(const struct online_source *src, int id, uint32_t request_hash, int response_code, const char *artwork_url, int max_w, int max_h) { struct online_search_history *history = src->search_history; - pthread_mutex_lock(&history->mutex); - history->last_id = id; history->last_hash = request_hash; history->last_response_code = response_code; history->last_timestamp = time(NULL); + history->last_max_w = max_w; + history->last_max_h = max_h; free(history->last_artwork_url); history->last_artwork_url = safe_strdup(artwork_url); // FIXME should free this on exit @@ -1290,8 +1291,6 @@ online_source_history_update(const struct online_source *src, int id, uint32_t r history->count_failures = 0; else history->count_failures++; - - pthread_mutex_unlock(&history->mutex); } static int @@ -1327,34 +1326,24 @@ auth_header_add(struct keyval *headers, const struct online_source *src) } static char * -online_source_search(const struct online_source *src, struct artwork_ctx *ctx) +online_source_artwork_url_get(const char *search_url, const struct online_source *src, int id, int max_w, int max_h) { char *artwork_url; struct http_client_ctx client = { 0 }; struct keyval output_headers = { 0 }; uint32_t hash; - char url[2048]; int ret; - DPRINTF(E_SPAM, L_ART, "Trying %s for %s\n", src->name, ctx->dbmfi->path); - - ret = online_source_request_url_make(url, sizeof(url), src, ctx); - if (ret < 0) - { - DPRINTF(E_WARN, L_ART, "Skipping artwork source %s, could not construct a request URL\n", src->name); - return NULL; - } - // Be nice to our peer + improve response times by not repeating search requests - hash = djb_hash(url, strlen(url)); - ret = online_source_search_check_last(&artwork_url, src, hash, ctx->req_params.max_w, ctx->req_params.max_h); + hash = djb_hash(search_url, strlen(search_url)); + ret = online_source_search_check_last(&artwork_url, src, hash, max_w, max_h); if (ret == 0) { return artwork_url; // Will be NULL if we are repeating a search that failed } // If our recent searches have been futile we may give the source a break - if (online_source_is_failing(src, ctx->id)) + if (online_source_is_failing(src, id)) { DPRINTF(E_DBG, L_ART, "Skipping artwork source %s, too many failed requests\n", src->name); return NULL; @@ -1367,18 +1356,18 @@ online_source_search(const struct online_source *src, struct artwork_ctx *ctx) } CHECK_NULL(L_ART, client.input_body = evbuffer_new()); - client.url = url; + client.url = search_url; client.output_headers = &output_headers; ret = http_client_request(&client, NULL); keyval_clear(&output_headers); if (ret < 0 || client.response_code != HTTP_OK) { - DPRINTF(E_WARN, L_ART, "Artwork request to '%s' failed, response code %d\n", url, client.response_code); + DPRINTF(E_WARN, L_ART, "Artwork request to '%s' failed, response code %d\n", search_url, client.response_code); goto error; } - ret = online_source_response_parse(&artwork_url, src, client.input_body, ctx->req_params.max_w, ctx->req_params.max_h); + ret = online_source_response_parse(&artwork_url, src, client.input_body, max_w, max_h); if (ret == ONLINE_SOURCE_PARSE_NOT_FOUND) DPRINTF(E_DBG, L_ART, "No image tag found in response from source '%s'\n", src->name); else if (ret == ONLINE_SOURCE_PARSE_INVALID) @@ -1391,16 +1380,41 @@ online_source_search(const struct online_source *src, struct artwork_ctx *ctx) if (ret != ONLINE_SOURCE_PARSE_OK) goto error; - online_source_history_update(src, ctx->id, hash, client.response_code, artwork_url); + online_source_history_update(src, id, hash, client.response_code, artwork_url, max_w, max_h); evbuffer_free(client.input_body); return artwork_url; error: - online_source_history_update(src, ctx->id, hash, client.response_code, NULL); + online_source_history_update(src, id, hash, client.response_code, NULL, max_w, max_h); evbuffer_free(client.input_body); return NULL; } +static char * +online_source_search(const struct online_source *src, struct artwork_ctx *ctx) +{ + struct online_search_history *history = src->search_history; + char search_url[2048]; + char *artwork_url; + int ret; + + DPRINTF(E_SPAM, L_ART, "Trying %s for %s\n", src->name, ctx->dbmfi->path); + + ret = online_source_search_url_make(search_url, sizeof(search_url), src, ctx); + if (ret < 0) + return NULL; + + // The protection against flooding the online source with requests requires + // that online_source_request() isn't called in parallel + pthread_mutex_lock(&history->mutex); + + artwork_url = online_source_artwork_url_get(search_url, src, ctx->id, ctx->req_params.max_w, ctx->req_params.max_h); + + pthread_mutex_unlock(&history->mutex); + + return artwork_url; +} + static bool online_source_is_enabled(const struct online_source *src) { diff --git a/src/http.c b/src/http.c index efb34080..9e6e6102 100644 --- a/src/http.c +++ b/src/http.c @@ -172,7 +172,7 @@ http_client_request(struct http_client_ctx *ctx, struct http_client_session *ses res = curl_easy_perform(curl); if (res != CURLE_OK) { - DPRINTF(E_LOG, L_HTTP, "Request to %s failed: %s\n", ctx->url, curl_easy_strerror(res)); + DPRINTF(E_WARN, L_HTTP, "Request to %s failed: %s\n", ctx->url, curl_easy_strerror(res)); curl_slist_free_all(headers); if (!session) { From 504c056c8791d63b05f674aece3271388c38c4f8 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Fri, 3 Jan 2025 10:13:02 +0100 Subject: [PATCH 4/7] [settings] Make settings convenience macros more convenient --- src/player.c | 14 ++++++-------- src/settings.c | 1 + src/settings.h | 12 ++++++------ 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/player.c b/src/player.c index 430a164b..bcbbcb7c 100644 --- a/src/player.c +++ b/src/player.c @@ -308,7 +308,6 @@ static int player_flush_pending; // Config values and player settings category static int speaker_autoselect; static int clear_queue_on_stop_disabled; -static struct settings_category *player_settings_category; // Player status static enum play_status player_state; @@ -3161,7 +3160,7 @@ repeat_set(void *arg, int *retval) } // Persist - SETTINGS_SETINT(player_settings_category, PLAYER_SETTINGS_MODE_REPEAT, repeat); + SETTINGS_SETINT("player", PLAYER_SETTINGS_MODE_REPEAT, repeat); if (repeat == REPEAT_ALL || repeat == REPEAT_SONG) { @@ -3203,7 +3202,7 @@ shuffle_set(void *arg, int *retval) shuffle = new_shuffle; // Persist - SETTINGS_SETBOOL(player_settings_category, PLAYER_SETTINGS_MODE_SHUFFLE, shuffle); + SETTINGS_SETBOOL("player", PLAYER_SETTINGS_MODE_SHUFFLE, shuffle); out: *retval = 0; @@ -3219,7 +3218,7 @@ consume_set(void *arg, int *retval) consume = cmdarg->intval; // Persist - SETTINGS_SETBOOL(player_settings_category, PLAYER_SETTINGS_MODE_CONSUME, consume); + SETTINGS_SETBOOL("player", PLAYER_SETTINGS_MODE_CONSUME, consume); if (consume) { @@ -3879,11 +3878,10 @@ player_init(void) clear_queue_on_stop_disabled = cfg_getbool(cfg_getsec(cfg, "mpd"), "clear_queue_on_stop_disable"); } - CHECK_NULL(L_PLAYER, player_settings_category = settings_category_get("player")); - ret = SETTINGS_GETINT(player_settings_category, PLAYER_SETTINGS_MODE_REPEAT); + ret = SETTINGS_GETINT("player", PLAYER_SETTINGS_MODE_REPEAT); repeat = (ret > 0) ? ret : REPEAT_OFF; - shuffle = SETTINGS_GETBOOL(player_settings_category, PLAYER_SETTINGS_MODE_SHUFFLE); - consume = SETTINGS_GETBOOL(player_settings_category, PLAYER_SETTINGS_MODE_CONSUME); + shuffle = SETTINGS_GETBOOL("player", PLAYER_SETTINGS_MODE_SHUFFLE); + consume = SETTINGS_GETBOOL("player", PLAYER_SETTINGS_MODE_CONSUME); player_state = PLAY_STOPPED; diff --git a/src/settings.c b/src/settings.c index 2ad947fa..8bb13fa0 100644 --- a/src/settings.c +++ b/src/settings.c @@ -55,6 +55,7 @@ static struct settings_option misc_options[] = { { "streamurl_keywords_artwork_url", SETTINGS_TYPE_STR }, { "streamurl_keywords_length", SETTINGS_TYPE_STR }, + { "streamurl_ignore", SETTINGS_TYPE_BOOL }, }; static struct settings_option player_options[] = diff --git a/src/settings.h b/src/settings.h index 852fa658..3e3d4693 100644 --- a/src/settings.h +++ b/src/settings.h @@ -59,9 +59,9 @@ settings_option_getbool(struct settings_option *option); char * settings_option_getstr(struct settings_option *option); -#define SETTINGS_GETINT(category, name) settings_option_getint(settings_option_get((category), (name))) -#define SETTINGS_GETBOOL(category, name) settings_option_getbool(settings_option_get((category), (name))) -#define SETTINGS_GETSTR(category, name) settings_option_getstr(settings_option_get((category), (name))) +#define SETTINGS_GETINT(category, name) settings_option_getint(settings_option_get(settings_category_get(category), (name))) +#define SETTINGS_GETBOOL(category, name) settings_option_getbool(settings_option_get(settings_category_get(category), (name))) +#define SETTINGS_GETSTR(category, name) settings_option_getstr(settings_option_get(settings_category_get(category), (name))) int settings_option_setint(struct settings_option *option, int value); @@ -72,9 +72,9 @@ settings_option_setbool(struct settings_option *option, bool value); int settings_option_setstr(struct settings_option *option, const char *value); -#define SETTINGS_SETINT(category, name, value) settings_option_setint(settings_option_get((category), (name)), (value)) -#define SETTINGS_SETBOOL(category, name, value) settings_option_setbool(settings_option_get((category), (name)), (value)) -#define SETTINGS_SETSTR(category, name, value) settings_option_setstr(settings_option_get((category), (name)), (value)) +#define SETTINGS_SETINT(category, name, value) settings_option_setint(settings_option_get(settings_category_get(category), (name)), (value)) +#define SETTINGS_SETBOOL(category, name, value) settings_option_setbool(settings_option_get(settings_category_get(category), (name)), (value)) +#define SETTINGS_SETSTR(category, name, value) settings_option_setstr(settings_option_get(settings_category_get(category), (name)), (value)) int From d9cd0f4142f3a5ef6d9c9d52f23ca2c9bdd128fd Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Fri, 3 Jan 2025 10:14:23 +0100 Subject: [PATCH 5/7] [http] Implement setting to ignore StreamUrl Some radio stations send garbage in the ICY StreamUrl tag, ref issue #1829. --- src/inputs/http.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/inputs/http.c b/src/inputs/http.c index a50e48fc..ad0c26dd 100644 --- a/src/inputs/http.c +++ b/src/inputs/http.c @@ -293,11 +293,14 @@ metadata_prepare(struct input_source *source) // Note we map title to album, because clients should show stream name as title swap_pointers(&prepared_metadata.parsed.album, &m->title); - // In this case we have to go async to download the url and process the content - if (m->url && !artwork_extension_is_artwork(m->url)) - worker_execute(streamurl_cb, m->url, strlen(m->url) + 1, 0); - else - swap_pointers(&prepared_metadata.parsed.artwork_url, &m->url); + if (! SETTINGS_GETBOOL("misc", "streamurl_ignore")) + { + // In this case we have to go async to download the url and process the content + if (m->url && !artwork_extension_is_artwork(m->url)) + worker_execute(streamurl_cb, m->url, strlen(m->url) + 1, 0); + else + swap_pointers(&prepared_metadata.parsed.artwork_url, &m->url); + } http_icy_metadata_free(m, 0); return 0; From 2497fe89dfa776469ef03cd416447cf49d472a12 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Fri, 10 Jan 2025 20:37:45 +0100 Subject: [PATCH 6/7] [settings] Move two settings to artwork category show_cover_artwork_in_album_lists and streamurl_ignore. Ref #1829. --- src/inputs/http.c | 2 +- src/settings.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/inputs/http.c b/src/inputs/http.c index ad0c26dd..c53ef10f 100644 --- a/src/inputs/http.c +++ b/src/inputs/http.c @@ -293,7 +293,7 @@ metadata_prepare(struct input_source *source) // Note we map title to album, because clients should show stream name as title swap_pointers(&prepared_metadata.parsed.album, &m->title); - if (! SETTINGS_GETBOOL("misc", "streamurl_ignore")) + if (! SETTINGS_GETBOOL("artwork", "streamurl_ignore")) { // In this case we have to go async to download the url and process the content if (m->url && !artwork_extension_is_artwork(m->url)) diff --git a/src/settings.c b/src/settings.c index 8bb13fa0..79582339 100644 --- a/src/settings.c +++ b/src/settings.c @@ -30,7 +30,6 @@ static struct settings_option webinterface_options[] = { "show_composer_now_playing", SETTINGS_TYPE_BOOL }, { "show_filepath_now_playing", SETTINGS_TYPE_BOOL }, { "show_composer_for_genre", SETTINGS_TYPE_STR }, - { "show_cover_artwork_in_album_lists", SETTINGS_TYPE_BOOL, { true } }, { "show_menu_item_playlists", SETTINGS_TYPE_BOOL, { true } }, { "show_menu_item_music", SETTINGS_TYPE_BOOL, { true } }, { "show_menu_item_podcasts", SETTINGS_TYPE_BOOL, { true } }, @@ -49,13 +48,14 @@ static struct settings_option artwork_options[] = { "use_artwork_source_spotify", SETTINGS_TYPE_BOOL, { true } }, { "use_artwork_source_discogs", SETTINGS_TYPE_BOOL, { false } }, { "use_artwork_source_coverartarchive", SETTINGS_TYPE_BOOL, { false } }, + { "show_cover_artwork_in_album_lists", SETTINGS_TYPE_BOOL, { true } }, + { "streamurl_ignore", SETTINGS_TYPE_BOOL, { false } }, }; static struct settings_option misc_options[] = { { "streamurl_keywords_artwork_url", SETTINGS_TYPE_STR }, { "streamurl_keywords_length", SETTINGS_TYPE_STR }, - { "streamurl_ignore", SETTINGS_TYPE_BOOL }, }; static struct settings_option player_options[] = From fbdc114288c2151cfc8c4f6ee752936df7199282 Mon Sep 17 00:00:00 2001 From: Alain Nussbaumer Date: Fri, 3 Jan 2025 16:01:03 +0100 Subject: [PATCH 7/7] [web] Add streamurl_ignore setting under artwork tab --- web-src/src/i18n/de.json | 5 +++-- web-src/src/i18n/en.json | 8 ++++---- web-src/src/i18n/fr.json | 10 +++++----- web-src/src/i18n/zh-CN.json | 8 ++++---- web-src/src/i18n/zh-TW.json | 8 ++++---- web-src/src/pages/PageSettingsArtwork.vue | 15 ++++++++++++++- web-src/src/pages/PageSettingsWebinterface.vue | 18 ------------------ 7 files changed, 34 insertions(+), 38 deletions(-) diff --git a/web-src/src/i18n/de.json b/web-src/src/i18n/de.json index 72292def..c01c9564 100644 --- a/web-src/src/i18n/de.json +++ b/web-src/src/i18n/de.json @@ -440,12 +440,13 @@ }, "settings": { "artwork": { - "artwork": "Artwork", + "title": "Artwork", "coverartarchive": "Cover Art Archive", "discogs": "Discogs", "explanation-1": "OwnTone verarbeitet PNG- und JPEG-Artwork, welches in einer eigenen Datei in der Bibliothek, in die Dateien eingebettet oder online von Radiostationen bereitgestellt werden kann.", "explanation-2": "Zusätzlich kann auf folgende Artwork-Anbieter zugegriffen werden:", - "spotify": "Spotify" + "spotify": "Spotify", + "streaming": "Bereitgestellte Artwork von Radiostationen ignorieren" }, "devices": { "no-active-pairing": "Keine aktive Pairing-Anfrage", diff --git a/web-src/src/i18n/en.json b/web-src/src/i18n/en.json index e22914e4..e68914ba 100644 --- a/web-src/src/i18n/en.json +++ b/web-src/src/i18n/en.json @@ -440,12 +440,14 @@ }, "settings": { "artwork": { - "artwork": "Artwork", + "title": "Artwork", "coverartarchive": "Cover Art Archive", "discogs": "Discogs", "explanation-1": "OwnTone supports PNG and JPEG artwork which is either placed as separate image files in the library, embedded in the media files or made available online by radio stations.", "explanation-2": "In addition to that, you can enable fetching artwork from the following artwork providers:", - "spotify": "Spotify" + "show-coverart": "Show cover artwork in album list", + "spotify": "Spotify", + "streaming": "Ignore artwork provided by radio stations" }, "devices": { "no-active-pairing": "No active pairing request.", @@ -459,7 +461,6 @@ "verify": "Verify" }, "general": { - "album-lists": "Album Lists", "audiobooks": "Audiobooks", "files": "Files", "genres": "Genres", @@ -481,7 +482,6 @@ "show-composer-genres": "Show composer only for listed genres", "show-composer-info": "If enabled the composer of the current playing track is shown on the \"Now playing page\"", "show-composer": "Show composer", - "show-coverart": "Show cover artwork in album list", "show-path": "Show filepath on the \"Now playing\" page" }, "services": { diff --git a/web-src/src/i18n/fr.json b/web-src/src/i18n/fr.json index ef27655d..01db1c42 100644 --- a/web-src/src/i18n/fr.json +++ b/web-src/src/i18n/fr.json @@ -440,12 +440,14 @@ }, "settings": { "artwork": { - "artwork": "Illustrations", + "title": "Illustrations", "coverartarchive": "Cover Art Archive", "discogs": "Discogs", - "explanation-1": "Prend en charge les illustrations au format PNG et JPEG qui sont soit placées dans la bibliothèque en tant que fichiers image séparés, soit intégrées dans les fichiers média, soit mises à disposition en ligne par les stations de radio.", + "explanation-1": "OwnTone prend en charge les illustrations au format PNG et JPEG qui sont soit placées dans la bibliothèque en tant que fichiers image séparés, soit intégrées dans les fichiers média, soit mises à disposition en ligne par les stations de radio.", "explanation-2": "En outre, vous pouvez activer la récupération des illustrations à partir des fournisseurs d’illustrations suivants :", - "spotify": "Spotify" + "show-coverart": "Afficher les illustrations dans la liste d’albums", + "spotify": "Spotify", + "streaming": "Ignorer les illustrations fournies par les stations de radio" }, "devices": { "no-active-pairing": "Aucune demande de jumelage active.", @@ -459,7 +461,6 @@ "verify": "Vérifier" }, "general": { - "album-lists": "Listes d’albums", "audiobooks": "Livres audio", "files": "Fichiers", "genres": "Genres", @@ -481,7 +482,6 @@ "show-composer-genres": "Afficher le compositeur uniquement pour les genres listés", "show-composer-info": "Si actif, le compositeur de la piste en cours de lecture est affiché sur la page « En cours de lecture »", "show-composer": "Afficher le compositeur", - "show-coverart": "Afficher les illustrations dans la liste d’albums", "show-path": "Afficher le chemin du fichier sur la page « En cours de lecture »" }, "services": { diff --git a/web-src/src/i18n/zh-CN.json b/web-src/src/i18n/zh-CN.json index dc3b89f3..25d5925d 100644 --- a/web-src/src/i18n/zh-CN.json +++ b/web-src/src/i18n/zh-CN.json @@ -440,12 +440,14 @@ }, "settings": { "artwork": { - "artwork": "封面", + "title": "封面", "coverartarchive": "Cover Art Archive", "discogs": "Discogs", "explanation-1": "OwnTone支持PNG和 JPEG封面,这些封面可以作为单独的图像文件放置在库中或嵌入到媒体文件中,也可以通过广播电台在线提供", "explanation-2": "除此之外,您还可以从以下素材提供者获取封面:", - "spotify": "Spotify" + "show-coverart": "在专辑列表中显示封面艺术作品", + "spotify": "Spotify", + "streaming": "忽略广播电台提供的作品" }, "devices": { "no-active-pairing": "没有活跃的配对请求", @@ -459,7 +461,6 @@ "verify": "验证" }, "general": { - "album-lists": "专辑列表", "audiobooks": "有声读物", "files": "文件", "genres": "流派", @@ -481,7 +482,6 @@ "show-composer-genres": "仅显示列出的流派的作曲家", "show-composer-info": "如果启用,当前播放曲目的作曲家将显示在“正在播放页面”上", "show-composer": "显示作曲家", - "show-coverart": "在专辑列表中显示封面艺术作品", "show-path": "在“正在播放”页面显示文件路径" }, "services": { diff --git a/web-src/src/i18n/zh-TW.json b/web-src/src/i18n/zh-TW.json index 6c7a807e..b8af040d 100644 --- a/web-src/src/i18n/zh-TW.json +++ b/web-src/src/i18n/zh-TW.json @@ -440,12 +440,14 @@ }, "settings": { "artwork": { - "artwork": "封面", + "title": "封面", "coverartarchive": "Cover Art Archive", "discogs": "Discogs", "explanation-1": "OwnTone支持PNG和 JPEG封面,這些封面可以作為單獨的圖像文件放置在庫中或嵌入到媒體文件中,也可以通過電台在線提供", "explanation-2": "除此之外,您還可以從以下素材提供者獲取封面:", - "spotify": "Spotify" + "show-coverart": "在專輯列表中顯示封面藝術作品", + "spotify": "Spotify", + "streaming": "忽略電台提供的作品" }, "devices": { "no-active-pairing": "沒有活躍的配對請求", @@ -459,7 +461,6 @@ "verify": "驗證" }, "general": { - "album-lists": "專輯列表", "audiobooks": "有聲書", "files": "文件", "genres": "音樂類型", @@ -481,7 +482,6 @@ "show-composer-genres": "僅顯示列出的音樂類型的作曲家", "show-composer-info": "如果啓用,當前播放曲目的作曲家將顯示在“正在播放頁面”上", "show-composer": "顯示作曲家", - "show-coverart": "在專輯列表中顯示封面藝術作品", "show-path": "在“正在播放”頁面顯示文件路徑" }, "services": { diff --git a/web-src/src/pages/PageSettingsArtwork.vue b/web-src/src/pages/PageSettingsArtwork.vue index 83f2cde8..903c5a8b 100644 --- a/web-src/src/pages/PageSettingsArtwork.vue +++ b/web-src/src/pages/PageSettingsArtwork.vue @@ -3,13 +3,26 @@ - - - -