forked from forks/ffprobe-wasm
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:
@@ -11,6 +11,7 @@ extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libavutil/bprint.h>
|
||||
#include <libavutil/imgutils.h>
|
||||
};
|
||||
|
||||
@@ -43,6 +44,19 @@ typedef struct Stream {
|
||||
int frame_size;
|
||||
} 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 {
|
||||
int frame_number;
|
||||
char pict_type;
|
||||
@@ -60,6 +74,8 @@ typedef struct FileInfoResponse {
|
||||
int nb_streams;
|
||||
int flags;
|
||||
std::vector<Stream> streams;
|
||||
int nb_chapters;
|
||||
std::vector<Chapter> chapters;
|
||||
} FileInfoResponse;
|
||||
|
||||
typedef struct FramesResponse {
|
||||
@@ -103,7 +119,8 @@ FileInfoResponse get_file_info(std::string filename) {
|
||||
.duration = (int)pFormatContext->duration,
|
||||
.url = pFormatContext->url,
|
||||
.nb_streams = (int)pFormatContext->nb_streams,
|
||||
.flags = pFormatContext->flags
|
||||
.flags = pFormatContext->flags,
|
||||
.nb_chapters = (int)pFormatContext->nb_chapters
|
||||
};
|
||||
|
||||
// Loop through the streams.
|
||||
@@ -138,6 +155,36 @@ FileInfoResponse get_file_info(std::string filename) {
|
||||
r.streams.push_back(stream);
|
||||
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);
|
||||
return r;
|
||||
}
|
||||
@@ -293,6 +340,21 @@ EMSCRIPTEN_BINDINGS(structs) {
|
||||
;
|
||||
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")
|
||||
.field("frame_number", &Frame::frame_number)
|
||||
.field("pict_type", &Frame::pict_type)
|
||||
@@ -310,6 +372,8 @@ EMSCRIPTEN_BINDINGS(structs) {
|
||||
.field("nb_streams", &FileInfoResponse::nb_streams)
|
||||
.field("flags", &FileInfoResponse::flags)
|
||||
.field("streams", &FileInfoResponse::streams)
|
||||
.field("nb_chapters", &FileInfoResponse::nb_chapters)
|
||||
.field("chapters", &FileInfoResponse::chapters)
|
||||
;
|
||||
function("get_file_info", &get_file_info);
|
||||
|
||||
|
||||
2
www/package-lock.json
generated
2
www/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ffprobe-wasm",
|
||||
"version": "0.6.0",
|
||||
"version": "0.7.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ffprobe-wasm",
|
||||
"version": "0.6.0",
|
||||
"version": "0.7.0",
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
|
||||
@@ -21,6 +21,19 @@ onmessage = (e) => {
|
||||
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 = {
|
||||
libavutil: Module.avutil_version(),
|
||||
libavcodec: Module.avcodec_version(),
|
||||
@@ -31,6 +44,7 @@ onmessage = (e) => {
|
||||
data = {
|
||||
...info,
|
||||
streams: s,
|
||||
chapters: c,
|
||||
versions,
|
||||
}
|
||||
postMessage(data);
|
||||
|
||||
@@ -8,6 +8,15 @@
|
||||
|
||||
<h4>Streams</h4>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
@@ -25,6 +34,7 @@ export default {
|
||||
url: this.info.url,
|
||||
nb_streams: this.info.nb_streams,
|
||||
flags: this.info.flags,
|
||||
nb_chapters: this.info.nb_chapters,
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user