mirror of
https://github.com/owntone/owntone-server.git
synced 2024-12-27 23:55:57 -05:00
[db,library] Add support for parsing lyrics and storing them in DB
This commit is contained in:
parent
d7d3a0767d
commit
cf8b3ecd3a
1
src/db.c
1
src/db.c
@ -777,6 +777,7 @@ free_mfi(struct media_file_info *mfi, int content_only)
|
||||
free(mfi->composer_sort);
|
||||
free(mfi->album_artist_sort);
|
||||
free(mfi->virtual_path);
|
||||
free(mfi->lyrics);
|
||||
|
||||
if (!content_only)
|
||||
free(mfi);
|
||||
|
1
src/db.h
1
src/db.h
@ -248,6 +248,7 @@ struct media_file_info {
|
||||
char *composer_sort;
|
||||
|
||||
uint32_t scan_kind; /* Identifies the library_source that created/updates this item */
|
||||
char *lyrics;
|
||||
};
|
||||
|
||||
#define mfi_offsetof(field) offsetof(struct media_file_info, field)
|
||||
|
@ -98,7 +98,8 @@
|
||||
" composer_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
|
||||
" channels INTEGER DEFAULT 0," \
|
||||
" usermark INTEGER DEFAULT 0," \
|
||||
" scan_kind INTEGER DEFAULT 0" \
|
||||
" scan_kind INTEGER DEFAULT 0," \
|
||||
" lyrics TEXT DEFAULT NULL COLLATE DAAP" \
|
||||
");"
|
||||
|
||||
#define T_PL \
|
||||
|
@ -26,7 +26,7 @@
|
||||
* is a major upgrade. In other words minor version upgrades permit downgrading
|
||||
* the server after the database was upgraded. */
|
||||
#define SCHEMA_VERSION_MAJOR 22
|
||||
#define SCHEMA_VERSION_MINOR 0
|
||||
#define SCHEMA_VERSION_MINOR 1
|
||||
|
||||
int
|
||||
db_init_indices(sqlite3 *hdl);
|
||||
|
@ -1227,6 +1227,26 @@ static const struct db_upgrade_query db_upgrade_v2200_queries[] =
|
||||
{ U_v2200_SCVER_MINOR, "set schema_version_minor to 00" },
|
||||
};
|
||||
|
||||
/* ---------------------------- 22.00 -> 22.01 ------------------------------ */
|
||||
|
||||
#define U_v2201_ALTER_FILES_ADD_LYRICS \
|
||||
"ALTER TABLE files ADD COLUMN lyrics TEXT DEFAULT NULL COLLATE DAAP;"
|
||||
|
||||
#define U_v2201_SCVER_MAJOR \
|
||||
"UPDATE admin SET value = '22' WHERE key = 'schema_version_major';"
|
||||
#define U_v2201_SCVER_MINOR \
|
||||
"UPDATE admin SET value = '01' WHERE key = 'schema_version_minor';"
|
||||
|
||||
static const struct db_upgrade_query db_upgrade_v2201_queries[] =
|
||||
{
|
||||
{ U_v2201_ALTER_FILES_ADD_LYRICS, "alter table files add column lyrics" },
|
||||
|
||||
{ U_v2201_SCVER_MAJOR, "set schema_version_major to 22" },
|
||||
{ U_v2201_SCVER_MINOR, "set schema_version_minor to 01" },
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* -------------------------- Main upgrade handler -------------------------- */
|
||||
|
||||
int
|
||||
@ -1437,6 +1457,12 @@ db_upgrade(sqlite3 *hdl, int db_ver)
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
/* FALLTHROUGH */
|
||||
|
||||
case 2200:
|
||||
ret = db_generic_upgrade(hdl, db_upgrade_v2201_queries, ARRAY_SIZE(db_upgrade_v2201_queries));
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
/* Last case statement is the only one that ends with a break statement! */
|
||||
break;
|
||||
|
@ -49,6 +49,8 @@ struct metadata_map {
|
||||
// Used for passing errors to DPRINTF (can't count on av_err2str being present)
|
||||
static char errbuf[64];
|
||||
|
||||
static int lyricsindex = -1;
|
||||
|
||||
static inline char *
|
||||
err2str(int errnum)
|
||||
{
|
||||
@ -186,6 +188,7 @@ static const struct metadata_map md_map_generic[] =
|
||||
{ "artist-sort", 0, mfi_offsetof(artist_sort), NULL },
|
||||
{ "album-sort", 0, mfi_offsetof(album_sort), NULL },
|
||||
{ "compilation", 1, mfi_offsetof(compilation), NULL },
|
||||
{ "lyrics", 0, mfi_offsetof(lyrics), NULL },
|
||||
|
||||
// ALAC sort tags
|
||||
{ "sort_name", 0, mfi_offsetof(title_sort), NULL },
|
||||
@ -277,16 +280,67 @@ static const struct metadata_map md_map_id3[] =
|
||||
{ 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
|
||||
iterate_metadata(struct media_file_info *mfi, const AVDictionaryEntry *mdt, const struct metadata_map *md_map)
|
||||
{
|
||||
char **strval;
|
||||
uint32_t *intval;
|
||||
int i;
|
||||
|
||||
if ((mdt->value == NULL) || (strlen(mdt->value) == 0))
|
||||
return 0;
|
||||
|
||||
if (strncmp(mdt->key, "lyrics-", sizeof("lyrics-") - 1) == 0)
|
||||
i = lyricsindex;
|
||||
else
|
||||
i = match_metadata(mdt->key, md_map);
|
||||
|
||||
if (i == -1)
|
||||
return 0;
|
||||
|
||||
if (md_map[i].handler_function)
|
||||
return md_map[i].handler_function(mfi, mdt->value);
|
||||
|
||||
if (!md_map[i].as_int)
|
||||
{
|
||||
strval = (char **) ((char *) mfi + md_map[i].offset);
|
||||
|
||||
if (*strval == NULL)
|
||||
*strval = strdup(mdt->value);
|
||||
}
|
||||
else
|
||||
{
|
||||
intval = (uint32_t *) ((char *) mfi + md_map[i].offset);
|
||||
|
||||
if (*intval == 0)
|
||||
{
|
||||
if (safe_atou32(mdt->value, intval) < 0)
|
||||
return 1; /* Should probably be 0 */
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
extract_metadata_core(struct media_file_info *mfi, AVDictionary *md, const struct metadata_map *md_map)
|
||||
{
|
||||
AVDictionaryEntry *mdt;
|
||||
char **strval;
|
||||
uint32_t *intval;
|
||||
const AVDictionaryEntry *mdt;
|
||||
int mdcount;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
#if 0
|
||||
/* Dump all the metadata reported by ffmpeg */
|
||||
@ -297,42 +351,21 @@ extract_metadata_core(struct media_file_info *mfi, AVDictionary *md, const struc
|
||||
|
||||
mdcount = 0;
|
||||
|
||||
/* Extract actual metadata */
|
||||
for (i = 0; md_map[i].key != NULL; i++)
|
||||
/* 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_iterate(md, NULL);
|
||||
while (mdt != NULL)
|
||||
{
|
||||
mdt = av_dict_get(md, md_map[i].key, NULL, 0);
|
||||
if (mdt == NULL)
|
||||
continue;
|
||||
|
||||
if ((mdt->value == NULL) || (strlen(mdt->value) == 0))
|
||||
continue;
|
||||
|
||||
if (md_map[i].handler_function)
|
||||
{
|
||||
mdcount += md_map[i].handler_function(mfi, mdt->value);
|
||||
continue;
|
||||
}
|
||||
|
||||
mdcount++;
|
||||
|
||||
if (!md_map[i].as_int)
|
||||
{
|
||||
strval = (char **) ((char *) mfi + md_map[i].offset);
|
||||
|
||||
if (*strval == NULL)
|
||||
*strval = strdup(mdt->value);
|
||||
}
|
||||
else
|
||||
{
|
||||
intval = (uint32_t *) ((char *) mfi + md_map[i].offset);
|
||||
|
||||
if (*intval == 0)
|
||||
{
|
||||
ret = safe_atou32(mdt->value, intval);
|
||||
if (ret < 0)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
mdcount += iterate_metadata(mfi, mdt, md_map);
|
||||
mdt = av_dict_iterate(md, mdt);
|
||||
}
|
||||
|
||||
return mdcount;
|
||||
|
Loading…
Reference in New Issue
Block a user