forked from forks/ffprobe-wasm
Merge pull request #7 from alfg/refactor/worker-module-calls
Refactor module calls into a Web Worker to prevent blocking main thread
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
FROM emscripten/emsdk as build
|
||||
FROM emscripten/emsdk:2.0.11 as build
|
||||
|
||||
ARG FFMPEG_VERSION=4.3.1
|
||||
ARG X264_VERSION=20170226-2245-stable
|
||||
@@ -30,7 +30,7 @@ RUN cd /tmp/ && \
|
||||
wget http://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.gz && \
|
||||
tar zxf ffmpeg-${FFMPEG_VERSION}.tar.gz && rm ffmpeg-${FFMPEG_VERSION}.tar.gz
|
||||
|
||||
ARG CFLAGS="-s USE_PTHREADS -O3 -I${PREFIX}/include"
|
||||
ARG CFLAGS="-s USE_PTHREADS=1 -O3 -I${PREFIX}/include"
|
||||
ARG LDFLAGS="$CFLAGS -L${PREFIX}/lib -s INITIAL_MEMORY=33554432"
|
||||
|
||||
# Compile ffmpeg.
|
||||
|
||||
3
Makefile
3
Makefile
@@ -6,7 +6,8 @@ dist/ffprobe-wasm.js:
|
||||
-I/opt/ffmpeg/include/ \
|
||||
-s EXTRA_EXPORTED_RUNTIME_METHODS="[FS, cwrap, ccall, getValue, setValue, writeAsciiToMemory]" \
|
||||
-s INITIAL_MEMORY=268435456 \
|
||||
-lavcodec -lavformat -lavfilter -lavdevice -lswresample -lswscale -lavutil -lm -lx264 \
|
||||
-pthread \
|
||||
-lavcodec -lavformat -lavfilter -lavdevice -lswresample -lswscale -lavutil -lm -lx264 -pthread \
|
||||
-lworkerfs.js \
|
||||
-o dist/ffprobe-wasm.js \
|
||||
src/ffprobe-wasm-wrapper.cpp
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <inttypes.h>
|
||||
#include <emscripten.h>
|
||||
#include <emscripten/bind.h>
|
||||
@@ -65,8 +66,8 @@ typedef struct FramesResponse {
|
||||
int nb_frames;
|
||||
} FramesResponse;
|
||||
|
||||
FileInfoResponse get_file_info() {
|
||||
FILE *file = fopen("file", "rb");
|
||||
FileInfoResponse get_file_info(std::string filename) {
|
||||
FILE *file = fopen(filename.c_str(), "rb");
|
||||
if (!file) {
|
||||
printf("cannot open file\n");
|
||||
}
|
||||
@@ -79,9 +80,8 @@ FileInfoResponse get_file_info() {
|
||||
|
||||
// Open the file and read header.
|
||||
int ret;
|
||||
if ((ret = avformat_open_input(&pFormatContext, "file", NULL, NULL)) < 0) {
|
||||
printf("ERROR: could not open the file. Error: %d\n", ret);
|
||||
printf("%s", av_err2str(ret));
|
||||
if ((ret = avformat_open_input(&pFormatContext, filename.c_str(), NULL, NULL)) < 0) {
|
||||
printf("ERROR: %s\n", av_err2str(ret));
|
||||
}
|
||||
|
||||
// Get stream info from format.
|
||||
@@ -135,10 +135,10 @@ FileInfoResponse get_file_info() {
|
||||
return r;
|
||||
}
|
||||
|
||||
FramesResponse get_frames(int offset) {
|
||||
FramesResponse get_frames(std::string filename, int offset) {
|
||||
av_log_set_level(AV_LOG_QUIET); // No logging output for libav.
|
||||
|
||||
FILE *file = fopen("file", "rb");
|
||||
FILE *file = fopen(filename.c_str(), "rb");
|
||||
if (!file) {
|
||||
printf("cannot open file\n");
|
||||
}
|
||||
@@ -151,9 +151,8 @@ FramesResponse get_frames(int offset) {
|
||||
|
||||
// Open the file and read header.
|
||||
int ret;
|
||||
if ((ret = avformat_open_input(&pFormatContext, "file", NULL, NULL)) < 0) {
|
||||
printf("ERROR: could not open the file. Error: %d\n", ret);
|
||||
printf("%s", av_err2str(ret));
|
||||
if ((ret = avformat_open_input(&pFormatContext, filename.c_str(), NULL, NULL)) < 0) {
|
||||
printf("ERROR: %s\n", av_err2str(ret));
|
||||
}
|
||||
|
||||
// Get stream info from format.
|
||||
@@ -239,7 +238,6 @@ FramesResponse get_frames(int offset) {
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
EMSCRIPTEN_BINDINGS(constants) {
|
||||
function("avformat_version", &c_avformat_version);
|
||||
function("avcodec_version", &c_avcodec_version);
|
||||
|
||||
2
www/.gitignore
vendored
2
www/.gitignore
vendored
@@ -22,5 +22,5 @@ pnpm-debug.log*
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
public/*.js
|
||||
public/*-wasm*.js
|
||||
public/*.wasm
|
||||
2
www/package-lock.json
generated
2
www/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ffprobe-wasm",
|
||||
"version": "0.2.1",
|
||||
"version": "0.3.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ffprobe-wasm",
|
||||
"version": "0.2.1",
|
||||
"version": "0.3.0",
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
|
||||
72
www/public/ffprobe-worker.js
Normal file
72
www/public/ffprobe-worker.js
Normal file
@@ -0,0 +1,72 @@
|
||||
onmessage = (e) => {
|
||||
const type = e.data[0];
|
||||
const file = e.data[1];
|
||||
|
||||
let data;
|
||||
|
||||
switch (type) {
|
||||
case 'get_file_info':
|
||||
// Mount FS for files.
|
||||
if (!FS.analyzePath('/work').exists) {
|
||||
FS.mkdir('/work');
|
||||
}
|
||||
FS.mount(WORKERFS, { files: [file] }, '/work');
|
||||
|
||||
// Call the wasm module.
|
||||
const info = Module.get_file_info('/work/' + file.name);
|
||||
|
||||
// Remap streams into collection.
|
||||
const s = [];
|
||||
for (let i = 0; i < info.streams.size(); i++) {
|
||||
s.push(info.streams.get(i));
|
||||
}
|
||||
|
||||
const versions = {
|
||||
libavutil: Module.avutil_version(),
|
||||
libavcodec: Module.avcodec_version(),
|
||||
libavformat: Module.avformat_version(),
|
||||
};
|
||||
|
||||
// Send back data response.
|
||||
data = {
|
||||
...info,
|
||||
streams: s,
|
||||
versions,
|
||||
}
|
||||
postMessage(data);
|
||||
|
||||
// Cleanup mount.
|
||||
FS.unmount('/work');
|
||||
break;
|
||||
|
||||
case 'get_frames':
|
||||
if (!FS.analyzePath('/work').exists) {
|
||||
FS.mkdir('/work');
|
||||
}
|
||||
FS.mount(WORKERFS, { files: [file] }, '/work');
|
||||
|
||||
const offset = e.data[2];
|
||||
const frames = Module.get_frames('/work/' + file.name, offset);
|
||||
|
||||
// Remap frames into collection.
|
||||
const f = [];
|
||||
for (let i = 0; i < frames.frames.size(); i++) {
|
||||
f.push(frames.frames.get(i));
|
||||
}
|
||||
|
||||
data = {
|
||||
...frames,
|
||||
frames: f,
|
||||
}
|
||||
postMessage(data);
|
||||
|
||||
// Cleanup mount.
|
||||
FS.unmount('/work');
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
self.importScripts('ffprobe-wasm.js'); // Load ffprobe into worker context.
|
||||
@@ -12,7 +12,6 @@
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<script src="ffprobe-wasm.js"></script>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -12,23 +12,17 @@
|
||||
></b-form-file>
|
||||
</b-form-group>
|
||||
|
||||
<b-progress
|
||||
height="2px"
|
||||
v-if="showProgress"
|
||||
:value="progress"
|
||||
max="100"></b-progress>
|
||||
|
||||
<div v-if="data">
|
||||
<div v-if="info">
|
||||
<div class="mt-3">Selected file: {{ file ? `${file.name}: ${file.size} bytes` : '' }}</div>
|
||||
|
||||
<b-tabs class="mt-4">
|
||||
<b-tab title="Overview" class="mt-2">
|
||||
<div v-if="data">
|
||||
<div v-if="info">
|
||||
<Overview :info="info" />
|
||||
</div>
|
||||
</b-tab>
|
||||
<b-tab title="Frames" class="mt-2" lazy>
|
||||
<Frames />
|
||||
<Frames :file="file" />
|
||||
</b-tab>
|
||||
</b-tabs>
|
||||
</div>
|
||||
@@ -49,37 +43,19 @@ export default {
|
||||
return {
|
||||
file: null,
|
||||
data: null,
|
||||
progress: 0,
|
||||
showProgress: false,
|
||||
info: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
info() {
|
||||
return this.data && window.Module.get_file_info();
|
||||
created() {
|
||||
this.$worker.onmessage = (e) => {
|
||||
this.info = e.data;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onFile(event) {
|
||||
this.data = null;
|
||||
this.progress = 0;
|
||||
this.showProgress = true;
|
||||
|
||||
const file = event.dataTransfer ? event.dataTransfer.files[0] : event.target.files[0];
|
||||
const reader = new FileReader();
|
||||
|
||||
// reader.onload = e => this.$emit("load", event.target.result);
|
||||
reader.onload = (event) => {
|
||||
this.progress = 100;
|
||||
this.data = new Uint8Array(event.target.result);
|
||||
window.Module.FS.writeFile('file', new Uint8Array(this.data));
|
||||
setTimeout(() => { this.showProgress = false; }, 2000);
|
||||
}
|
||||
reader.onprogress = (event) => {
|
||||
if (event.lengthComputable) {
|
||||
this.progress = parseInt(((event.loaded / event.total) * 100), 10);
|
||||
}
|
||||
}
|
||||
reader.readAsArrayBuffer(file);
|
||||
this.$worker.postMessage([ 'get_file_info', file ]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,62 +1,69 @@
|
||||
<template>
|
||||
<div>
|
||||
<h4>Frames</h4>
|
||||
<p class="text-right">Total: {{ data.nb_frames }}</p>
|
||||
<div v-if="!data">Loading...</div>
|
||||
<div v-if="data">
|
||||
<p class="text-right">Total: {{ data.nb_frames }}</p>
|
||||
|
||||
<b-pagination
|
||||
v-model="currentPage"
|
||||
@change="onPageChanged"
|
||||
:total-rows="pages"
|
||||
:per-page="perPage"
|
||||
align="right"
|
||||
></b-pagination>
|
||||
<b-pagination
|
||||
v-model="currentPage"
|
||||
@change="onPageChanged"
|
||||
:total-rows="pages"
|
||||
:per-page="perPage"
|
||||
align="right"
|
||||
></b-pagination>
|
||||
|
||||
<b-table striped hover :items="frames">
|
||||
<template #cell(pict_type)="data">
|
||||
{{ String.fromCharCode(data.value) }}
|
||||
</template>
|
||||
</b-table>
|
||||
<b-table striped hover :items="data.frames" :busy="isBusy">
|
||||
<template #table-busy>
|
||||
<div class="text-center text-primary my-2">
|
||||
<b-spinner class="align-middle"></b-spinner>
|
||||
</div>
|
||||
</template>
|
||||
<template #cell(pict_type)="data">
|
||||
{{ String.fromCharCode(data.value) }}
|
||||
</template>
|
||||
</b-table>
|
||||
|
||||
<b-pagination
|
||||
v-model="currentPage"
|
||||
@change="onPageChanged"
|
||||
:total-rows="pages"
|
||||
:per-page="perPage"
|
||||
align="right"
|
||||
></b-pagination>
|
||||
<b-pagination
|
||||
v-model="currentPage"
|
||||
@change="onPageChanged"
|
||||
:total-rows="pages"
|
||||
:per-page="perPage"
|
||||
align="right"
|
||||
></b-pagination>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Frames',
|
||||
props: ['file'],
|
||||
data() {
|
||||
return {
|
||||
data: null,
|
||||
currentPage: 1,
|
||||
perPage: 48,
|
||||
isBusy: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
pages() {
|
||||
return this.data.nb_frames;
|
||||
},
|
||||
frames() {
|
||||
if (!this.data) return [];
|
||||
|
||||
const s = [];
|
||||
for (let i = 0; i < this.data.frames.size(); i++) {
|
||||
s.push(this.data.frames.get(i));
|
||||
}
|
||||
return s;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.data = window.Module.get_frames(0);
|
||||
this.$worker.onmessage = (e) => {
|
||||
this.data = e.data;
|
||||
this.isBusy = false;
|
||||
}
|
||||
this.$worker.postMessage([ 'get_frames', this.file, 0 ]);
|
||||
|
||||
},
|
||||
methods: {
|
||||
onPageChanged(page) {
|
||||
this.data = window.Module.get_frames(this.perPage * (page - 1));
|
||||
this.isBusy = true;
|
||||
this.$worker.postMessage([ 'get_frames', this.file, this.perPage * (page - 1) ]);
|
||||
window.scrollTo(0, 0);
|
||||
},
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<b-table stacked :items="items"></b-table>
|
||||
|
||||
<h4>Streams</h4>
|
||||
<b-table striped hover :items="streams"></b-table>
|
||||
<b-table striped hover :items="info.streams"></b-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -15,15 +15,6 @@
|
||||
export default {
|
||||
name: 'Overview',
|
||||
props: ['info'],
|
||||
computed: {
|
||||
streams() {
|
||||
const s = [];
|
||||
for (let i = 0; i < this.info.streams.size(); i++) {
|
||||
s.push(this.info.streams.get(i));
|
||||
}
|
||||
return s;
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
items: [
|
||||
@@ -38,9 +29,9 @@ export default {
|
||||
],
|
||||
versions: [
|
||||
{
|
||||
libavutil: window.Module.avutil_version(),
|
||||
libavcodec: window.Module.avcodec_version(),
|
||||
libavformat: window.Module.avformat_version(),
|
||||
libavutil: this.info.versions.libavutil,
|
||||
libavcodec: this.info.versions.libavcodec,
|
||||
libavformat: this.info.versions.libavformat,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,6 +5,9 @@ import App from './App.vue'
|
||||
import 'bootstrap/dist/css/bootstrap.css';
|
||||
import 'bootstrap-vue/dist/bootstrap-vue.css';
|
||||
|
||||
const worker = new Worker('ffprobe-worker.js');
|
||||
Vue.prototype.$worker = worker;
|
||||
|
||||
Vue.use(BootstrapVue);
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
Reference in New Issue
Block a user