mirror of
https://github.com/owntone/owntone-server.git
synced 2025-01-31 00:16:01 -05:00
Merge pull request #1836 from owntone/streamurl_direct2
Streamurl options and artwork fixes, see issue #1829
This commit is contained in:
commit
450a333fd6
@ -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,6 +1173,13 @@ online_source_request_url_make(char *url, size_t url_size, const struct online_s
|
||||
goto error;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
for (i = 0; src->query_parts[i].key; i++)
|
||||
{
|
||||
if (!album && strstr(src->query_parts[i].template, "$ALBUM$"))
|
||||
@ -1185,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)
|
||||
@ -1220,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;
|
||||
}
|
||||
|
||||
@ -1241,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;
|
||||
@ -1259,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
|
||||
@ -1283,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
|
||||
@ -1320,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;
|
||||
@ -1360,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)
|
||||
@ -1384,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)
|
||||
{
|
||||
|
38
src/http.c
38
src/http.c
@ -36,6 +36,7 @@
|
||||
#include <libavutil/opt.h>
|
||||
|
||||
#include <event2/event.h>
|
||||
#include <event2/keyvalq_struct.h>
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
@ -171,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)
|
||||
{
|
||||
@ -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)
|
||||
{
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
@ -276,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("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))
|
||||
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;
|
||||
|
14
src/player.c
14
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;
|
||||
|
||||
|
@ -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,6 +48,8 @@ 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[] =
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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": {
|
||||
|
@ -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": {
|
||||
|
@ -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": {
|
||||
|
@ -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": {
|
||||
|
@ -3,13 +3,26 @@
|
||||
<tabs-settings />
|
||||
<content-with-heading>
|
||||
<template #heading-left>
|
||||
<div class="title is-4" v-text="$t('page.settings.artwork.artwork')" />
|
||||
<div class="title is-4" v-text="$t('page.settings.artwork.title')" />
|
||||
</template>
|
||||
<template #content>
|
||||
<div
|
||||
class="content"
|
||||
v-text="$t('page.settings.artwork.explanation-1')"
|
||||
/>
|
||||
<settings-checkbox category="artwork" name="streamurl_ignore">
|
||||
<template #label>
|
||||
<span v-text="$t('page.settings.artwork.streaming')" />
|
||||
</template>
|
||||
</settings-checkbox>
|
||||
<settings-checkbox
|
||||
category="artwork"
|
||||
name="show_cover_artwork_in_album_lists"
|
||||
>
|
||||
<template #label>
|
||||
<span v-text="$t('page.settings.artwork.show-coverart')" />
|
||||
</template>
|
||||
</settings-checkbox>
|
||||
<div
|
||||
class="content"
|
||||
v-text="$t('page.settings.artwork.explanation-2')"
|
||||
|
@ -71,24 +71,6 @@
|
||||
</settings-checkbox>
|
||||
</template>
|
||||
</content-with-heading>
|
||||
<content-with-heading>
|
||||
<template #heading-left>
|
||||
<div
|
||||
class="title is-4"
|
||||
v-text="$t('page.settings.general.album-lists')"
|
||||
/>
|
||||
</template>
|
||||
<template #content>
|
||||
<settings-checkbox
|
||||
category="webinterface"
|
||||
name="show_cover_artwork_in_album_lists"
|
||||
>
|
||||
<template #label>
|
||||
<span v-text="$t('page.settings.general.show-coverart')" />
|
||||
</template>
|
||||
</settings-checkbox>
|
||||
</template>
|
||||
</content-with-heading>
|
||||
<content-with-heading>
|
||||
<template #heading-left>
|
||||
<div
|
||||
|
Loading…
x
Reference in New Issue
Block a user