[scan] Read local file chapter metadata into mfi->chapters

Chapter metadata is an array of structs, one for each chapter. Saving that in
the normal way would bring much database complexity for a narrow use case, so
instead we just save it as json string in mfi->chapters. That's anyway the form
that we will be serving via the json API.

Should we need other forms in the future, we can use sqlite's json_each().
This commit is contained in:
ejurgensen
2025-08-12 00:02:39 +02:00
parent 2225eb9e0a
commit 8821b53cdb

View File

@@ -39,11 +39,12 @@
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/opt.h>
#include <libavutil/opt.h> // includes avutil.h
#include "db.h"
#include "logger.h"
#include "misc.h"
#include "misc_json.h"
#include "http.h"
#include "conffile.h"
@@ -378,6 +379,53 @@ extract_metadata_from_dict(struct media_file_info *mfi, AVDictionary *md, const
return mdcount;
}
static void
chapter_add(json_object *jchapters, AVChapter *chapter)
{
json_object *jchapter;
const AVDictionaryEntry *tag = NULL;
AVRational ms = { .num = 1, .den = 1000 };
int64_t start_ms;
int64_t end_ms;
start_ms = av_rescale_q(chapter->start, chapter->time_base, ms);
end_ms = av_rescale_q(chapter->end, chapter->time_base, ms);
jchapter = json_object_new_object();
json_object_array_add(jchapters, jchapter);
json_object_object_add(jchapter, "id", json_object_new_int64(chapter->id));
json_object_object_add(jchapter, "start_ms", json_object_new_int64(start_ms));
json_object_object_add(jchapter, "end_ms", json_object_new_int64(end_ms));
if (!chapter->metadata)
return;
while ((tag = av_dict_iterate(chapter->metadata, tag)))
{
json_object_object_add(jchapter, tag->key, json_object_new_string(tag->value));
// DPRINTF(E_DBG, L_SCAN, "-> %s: %s\n", tag->key, tag->value);
}
}
static int
extract_chapters(struct media_file_info *mfi, AVFormatContext *ctx)
{
json_object *jchapters;
int i;
jchapters = json_object_new_array();
for (i = 0; i < ctx->nb_chapters; i++)
chapter_add(jchapters, ctx->chapters[i]);
mfi->chapters = safe_strdup(json_object_to_json_string(jchapters));
jparse_free(jchapters);
return ctx->nb_chapters;
}
static int
extract_metadata(struct media_file_info *mfi, AVFormatContext *ctx, AVStream *audio_stream, AVStream *video_stream, const struct metadata_map *md_map)
{
@@ -771,6 +819,14 @@ scan_metadata_ffmpeg(struct media_file_info *mfi, const char *file)
DPRINTF(E_DBG, L_SCAN, "Picked up %d tags with extra md_map\n", ret);
}
if (ctx->nb_chapters > 0)
{
ret = extract_chapters(mfi, ctx);
mdcount += 1; // All chapters saved to mfi->chapters so just count as 1
DPRINTF(E_DBG, L_SCAN, "Picked up %d chapters\n", ret);
}
ret = extract_metadata(mfi, ctx, audio_stream, video_stream, md_map_generic);
mdcount += ret;