munim-ffmpeg 0.4.1 โ 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NitroMunimFfmpeg.podspec +2 -1
- package/README.md +87 -7
- package/android/build.gradle +6 -5
- package/android/src/main/java/com/margelo/nitro/munimffmpeg/FFmpegNative.kt +36 -0
- package/lib/index.d.ts +8 -0
- package/lib/index.js +74 -0
- package/package.json +6 -1
- package/scripts/binaries.json +1 -1
- package/scripts/fetch-binaries.mjs +0 -0
- package/src/index.ts +91 -0
package/NitroMunimFfmpeg.podspec
CHANGED
|
@@ -30,7 +30,8 @@ Pod::Spec.new do |s|
|
|
|
30
30
|
s.pod_target_xcconfig = {
|
|
31
31
|
"HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/ios\"",
|
|
32
32
|
}
|
|
33
|
-
|
|
33
|
+
# CoreText and CoreGraphics are libass's font provider on iOS.
|
|
34
|
+
s.frameworks = "AudioToolbox", "VideoToolbox", "CoreMedia", "AVFoundation", "CoreVideo", "Security", "CoreText", "CoreGraphics"
|
|
34
35
|
s.libraries = "bz2", "z", "iconv", "c++"
|
|
35
36
|
|
|
36
37
|
# Must be public so CocoaPods puts it in the module umbrella, which is how the
|
package/README.md
CHANGED
|
@@ -112,7 +112,8 @@
|
|
|
112
112
|
- ๐ฑ **iOS and Android:** Native implementations in Swift and Kotlin
|
|
113
113
|
- ๐งฌ **Nitro Modules:** Generated high-performance native bindings
|
|
114
114
|
- ๐ **Expo compatible:** Autolinking, config plugin, and an Expo development example
|
|
115
|
-
- ๐งช **Capability discovery:** Ask the bundled build which encoders and
|
|
115
|
+
- ๐งช **Capability discovery:** Ask the bundled build which encoders, decoders, muxers, demuxers, filters, and protocols it actually has
|
|
116
|
+
- ๐ฌ **Subtitle burn-in:** libass renders ASS/SSA and SRT subtitles โ styling, positioning, outlines, shadows, and proper Arabic/Urdu shaping via HarfBuzz and FriBidi
|
|
116
117
|
- ๐ฏ **TypeScript:** Complete public callback and result types
|
|
117
118
|
- ๐๏ธ **16 KB Android pages:** Built with the alignment Google Play requires
|
|
118
119
|
|
|
@@ -129,7 +130,8 @@
|
|
|
129
130
|
| Cancel one FFmpeg session | โ
| โ
| Pass the positive safe-integer ID received by `execute`'s `onSessionCreated`. The native dependency does not expose FFprobe cancellation. |
|
|
130
131
|
| Cancel all FFmpeg sessions | โ
| โ
| Use `cancelAll()` or call `cancel()` without an ID. |
|
|
131
132
|
| Expo Go | โ | โ | A native development build is required. |
|
|
132
|
-
| Capability discovery | โ
| โ
| `listEncoders()`, `listDecoders()`, and `pickEncoder()` report what the bundled build supports.
|
|
133
|
+
| Capability discovery | โ
| โ
| `listEncoders()`, `listDecoders()`, `listMuxers()`, `listDemuxers()`, `listFilters()`, `listProtocols()`, and `pickEncoder()` report what the bundled build supports. |
|
|
134
|
+
| Subtitle burn-in | โ
| โ
| libass with system fonts: Core Text on iOS, fontconfig over `/system/fonts` on Android. |
|
|
133
135
|
| H.264 encoding | VideoToolbox | libx264 | The builds differ; use `pickEncoder(['libx264', 'h264_videotoolbox'])` instead of hard-coding an encoder. |
|
|
134
136
|
| Remote HTTP(S) inputs | โ
| โ
| Both builds link GnuTLS. Remote server behaviour still varies; prefer local files for predictable app workflows. |
|
|
135
137
|
|
|
@@ -145,9 +147,10 @@ Every release runs the example's 25-check device suite. For 0.4.x:
|
|
|
145
147
|
| iOS Simulator, arm64 | 25/25 |
|
|
146
148
|
| Galaxy A14 5G, arm64-v8a | 25/25 |
|
|
147
149
|
| Android emulator, arm64 | Software encoding passes; hardware encoding does not โ see below |
|
|
148
|
-
| Android
|
|
150
|
+
| Android emulator, `x86_64` | Same: `libopenh264` passes, MediaCodec does not |
|
|
151
|
+
| Android `armeabi-v7a` | Built and statically checked, not executed |
|
|
149
152
|
|
|
150
|
-
`x86_64`
|
|
153
|
+
`x86_64` was verified on an Intel Windows machine, since the Android emulator refuses non-native system images on Apple Silicon: FFmpeg 9.0.1, 185 encoders, and the software H.264 encode passes. `armeabi-v7a` has no hardware to hand, so it was checked statically instead โ correct ELF architecture, the expected JNI exports, only system libraries unresolved, and the same FFmpeg and codec set as arm64.
|
|
151
154
|
|
|
152
155
|
### Android emulators cannot encode video
|
|
153
156
|
|
|
@@ -176,13 +179,13 @@ Both platforms run **FFmpeg 9.0.1**, built from [ffmpeg.org](https://www.ffmpeg.
|
|
|
176
179
|
| TLS | SecureTransport | mbedTLS |
|
|
177
180
|
| Minimum | iOS 15.1 | API 24, 16 KB pages |
|
|
178
181
|
|
|
179
|
-
Linked libraries, identical on both: **LAME** (MP3), **Opus**, **libvpx** (VP8/VP9), **dav1d** (AV1 decoding), **openh264** (software H.264), plus everything FFmpeg builds natively.
|
|
182
|
+
Linked libraries, identical on both: **LAME** (MP3), **Opus**, **libvpx** (VP8/VP9), **dav1d** (AV1 decoding), **openh264** (software H.264), **libass** with **FreeType**, **HarfBuzz**, and **FriBidi** (subtitle rendering and text shaping), plus everything FFmpeg builds natively. Android additionally links **fontconfig** and **expat** so libass can discover the system fonts; iOS uses Core Text for the same job.
|
|
180
183
|
|
|
181
184
|
FFmpeg's own `ffmpeg` and `ffprobe` tools are compiled to run inside your app process, so the argument arrays you pass are handled by the real command-line code paths rather than a reimplementation.
|
|
182
185
|
|
|
183
186
|
### Encoders
|
|
184
187
|
|
|
185
|
-
Verified by running the example's device suite: iOS reports
|
|
188
|
+
Verified by running the example's device suite: iOS reports 187 encoders, Android 185. Everything FFmpeg builds natively (`aac`, `alac`, `flac`, `mpeg4`, `mjpeg`, `png`, `gif`, `pcm_*`, โฆ) is on both, as are `libmp3lame`, `libopus`, `libvpx`, and `libvpx-vp9`.
|
|
186
189
|
|
|
187
190
|
H.264 and HEVC come from the platform's hardware encoder, which is faster and uses less power than a software encoder. `libopenh264` is there as a software H.264 fallback for anywhere hardware encoding is unavailable โ an emulator, for instance:
|
|
188
191
|
|
|
@@ -462,6 +465,31 @@ Returns the decoder names the bundled FFmpeg build can read.
|
|
|
462
465
|
function listDecoders(): Promise<string[]>
|
|
463
466
|
```
|
|
464
467
|
|
|
468
|
+
### `listMuxers()` / `listDemuxers()`
|
|
469
|
+
|
|
470
|
+
Return the container formats the bundled FFmpeg build can write and read, e.g. `mp4`, `matroska`, `webm`. Cached after the first call.
|
|
471
|
+
|
|
472
|
+
```typescript
|
|
473
|
+
function listMuxers(): Promise<string[]>
|
|
474
|
+
function listDemuxers(): Promise<string[]>
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### `listFilters()`
|
|
478
|
+
|
|
479
|
+
Returns the filter names the bundled FFmpeg build provides, e.g. `subtitles`, `ass`, `drawtext`, `scale`.
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
482
|
+
function listFilters(): Promise<string[]>
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
### `listProtocols()`
|
|
486
|
+
|
|
487
|
+
Returns the protocol names the bundled FFmpeg build provides, e.g. `file`, `https`, `concat`.
|
|
488
|
+
|
|
489
|
+
```typescript
|
|
490
|
+
function listProtocols(): Promise<string[]>
|
|
491
|
+
```
|
|
492
|
+
|
|
465
493
|
### `pickEncoder(candidates)`
|
|
466
494
|
|
|
467
495
|
Returns the first name in `candidates` that the build provides, or `undefined` when none are available. Use it to write one command that runs on both platforms.
|
|
@@ -579,6 +607,58 @@ const result = await execute([
|
|
|
579
607
|
])
|
|
580
608
|
```
|
|
581
609
|
|
|
610
|
+
### Burn subtitles into a video
|
|
611
|
+
|
|
612
|
+
The bundled builds include libass with FreeType, HarfBuzz, and FriBidi, so ASS/SSA styling and complex scripts (Arabic, Urdu, and other RTL or shaped text) render correctly. System fonts are found automatically โ through Core Text on iOS and through fontconfig scanning `/system/fonts` on Android.
|
|
613
|
+
|
|
614
|
+
```typescript
|
|
615
|
+
import { execute, normalizePath } from 'munim-ffmpeg'
|
|
616
|
+
|
|
617
|
+
// ASS/SSA keeps its embedded styling: fonts, colours, outlines,
|
|
618
|
+
// shadows, positioning, karaoke โ everything the format supports.
|
|
619
|
+
await execute([
|
|
620
|
+
'-y',
|
|
621
|
+
'-i', inputPath,
|
|
622
|
+
'-vf', `ass=filename=${normalizePath(subtitlePath)}`,
|
|
623
|
+
'-c:a', 'copy',
|
|
624
|
+
outputPath,
|
|
625
|
+
])
|
|
626
|
+
|
|
627
|
+
// SRT can be styled at burn time with force_style.
|
|
628
|
+
await execute([
|
|
629
|
+
'-y',
|
|
630
|
+
'-i', inputPath,
|
|
631
|
+
'-vf', `subtitles=filename=${normalizePath(srtPath)}:force_style='Fontsize=28,PrimaryColour=&H00FFFF00,Outline=2'`,
|
|
632
|
+
'-c:a', 'copy',
|
|
633
|
+
outputPath,
|
|
634
|
+
])
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
To ship your own fonts instead of relying on the device's, put them in a directory and add `:fontsdir=/path/to/fonts` to the filter. Note that filter arguments are colon-separated, so a path containing `:` must be escaped โ app sandbox paths on both platforms are safe as-is.
|
|
638
|
+
|
|
639
|
+
### Work with MKV and multiple tracks
|
|
640
|
+
|
|
641
|
+
Matroska muxing and demuxing is compiled in, along with FFmpeg's standard `-map` stream selection and `-c copy` remuxing:
|
|
642
|
+
|
|
643
|
+
```typescript
|
|
644
|
+
import { execute } from 'munim-ffmpeg'
|
|
645
|
+
|
|
646
|
+
// Bundle one video, two audio languages, and a subtitle track into MKV.
|
|
647
|
+
await execute([
|
|
648
|
+
'-y',
|
|
649
|
+
'-i', videoPath, '-i', urduAudioPath, '-i', subtitlePath,
|
|
650
|
+
'-map', '0:v:0', '-map', '0:a:0', '-map', '1:a:0', '-map', '2:s:0',
|
|
651
|
+
'-c:v', 'copy', '-c:a', 'aac', '-c:s', 'srt',
|
|
652
|
+
'-metadata:s:a:1', 'language=urd',
|
|
653
|
+
outputMkvPath,
|
|
654
|
+
])
|
|
655
|
+
|
|
656
|
+
// Extract the second audio track without re-encoding.
|
|
657
|
+
await execute([
|
|
658
|
+
'-y', '-i', outputMkvPath, '-map', '0:a:1', '-c', 'copy', trackPath,
|
|
659
|
+
])
|
|
660
|
+
```
|
|
661
|
+
|
|
582
662
|
### Cancel a long-running command
|
|
583
663
|
|
|
584
664
|
```typescript
|
|
@@ -631,7 +711,7 @@ if (result.success) {
|
|
|
631
711
|
|
|
632
712
|
The JavaScript, TypeScript, Swift, Kotlin, C core, and generated Nitro bridge in this repository are Apache-2.0.
|
|
633
713
|
|
|
634
|
-
The bundled FFmpeg 9.0.1 is **LGPLv3**, on both platforms. It is configured without `--enable-gpl`, so no x264, x265, xvid, or vid.stab. The external libraries it links are LAME (LGPL), Opus (BSD), libvpx (BSD), dav1d (BSD), openh264 (BSD 2-clause), and mbedTLS (Apache-2.0)
|
|
714
|
+
The bundled FFmpeg 9.0.1 is **LGPLv3**, on both platforms. It is configured without `--enable-gpl`, so no x264, x265, xvid, or vid.stab. The external libraries it links are LAME (LGPL), Opus (BSD), libvpx (BSD), dav1d (BSD), openh264 (BSD 2-clause), libass (ISC), FreeType (FTL, BSD-style with credit), HarfBuzz (MIT-style), FriBidi (LGPL), and, on Android only, mbedTLS (Apache-2.0), fontconfig (MIT-style), and expat (MIT). None of them change the LGPL story.
|
|
635
715
|
|
|
636
716
|
> **A note on H.264 patents.** Hardware encoders are covered by the licences device manufacturers already pay for. Software H.264 encoding through `libopenh264` is not: Cisco's royalty coverage applies to *their* prebuilt binary, and this package builds openh264 from source. If you ship software H.264 encoding at scale, check where you stand with AVC licensing. Hardware encoders avoid the question entirely, which is why `pickEncoder` should list them first.
|
|
637
717
|
|
package/android/build.gradle
CHANGED
|
@@ -23,11 +23,12 @@ apply plugin: 'org.jetbrains.kotlin.android'
|
|
|
23
23
|
apply from: '../nitrogen/generated/android/NitroMunimFfmpeg+autolinking.gradle'
|
|
24
24
|
apply from: "./fix-prefab.gradle"
|
|
25
25
|
|
|
26
|
-
// Deliberately not applying the "com.facebook.react" plugin
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
26
|
+
// Deliberately not applying the "com.facebook.react" plugin. Nitro generates its
|
|
27
|
+
// own bindings, so React Native's codegen has nothing to do here โ and applying
|
|
28
|
+
// the plugin *without* a matching `react { libraryName = ... }` block makes it
|
|
29
|
+
// emit React Native's entire TurboModule spec set into this AAR. Those collide
|
|
30
|
+
// with the app's own copies and release builds fail with
|
|
31
|
+
// "Type com.facebook.fbreact.specs.* is defined multiple times".
|
|
31
32
|
|
|
32
33
|
def getExtOrDefault(name) {
|
|
33
34
|
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["NitroMunimFfmpeg_" + name]
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
package com.margelo.nitro.munimffmpeg
|
|
2
2
|
|
|
3
|
+
import android.system.Os
|
|
3
4
|
import androidx.annotation.Keep
|
|
4
5
|
import com.facebook.proguard.annotations.DoNotStrip
|
|
6
|
+
import java.io.File
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Thin wrapper over FFmpeg 9's own command-line tools, compiled to run inside
|
|
@@ -15,9 +17,43 @@ import com.facebook.proguard.annotations.DoNotStrip
|
|
|
15
17
|
@DoNotStrip
|
|
16
18
|
object FFmpegNative {
|
|
17
19
|
init {
|
|
20
|
+
configureFontconfig()
|
|
18
21
|
System.loadLibrary("munimffmpeg9")
|
|
19
22
|
}
|
|
20
23
|
|
|
24
|
+
/**
|
|
25
|
+
* libass discovers fonts through fontconfig, and Android has no fonts.conf,
|
|
26
|
+
* so one pointing at the system font directories is written to the app's
|
|
27
|
+
* cache and exported before FFmpeg first runs. Without it, `subtitles=` and
|
|
28
|
+
* `drawtext` render nothing. Apps that manage their own fontconfig setup can
|
|
29
|
+
* set FONTCONFIG_FILE first; it is never overwritten.
|
|
30
|
+
*/
|
|
31
|
+
private fun configureFontconfig() {
|
|
32
|
+
runCatching {
|
|
33
|
+
if (!System.getenv("FONTCONFIG_FILE").isNullOrEmpty()) return
|
|
34
|
+
|
|
35
|
+
val root = File(System.getProperty("java.io.tmpdir"), "munim-ffmpeg-fontconfig")
|
|
36
|
+
val cache = File(root, "cache")
|
|
37
|
+
cache.mkdirs()
|
|
38
|
+
|
|
39
|
+
val configuration = File(root, "fonts.conf")
|
|
40
|
+
configuration.writeText(
|
|
41
|
+
"""
|
|
42
|
+
<?xml version="1.0"?>
|
|
43
|
+
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
|
44
|
+
<fontconfig>
|
|
45
|
+
<dir>/system/fonts</dir>
|
|
46
|
+
<dir>/system/font</dir>
|
|
47
|
+
<dir>/product/fonts</dir>
|
|
48
|
+
<cachedir>${cache.absolutePath}</cachedir>
|
|
49
|
+
</fontconfig>
|
|
50
|
+
""".trimIndent()
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
Os.setenv("FONTCONFIG_FILE", configuration.absolutePath, true)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
21
57
|
/** Return code the tools report when a run was cancelled. */
|
|
22
58
|
const val CANCELLED = 255
|
|
23
59
|
|
package/lib/index.d.ts
CHANGED
|
@@ -21,6 +21,14 @@ export declare function getFFmpegVersion(): string;
|
|
|
21
21
|
export declare function listEncoders(): Promise<string[]>;
|
|
22
22
|
/** Decoder names the bundled FFmpeg build can read, e.g. `h264`. */
|
|
23
23
|
export declare function listDecoders(): Promise<string[]>;
|
|
24
|
+
/** Container formats the bundled FFmpeg build can write, e.g. `matroska`. */
|
|
25
|
+
export declare function listMuxers(): Promise<string[]>;
|
|
26
|
+
/** Container formats the bundled FFmpeg build can read, e.g. `matroska`. */
|
|
27
|
+
export declare function listDemuxers(): Promise<string[]>;
|
|
28
|
+
/** Filter names the bundled FFmpeg build provides, e.g. `subtitles`. */
|
|
29
|
+
export declare function listFilters(): Promise<string[]>;
|
|
30
|
+
/** Protocol names the bundled FFmpeg build provides, e.g. `https`. */
|
|
31
|
+
export declare function listProtocols(): Promise<string[]>;
|
|
24
32
|
/**
|
|
25
33
|
* Returns the first available encoder from `candidates`, so one command can
|
|
26
34
|
* run on both platforms:
|
package/lib/index.js
CHANGED
|
@@ -76,6 +76,80 @@ export function listEncoders() {
|
|
|
76
76
|
export function listDecoders() {
|
|
77
77
|
return listCodecs('-decoders');
|
|
78
78
|
}
|
|
79
|
+
// Muxers, demuxers and filters print with different flag columns than codecs,
|
|
80
|
+
// and protocols use indented sections instead of a table, so each report gets
|
|
81
|
+
// its own parser. All of them are cached like the codec lists.
|
|
82
|
+
const reportCache = new Map();
|
|
83
|
+
function listReport(flag, parse) {
|
|
84
|
+
const cached = reportCache.get(flag);
|
|
85
|
+
if (cached)
|
|
86
|
+
return cached;
|
|
87
|
+
const request = execute(['-hide_banner', flag])
|
|
88
|
+
.then((result) => {
|
|
89
|
+
if (!result.success) {
|
|
90
|
+
throw new Error(result.failStackTrace ?? result.output);
|
|
91
|
+
}
|
|
92
|
+
return parse(result.output);
|
|
93
|
+
})
|
|
94
|
+
.catch((error) => {
|
|
95
|
+
reportCache.delete(flag);
|
|
96
|
+
throw error;
|
|
97
|
+
});
|
|
98
|
+
reportCache.set(flag, request);
|
|
99
|
+
return request;
|
|
100
|
+
}
|
|
101
|
+
// `-muxers`/`-demuxers` entries look like ` E mp4 MP4 (MPEG-4 Part 14)`
|
|
102
|
+
// below a `--` separator line.
|
|
103
|
+
function parseFormats(output) {
|
|
104
|
+
const body = output.split(/^\s*-+\s*$/m).pop() ?? '';
|
|
105
|
+
return body
|
|
106
|
+
.split('\n')
|
|
107
|
+
.map((line) => line.trim().split(/\s+/))
|
|
108
|
+
.filter((columns) => columns.length >= 2 && /^[DE.]{1,2}$/.test(columns[0]))
|
|
109
|
+
.flatMap((columns) => columns[1].split(','));
|
|
110
|
+
}
|
|
111
|
+
// `-filters` entries look like ` TS scale V->V Scale the input video.`
|
|
112
|
+
// The flag column has held two or three characters across FFmpeg releases.
|
|
113
|
+
function parseFilters(output) {
|
|
114
|
+
return output
|
|
115
|
+
.split('\n')
|
|
116
|
+
.map((line) => line.trim().split(/\s+/))
|
|
117
|
+
.filter((columns) => columns.length >= 3 &&
|
|
118
|
+
/^[TSC.]{2,3}$/.test(columns[0]) &&
|
|
119
|
+
/->/.test(columns[2]))
|
|
120
|
+
.map((columns) => columns[1]);
|
|
121
|
+
}
|
|
122
|
+
// `-protocols` prints `Input:` and `Output:` sections of indented names.
|
|
123
|
+
function parseProtocols(output) {
|
|
124
|
+
const names = new Set();
|
|
125
|
+
let inSection = false;
|
|
126
|
+
for (const line of output.split('\n')) {
|
|
127
|
+
if (/^(Input|Output):/.test(line.trim())) {
|
|
128
|
+
inSection = true;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
const name = line.trim();
|
|
132
|
+
if (inSection && /^[a-z0-9_]+$/.test(name))
|
|
133
|
+
names.add(name);
|
|
134
|
+
}
|
|
135
|
+
return [...names];
|
|
136
|
+
}
|
|
137
|
+
/** Container formats the bundled FFmpeg build can write, e.g. `matroska`. */
|
|
138
|
+
export function listMuxers() {
|
|
139
|
+
return listReport('-muxers', parseFormats);
|
|
140
|
+
}
|
|
141
|
+
/** Container formats the bundled FFmpeg build can read, e.g. `matroska`. */
|
|
142
|
+
export function listDemuxers() {
|
|
143
|
+
return listReport('-demuxers', parseFormats);
|
|
144
|
+
}
|
|
145
|
+
/** Filter names the bundled FFmpeg build provides, e.g. `subtitles`. */
|
|
146
|
+
export function listFilters() {
|
|
147
|
+
return listReport('-filters', parseFilters);
|
|
148
|
+
}
|
|
149
|
+
/** Protocol names the bundled FFmpeg build provides, e.g. `https`. */
|
|
150
|
+
export function listProtocols() {
|
|
151
|
+
return listReport('-protocols', parseProtocols);
|
|
152
|
+
}
|
|
79
153
|
/**
|
|
80
154
|
* Returns the first available encoder from `candidates`, so one command can
|
|
81
155
|
* run on both platforms:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "munim-ffmpeg",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Fast FFmpeg and FFprobe for Expo and React Native, powered by Nitro Modules",
|
|
5
5
|
"main": "lib/index",
|
|
6
6
|
"module": "lib/index",
|
|
@@ -78,6 +78,11 @@
|
|
|
78
78
|
"workspaces": [
|
|
79
79
|
"example"
|
|
80
80
|
],
|
|
81
|
+
"overrides": {
|
|
82
|
+
"xcode": {
|
|
83
|
+
"uuid": "^11.1.1"
|
|
84
|
+
}
|
|
85
|
+
},
|
|
81
86
|
"repository": {
|
|
82
87
|
"type": "git",
|
|
83
88
|
"url": "git+https://github.com/munimtechnologies/munim-ffmpeg.git"
|
package/scripts/binaries.json
CHANGED
|
File without changes
|
package/src/index.ts
CHANGED
|
@@ -129,6 +129,97 @@ export function listDecoders(): Promise<string[]> {
|
|
|
129
129
|
return listCodecs('-decoders')
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
// Muxers, demuxers and filters print with different flag columns than codecs,
|
|
133
|
+
// and protocols use indented sections instead of a table, so each report gets
|
|
134
|
+
// its own parser. All of them are cached like the codec lists.
|
|
135
|
+
const reportCache = new Map<string, Promise<string[]>>()
|
|
136
|
+
|
|
137
|
+
function listReport(
|
|
138
|
+
flag: string,
|
|
139
|
+
parse: (output: string) => string[]
|
|
140
|
+
): Promise<string[]> {
|
|
141
|
+
const cached = reportCache.get(flag)
|
|
142
|
+
if (cached) return cached
|
|
143
|
+
|
|
144
|
+
const request = execute(['-hide_banner', flag])
|
|
145
|
+
.then((result) => {
|
|
146
|
+
if (!result.success) {
|
|
147
|
+
throw new Error(result.failStackTrace ?? result.output)
|
|
148
|
+
}
|
|
149
|
+
return parse(result.output)
|
|
150
|
+
})
|
|
151
|
+
.catch((error) => {
|
|
152
|
+
reportCache.delete(flag)
|
|
153
|
+
throw error
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
reportCache.set(flag, request)
|
|
157
|
+
return request
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// `-muxers`/`-demuxers` entries look like ` E mp4 MP4 (MPEG-4 Part 14)`
|
|
161
|
+
// below a `--` separator line.
|
|
162
|
+
function parseFormats(output: string): string[] {
|
|
163
|
+
const body = output.split(/^\s*-+\s*$/m).pop() ?? ''
|
|
164
|
+
return body
|
|
165
|
+
.split('\n')
|
|
166
|
+
.map((line) => line.trim().split(/\s+/))
|
|
167
|
+
.filter(
|
|
168
|
+
(columns) => columns.length >= 2 && /^[DE.]{1,2}$/.test(columns[0]!)
|
|
169
|
+
)
|
|
170
|
+
.flatMap((columns) => columns[1]!.split(','))
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// `-filters` entries look like ` TS scale V->V Scale the input video.`
|
|
174
|
+
// The flag column has held two or three characters across FFmpeg releases.
|
|
175
|
+
function parseFilters(output: string): string[] {
|
|
176
|
+
return output
|
|
177
|
+
.split('\n')
|
|
178
|
+
.map((line) => line.trim().split(/\s+/))
|
|
179
|
+
.filter(
|
|
180
|
+
(columns) =>
|
|
181
|
+
columns.length >= 3 &&
|
|
182
|
+
/^[TSC.]{2,3}$/.test(columns[0]!) &&
|
|
183
|
+
/->/.test(columns[2]!)
|
|
184
|
+
)
|
|
185
|
+
.map((columns) => columns[1]!)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// `-protocols` prints `Input:` and `Output:` sections of indented names.
|
|
189
|
+
function parseProtocols(output: string): string[] {
|
|
190
|
+
const names = new Set<string>()
|
|
191
|
+
let inSection = false
|
|
192
|
+
for (const line of output.split('\n')) {
|
|
193
|
+
if (/^(Input|Output):/.test(line.trim())) {
|
|
194
|
+
inSection = true
|
|
195
|
+
continue
|
|
196
|
+
}
|
|
197
|
+
const name = line.trim()
|
|
198
|
+
if (inSection && /^[a-z0-9_]+$/.test(name)) names.add(name)
|
|
199
|
+
}
|
|
200
|
+
return [...names]
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Container formats the bundled FFmpeg build can write, e.g. `matroska`. */
|
|
204
|
+
export function listMuxers(): Promise<string[]> {
|
|
205
|
+
return listReport('-muxers', parseFormats)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Container formats the bundled FFmpeg build can read, e.g. `matroska`. */
|
|
209
|
+
export function listDemuxers(): Promise<string[]> {
|
|
210
|
+
return listReport('-demuxers', parseFormats)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Filter names the bundled FFmpeg build provides, e.g. `subtitles`. */
|
|
214
|
+
export function listFilters(): Promise<string[]> {
|
|
215
|
+
return listReport('-filters', parseFilters)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Protocol names the bundled FFmpeg build provides, e.g. `https`. */
|
|
219
|
+
export function listProtocols(): Promise<string[]> {
|
|
220
|
+
return listReport('-protocols', parseProtocols)
|
|
221
|
+
}
|
|
222
|
+
|
|
132
223
|
/**
|
|
133
224
|
* Returns the first available encoder from `candidates`, so one command can
|
|
134
225
|
* run on both platforms:
|