[db,library] Add support for parsing lyrics and storing them in DB

This commit is contained in:
X-Ryl669 2023-09-13 18:15:38 +02:00 committed by ejurgensen
parent d7d3a0767d
commit cf8b3ecd3a
6 changed files with 105 additions and 43 deletions

View File

@ -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);

View File

@ -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)

View File

@ -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 \

View File

@ -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);

View File

@ -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;

View File

@ -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;
@ -518,7 +551,7 @@ scan_metadata_ffmpeg(struct media_file_info *mfi, const char *file)
if (mfi->bits_per_sample == 0)
mfi->bits_per_sample = av_get_bits_per_sample(codec_id);
mfi->channels = channels;
}
}
break;
default: