feature: add support for displaying chapters (#15)

* feat: add initial code for reading chapters

* feat: add more fields for supporting chapters and chapter tags.
This commit is contained in:
Alfred Gutierrez
2022-01-17 20:02:28 -08:00
committed by GitHub
parent b1562b6a05
commit ff36b01373
5 changed files with 91 additions and 3 deletions

View File

@@ -11,6 +11,7 @@ extern "C" {
#include <libavcodec/avcodec.h> #include <libavcodec/avcodec.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include <libavutil/avutil.h> #include <libavutil/avutil.h>
#include <libavutil/bprint.h>
#include <libavutil/imgutils.h> #include <libavutil/imgutils.h>
}; };
@@ -43,6 +44,19 @@ typedef struct Stream {
int frame_size; int frame_size;
} Stream; } Stream;
typedef struct Tag {
std::string key;
std::string value;
} Tag;
typedef struct Chapter {
int id;
std::string time_base;
int start;
int end;
std::vector<Tag> tags;
} Chapter;
typedef struct Frame { typedef struct Frame {
int frame_number; int frame_number;
char pict_type; char pict_type;
@@ -60,6 +74,8 @@ typedef struct FileInfoResponse {
int nb_streams; int nb_streams;
int flags; int flags;
std::vector<Stream> streams; std::vector<Stream> streams;
int nb_chapters;
std::vector<Chapter> chapters;
} FileInfoResponse; } FileInfoResponse;
typedef struct FramesResponse { typedef struct FramesResponse {
@@ -103,7 +119,8 @@ FileInfoResponse get_file_info(std::string filename) {
.duration = (int)pFormatContext->duration, .duration = (int)pFormatContext->duration,
.url = pFormatContext->url, .url = pFormatContext->url,
.nb_streams = (int)pFormatContext->nb_streams, .nb_streams = (int)pFormatContext->nb_streams,
.flags = pFormatContext->flags .flags = pFormatContext->flags,
.nb_chapters = (int)pFormatContext->nb_chapters
}; };
// Loop through the streams. // Loop through the streams.
@@ -138,6 +155,36 @@ FileInfoResponse get_file_info(std::string filename) {
r.streams.push_back(stream); r.streams.push_back(stream);
free(fourcc); free(fourcc);
} }
// Loop through the chapters (if any).
for (int i = 0; i < pFormatContext->nb_chapters; i++) {
AVChapter *chapter = pFormatContext->chapters[i];
// Format timebase string to buf.
AVBPrint buf;
av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
av_bprintf(&buf, "%d%s%d", chapter->time_base.num, (char *)"/", chapter->time_base.den);
Chapter c = {
.id = (int)chapter->id,
.time_base = buf.str,
.start = (int)chapter->start,
.end = (int)chapter->end,
};
// Add tags to chapter.
const AVDictionaryEntry *tag = NULL;
while ((tag = av_dict_get(chapter->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) {
Tag t = {
.key = tag->key,
.value = tag->value,
};
c.tags.push_back(t);
}
r.chapters.push_back(c);
}
avformat_close_input(&pFormatContext); avformat_close_input(&pFormatContext);
return r; return r;
} }
@@ -293,6 +340,21 @@ EMSCRIPTEN_BINDINGS(structs) {
; ;
register_vector<Stream>("Stream"); register_vector<Stream>("Stream");
emscripten::value_object<Tag>("Tag")
.field("key", &Tag::key)
.field("value", &Tag::value)
;
register_vector<Tag>("Tag");
emscripten::value_object<Chapter>("Chapter")
.field("id", &Chapter::id)
.field("time_base", &Chapter::time_base)
.field("start", &Chapter::start)
.field("end", &Chapter::end)
.field("tags", &Chapter::tags)
;
register_vector<Chapter>("Chapter");
emscripten::value_object<Frame>("Frame") emscripten::value_object<Frame>("Frame")
.field("frame_number", &Frame::frame_number) .field("frame_number", &Frame::frame_number)
.field("pict_type", &Frame::pict_type) .field("pict_type", &Frame::pict_type)
@@ -310,6 +372,8 @@ EMSCRIPTEN_BINDINGS(structs) {
.field("nb_streams", &FileInfoResponse::nb_streams) .field("nb_streams", &FileInfoResponse::nb_streams)
.field("flags", &FileInfoResponse::flags) .field("flags", &FileInfoResponse::flags)
.field("streams", &FileInfoResponse::streams) .field("streams", &FileInfoResponse::streams)
.field("nb_chapters", &FileInfoResponse::nb_chapters)
.field("chapters", &FileInfoResponse::chapters)
; ;
function("get_file_info", &get_file_info); function("get_file_info", &get_file_info);

2
www/package-lock.json generated
View File

@@ -1,6 +1,6 @@
{ {
"name": "ffprobe-wasm", "name": "ffprobe-wasm",
"version": "0.6.0", "version": "0.7.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "ffprobe-wasm", "name": "ffprobe-wasm",
"version": "0.6.0", "version": "0.7.0",
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve",
"build": "vue-cli-service build", "build": "vue-cli-service build",

View File

@@ -21,6 +21,19 @@ onmessage = (e) => {
s.push(info.streams.get(i)); s.push(info.streams.get(i));
} }
// Remap chapters into collection.
const c = [];
for (let i = 0; i < info.chapters.size(); i++) {
const t = info.chapters.get(i).tags.get(0);
// Remap tags too.
const tags = [];
const obj = {};
obj[t.key] = t.value;
tags.push(obj);
c.push({...info.chapters.get(i), ...{tags}});
}
const versions = { const versions = {
libavutil: Module.avutil_version(), libavutil: Module.avutil_version(),
libavcodec: Module.avcodec_version(), libavcodec: Module.avcodec_version(),
@@ -31,6 +44,7 @@ onmessage = (e) => {
data = { data = {
...info, ...info,
streams: s, streams: s,
chapters: c,
versions, versions,
} }
postMessage(data); postMessage(data);

View File

@@ -8,6 +8,15 @@
<h4>Streams</h4> <h4>Streams</h4>
<b-table striped hover :items="info.streams"></b-table> <b-table striped hover :items="info.streams"></b-table>
<div v-show="info.chapters.length > 0">
<h4>Chapters</h4>
<b-table striped hover :items="info.chapters">
<template #cell(tags)="data">
<b-table-lite small stacked outlined :items="data.item.tags"></b-table-lite>
</template>
</b-table>
</div>
</div> </div>
</template> </template>
@@ -25,6 +34,7 @@ export default {
url: this.info.url, url: this.info.url,
nb_streams: this.info.nb_streams, nb_streams: this.info.nb_streams,
flags: this.info.flags, flags: this.info.flags,
nb_chapters: this.info.nb_chapters,
}, },
] ]
}, },