Commit 09ebfee7 authored by Jerome Wu's avatar Jerome Wu

Update tests and fix st version

parent 782f3daa
...@@ -23,9 +23,8 @@ FLAGS=( ...@@ -23,9 +23,8 @@ FLAGS=(
-I. -I./fftools -I$BUILD_DIR/include -I. -I./fftools -I$BUILD_DIR/include
-Llibavcodec -Llibavdevice -Llibavfilter -Llibavformat -Llibavresample -Llibavutil -Lharfbuzz -Llibass -Lfribidi -Llibpostproc -Llibswscale -Llibswresample -L$BUILD_DIR/lib -Llibavcodec -Llibavdevice -Llibavfilter -Llibavformat -Llibavresample -Llibavutil -Lharfbuzz -Llibass -Lfribidi -Llibpostproc -Llibswscale -Llibswresample -L$BUILD_DIR/lib
-Wno-deprecated-declarations -Wno-pointer-sign -Wno-implicit-int-float-conversion -Wno-switch -Wno-parentheses -Qunused-arguments -Wno-deprecated-declarations -Wno-pointer-sign -Wno-implicit-int-float-conversion -Wno-switch -Wno-parentheses -Qunused-arguments
-lavdevice -lavfilter -lavformat -lavcodec -lswresample -lswscale -lavutil -lpostproc -lm -lharfbuzz -lfribidi -llibass -lx264 -lx265 -lvpx -lwavpack -lmp3lame -lfdk-aac -lvorbis -lvorbisenc -lvorbisfile -logg -ltheora -ltheoraenc -ltheoradec -lz -lfreetype -lopus -lwebp -lavdevice -lavfilter -lavformat -lavcodec -lswresample -lswscale -lavutil -lpostproc -lm -lharfbuzz -lfribidi -lass -lx264 -lx265 -lvpx -lwavpack -lmp3lame -lfdk-aac -lvorbis -lvorbisenc -lvorbisfile -logg -ltheora -ltheoraenc -ltheoradec -lz -lfreetype -lopus -lwebp
fftools/ffmpeg_opt.c fftools/ffmpeg_filter.c fftools/ffmpeg_hw.c fftools/cmdutils.c fftools/ffmpeg.c fftools/ffmpeg_opt.c fftools/ffmpeg_filter.c fftools/ffmpeg_hw.c fftools/cmdutils.c fftools/ffmpeg.c
-o wasm/dist/ffmpeg-core.js
-s USE_SDL=2 # use SDL2 -s USE_SDL=2 # use SDL2
-s INVOKE_RUN=0 # not to run the main() in the beginning -s INVOKE_RUN=0 # not to run the main() in the beginning
-s EXIT_RUNTIME=1 # exit runtime after execution -s EXIT_RUNTIME=1 # exit runtime after execution
...@@ -34,7 +33,8 @@ FLAGS=( ...@@ -34,7 +33,8 @@ FLAGS=(
-s EXPORTED_FUNCTIONS="$EXPORTED_FUNCTIONS" # export main and proxy_main funcs -s EXPORTED_FUNCTIONS="$EXPORTED_FUNCTIONS" # export main and proxy_main funcs
-s EXTRA_EXPORTED_RUNTIME_METHODS="[FS, cwrap, ccall, setValue, writeAsciiToMemory]" # export preamble funcs -s EXTRA_EXPORTED_RUNTIME_METHODS="[FS, cwrap, ccall, setValue, writeAsciiToMemory]" # export preamble funcs
-s INITIAL_MEMORY=2146435072 # 64 KB * 1024 * 16 * 2047 = 2146435072 bytes ~= 2 GB -s INITIAL_MEMORY=2146435072 # 64 KB * 1024 * 16 * 2047 = 2146435072 bytes ~= 2 GB
--post-js wasm/src/post-js.js --post-js wasm/src/post.js
--pre-js wasm/src/pre.js
$OPTIM_FLAGS $OPTIM_FLAGS
${EXTRA_FLAGS[@]} ${EXTRA_FLAGS[@]}
) )
......
...@@ -4,6 +4,14 @@ set -euo pipefail ...@@ -4,6 +4,14 @@ set -euo pipefail
source $(dirname $0)/var.sh source $(dirname $0)/var.sh
LIB_PATH=third_party/harfbuzz LIB_PATH=third_party/harfbuzz
CFLAGS="$CFLAGS -DHB_NO_PRAGMA_GCC_DIAGNOSTIC_ERROR"
# A hacky way to disable pthread
if [[ "$FFMPEG_ST" == "yes" ]]; then
sed -i 's#\[have_pthread=true\]#\[have_pthread=false\]#g' $LIB_PATH/configure.ac
else
sed -i 's#\[have_pthread=false\]#\[have_pthread=true\]#g' $LIB_PATH/configure.ac
fi
CXXFLAGS=$CFLAGS
CONF_FLAGS=( CONF_FLAGS=(
--prefix=$BUILD_DIR # install library in a build directory for FFmpeg to include --prefix=$BUILD_DIR # install library in a build directory for FFmpeg to include
--host=i686-gnu # use i686 linux --host=i686-gnu # use i686 linux
......
...@@ -6,9 +6,7 @@ source $(dirname $0)/var.sh ...@@ -6,9 +6,7 @@ source $(dirname $0)/var.sh
LIB_PATH=third_party/libvpx LIB_PATH=third_party/libvpx
if [[ "$FFMPEG_ST" == "yes" ]]; then if [[ "$FFMPEG_ST" == "yes" ]]; then
EXTRA_CONF_FLAGS=( EXTRA_CONF_FLAGS="--disable-multithread"
--disable-multithread
)
fi fi
CONF_FLAGS=( CONF_FLAGS=(
......
...@@ -6,9 +6,7 @@ source $(dirname $0)/var.sh ...@@ -6,9 +6,7 @@ source $(dirname $0)/var.sh
LIB_PATH=third_party/x264 LIB_PATH=third_party/x264
if [[ "$FFMPEG_ST" == "yes" ]]; then if [[ "$FFMPEG_ST" == "yes" ]]; then
EXTRA_CONF_FLAGS=( EXTRA_CONF_FLAGS="--disable-thread"
--disable-thread
)
fi fi
CONF_FLAGS=( CONF_FLAGS=(
......
...@@ -26,29 +26,22 @@ TOOLCHAIN_FILE=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmak ...@@ -26,29 +26,22 @@ TOOLCHAIN_FILE=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmak
# Flags for code optimization, focus on speed instead # Flags for code optimization, focus on speed instead
# of size # of size
OPTIM_FLAGS=( OPTIM_FLAGS="-O3"
-O3
)
if [[ "$OSTYPE" == "linux-gnu"* ]]; then if [[ "$OSTYPE" == "linux-gnu"* ]]; then
# Use closure complier only in linux environment # Use closure complier only in linux environment
OPTIM_FLAGS=( OPTIM_FLAGS="$OPTIM_FLAGS --closure 1"
"${OPTIM_FLAGS[@]}"
--closure 1
)
fi fi
# Convert array to string # Unset OPTIM_FLAGS can speed up build
OPTIM_FLAGS="${OPTIM_FLAGS[@]}" # OPTIM_FLAGS=""
CFLAGS_BASE="-I$BUILD_DIR/include $OPTIM_FLAGS" CFLAGS_BASE="$OPTIM_FLAGS -I$BUILD_DIR/include"
CFLAGS="$CFLAGS_BASE -s USE_PTHREADS=1" CFLAGS="$CFLAGS_BASE -s USE_PTHREADS=1"
if [[ "$FFMPEG_ST" == "yes" ]]; then if [[ "$FFMPEG_ST" == "yes" ]]; then
CFLAGS="$CFLAGS_BASE -s USE_PTHREADS=0" CFLAGS="$CFLAGS_BASE"
EXTRA_FFMPEG_CONF_FLAGS=( EXTRA_FFMPEG_CONF_FLAGS="--disable-pthreads --disable-w32threads --disable-os2threads"
--disable-pthreads
)
fi fi
export CFLAGS=$CFLAGS export CFLAGS=$CFLAGS
......
...@@ -676,6 +676,12 @@ ...@@ -676,6 +676,12 @@
"@sinonjs/commons": "^1.7.0" "@sinonjs/commons": "^1.7.0"
} }
}, },
"@tokenizer/token": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.1.1.tgz",
"integrity": "sha512-XO6INPbZCxdprl+9qa/AAbFFOMzzwqYxpjPgLICrMD6C2FCw6qfJOPcBk6JqqPLSaZ/Qx87qn4rpPmPMwaAK6w==",
"dev": true
},
"@tootallnate/once": { "@tootallnate/once": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
...@@ -1704,6 +1710,17 @@ ...@@ -1704,6 +1710,17 @@
"bser": "2.1.1" "bser": "2.1.1"
} }
}, },
"file-type": {
"version": "16.5.1",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.1.tgz",
"integrity": "sha512-Pi1G43smrCy82Q3be3sfKaeS5uHdfj905dP88YqhroG6TYbVY2ljTdDXeXqa6Cn5nOk6znOjWM2uZptA8vH/qQ==",
"dev": true,
"requires": {
"readable-web-to-node-stream": "^3.0.0",
"strtok3": "^6.0.3",
"token-types": "^2.0.0"
}
},
"fill-range": { "fill-range": {
"version": "7.0.1", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
...@@ -1869,6 +1886,12 @@ ...@@ -1869,6 +1886,12 @@
"safer-buffer": ">= 2.1.2 < 3" "safer-buffer": ">= 2.1.2 < 3"
} }
}, },
"ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"dev": true
},
"import-local": { "import-local": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz",
...@@ -2908,6 +2931,12 @@ ...@@ -2908,6 +2931,12 @@
"integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==", "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==",
"dev": true "dev": true
}, },
"peek-readable": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.0.0.tgz",
"integrity": "sha512-kLbU4cz6h86poGVBKgAVMpFmD47nX04fPPQNKnv9fuj+IJZYkEBjsYAVu5nDbZWx0ZsWwWlMzeG90zQa5KLBaA==",
"dev": true
},
"picomatch": { "picomatch": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
...@@ -3020,6 +3049,26 @@ ...@@ -3020,6 +3049,26 @@
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
"dev": true "dev": true
}, },
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"dev": true,
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
},
"readable-web-to-node-stream": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz",
"integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==",
"dev": true,
"requires": {
"readable-stream": "^3.6.0"
}
},
"registry-auth-token": { "registry-auth-token": {
"version": "3.3.2", "version": "3.3.2",
"resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz",
...@@ -3315,6 +3364,23 @@ ...@@ -3315,6 +3364,23 @@
} }
} }
}, },
"string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"dev": true,
"requires": {
"safe-buffer": "~5.2.0"
},
"dependencies": {
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"dev": true
}
}
},
"strip-ansi": { "strip-ansi": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
...@@ -3348,6 +3414,15 @@ ...@@ -3348,6 +3414,15 @@
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
"dev": true "dev": true
}, },
"strtok3": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.2.0.tgz",
"integrity": "sha512-hBbPN4+f9fypbfTs0NImALgzYcb6k/blFr2mJVX6bUOmJCbXe/trDHdIC+Ir5XUXRMGFvq487ecwLitDoHVoew==",
"dev": true,
"requires": {
"peek-readable": "^4.0.0"
}
},
"supports-color": { "supports-color": {
"version": "7.2.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
...@@ -3509,6 +3584,16 @@ ...@@ -3509,6 +3584,16 @@
"is-number": "^7.0.0" "is-number": "^7.0.0"
} }
}, },
"token-types": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/token-types/-/token-types-2.1.1.tgz",
"integrity": "sha512-wnQcqlreS6VjthyHO3Y/kpK/emflxDBNhlNUPfh7wE39KnuDdOituXomIbyI79vBtF0Ninpkh72mcuRHo+RG3Q==",
"dev": true,
"requires": {
"@tokenizer/token": "^0.1.1",
"ieee754": "^1.2.1"
}
},
"tough-cookie": { "tough-cookie": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz",
...@@ -3608,6 +3693,12 @@ ...@@ -3608,6 +3693,12 @@
} }
} }
}, },
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
"dev": true
},
"v8-to-istanbul": { "v8-to-istanbul": {
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.0.0.tgz", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.0.0.tgz",
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
"author": "jeromewus@gmail.com", "author": "jeromewus@gmail.com",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"file-type": "^16.5.1",
"jest": "^27.0.6", "jest": "^27.0.6",
"serve": "^12.0.0" "serve": "^12.0.0"
} }
......
Module["exit"] = exit;
Module['exit'] = exit;
/**
* Replace Moudle['quit'] to avoid process.exit();
*
* @ref: https://github.com/Kagami/ffmpeg.js/blob/v4.2.9003/build/pre.js#L48
*/
Module['quit'] = function(status) {
if (Module["onExit"]) Module["onExit"](status);
throw new ExitStatus(status);
}
const fs = require('fs');
const path = require('path');
const base = path.join(__dirname, 'data');
const avi = Uint8Array.from(fs.readFileSync(path.join(base, 'video-1s.avi')));
const wav = Uint8Array.from(fs.readFileSync(path.join(base, 'audio-1s.wav')));
const arial = Uint8Array.from(fs.readFileSync(path.join(base, 'arial.ttf')));
const png = Uint8Array.from(fs.readFileSync(path.join(base, 'image.png')));
const srt = `
1
00:00:00,000 --> 00:00:01,000
ffmpeg.wasm test
`;
const ass = `
[Script Info]
; Script generated by FFmpeg/Lavc58.91.100
ScriptType: v4.00+
PlayResX: 240
PlayResY: 256
ScaledBorderAndShadow: yes
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Arial,16,&Hffffff,&Hffffff,&H0,&H0,0,0,0,0,100,100,0,0,1,1,0,2,10,10,10,0
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,0:00:00.00,0:00:01.00,Default,,0,0,0,,ffmpeg.wasm test
`;
const CASES = [
{
name: 'wav to aac',
args: ['-i', 'audio.wav', '-c:a', 'libfdk_aac','audio.aac'],
input: [
{ name: 'audio.wav', data: wav },
],
output: [
{ name: 'audio.aac', type: 'audio/aac' },
]
},
{
name: 'wav to flac',
args: ['-i', 'audio.wav', 'audio.flac'],
input: [
{ name: 'audio.wav', data: wav },
],
output: [
{ name: 'audio.flac', type: 'audio/x-flac' },
]
},
{
name: 'avi to x264 mp4 with drawtext',
args: ['-i', 'video.avi', '-vf', 'drawtext=fontfile=/arial.ttf:text=\'Artist\':fontcolor=white:fontsize=24:x=(w-text_w)/2:y=(h-text_h)/2', 'video.mp4'],
input: [
{ name: 'video.avi', data: avi },
{ name: 'arial.ttf', data: arial },
],
output: [
{ name: 'video.mp4', type: 'video/mp4' },
]
},
{
name: 'avi to gif',
args: ['-i', 'video.avi', '-f', 'gif', 'video.gif'],
input: [
{ name: 'video.avi', data: avi },
],
output: [
{ name: 'video.gif', type: 'image/gif' },
]
},
{
name: 'wav to mp3',
args: ['-i', 'audio.wav', 'audio.mp3'],
input: [
{ name: 'audio.wav', data: wav },
],
output: [
{ name: 'audio.mp3', type: 'audio/mpeg' },
]
},
{
name: 'avi to x264 mp4 with srt',
args: ['-i', 'video.avi', '-vf', 'subtitles=test.srt:fontsdir=/fonts:force_style="Fontname=Arial"', 'video.mp4'],
dirs: ['fonts'],
input: [
{ name: 'video.avi', data: avi },
{ name: '/fonts/arial.ttf', data: arial },
{ name: 'test.srt', data: srt },
],
output: [
{ name: 'video.mp4', type: 'video/mp4' },
]
},
{
name: 'avi to x264 mp4 with ass',
args: ['-i', 'video.avi', '-vf', 'ass=test.ass:fontsdir=/fonts', 'video.mp4'],
dirs: ['fonts'],
input: [
{ name: 'video.avi', data: avi },
{ name: '/fonts/arial.ttf', data: arial },
{ name: 'test.ass', data: ass },
],
output: [
{ name: 'video.mp4', type: 'video/mp4' },
]
},
{
name: 'avi to vp9 webm',
args: ['-i', 'video.avi', 'video.webm'],
input: [
{ name: 'video.avi', data: avi },
],
output: [
{ name: 'video.webm', type: 'video/webm' },
]
},
{
name: 'avi to vp9 webm with mt',
args: ['-i', 'video.avi', '-row-mt', '1', 'video.webm'],
input: [
{ name: 'video.avi', data: avi },
],
output: [
{ name: 'video.webm', type: 'video/webm' },
]
},
{
name: 'png to webp',
args: ['-i', 'image.png', 'image.webp'],
input: [
{ name: 'image.png', data: png },
],
output: [
{ name: 'image.webp', type: 'image/webp' },
]
},
{
name: 'avi to mpeg1',
args: ['-i', 'video.avi', '-c:v', 'mpeg1video', 'video.mpeg'],
input: [
{ name: 'video.avi', data: avi },
],
output: [
{ name: 'video.mpeg', type: 'video/MP1S' },
]
},
{
name: 'avi to mpeg2',
args: ['-i', 'video.avi', '-c:v', 'mpeg2video', 'video.mpg'],
input: [
{ name: 'video.avi', data: avi },
],
output: [
{ name: 'video.mpg', type: 'video/MP1S' },
]
},
{
name: 'wav to opus',
args: ['-i', 'audio.wav', 'audio.opus'],
input: [
{ name: 'audio.wav', data: wav },
],
output: [
{ name: 'audio.opus', type: 'audio/opus' },
]
},
{
name: 'avi to ogv',
args: ['-i', 'video.avi', '-c:v', 'libtheora', 'video.ogv'],
input: [
{ name: 'video.avi', data: avi },
],
output: [
{ name: 'video.ogv', type: 'video/ogg' },
]
},
{
name: 'wav to ogg',
args: ['-i', 'audio.wav', '-c:a', 'libvorbis', 'audio.ogg'],
input: [
{ name: 'audio.wav', data: wav },
],
output: [
{ name: 'audio.ogg', type: 'audio/ogg' },
]
},
{
name: 'wav to wv',
args: ['-i', 'audio.wav', 'audio.wv'],
input: [
{ name: 'audio.wav', data: wav },
],
output: [
{ name: 'audio.wv', type: 'audio/wavpack' },
]
},
{
name: 'avi to x264 mp4',
args: ['-i', 'video.avi', 'video.mp4'],
input: [
{ name: 'video.avi', data: avi },
],
output: [
{ name: 'video.mp4', type: 'video/mp4' },
]
},
// It takes too much time for single thread version to
// transcode x265, thus skip.
{
name: 'avi to x265 10bit mp4',
args: ['-i', 'video.avi', '-c:v', 'libx265', '-pix_fmt', 'yuv420p10le', 'video.mp4'],
input: [
{ name: 'video.avi', data: avi },
],
output: [
{ name: 'video.mp4', type: 'video/mp4' },
],
st: false,
},
{
name: 'avi to x265 12bit mp4',
args: ['-i', 'video.avi', '-c:v', 'libx265', '-pix_fmt', 'yuv420p12le', 'video.mp4'],
input: [
{ name: 'video.avi', data: avi },
],
output: [
{ name: 'video.mp4', type: 'video/mp4' },
],
st: false,
},
];
module.exports = { module.exports = {
TIMEOUT: 300000, TIMEOUT: 300000,
CASES,
}; };
const fs = require('fs');
const path = require('path');
const { TIMEOUT } = require('./config');
const { runFFmpeg } = require('./utils');
const IN_FILE_NAME = 'audio-1s.wav';
const OUT_FILE_NAME = 'audio.aac';
const FILE_SIZE = 4239;
let wavData = null;
beforeAll(() => {
wavData = Uint8Array.from(fs.readFileSync(path.join(__dirname, 'data', IN_FILE_NAME)));
});
test('convert wav to aac', async () => {
const args = ['-i', IN_FILE_NAME, '-c:a', 'libfdk_aac', OUT_FILE_NAME];
const { fileSize } = await runFFmpeg(IN_FILE_NAME, wavData, args, OUT_FILE_NAME);
expect(fileSize).toBe(FILE_SIZE);
}, TIMEOUT);
const fs = require('fs');
const path = require('path');
const { TIMEOUT } = require('./config');
const { runFFmpeg } = require('./utils');
const IN_FILE_NAME = 'audio-1s.wav';
const OUT_FILE_NAME = 'audio.flac';
const FILE_SIZE = 31871;
let wavData = null;
beforeAll(() => {
wavData = Uint8Array.from(fs.readFileSync(path.join(__dirname, 'data', IN_FILE_NAME)));
});
test('convert wav to flac', async () => {
const args = ['-i', IN_FILE_NAME, OUT_FILE_NAME];
const { fileSize } = await runFFmpeg(IN_FILE_NAME, wavData, args, OUT_FILE_NAME);
expect(fileSize).toBe(FILE_SIZE);
}, TIMEOUT);
const fs = require('fs');
const path = require('path');
const { TIMEOUT } = require('./config');
const { runFFmpeg, b64ToUint8Array } = require('./utils');
const ARIAL_TTF = require('./data/arial.ttf.js');
const IN_FILE_NAME = 'video-1s.avi';
const OUT_FILE_NAME = 'video.mp4';
const FILE_SIZE = 37243;
let aviData = null;
beforeAll(() => {
aviData = Uint8Array.from(fs.readFileSync(path.join(__dirname, 'data', IN_FILE_NAME)));
});
test('transcode avi to x264 mp4 with drawtext', async () => {
const args = ['-i', IN_FILE_NAME, '-vf', 'drawtext=fontfile=/arial.ttf:text=\'Artist\':fontcolor=white:fontsize=24:x=(w-text_w)/2:y=(h-text_h)/2', OUT_FILE_NAME];
const { fileSize } = await runFFmpeg(IN_FILE_NAME, aviData, args, OUT_FILE_NAME, [{ name: 'arial.ttf', data: b64ToUint8Array(ARIAL_TTF) }]);
expect(fileSize).toBe(FILE_SIZE);
}, TIMEOUT);
const fs = require('fs');
const path = require('path');
const { TIMEOUT } = require('./config');
const { runFFmpeg } = require('./utils');
const IN_FILE_NAME = 'video-1s.avi';
const OUT_FILE_NAME = 'video.gif';
const FILE_SIZE = 181674;
let aviData = null;
beforeAll(() => {
aviData = Uint8Array.from(fs.readFileSync(path.join(__dirname, 'data', IN_FILE_NAME)));
});
test('transcode avi to gif', async () => {
const args = ['-i', IN_FILE_NAME, '-f', 'gif', OUT_FILE_NAME];
const { fileSize } = await runFFmpeg(IN_FILE_NAME, aviData, args, OUT_FILE_NAME);
expect(fileSize).toBe(FILE_SIZE);
}, TIMEOUT);
const fs = require('fs');
const path = require('path');
const { TIMEOUT } = require('./config');
const { runFFmpeg } = require('./utils');
const IN_FILE_NAME = 'audio-1s.wav';
const OUT_FILE_NAME = 'audio.mp3';
const FILE_SIZE = 4039;
let wavData = null;
beforeAll(() => {
wavData = Uint8Array.from(fs.readFileSync(path.join(__dirname, 'data', IN_FILE_NAME)));
});
test('convert wav to mp3', async () => {
const args = ['-i', IN_FILE_NAME, OUT_FILE_NAME];
const { fileSize } = await runFFmpeg(IN_FILE_NAME, wavData, args, OUT_FILE_NAME);
expect(fileSize).toBe(FILE_SIZE);
}, TIMEOUT);
const fs = require('fs');
const path = require('path');
const { TIMEOUT } = require('./config');
const { runFFmpeg, b64ToUint8Array } = require('./utils');
const ARIAL_TTF = require('./data/arial.ttf.js');
const SRT_FILE = `
1
00:00:00,000 --> 00:00:01,000
ffmpeg.wasm test
`;
const ASS_FILE = `
[Script Info]
; Script generated by FFmpeg/Lavc58.91.100
ScriptType: v4.00+
PlayResX: 240
PlayResY: 256
ScaledBorderAndShadow: yes
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Arial,16,&Hffffff,&Hffffff,&H0,&H0,0,0,0,0,100,100,0,0,1,1,0,2,10,10,10,0
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,0:00:00.00,0:00:01.00,Default,,0,0,0,,ffmpeg.wasm test
`;
const IN_FILE_NAME = 'video-1s.avi';
const OUT_FILE_NAME = 'video.mp4';
const MP4_WITH_SRT_FILE_SIZE = 38695;
const MP4_WITH_ASS_FILE_SIZE = 38532;
let aviData = null;
beforeAll(() => {
aviData = Uint8Array.from(fs.readFileSync(path.join(__dirname, 'data', IN_FILE_NAME)));
});
test('transcode avi to x264 mp4 with *.srt subtitle', async () => {
const args = ['-i', IN_FILE_NAME, '-vf', 'subtitles=test.srt:fontsdir=/fonts:force_style="Fontname=Arial"', OUT_FILE_NAME];
const { fileSize } = await runFFmpeg(IN_FILE_NAME, aviData, args, OUT_FILE_NAME, [{ name: '/fonts/arial.ttf', data: b64ToUint8Array(ARIAL_TTF) }, { name: 'test.srt', data: SRT_FILE }], ['/fonts']);
expect(fileSize).toBe(MP4_WITH_SRT_FILE_SIZE);
}, TIMEOUT);
test('transcode avi to x264 mp4 with *.ass subtitle', async () => {
const args = ['-i', IN_FILE_NAME, '-vf', 'ass=test.ass:fontsdir=/fonts', OUT_FILE_NAME];
const { fileSize } = await runFFmpeg(IN_FILE_NAME, aviData, args, OUT_FILE_NAME, [{ name: '/fonts/arial.ttf', data: b64ToUint8Array(ARIAL_TTF) }, { name: 'test.ass', data: ASS_FILE }], ['/fonts']);
expect(fileSize).toBe(MP4_WITH_ASS_FILE_SIZE);
}, TIMEOUT);
const fs = require('fs');
const path = require('path');
const { TIMEOUT } = require('./config');
const { runFFmpeg } = require('./utils');
const aviFilePath = path.join(__dirname, 'data', 'video-1s.avi');
const IN_FILE_NAME = 'video-1s.avi';
const OUT_FILE_NAME = 'video.webm';
const FILE_SIZE = 41904;
const FILE_MT_SIZE = 41878;
let aviData = null;
let BASELINE_TIME = 0;
beforeAll(() => {
aviData = Uint8Array.from(fs.readFileSync(path.join(__dirname, 'data', IN_FILE_NAME)));
});
test('transcode avi to vp9 webm', async () => {
const args = ['-i', IN_FILE_NAME, OUT_FILE_NAME];
const start = Date.now();
const { fileSize } = await runFFmpeg(IN_FILE_NAME, aviData, args, OUT_FILE_NAME);
BASELINE_TIME = Date.now() - start;
expect(fileSize).toBe(FILE_SIZE);
}, TIMEOUT);
test('transcode avi to vp9 webm with multithread', async () => {
const args = ['-i', IN_FILE_NAME, '-row-mt', '1', OUT_FILE_NAME];
const start = Date.now();
const { fileSize } = await runFFmpeg(IN_FILE_NAME, aviData, args, OUT_FILE_NAME);
const timediff = Date.now() - start;
expect(fileSize).toBe(FILE_MT_SIZE);
expect(timediff < BASELINE_TIME).toBe(true);
}, TIMEOUT);
const fs = require('fs');
const path = require('path');
const { TIMEOUT } = require('./config');
const { runFFmpeg } = require('./utils');
const IN_FILE_NAME = 'image.png';
const OUT_FILE_NAME = 'image.webp';
const FILE_SIZE = 6376;
let pngData = null;
beforeAll(() => {
pngData = Uint8Array.from(fs.readFileSync(path.join(__dirname, 'data', IN_FILE_NAME)));
});
test('transcode png to webp', async () => {
const args = ['-i', IN_FILE_NAME, OUT_FILE_NAME];
const { fileSize } = await runFFmpeg(IN_FILE_NAME, pngData, args, OUT_FILE_NAME);
expect(fileSize).toBe(FILE_SIZE);
}, TIMEOUT);
const fs = require('fs');
const path = require('path');
const { TIMEOUT } = require('./config');
const { runFFmpeg } = require('./utils');
const IN_FILE_NAME = 'video-1s.avi';
const OUT_FILE_NAME = 'video.mpeg';
const FILE_SIZE = 53248;
let aviData = null;
beforeAll(() => {
aviData = Uint8Array.from(fs.readFileSync(path.join(__dirname, 'data', IN_FILE_NAME)));
});
test('transcode avi to mpeg1', async () => {
const args = ['-i', IN_FILE_NAME, '-c:v', 'mpeg1video', OUT_FILE_NAME];
const { fileSize } = await runFFmpeg(IN_FILE_NAME, aviData, args, OUT_FILE_NAME);
expect(fileSize).toBe(FILE_SIZE);
}, TIMEOUT);
const fs = require('fs');
const path = require('path');
const { TIMEOUT } = require('./config');
const { runFFmpeg } = require('./utils');
const IN_FILE_NAME = 'video-1s.avi';
const OUT_FILE_NAME = 'video.mpg';
const FILE_SIZE = 49152;
let aviData = null;
beforeAll(() => {
aviData = Uint8Array.from(fs.readFileSync(path.join(__dirname, 'data', IN_FILE_NAME)));
});
test('transcode avi to mpeg1', async () => {
const args = ['-i', IN_FILE_NAME, '-c:v', 'mpeg2video', OUT_FILE_NAME];
const { fileSize } = await runFFmpeg(IN_FILE_NAME, aviData, args, OUT_FILE_NAME);
expect(fileSize).toBe(FILE_SIZE);
}, TIMEOUT);
const fs = require('fs');
const path = require('path');
const { TIMEOUT } = require('./config');
const { runFFmpeg } = require('./utils');
const IN_FILE_NAME = 'audio-1s.wav';
const OUT_FILE_NAME = 'audio.opus';
const FILE_SIZE = 12793;
let wavData = null;
beforeAll(() => {
wavData = Uint8Array.from(fs.readFileSync(path.join(__dirname, 'data', IN_FILE_NAME)));
});
test('convert wav to mp3', async () => {
const args = ['-i', IN_FILE_NAME, '-c:a', 'libopus', OUT_FILE_NAME];
const { fileSize } = await runFFmpeg(IN_FILE_NAME, wavData, args, OUT_FILE_NAME);
expect(fileSize).toBe(FILE_SIZE);
}, TIMEOUT);
const fs = require('fs');
const path = require('path');
const { TIMEOUT } = require('./config');
const { runFFmpeg } = require('./utils');
const IN_FILE_NAME = 'video-1s.avi';
const OUT_FILE_NAME = 'video.ogv';
const FILE_SIZE = 29561;
let aviData = null;
beforeAll(() => {
aviData = Uint8Array.from(fs.readFileSync(path.join(__dirname, 'data', IN_FILE_NAME)));
});
test('convert avi to ogv', async () => {
const args = ['-i', IN_FILE_NAME, '-c:v', 'libtheora', OUT_FILE_NAME];
const { fileSize } = await runFFmpeg(IN_FILE_NAME, aviData, args, OUT_FILE_NAME);
expect(fileSize).toBe(FILE_SIZE);
}, TIMEOUT);
const FileType = require('file-type');
const { CASES } = require('./config');
let mode = 'mt';
if (process.argv.length > 2) {
mode = process.argv[2];
}
const { getCore, ffmpeg } = require('./utils')(mode);
(async () => {
for (let i = 0; i < CASES.length; i++) {
const { name, args, dirs = [], input, output, st } = CASES[i];
if (mode === 'st' && st === false) { continue; }
const core = await getCore();
for (let i = 0; i < dirs.length; i++) {
await core.FS.mkdir(dirs[i]);
}
for (let i = 0; i < input.length; i++) {
const { name, data } = input[i];
await core.FS.writeFile(name, data);
}
await ffmpeg({ core, args });
for (let i = 0; i < output.length; i++) {
const data = await core.FS.readFile(output[i].name);
const { mime } = await FileType.fromBuffer(data);
console.log(name, mime);
}
try {
await core.exit();
} catch(e) {}
}
})();
const FileType = require('file-type');
const { CASES, TIMEOUT } = require('./config');
require('events').EventEmitter.defaultMaxListeners = 64;
[
{ mode: 'mt', ...require('./utils')('mt') },
{ mode: 'st', ...require('./utils')('st') },
].forEach(({ mode, getCore, ffmpeg }) => {
CASES.forEach(({
name,
args,
dirs = [],
input,
output,
st,
}) => {
if (mode === 'st' && st === false) { return; }
test(`[${mode}] ${name}`, async () => {
const core = await getCore();
for (let i = 0; i < dirs.length; i++) {
await core.FS.mkdir(dirs[i]);
}
for (let i = 0; i < input.length; i++) {
const { name, data } = input[i];
await core.FS.writeFile(name, data);
}
await ffmpeg({ core, args });
for (let i = 0; i < output.length; i++) {
const { name, type } = output[i];
const data = await core.FS.readFile(name);
expect(data.length).not.toBe(0);
const { mime } = await FileType.fromBuffer(data);
expect(type).toBe(mime);
}
try {
await core.exit();
} catch(e) {}
}, TIMEOUT);
});
})
const createFFmpegCore = require('../packages/core/dist/ffmpeg-core');
const parseArgs = (Core, args) => {
const argsPtr = Core._malloc(args.length * Uint32Array.BYTES_PER_ELEMENT);
args.forEach((s, idx) => {
const buf = Core._malloc(s.length + 1);
Core.writeAsciiToMemory(s, buf);
Core.setValue(argsPtr + (Uint32Array.BYTES_PER_ELEMENT * idx), buf, 'i32');
});
return [args.length, argsPtr];
};
const ffmpeg = (Core, args) => {
Core.ccall(
'proxy_main',
'number',
['number', 'number'],
parseArgs(Core, ['ffmpeg', '-hide_banner', '-nostdin', ...args]),
);
};
const runFFmpeg = async (ifilename, data, args, ofilename, extraFiles = [], extraFolders = [], msBeforeExit = -1) => {
let resolve = null;
let file = null;
let fileSize = -1;
const Core = await createFFmpegCore({
printErr: () => {},
print: (m) => {
if (m.startsWith('FFMPEG_END')) {
resolve();
}
},
});
extraFolders.forEach((f) => {
Core.FS.mkdir(f);
});
extraFiles.forEach(({ name, data: d }) => {
Core.FS.writeFile(name, d);
});
Core.FS.writeFile(ifilename, data);
ffmpeg(Core, args);
if (msBeforeExit !== -1) {
setTimeout(() => {
Core.exit();
resolve();
}, msBeforeExit);
}
await new Promise((_resolve) => { resolve = _resolve });
if (typeof ofilename !== 'undefined') {
file = Core.FS.readFile(ofilename);
fileSize = file.length;
Core.FS.unlink(ofilename);
}
return { Core, file, fileSize };
};
const b64ToUint8Array = (str) => (Buffer.from(str, 'base64'));
module.exports = {
runFFmpeg,
b64ToUint8Array,
ffmpeg,
};
module.exports = (str) => (Buffer.from(str, 'base64'));
const mt = require('./mt');
const st = require('./st');
const b64ToUint8Array = require('./b64ToUint8Array');
module.exports = (v) => {
const pkg = v === 'st' ? st : mt;
return {
b64ToUint8Array,
...pkg,
}
};
const parseArgs = require('../parseArgs');
let resolve = null;
const ffmpeg = ({ core, args }) => {
try {
core.ccall(
'proxy_main', // use emscripten_proxy_main if emscripten upgraded
'number',
['number', 'number'],
parseArgs(core, ['ffmpeg', '-hide_banner', '-nostdin', ...args]),
);
} catch(e) {
// TODO: only ignore certain exceptions
}
return new Promise((_resolve) => { resolve = _resolve; });
};
const getCore = () => (
require('../../../packages/core/dist/ffmpeg-core')({
printErr: () => {},
print: (m) => {
if (m.startsWith('FFMPEG_END')) {
resolve();
}
},
})
);
module.exports = {
ffmpeg,
getCore,
};
module.exports = (core, args) => {
const argsPtr = core._malloc(args.length * Uint32Array.BYTES_PER_ELEMENT);
args.forEach((s, idx) => {
const buf = core._malloc(s.length + 1);
core.writeAsciiToMemory(s, buf);
core.setValue(argsPtr + (Uint32Array.BYTES_PER_ELEMENT * idx), buf, 'i32');
});
return [args.length, argsPtr];
};
const { Worker } = require('worker_threads');
const path = require('path');
const getCore = async () => {
const resolves = {};
let resolveExit = null;
let _id = 0;
const getID = () => _id++;
const worker = new Worker(path.join(__dirname, 'worker.js'));
const getHandler = (payload) => new Promise((resolve) => {
const id = getID();
worker.postMessage({ id, ...payload });
resolves[id] = resolve;
});
worker.on('message', ({ id, data }) => {
resolves[id](data);
});
worker.on('exit', () => {
resolveExit();
});
await getHandler({ type: 'INIT' });
return {
getHandler,
exit: () => new Promise((resolve) => {
worker.terminate();
resolveExit = resolve;
}),
FS: ['mkdir', 'writeFile', 'readFile'].reduce((acc, cmd) => {
acc[cmd] = (...args) => getHandler({ type: 'FS', cmd, args });
return acc;
}, {}),
}
};
module.exports = {
getCore,
ffmpeg: ({ core, args }) => core.getHandler({ type: 'RUN', args }),
};
const { parentPort } = require('worker_threads');
const createFFmpegCore = require('../../../packages/core-st/dist/ffmpeg-core');
const parseArgs = require('../parseArgs');
let core = null;
parentPort.on('message', async ({ id, type, cmd, args }) => {
switch(type) {
case 'INIT':
core = await createFFmpegCore({
printErr: () => {},
print: () => {},
});
parentPort.postMessage({ id, type: 'INIT' });
break;
case 'FS':
const blocklist = ['mkdir']
const data = core.FS[cmd](...args);
parentPort.postMessage({ id, type: 'FS', data: !blocklist.includes(cmd) ? data : null});
break;
case 'RUN':
try {
core.ccall(
'main',
'number',
['number', 'number'],
parseArgs(core, ['ffmpeg', '-hide_banner', '-nostdin', ...args]),
)
} catch(e) {}
parentPort.postMessage({ id, type: 'RUN' });
break;
default:
console.log('unknown message type: ', type);
}
});
const fs = require('fs');
const path = require('path');
const { TIMEOUT } = require('./config');
const { runFFmpeg } = require('./utils');
const IN_FILE_NAME = 'audio-1s.wav';
const OUT_FILE_NAME = 'audio.ogg';
const FILE_SIZE = 7712;
let wavData = null;
beforeAll(() => {
wavData = Uint8Array.from(fs.readFileSync(path.join(__dirname, 'data', IN_FILE_NAME)));
});
test('convert wav to aac', async () => {
const args = ['-i', IN_FILE_NAME, '-c:a', 'libvorbis', OUT_FILE_NAME];
const { fileSize } = await runFFmpeg(IN_FILE_NAME, wavData, args, OUT_FILE_NAME);
expect(fileSize).toBe(FILE_SIZE);
}, TIMEOUT);
const fs = require('fs');
const path = require('path');
const { TIMEOUT } = require('./config');
const { runFFmpeg } = require('./utils');
const IN_FILE_NAME = 'audio-1s.wav';
const OUT_FILE_NAME = 'audio.wv';
const FILE_SIZE = 23502;
let wavData = null;
beforeAll(() => {
wavData = Uint8Array.from(fs.readFileSync(path.join(__dirname, 'data', IN_FILE_NAME)));
});
test('convert wav to wv', async () => {
const args = ['-i', IN_FILE_NAME, OUT_FILE_NAME];
const { fileSize } = await runFFmpeg(IN_FILE_NAME, wavData, args, OUT_FILE_NAME);
expect(fileSize).toBe(FILE_SIZE);
}, TIMEOUT);
const fs = require('fs');
const path = require('path');
const { TIMEOUT } = require('./config');
const { runFFmpeg } = require('./utils');
const IN_FILE_NAME = 'video-1s.avi';
const OUT_FILE_NAME = 'video.mp4';
const FILE_SIZE = 38372;
let aviData = null;
beforeAll(() => {
aviData = Uint8Array.from(fs.readFileSync(path.join(__dirname, 'data', IN_FILE_NAME)));
});
test('transcode avi to x264 mp4', async () => {
const args = ['-i', IN_FILE_NAME, OUT_FILE_NAME];
const { fileSize } = await runFFmpeg(IN_FILE_NAME, aviData, args, OUT_FILE_NAME);
expect(fileSize).toBe(FILE_SIZE);
}, TIMEOUT);
const fs = require('fs');
const path = require('path');
const { TIMEOUT } = require('./config');
const { runFFmpeg } = require('./utils');
const IN_FILE_NAME = 'video-1s.avi';
const OUT_FILE_NAME = 'video.mp4';
const FILE_10BIT_SIZES = [22507, 22520];
const FILE_12BIT_SIZE = 22718;
let aviData = null;
beforeAll(() => {
aviData = Uint8Array.from(fs.readFileSync(path.join(__dirname, 'data', IN_FILE_NAME)));
});
test('transcode avi to x265 10bit mp4', async () => {
const args = ['-i', IN_FILE_NAME, '-c:v', 'libx265', '-pix_fmt', 'yuv420p10le', OUT_FILE_NAME];
const { fileSize } = await runFFmpeg(IN_FILE_NAME, aviData, args, OUT_FILE_NAME);
expect(FILE_10BIT_SIZES.includes(fileSize)).toBe(true);
}, TIMEOUT);
test('transcode avi to x265 12bit mp4', async () => {
const args = ['-i', IN_FILE_NAME, '-c:v', 'libx265', '-pix_fmt', 'yuv420p12le', OUT_FILE_NAME];
const { fileSize } = await runFFmpeg(IN_FILE_NAME, aviData, args, OUT_FILE_NAME);
expect(fileSize).toBe(FILE_12BIT_SIZE);
}, TIMEOUT);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment