mirror of
https://github.com/owntone/owntone-server.git
synced 2025-05-02 07:53:48 -04:00
[scan] Fixup lyrics changes
This commit is contained in:
parent
98a844b409
commit
8796368b01
@ -251,6 +251,8 @@ OWNTONE_MODULES_CHECK([OWNTONE], [LIBAV],
|
|||||||
[libavutil/avutil.h])
|
[libavutil/avutil.h])
|
||||||
OWNTONE_CHECK_DECLS([avformat_network_init],
|
OWNTONE_CHECK_DECLS([avformat_network_init],
|
||||||
[libavformat/avformat.h])
|
[libavformat/avformat.h])
|
||||||
|
OWNTONE_CHECK_DECLS([av_dict_iterate],
|
||||||
|
[libavutil/dict.h])
|
||||||
])
|
])
|
||||||
|
|
||||||
AC_CHECK_SIZEOF([void *])
|
AC_CHECK_SIZEOF([void *])
|
||||||
|
@ -37,20 +37,23 @@
|
|||||||
#include "http.h"
|
#include "http.h"
|
||||||
#include "conffile.h"
|
#include "conffile.h"
|
||||||
|
|
||||||
|
// From libavutil 57.37.100
|
||||||
|
#if !defined(HAVE_DECL_AV_DICT_ITERATE) || !(HAVE_DECL_AV_DICT_ITERATE)
|
||||||
|
# define av_dict_iterate(dict, entry) av_dict_get((dict), "", (entry), AV_DICT_IGNORE_SUFFIX)
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Mapping between the metadata name(s) and the offset
|
/* Mapping between the metadata name(s) and the offset
|
||||||
* of the equivalent metadata field in struct media_file_info */
|
* of the equivalent metadata field in struct media_file_info */
|
||||||
struct metadata_map {
|
struct metadata_map {
|
||||||
char *key;
|
char *key;
|
||||||
int as_int;
|
int as_int;
|
||||||
size_t offset;
|
size_t offset;
|
||||||
int (*handler_function)(struct media_file_info *, char *);
|
int (*handler_function)(struct media_file_info *, const char *);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Used for passing errors to DPRINTF (can't count on av_err2str being present)
|
// Used for passing errors to DPRINTF (can't count on av_err2str being present)
|
||||||
static char errbuf[64];
|
static char errbuf[64];
|
||||||
|
|
||||||
static int lyricsindex = -1;
|
|
||||||
|
|
||||||
static inline char *
|
static inline char *
|
||||||
err2str(int errnum)
|
err2str(int errnum)
|
||||||
{
|
{
|
||||||
@ -59,7 +62,7 @@ err2str(int errnum)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
parse_genre(struct media_file_info *mfi, char *genre_string)
|
parse_genre(struct media_file_info *mfi, const char *genre_string)
|
||||||
{
|
{
|
||||||
char **genre = (char**)((char *) mfi + mfi_offsetof(genre));
|
char **genre = (char**)((char *) mfi + mfi_offsetof(genre));
|
||||||
char *ptr;
|
char *ptr;
|
||||||
@ -80,12 +83,17 @@ parse_genre(struct media_file_info *mfi, char *genre_string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
parse_slash_separated_ints(char *string, uint32_t *firstval, uint32_t *secondval)
|
parse_slash_separated_ints(const char *string, uint32_t *firstval, uint32_t *secondval)
|
||||||
{
|
{
|
||||||
int numvals = 0;
|
int numvals = 0;
|
||||||
|
char buf[64];
|
||||||
char *ptr;
|
char *ptr;
|
||||||
|
|
||||||
ptr = strchr(string, '/');
|
// dict.h: "The returned entry key or value must not be changed, or it will
|
||||||
|
// cause undefined behavior" -> so we must make a copy
|
||||||
|
snprintf(buf, sizeof(buf), "%s", string);
|
||||||
|
|
||||||
|
ptr = strchr(buf, '/');
|
||||||
if (ptr)
|
if (ptr)
|
||||||
{
|
{
|
||||||
*ptr = '\0';
|
*ptr = '\0';
|
||||||
@ -93,14 +101,14 @@ parse_slash_separated_ints(char *string, uint32_t *firstval, uint32_t *secondval
|
|||||||
numvals++;
|
numvals++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (safe_atou32(string, firstval) == 0)
|
if (safe_atou32(buf, firstval) == 0)
|
||||||
numvals++;
|
numvals++;
|
||||||
|
|
||||||
return numvals;
|
return numvals;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
parse_track(struct media_file_info *mfi, char *track_string)
|
parse_track(struct media_file_info *mfi, const char *track_string)
|
||||||
{
|
{
|
||||||
uint32_t *track = (uint32_t *) ((char *) mfi + mfi_offsetof(track));
|
uint32_t *track = (uint32_t *) ((char *) mfi + mfi_offsetof(track));
|
||||||
uint32_t *total_tracks = (uint32_t *) ((char *) mfi + mfi_offsetof(total_tracks));
|
uint32_t *total_tracks = (uint32_t *) ((char *) mfi + mfi_offsetof(total_tracks));
|
||||||
@ -109,7 +117,7 @@ parse_track(struct media_file_info *mfi, char *track_string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
parse_disc(struct media_file_info *mfi, char *disc_string)
|
parse_disc(struct media_file_info *mfi, const char *disc_string)
|
||||||
{
|
{
|
||||||
uint32_t *disc = (uint32_t *) ((char *) mfi + mfi_offsetof(disc));
|
uint32_t *disc = (uint32_t *) ((char *) mfi + mfi_offsetof(disc));
|
||||||
uint32_t *total_discs = (uint32_t *) ((char *) mfi + mfi_offsetof(total_discs));
|
uint32_t *total_discs = (uint32_t *) ((char *) mfi + mfi_offsetof(total_discs));
|
||||||
@ -118,7 +126,7 @@ parse_disc(struct media_file_info *mfi, char *disc_string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
parse_date(struct media_file_info *mfi, char *date_string)
|
parse_date(struct media_file_info *mfi, const char *date_string)
|
||||||
{
|
{
|
||||||
char year_string[32];
|
char year_string[32];
|
||||||
uint32_t *year = (uint32_t *) ((char *) mfi + mfi_offsetof(year));
|
uint32_t *year = (uint32_t *) ((char *) mfi + mfi_offsetof(year));
|
||||||
@ -154,7 +162,7 @@ parse_date(struct media_file_info *mfi, char *date_string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
parse_albumid(struct media_file_info *mfi, char *id_string)
|
parse_albumid(struct media_file_info *mfi, const char *id_string)
|
||||||
{
|
{
|
||||||
// Already set by a previous tag that we give higher priority
|
// Already set by a previous tag that we give higher priority
|
||||||
if (mfi->songalbumid)
|
if (mfi->songalbumid)
|
||||||
@ -280,92 +288,65 @@ static const struct metadata_map md_map_id3[] =
|
|||||||
{ NULL, 0, 0, NULL }
|
{ NULL, 0, 0, NULL }
|
||||||
};
|
};
|
||||||
|
|
||||||
/* TODO: If the md_map was sorted, the search would be O(log N) instead of O(N) */
|
|
||||||
static int
|
|
||||||
match_metadata(const char *key, const struct metadata_map *md_map)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for (i = 0; md_map[i].key != NULL; i++)
|
|
||||||
{
|
|
||||||
if (strcmp(key, md_map[i].key) == 0)
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
iterate_metadata(struct media_file_info *mfi, const AVDictionaryEntry *mdt, const struct metadata_map *md_map)
|
extract_metadata_from_kv(struct media_file_info *mfi, const char *key, const char *value, const struct metadata_map *md_map)
|
||||||
{
|
{
|
||||||
char **strval;
|
char **strval;
|
||||||
uint32_t *intval;
|
uint32_t *intval;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
if ((mdt->value == NULL) || (strlen(mdt->value) == 0))
|
if ((value == NULL) || (strlen(value) == 0))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (strncmp(mdt->key, "lyrics-", sizeof("lyrics-") - 1) == 0)
|
if (strncmp(key, "lyrics-", sizeof("lyrics-") - 1) == 0)
|
||||||
i = lyricsindex;
|
key = "lyrics";
|
||||||
else
|
|
||||||
i = match_metadata(mdt->key, md_map);
|
|
||||||
|
|
||||||
if (i == -1)
|
for (i = 0; md_map[i].key != NULL; i++)
|
||||||
return 0;
|
{
|
||||||
|
if (strcmp(key, md_map[i].key) == 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (md_map[i].key == NULL)
|
||||||
|
return 0; // Not found in map
|
||||||
|
|
||||||
if (md_map[i].handler_function)
|
if (md_map[i].handler_function)
|
||||||
return md_map[i].handler_function(mfi, mdt->value);
|
return md_map[i].handler_function(mfi, value);
|
||||||
|
|
||||||
if (!md_map[i].as_int)
|
if (!md_map[i].as_int)
|
||||||
{
|
{
|
||||||
strval = (char **) ((char *) mfi + md_map[i].offset);
|
strval = (char **) ((char *) mfi + md_map[i].offset);
|
||||||
|
|
||||||
if (*strval == NULL)
|
if (*strval != NULL)
|
||||||
*strval = strdup(mdt->value);
|
return 0;
|
||||||
|
|
||||||
|
*strval = strdup(value);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
intval = (uint32_t *) ((char *) mfi + md_map[i].offset);
|
intval = (uint32_t *) ((char *) mfi + md_map[i].offset);
|
||||||
|
|
||||||
if (*intval == 0)
|
if (*intval != 0)
|
||||||
{
|
return 0;
|
||||||
if (safe_atou32(mdt->value, intval) < 0)
|
|
||||||
return 0;
|
if (safe_atou32(value, intval) < 0)
|
||||||
}
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
extract_metadata_core(struct media_file_info *mfi, AVDictionary *md, const struct metadata_map *md_map)
|
extract_metadata_from_dict(struct media_file_info *mfi, AVDictionary *md, const struct metadata_map *md_map)
|
||||||
{
|
{
|
||||||
const AVDictionaryEntry *mdt;
|
const AVDictionaryEntry *mdt = NULL;
|
||||||
int mdcount;
|
int mdcount = 0;
|
||||||
|
|
||||||
#if 0
|
while ((mdt = av_dict_iterate(md, mdt)))
|
||||||
/* Dump all the metadata reported by ffmpeg */
|
|
||||||
mdt = NULL;
|
|
||||||
while ((mdt = av_dict_get(md, "", mdt, AV_DICT_IGNORE_SUFFIX)) != NULL)
|
|
||||||
DPRINTF(E_DBG, L_SCAN, " -> %s = %s\n", mdt->key, mdt->value);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
mdcount = 0;
|
|
||||||
|
|
||||||
/* Cache this search once to avoid doing it per metadata key */
|
|
||||||
if (lyricsindex == -1)
|
|
||||||
lyricsindex = match_metadata("lyrics", md_map);
|
|
||||||
|
|
||||||
/* Extract lyrics if any found.
|
|
||||||
FFMPEG creates a metadata key that's embedding the language code in it, like 'lyrics-eng' or 'lyrics-XXX'
|
|
||||||
So it's not possible to query this key directly except by brute-forcing all languages.
|
|
||||||
Instead, we are reversing the metadata searching algorithm to query all metadata key that FFMPEG fetched
|
|
||||||
and matching them against our own map. This is the most efficient method to search it without having 2 pass
|
|
||||||
on the KV store */
|
|
||||||
mdt = av_dict_get(md, "", NULL, AV_DICT_IGNORE_SUFFIX);
|
|
||||||
while (mdt != NULL)
|
|
||||||
{
|
{
|
||||||
mdcount += iterate_metadata(mfi, mdt, md_map);
|
// DPRINTF(E_DBG, L_SCAN, " -> %s = %s\n", mdt->key, mdt->value);
|
||||||
mdt = av_dict_get(md, "", mdt, AV_DICT_IGNORE_SUFFIX);
|
mdcount += extract_metadata_from_kv(mfi, mdt->key, mdt->value, md_map);
|
||||||
}
|
}
|
||||||
|
|
||||||
return mdcount;
|
return mdcount;
|
||||||
@ -381,7 +362,7 @@ extract_metadata(struct media_file_info *mfi, AVFormatContext *ctx, AVStream *au
|
|||||||
|
|
||||||
if (ctx->metadata)
|
if (ctx->metadata)
|
||||||
{
|
{
|
||||||
ret = extract_metadata_core(mfi, ctx->metadata, md_map);
|
ret = extract_metadata_from_dict(mfi, ctx->metadata, md_map);
|
||||||
mdcount += ret;
|
mdcount += ret;
|
||||||
|
|
||||||
DPRINTF(E_DBG, L_SCAN, "Picked up %d tags from file metadata\n", ret);
|
DPRINTF(E_DBG, L_SCAN, "Picked up %d tags from file metadata\n", ret);
|
||||||
@ -389,7 +370,7 @@ extract_metadata(struct media_file_info *mfi, AVFormatContext *ctx, AVStream *au
|
|||||||
|
|
||||||
if (audio_stream->metadata)
|
if (audio_stream->metadata)
|
||||||
{
|
{
|
||||||
ret = extract_metadata_core(mfi, audio_stream->metadata, md_map);
|
ret = extract_metadata_from_dict(mfi, audio_stream->metadata, md_map);
|
||||||
mdcount += ret;
|
mdcount += ret;
|
||||||
|
|
||||||
DPRINTF(E_DBG, L_SCAN, "Picked up %d tags from audio stream metadata\n", ret);
|
DPRINTF(E_DBG, L_SCAN, "Picked up %d tags from audio stream metadata\n", ret);
|
||||||
@ -397,7 +378,7 @@ extract_metadata(struct media_file_info *mfi, AVFormatContext *ctx, AVStream *au
|
|||||||
|
|
||||||
if (video_stream && video_stream->metadata)
|
if (video_stream && video_stream->metadata)
|
||||||
{
|
{
|
||||||
ret = extract_metadata_core(mfi, video_stream->metadata, md_map);
|
ret = extract_metadata_from_dict(mfi, video_stream->metadata, md_map);
|
||||||
mdcount += ret;
|
mdcount += ret;
|
||||||
|
|
||||||
DPRINTF(E_DBG, L_SCAN, "Picked up %d tags from video stream metadata\n", ret);
|
DPRINTF(E_DBG, L_SCAN, "Picked up %d tags from video stream metadata\n", ret);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user