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 FFMPEG_VERSION=4.3.1
|
||||||
ARG X264_VERSION=20170226-2245-stable
|
ARG X264_VERSION=20170226-2245-stable
|
||||||
@@ -30,7 +30,7 @@ RUN cd /tmp/ && \
|
|||||||
wget http://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.gz && \
|
wget http://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.gz && \
|
||||||
tar zxf ffmpeg-${FFMPEG_VERSION}.tar.gz && rm 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"
|
ARG LDFLAGS="$CFLAGS -L${PREFIX}/lib -s INITIAL_MEMORY=33554432"
|
||||||
|
|
||||||
# Compile ffmpeg.
|
# Compile ffmpeg.
|
||||||
|
|||||||
3
Makefile
3
Makefile
@@ -6,7 +6,8 @@ dist/ffprobe-wasm.js:
|
|||||||
-I/opt/ffmpeg/include/ \
|
-I/opt/ffmpeg/include/ \
|
||||||
-s EXTRA_EXPORTED_RUNTIME_METHODS="[FS, cwrap, ccall, getValue, setValue, writeAsciiToMemory]" \
|
-s EXTRA_EXPORTED_RUNTIME_METHODS="[FS, cwrap, ccall, getValue, setValue, writeAsciiToMemory]" \
|
||||||
-s INITIAL_MEMORY=268435456 \
|
-s INITIAL_MEMORY=268435456 \
|
||||||
|
-lavcodec -lavformat -lavfilter -lavdevice -lswresample -lswscale -lavutil -lm -lx264 \
|
||||||
-pthread \
|
-pthread \
|
||||||
-lavcodec -lavformat -lavfilter -lavdevice -lswresample -lswscale -lavutil -lm -lx264 -pthread \
|
-lworkerfs.js \
|
||||||
-o dist/ffprobe-wasm.js \
|
-o dist/ffprobe-wasm.js \
|
||||||
src/ffprobe-wasm-wrapper.cpp
|
src/ffprobe-wasm-wrapper.cpp
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <emscripten.h>
|
#include <emscripten.h>
|
||||||
#include <emscripten/bind.h>
|
#include <emscripten/bind.h>
|
||||||
@@ -65,8 +66,8 @@ typedef struct FramesResponse {
|
|||||||
int nb_frames;
|
int nb_frames;
|
||||||
} FramesResponse;
|
} FramesResponse;
|
||||||
|
|
||||||
FileInfoResponse get_file_info() {
|
FileInfoResponse get_file_info(std::string filename) {
|
||||||
FILE *file = fopen("file", "rb");
|
FILE *file = fopen(filename.c_str(), "rb");
|
||||||
if (!file) {
|
if (!file) {
|
||||||
printf("cannot open file\n");
|
printf("cannot open file\n");
|
||||||
}
|
}
|
||||||
@@ -79,9 +80,8 @@ FileInfoResponse get_file_info() {
|
|||||||
|
|
||||||
// Open the file and read header.
|
// Open the file and read header.
|
||||||
int ret;
|
int ret;
|
||||||
if ((ret = avformat_open_input(&pFormatContext, "file", NULL, NULL)) < 0) {
|
if ((ret = avformat_open_input(&pFormatContext, filename.c_str(), NULL, NULL)) < 0) {
|
||||||
printf("ERROR: could not open the file. Error: %d\n", ret);
|
printf("ERROR: %s\n", av_err2str(ret));
|
||||||
printf("%s", av_err2str(ret));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get stream info from format.
|
// Get stream info from format.
|
||||||
@@ -135,10 +135,10 @@ FileInfoResponse get_file_info() {
|
|||||||
return r;
|
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.
|
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) {
|
if (!file) {
|
||||||
printf("cannot open file\n");
|
printf("cannot open file\n");
|
||||||
}
|
}
|
||||||
@@ -151,9 +151,8 @@ FramesResponse get_frames(int offset) {
|
|||||||
|
|
||||||
// Open the file and read header.
|
// Open the file and read header.
|
||||||
int ret;
|
int ret;
|
||||||
if ((ret = avformat_open_input(&pFormatContext, "file", NULL, NULL)) < 0) {
|
if ((ret = avformat_open_input(&pFormatContext, filename.c_str(), NULL, NULL)) < 0) {
|
||||||
printf("ERROR: could not open the file. Error: %d\n", ret);
|
printf("ERROR: %s\n", av_err2str(ret));
|
||||||
printf("%s", av_err2str(ret));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get stream info from format.
|
// Get stream info from format.
|
||||||
@@ -239,7 +238,6 @@ FramesResponse get_frames(int offset) {
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
EMSCRIPTEN_BINDINGS(constants) {
|
EMSCRIPTEN_BINDINGS(constants) {
|
||||||
function("avformat_version", &c_avformat_version);
|
function("avformat_version", &c_avformat_version);
|
||||||
function("avcodec_version", &c_avcodec_version);
|
function("avcodec_version", &c_avcodec_version);
|
||||||
|
|||||||
2
www/.gitignore
vendored
2
www/.gitignore
vendored
@@ -22,5 +22,5 @@ pnpm-debug.log*
|
|||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
public/*.js
|
public/*-wasm*.js
|
||||||
public/*.wasm
|
public/*.wasm
|
||||||
2
www/package-lock.json
generated
2
www/package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ffprobe-wasm",
|
"name": "ffprobe-wasm",
|
||||||
"version": "0.2.1",
|
"version": "0.3.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ffprobe-wasm",
|
"name": "ffprobe-wasm",
|
||||||
"version": "0.2.1",
|
"version": "0.3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"serve": "vue-cli-service serve",
|
||||||
"build": "vue-cli-service build",
|
"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>
|
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||||
</noscript>
|
</noscript>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script src="ffprobe-wasm.js"></script>
|
|
||||||
<!-- built files will be auto injected -->
|
<!-- built files will be auto injected -->
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -12,23 +12,17 @@
|
|||||||
></b-form-file>
|
></b-form-file>
|
||||||
</b-form-group>
|
</b-form-group>
|
||||||
|
|
||||||
<b-progress
|
<div v-if="info">
|
||||||
height="2px"
|
|
||||||
v-if="showProgress"
|
|
||||||
:value="progress"
|
|
||||||
max="100"></b-progress>
|
|
||||||
|
|
||||||
<div v-if="data">
|
|
||||||
<div class="mt-3">Selected file: {{ file ? `${file.name}: ${file.size} bytes` : '' }}</div>
|
<div class="mt-3">Selected file: {{ file ? `${file.name}: ${file.size} bytes` : '' }}</div>
|
||||||
|
|
||||||
<b-tabs class="mt-4">
|
<b-tabs class="mt-4">
|
||||||
<b-tab title="Overview" class="mt-2">
|
<b-tab title="Overview" class="mt-2">
|
||||||
<div v-if="data">
|
<div v-if="info">
|
||||||
<Overview :info="info" />
|
<Overview :info="info" />
|
||||||
</div>
|
</div>
|
||||||
</b-tab>
|
</b-tab>
|
||||||
<b-tab title="Frames" class="mt-2" lazy>
|
<b-tab title="Frames" class="mt-2" lazy>
|
||||||
<Frames />
|
<Frames :file="file" />
|
||||||
</b-tab>
|
</b-tab>
|
||||||
</b-tabs>
|
</b-tabs>
|
||||||
</div>
|
</div>
|
||||||
@@ -49,37 +43,19 @@ export default {
|
|||||||
return {
|
return {
|
||||||
file: null,
|
file: null,
|
||||||
data: null,
|
data: null,
|
||||||
progress: 0,
|
info: null,
|
||||||
showProgress: false,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
created() {
|
||||||
info() {
|
this.$worker.onmessage = (e) => {
|
||||||
return this.data && window.Module.get_file_info();
|
this.info = e.data;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onFile(event) {
|
onFile(event) {
|
||||||
this.data = null;
|
this.data = null;
|
||||||
this.progress = 0;
|
|
||||||
this.showProgress = true;
|
|
||||||
|
|
||||||
const file = event.dataTransfer ? event.dataTransfer.files[0] : event.target.files[0];
|
const file = event.dataTransfer ? event.dataTransfer.files[0] : event.target.files[0];
|
||||||
const reader = new FileReader();
|
this.$worker.postMessage([ 'get_file_info', file ]);
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h4>Frames</h4>
|
<h4>Frames</h4>
|
||||||
|
<div v-if="!data">Loading...</div>
|
||||||
|
<div v-if="data">
|
||||||
<p class="text-right">Total: {{ data.nb_frames }}</p>
|
<p class="text-right">Total: {{ data.nb_frames }}</p>
|
||||||
|
|
||||||
<b-pagination
|
<b-pagination
|
||||||
@@ -11,7 +13,12 @@
|
|||||||
align="right"
|
align="right"
|
||||||
></b-pagination>
|
></b-pagination>
|
||||||
|
|
||||||
<b-table striped hover :items="frames">
|
<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">
|
<template #cell(pict_type)="data">
|
||||||
{{ String.fromCharCode(data.value) }}
|
{{ String.fromCharCode(data.value) }}
|
||||||
</template>
|
</template>
|
||||||
@@ -25,38 +32,38 @@
|
|||||||
align="right"
|
align="right"
|
||||||
></b-pagination>
|
></b-pagination>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'Frames',
|
name: 'Frames',
|
||||||
|
props: ['file'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
data: null,
|
data: null,
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
perPage: 48,
|
perPage: 48,
|
||||||
|
isBusy: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
pages() {
|
pages() {
|
||||||
return this.data.nb_frames;
|
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() {
|
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: {
|
methods: {
|
||||||
onPageChanged(page) {
|
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);
|
window.scrollTo(0, 0);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<b-table stacked :items="items"></b-table>
|
<b-table stacked :items="items"></b-table>
|
||||||
|
|
||||||
<h4>Streams</h4>
|
<h4>Streams</h4>
|
||||||
<b-table striped hover :items="streams"></b-table>
|
<b-table striped hover :items="info.streams"></b-table>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -15,15 +15,6 @@
|
|||||||
export default {
|
export default {
|
||||||
name: 'Overview',
|
name: 'Overview',
|
||||||
props: ['info'],
|
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() {
|
data() {
|
||||||
return {
|
return {
|
||||||
items: [
|
items: [
|
||||||
@@ -38,9 +29,9 @@ export default {
|
|||||||
],
|
],
|
||||||
versions: [
|
versions: [
|
||||||
{
|
{
|
||||||
libavutil: window.Module.avutil_version(),
|
libavutil: this.info.versions.libavutil,
|
||||||
libavcodec: window.Module.avcodec_version(),
|
libavcodec: this.info.versions.libavcodec,
|
||||||
libavformat: window.Module.avformat_version(),
|
libavformat: this.info.versions.libavformat,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import App from './App.vue'
|
|||||||
import 'bootstrap/dist/css/bootstrap.css';
|
import 'bootstrap/dist/css/bootstrap.css';
|
||||||
import 'bootstrap-vue/dist/bootstrap-vue.css';
|
import 'bootstrap-vue/dist/bootstrap-vue.css';
|
||||||
|
|
||||||
|
const worker = new Worker('ffprobe-worker.js');
|
||||||
|
Vue.prototype.$worker = worker;
|
||||||
|
|
||||||
Vue.use(BootstrapVue);
|
Vue.use(BootstrapVue);
|
||||||
|
|
||||||
Vue.config.productionTip = false
|
Vue.config.productionTip = false
|
||||||
|
|||||||
Reference in New Issue
Block a user