munim-ffmpeg 0.5.1 โ 0.6.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/README.md
CHANGED
|
@@ -114,26 +114,31 @@
|
|
|
114
114
|
- ๐ **Expo compatible:** Autolinking, config plugin, and an Expo development example
|
|
115
115
|
- ๐งช **Capability discovery:** Ask the bundled build which encoders, decoders, muxers, demuxers, filters, and protocols it actually has
|
|
116
116
|
- ๐ฌ **Subtitle burn-in:** libass renders ASS/SSA and SRT subtitles โ styling, positioning, outlines, shadows, and proper Arabic/Urdu shaping via HarfBuzz and FriBidi
|
|
117
|
+
- ๐ **Soft subtitle embedding:** Mux SRT/ASS tracks into MKV or MP4 so players can toggle them without re-encoding the video
|
|
118
|
+
- ๐ผ๏ธ **AVIF and AV1:** libaom encodes AV1 video and AVIF stills, dav1d decodes them
|
|
119
|
+
- ๐ฆ **One native library per platform:** a single `libmunimffmpeg.so` per Android ABI and one static library in the iOS xcframework, so nothing else has to be linked, loaded, or packaged
|
|
117
120
|
- ๐ฏ **TypeScript:** Complete public callback and result types
|
|
118
121
|
- ๐๏ธ **16 KB Android pages:** Built with the alignment Google Play requires
|
|
119
122
|
|
|
120
123
|
## Platform support matrix
|
|
121
124
|
|
|
122
|
-
| Capability | iOS
|
|
123
|
-
| ---------------------------- |
|
|
124
|
-
| FFmpeg argument execution | โ
|
|
125
|
-
| FFprobe argument execution | โ
|
|
126
|
-
| Parsed media information | โ
|
|
127
|
-
| Log callback | โ
|
|
128
|
-
| Encoding-statistics callback | โ
|
|
129
|
-
| Immediate session ID | โ
|
|
130
|
-
| Cancel one FFmpeg session | โ
|
|
131
|
-
| Cancel all FFmpeg sessions | โ
|
|
132
|
-
| Expo Go | โ
|
|
133
|
-
| Capability discovery | โ
|
|
134
|
-
| Subtitle burn-in | โ
|
|
135
|
-
| H.264 encoding | VideoToolbox
|
|
136
|
-
| Remote HTTP(S) inputs | โ
|
|
125
|
+
| Capability | iOS | Android | Notes |
|
|
126
|
+
| ---------------------------- | ------------ | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
127
|
+
| FFmpeg argument execution | โ
| โ
| Commands run asynchronously through the native compatibility library. |
|
|
128
|
+
| FFprobe argument execution | โ
| โ
| Custom FFprobe arguments return `FFmpegSessionResult`. |
|
|
129
|
+
| Parsed media information | โ
| โ
| `getMediaInformation()` returns parsed FFprobe JSON. |
|
|
130
|
+
| Log callback | โ
| โ
| Logs are delivered while a session is active. |
|
|
131
|
+
| Encoding-statistics callback | โ
| โ
| Available for FFmpeg execution. |
|
|
132
|
+
| Immediate session ID | โ
| โ
| `onSessionCreated` fires after the native session is created. |
|
|
133
|
+
| Cancel one FFmpeg session | โ
| โ
| Pass the positive safe-integer ID received by `execute`'s `onSessionCreated`. The native dependency does not expose FFprobe cancellation. |
|
|
134
|
+
| Cancel all FFmpeg sessions | โ
| โ
| Use `cancelAll()` or call `cancel()` without an ID. |
|
|
135
|
+
| Expo Go | โ | โ | A native development build is required. |
|
|
136
|
+
| Capability discovery | โ
| โ
| `listEncoders()`, `listDecoders()`, `listMuxers()`, `listDemuxers()`, `listFilters()`, `listProtocols()`, and `pickEncoder()` report what the bundled build supports. |
|
|
137
|
+
| Subtitle burn-in | โ
| โ
| libass with system fonts: Core Text on iOS, fontconfig over `/system/fonts` on Android. |
|
|
138
|
+
| H.264 encoding | VideoToolbox | MediaCodec | Hardware on both, `libopenh264` as the software fallback; use `pickEncoder(['h264_videotoolbox', 'h264_mediacodec', 'libopenh264'])` instead of hard-coding an encoder. |
|
|
139
|
+
| Remote HTTP(S) inputs | โ
| โ
| iOS links SecureTransport, Android links mbedTLS. Remote server behaviour still varies; prefer local files for predictable app workflows. |
|
|
140
|
+
| Soft subtitle embedding | โ
| โ
| Mux SRT/ASS as toggleable tracks (MKV: `srt`/`ass`, MP4: `mov_text`). |
|
|
141
|
+
| AVIF / AV1 encoding | โ
| โ
| `libaom-av1` encodes (add `-still-picture 1 -f avif` for images); `libdav1d` decodes. |
|
|
137
142
|
|
|
138
143
|
Codec availability is determined by the native FFmpeg builds described in [Bundled FFmpeg builds](#bundled-ffmpeg-builds). Do not assume every FFmpeg codec or external library is present.
|
|
139
144
|
|
|
@@ -141,14 +146,14 @@ Codec availability is determined by the native FFmpeg builds described in [Bundl
|
|
|
141
146
|
|
|
142
147
|
Every release runs the example's 25-check device suite. For 0.4.x:
|
|
143
148
|
|
|
144
|
-
| Target
|
|
145
|
-
|
|
|
146
|
-
| iPad Air (M3), iOS 26
|
|
147
|
-
| iOS Simulator, arm64
|
|
148
|
-
| Galaxy A14 5G, arm64-v8a
|
|
149
|
-
| Android emulator, arm64
|
|
150
|
-
| Android emulator, `x86_64` | Same: `libopenh264` passes, MediaCodec does not
|
|
151
|
-
| Android `armeabi-v7a`
|
|
149
|
+
| Target | Result |
|
|
150
|
+
| -------------------------- | ---------------------------------------------------------------- |
|
|
151
|
+
| iPad Air (M3), iOS 26 | 25/25 |
|
|
152
|
+
| iOS Simulator, arm64 | 25/25 |
|
|
153
|
+
| Galaxy A14 5G, arm64-v8a | 25/25 |
|
|
154
|
+
| Android emulator, arm64 | Software encoding passes; hardware encoding does not โ see below |
|
|
155
|
+
| Android emulator, `x86_64` | Same: `libopenh264` passes, MediaCodec does not |
|
|
156
|
+
| Android `armeabi-v7a` | Built and statically checked, not executed |
|
|
152
157
|
|
|
153
158
|
`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.
|
|
154
159
|
|
|
@@ -158,11 +163,20 @@ An Android emulator has no working MediaCodec **encoder**. `h264_mediacodec` and
|
|
|
158
163
|
|
|
159
164
|
This is an emulator limitation, not a package one, but it is worth knowing before debugging: **test video encoding on a physical device**.
|
|
160
165
|
|
|
161
|
-
`pickEncoder` cannot detect it, because MediaCodec
|
|
166
|
+
`pickEncoder` cannot detect it, because MediaCodec _is_ present in an emulator โ it just does not work. When you need encoding to succeed regardless of environment, ask for the software encoder by name:
|
|
162
167
|
|
|
163
168
|
```typescript
|
|
164
169
|
// Deterministic anywhere: emulators, CI, older devices.
|
|
165
|
-
await execute([
|
|
170
|
+
await execute([
|
|
171
|
+
'-y',
|
|
172
|
+
'-i',
|
|
173
|
+
input,
|
|
174
|
+
'-c:v',
|
|
175
|
+
'libopenh264',
|
|
176
|
+
'-pix_fmt',
|
|
177
|
+
'yuv420p',
|
|
178
|
+
output,
|
|
179
|
+
])
|
|
166
180
|
```
|
|
167
181
|
|
|
168
182
|
`libopenh264`, `mpeg4` and `libvpx-vp9` are all software encoders and work everywhere.
|
|
@@ -171,30 +185,34 @@ await execute(['-y', '-i', input, '-c:v', 'libopenh264', '-pix_fmt', 'yuv420p',
|
|
|
171
185
|
|
|
172
186
|
Both platforms run **FFmpeg 9.0.1**, built from [ffmpeg.org](https://www.ffmpeg.org/) by the scripts in [`scripts/ffmpeg/`](./scripts/ffmpeg). There is no FFmpegKit here: that project was retired in 2025 and pinned to FFmpeg 6.0.
|
|
173
187
|
|
|
174
|
-
|
|
|
175
|
-
|
|
|
176
|
-
| FFmpeg
|
|
177
|
-
| Architectures
|
|
178
|
-
| Hardware codecs | VideoToolbox, AudioToolbox
|
|
179
|
-
| TLS
|
|
180
|
-
| Minimum
|
|
188
|
+
| | iOS | Android |
|
|
189
|
+
| --------------- | ------------------------------------------------------- | ------------------------------- |
|
|
190
|
+
| FFmpeg | 9.0.1 | 9.0.1 |
|
|
191
|
+
| Architectures | arm64 device, arm64 + x86_64 simulator | arm64-v8a, armeabi-v7a, x86_64 |
|
|
192
|
+
| Hardware codecs | VideoToolbox, AudioToolbox | MediaCodec |
|
|
193
|
+
| TLS | SecureTransport | mbedTLS |
|
|
194
|
+
| Minimum | iOS 15.1 | API 24, 16 KB pages |
|
|
195
|
+
| Ships as | `MunimFFmpeg.xcframework`, one static library per slice | one `libmunimffmpeg.so` per ABI |
|
|
181
196
|
|
|
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.
|
|
197
|
+
Linked libraries, identical on both: **LAME** (MP3), **Opus**, **libvpx** (VP8/VP9), **dav1d** (AV1 decoding), **libaom** (AV1 and AVIF encoding), **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.
|
|
183
198
|
|
|
184
199
|
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.
|
|
185
200
|
|
|
201
|
+
Everything above is linked statically into a single library per platform: FFmpeg, both tools, the core, and every external library. On Android that is one `libmunimffmpeg.so` per ABI whose only dependencies are the system libraries and the `libc++_shared.so` React Native already bundles; on iOS it is one static library per slice inside `MunimFFmpeg.xcframework`. Only the JNI entry points are exported on Android, so the bundled FFmpeg cannot collide with another copy an app might carry.
|
|
202
|
+
|
|
186
203
|
### Encoders
|
|
187
204
|
|
|
188
205
|
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`.
|
|
189
206
|
|
|
190
207
|
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:
|
|
191
208
|
|
|
192
|
-
| Encoder
|
|
193
|
-
|
|
|
194
|
-
| `h264_videotoolbox`, `hevc_videotoolbox`, `prores_videotoolbox`
|
|
195
|
-
| `h264_mediacodec`, `hevc_mediacodec`, `vp8_mediacodec`, `vp9_mediacodec` | โ
|
|
196
|
-
| `aac_at`, `alac_at` (AudioToolbox)
|
|
197
|
-
| `libopenh264` (H.264, software)
|
|
209
|
+
| Encoder | iOS | Android |
|
|
210
|
+
| ------------------------------------------------------------------------ | --- | ------- |
|
|
211
|
+
| `h264_videotoolbox`, `hevc_videotoolbox`, `prores_videotoolbox` | โ
| โ |
|
|
212
|
+
| `h264_mediacodec`, `hevc_mediacodec`, `vp8_mediacodec`, `vp9_mediacodec` | โ | โ
|
|
|
213
|
+
| `aac_at`, `alac_at` (AudioToolbox) | โ
| โ |
|
|
214
|
+
| `libopenh264` (H.264, software) | โ
| โ
|
|
|
215
|
+
| `libaom-av1` (AV1, software; AVIF stills) | โ
| โ
|
|
|
198
216
|
|
|
199
217
|
Resolve the name at runtime instead of branching on `Platform.OS`:
|
|
200
218
|
|
|
@@ -214,7 +232,7 @@ await execute(['-y', '-i', inputPath, '-c:v', h264, outputPath])
|
|
|
214
232
|
|
|
215
233
|
Two things to know about hardware encoders: they want NV12 input on Android (`-pix_fmt nv12`) and planar YUV on iOS, and they reject very small frames โ 176ร144 is the smallest size that works everywhere.
|
|
216
234
|
|
|
217
|
-
Decoding is uniform: H.264, HEVC, VP8/VP9, AV1, MPEG-4, MP3, AAC, Vorbis, Opus, FLAC and the usual containers, on both platforms. Both link TLS, so `https://` inputs work.
|
|
235
|
+
Decoding is uniform: H.264, HEVC, VP8/VP9, AV1 (via dav1d, including AVIF images), MPEG-4, MP3, AAC, Vorbis, Opus, FLAC and the usual containers, on both platforms. Both link TLS, so `https://` inputs work.
|
|
218
236
|
|
|
219
237
|
## ๐ฆ Installation
|
|
220
238
|
|
|
@@ -279,7 +297,7 @@ You can also create an [EAS development build](https://docs.expo.dev/develop/dev
|
|
|
279
297
|
|
|
280
298
|
### Native binaries
|
|
281
299
|
|
|
282
|
-
The FFmpeg libraries are
|
|
300
|
+
The FFmpeg libraries are well over 100 MB across all six architectures, which does not belong in an npm tarball, so they are downloaded from the matching GitHub release when the package installs and verified against the checksum in `scripts/binaries.json`.
|
|
283
301
|
|
|
284
302
|
If your environment blocks install scripts (`npm install --ignore-scripts`), fetch them explicitly:
|
|
285
303
|
|
|
@@ -567,7 +585,11 @@ if (!result.success) {
|
|
|
567
585
|
```typescript
|
|
568
586
|
import { execute, pickEncoder } from 'munim-ffmpeg'
|
|
569
587
|
|
|
570
|
-
const encoder = await pickEncoder([
|
|
588
|
+
const encoder = await pickEncoder([
|
|
589
|
+
'h264_videotoolbox',
|
|
590
|
+
'h264_mediacodec',
|
|
591
|
+
'libopenh264',
|
|
592
|
+
])
|
|
571
593
|
if (!encoder) throw new Error('No H.264 encoder available in this build')
|
|
572
594
|
|
|
573
595
|
// MediaCodec wants NV12 input; the others take planar YUV.
|
|
@@ -608,6 +630,39 @@ const result = await execute([
|
|
|
608
630
|
])
|
|
609
631
|
```
|
|
610
632
|
|
|
633
|
+
### Write an AVIF still
|
|
634
|
+
|
|
635
|
+
AVIF is AV1 in an image container. `libaom-av1` encodes it; `-still-picture 1` switches the encoder into single-image mode and `-f avif` picks the container. Decoding an AVIF back โ or any AV1 video โ goes through dav1d automatically.
|
|
636
|
+
|
|
637
|
+
```typescript
|
|
638
|
+
await execute([
|
|
639
|
+
'-y',
|
|
640
|
+
'-ss',
|
|
641
|
+
'1.5',
|
|
642
|
+
'-i',
|
|
643
|
+
inputPath,
|
|
644
|
+
'-frames:v',
|
|
645
|
+
'1',
|
|
646
|
+
'-vf',
|
|
647
|
+
'scale=-2:720',
|
|
648
|
+
'-c:v',
|
|
649
|
+
'libaom-av1',
|
|
650
|
+
'-still-picture',
|
|
651
|
+
'1',
|
|
652
|
+
'-cpu-used',
|
|
653
|
+
'6', // 0 (slowest, best) โฆ 8 (fastest)
|
|
654
|
+
'-crf',
|
|
655
|
+
'28', // quality; lower is larger
|
|
656
|
+
'-pix_fmt',
|
|
657
|
+
'yuv420p',
|
|
658
|
+
'-f',
|
|
659
|
+
'avif',
|
|
660
|
+
outputPath, // ends in .avif
|
|
661
|
+
])
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
React Native's `<Image>` displays AVIF natively on iOS 16+ and Android 12+.
|
|
665
|
+
|
|
611
666
|
### Burn subtitles into a video
|
|
612
667
|
|
|
613
668
|
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.
|
|
@@ -619,18 +674,24 @@ import { execute, normalizePath } from 'munim-ffmpeg'
|
|
|
619
674
|
// shadows, positioning, karaoke โ everything the format supports.
|
|
620
675
|
await execute([
|
|
621
676
|
'-y',
|
|
622
|
-
'-i',
|
|
623
|
-
|
|
624
|
-
'-
|
|
677
|
+
'-i',
|
|
678
|
+
inputPath,
|
|
679
|
+
'-vf',
|
|
680
|
+
`ass=filename=${normalizePath(subtitlePath)}`,
|
|
681
|
+
'-c:a',
|
|
682
|
+
'copy',
|
|
625
683
|
outputPath,
|
|
626
684
|
])
|
|
627
685
|
|
|
628
686
|
// SRT can be styled at burn time with force_style.
|
|
629
687
|
await execute([
|
|
630
688
|
'-y',
|
|
631
|
-
'-i',
|
|
632
|
-
|
|
633
|
-
'-
|
|
689
|
+
'-i',
|
|
690
|
+
inputPath,
|
|
691
|
+
'-vf',
|
|
692
|
+
`subtitles=filename=${normalizePath(srtPath)}:force_style='Fontsize=28,PrimaryColour=&H00FFFF00,Outline=2'`,
|
|
693
|
+
'-c:a',
|
|
694
|
+
'copy',
|
|
634
695
|
outputPath,
|
|
635
696
|
])
|
|
636
697
|
```
|
|
@@ -647,19 +708,114 @@ import { execute } from 'munim-ffmpeg'
|
|
|
647
708
|
// Bundle one video, two audio languages, and a subtitle track into MKV.
|
|
648
709
|
await execute([
|
|
649
710
|
'-y',
|
|
650
|
-
'-i',
|
|
651
|
-
|
|
652
|
-
'-
|
|
653
|
-
|
|
711
|
+
'-i',
|
|
712
|
+
videoPath,
|
|
713
|
+
'-i',
|
|
714
|
+
urduAudioPath,
|
|
715
|
+
'-i',
|
|
716
|
+
subtitlePath,
|
|
717
|
+
'-map',
|
|
718
|
+
'0:v:0',
|
|
719
|
+
'-map',
|
|
720
|
+
'0:a:0',
|
|
721
|
+
'-map',
|
|
722
|
+
'1:a:0',
|
|
723
|
+
'-map',
|
|
724
|
+
'2:s:0',
|
|
725
|
+
'-c:v',
|
|
726
|
+
'copy',
|
|
727
|
+
'-c:a',
|
|
728
|
+
'aac',
|
|
729
|
+
'-c:s',
|
|
730
|
+
'srt',
|
|
731
|
+
'-metadata:s:a:1',
|
|
732
|
+
'language=urd',
|
|
654
733
|
outputMkvPath,
|
|
655
734
|
])
|
|
656
735
|
|
|
657
736
|
// Extract the second audio track without re-encoding.
|
|
658
737
|
await execute([
|
|
659
|
-
'-y',
|
|
738
|
+
'-y',
|
|
739
|
+
'-i',
|
|
740
|
+
outputMkvPath,
|
|
741
|
+
'-map',
|
|
742
|
+
'0:a:1',
|
|
743
|
+
'-c',
|
|
744
|
+
'copy',
|
|
745
|
+
trackPath,
|
|
746
|
+
])
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
### Embed soft subtitles
|
|
750
|
+
|
|
751
|
+
The other subtitle workflow: instead of burning text into the frames, mux the subtitle file in as its own stream, so players can toggle it and the video is never re-encoded. Each container wants its own subtitle codec โ MKV takes `srt` and `ass` (ASS keeps its styling), MP4 takes `mov_text`, WebM takes `webvtt`.
|
|
752
|
+
|
|
753
|
+
```typescript
|
|
754
|
+
import { execute } from 'munim-ffmpeg'
|
|
755
|
+
|
|
756
|
+
// MKV with English SRT and styled Urdu ASS tracks, video and audio untouched.
|
|
757
|
+
await execute([
|
|
758
|
+
'-y',
|
|
759
|
+
'-i',
|
|
760
|
+
videoPath,
|
|
761
|
+
'-i',
|
|
762
|
+
englishSrtPath,
|
|
763
|
+
'-i',
|
|
764
|
+
urduAssPath,
|
|
765
|
+
'-map',
|
|
766
|
+
'0:v:0',
|
|
767
|
+
'-map',
|
|
768
|
+
'0:a:0',
|
|
769
|
+
'-map',
|
|
770
|
+
'1:0',
|
|
771
|
+
'-map',
|
|
772
|
+
'2:0',
|
|
773
|
+
'-c:v',
|
|
774
|
+
'copy',
|
|
775
|
+
'-c:a',
|
|
776
|
+
'copy',
|
|
777
|
+
'-c:s:0',
|
|
778
|
+
'srt',
|
|
779
|
+
'-c:s:1',
|
|
780
|
+
'ass',
|
|
781
|
+
'-metadata:s:s:0',
|
|
782
|
+
'language=eng',
|
|
783
|
+
'-metadata:s:s:0',
|
|
784
|
+
'title=English',
|
|
785
|
+
'-metadata:s:s:1',
|
|
786
|
+
'language=urd',
|
|
787
|
+
'-metadata:s:s:1',
|
|
788
|
+
'title=Urdu',
|
|
789
|
+
outputMkvPath,
|
|
790
|
+
])
|
|
791
|
+
|
|
792
|
+
// MP4 needs mov_text instead.
|
|
793
|
+
await execute([
|
|
794
|
+
'-y',
|
|
795
|
+
'-i',
|
|
796
|
+
videoPath,
|
|
797
|
+
'-i',
|
|
798
|
+
subtitlePath,
|
|
799
|
+
'-map',
|
|
800
|
+
'0:v:0',
|
|
801
|
+
'-map',
|
|
802
|
+
'0:a:0',
|
|
803
|
+
'-map',
|
|
804
|
+
'1:0',
|
|
805
|
+
'-c:v',
|
|
806
|
+
'copy',
|
|
807
|
+
'-c:a',
|
|
808
|
+
'copy',
|
|
809
|
+
'-c:s',
|
|
810
|
+
'mov_text',
|
|
811
|
+
'-metadata:s:s:0',
|
|
812
|
+
'language=eng',
|
|
813
|
+
outputMp4Path,
|
|
660
814
|
])
|
|
661
815
|
```
|
|
662
816
|
|
|
817
|
+
Existing subtitle streams survive remuxing with `-map 0 -c copy`, and a soft track can later be burned in with `-vf subtitles=filename=input.mkv:si=0` (the `si` option picks the subtitle stream index).
|
|
818
|
+
|
|
663
819
|
### Cancel a long-running command
|
|
664
820
|
|
|
665
821
|
```typescript
|
|
@@ -712,9 +868,9 @@ if (result.success) {
|
|
|
712
868
|
|
|
713
869
|
The JavaScript, TypeScript, Swift, Kotlin, C core, and generated Nitro bridge in this repository are Apache-2.0.
|
|
714
870
|
|
|
715
|
-
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.
|
|
871
|
+
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), libaom (BSD 2-clause with the Alliance for Open Media patent licence), 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.
|
|
716
872
|
|
|
717
|
-
> **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
|
|
873
|
+
> **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.
|
|
718
874
|
|
|
719
875
|
In practice that means your application does **not** inherit GPL obligations. LGPL still applies: the FFmpeg libraries are linked and their license and notices must be conveyed with your app, and users must be able to relink against a modified FFmpeg. The exact configuration used is recorded in [`scripts/ffmpeg/build-ios.sh`](./scripts/ffmpeg/build-ios.sh) and [`build-android.sh`](./scripts/ffmpeg/build-android.sh), and the binaries can be reproduced from them.
|
|
720
876
|
|
|
@@ -773,7 +929,10 @@ Nitrogen output under `nitrogen/generated` is committed. Change the `.nitro.ts`
|
|
|
773
929
|
|
|
774
930
|
### Example app
|
|
775
931
|
|
|
776
|
-
`example/` is an Expo app
|
|
932
|
+
`example/` is an Expo SDK 57 app โ a development build, since Expo Go cannot load native modules โ with two screens:
|
|
933
|
+
|
|
934
|
+
- **Playground** picks a video with `expo-document-picker` (or generates one from JavaScript fixtures), inspects it, transcodes it through the device's hardware H.264 encoder via `pickEncoder`, writes an AVIF still, embeds soft subtitles into an MKV, burns them in with libass, and shows progress from the statistics callback with a cancel button wired to `onSessionCreated`. Each action is a plain argument array, so [`example/Playground.tsx`](./example/Playground.tsx) doubles as a recipe book.
|
|
935
|
+
- **Device suite** runs the 30+ checks used to verify every release: H.264 and HEVC encoding, VP9/Opus in WebM, MP3, AAC, AVIF, scaling and multi-step filter graphs, software H.264 via openh264, subtitle burn-in and embedding, muxing, demuxing, trimming, concatenation, thumbnails, audio resampling, awkward file paths, concurrent sessions, single and global cancellation, protocol support, and both failure paths. It runs on launch, renders each result, writes `munim-ffmpeg-suite.json` to the app's document directory, and logs it as `MUNIM_FFMPEG_SUITE_RESULT`.
|
|
777
936
|
|
|
778
937
|
```bash
|
|
779
938
|
npm run example:ios
|
|
@@ -781,7 +940,7 @@ npm run example:ios
|
|
|
781
940
|
npm run example:android
|
|
782
941
|
```
|
|
783
942
|
|
|
784
|
-
FFmpeg encoding is slow in a simulator or emulator; run
|
|
943
|
+
FFmpeg encoding is slow in a simulator or emulator, and emulators have no working hardware encoder; run it on a physical device. [`example/README.md`](./example/README.md) has the details.
|
|
785
944
|
|
|
786
945
|
### Rebuilding FFmpeg
|
|
787
946
|
|
|
@@ -794,14 +953,16 @@ See [`scripts/ffmpeg/README.md`](./scripts/ffmpeg/README.md) for what the build
|
|
|
794
953
|
|
|
795
954
|
### Releasing
|
|
796
955
|
|
|
797
|
-
Releases run locally from a clean `main
|
|
956
|
+
Releases run locally from a clean `main`:
|
|
798
957
|
|
|
799
958
|
```bash
|
|
800
959
|
npm run check
|
|
801
960
|
npm run release:local
|
|
802
961
|
```
|
|
803
962
|
|
|
804
|
-
`release:local` runs semantic-release with the npm token from the macOS Keychain and the GitHub CLI token, so commit messages must follow Conventional Commits. It also uploads `dist-binaries/munim-ffmpeg-binaries.tar.gz` to the GitHub release, which is where `postinstall` fetches
|
|
963
|
+
`release:local` runs semantic-release with the npm token from the macOS Keychain and the GitHub CLI token, so commit messages must follow Conventional Commits. It also uploads `dist-binaries/munim-ffmpeg-binaries.tar.gz` and `build-info.txt` to the GitHub release, which is where `postinstall` fetches the binaries from โ so run `npm run binaries:package` first.
|
|
964
|
+
|
|
965
|
+
Two GitHub Actions workflows back this up: `CI` checks the JavaScript surface on every push and pull request, and `Build binaries` compiles every iOS and Android slice in parallel, on demand or when a pull request touches `scripts/ffmpeg/`, and can attach the result to a release. See [`scripts/ffmpeg/README.md`](./scripts/ffmpeg/README.md#building-in-github-actions).
|
|
805
966
|
|
|
806
967
|
## ๐ Contributing
|
|
807
968
|
|
package/package.json
CHANGED
package/scripts/binaries.json
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
/*
|
|
3
3
|
* Downloads the prebuilt FFmpeg binaries for this version of munim-ffmpeg.
|
|
4
4
|
*
|
|
5
|
-
* They are not published to npm: the bundle is
|
|
6
|
-
* shared objects, which does not belong in a registry tarball. It ships as a
|
|
5
|
+
* They are not published to npm: the bundle is well over 100 MB of static
|
|
6
|
+
* libraries and shared objects, which does not belong in a registry tarball. It ships as a
|
|
7
7
|
* GitHub release asset instead, pinned by the checksum in scripts/binaries.json
|
|
8
8
|
* so a build cannot silently pick up different bytes.
|
|
9
9
|
*
|
|
@@ -27,7 +27,7 @@ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
|
27
27
|
const REPOSITORY = 'munimtechnologies/munim-ffmpeg'
|
|
28
28
|
const MARKERS = [
|
|
29
29
|
'ios/MunimFFmpeg.xcframework',
|
|
30
|
-
'android/src/main/jniLibs/arm64-v8a/
|
|
30
|
+
'android/src/main/jniLibs/arm64-v8a/libmunimffmpeg.so',
|
|
31
31
|
]
|
|
32
32
|
|
|
33
33
|
async function exists(target) {
|
|
@@ -90,6 +90,14 @@ async function main() {
|
|
|
90
90
|
)
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
// A previous version may have left different files behind (older releases
|
|
94
|
+
// shipped several .so files per ABI), so the targets start empty.
|
|
95
|
+
for (const target of [
|
|
96
|
+
'ios/MunimFFmpeg.xcframework',
|
|
97
|
+
'android/src/main/jniLibs',
|
|
98
|
+
]) {
|
|
99
|
+
await rm(path.join(root, target), { force: true, recursive: true })
|
|
100
|
+
}
|
|
93
101
|
await run('tar', ['xzf', archive, '-C', root])
|
|
94
102
|
await rm(cache, { force: true, recursive: true })
|
|
95
103
|
|