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 <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
2
www/package-lock.json
generated
@@ -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": {
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user