Skip to content

feat: add convertAudio API with iOS m4a support#10

Merged
riderx merged 7 commits intomainfrom
codex/add-convert-audio-api
Apr 13, 2026
Merged

feat: add convertAudio API with iOS m4a support#10
riderx merged 7 commits intomainfrom
codex/add-convert-audio-api

Conversation

@riderx
Copy link
Copy Markdown
Member

@riderx riderx commented Apr 13, 2026

What

  • add a new convertAudio plugin API
  • implement native iOS audio conversion to m4a
  • expose convertAudio as unsupported on Android and web for now
  • add API, native, and platform-contract tests
  • update docs and feature matrix to match the shipped scope

Why

  • issue feat: Audio Format Conversion #3 asked for audio conversion support in the plugin
  • the repo did not expose an audio conversion API yet, so users had no supported path for this use case

How

  • extend src/definitions.ts with convertAudio, ConvertAudioOptions, and ConvertAudioResult
  • wire the new method through the Capacitor bridge
  • implement iOS conversion with AVAssetExportSession using the Apple M4A preset
  • add an injectable export-session seam so the iOS success and failure paths can be tested deterministically
  • keep Android and web behavior explicit by returning UNIMPLEMENTED with capability metadata
  • update README and support docs to reflect the current platform matrix
  • this PR was prepared with AI assistance using Codex

Testing

  • bun run build
  • bun run verify
  • bun run lint
  • bun run test:web
  • bun run test:android
  • bun run test:ios

Not Tested

  • a real end-to-end media export assertion against AVAssetExportSession output in CI; the iOS success path is covered with a deterministic mocked export session after validating the input asset contains audio

Summary by CodeRabbit

  • New Features

    • Added convertAudio() API: iOS supports m4a output; Android and web return UNIMPLEMENTED with iOS-only messaging.
  • Documentation

    • Updated feature matrix, API reference, roadmap, and test inventory to document convertAudio, platform notes, types, and updated last-updated dates.
  • Tests

    • Added unit and integration tests for capability reporting, input/format validation, successful m4a export, and cleanup on failed conversions.
  • Example app

    • UI labels and test helpers updated to surface the new convertAudio capability.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 13, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Added a new public API convertAudio(options); iOS implements audio conversion to m4a via AVAssetExportSession (injectable factory, validation, temp-file atomic replace, and new transcodeFailed error); Android and web return UNIMPLEMENTED. Types, docs, example app, and tests updated.

Changes

Cohort / File(s) Summary
Docs & Matrices
FEATURE_MATRIX.md, README.md, ROADMAP.md, test/TEST_INVENTORY.md
Added convertAudio to matrices/docs/roadmap; documented iOS m4a limitation and bumped "Last updated" dates.
Type Definitions
src/definitions.ts
Added AudioOutputFormat = 'm4a', ConvertAudioOptions, ConvertAudioResult, optional convertAudio on CapacitorFFmpegPlugin, and extended FFmpegCapabilitiesFeatures.
Web
src/web.ts, test/web.test.ts
Added web stub convertAudio(options) that throws UNIMPLEMENTED; added capability report and tests asserting unimplemented behavior.
Android
android/src/.../CapacitorFFmpegPlugin.java, android/src/test/.../CapacitorFFmpegPluginTest.java
Added convertAudio(PluginCall) returning unimplemented(...); updated capabilities payload and Android tests for status/reason.
iOS Implementation
ios/Sources/CapacitorFFmpegPlugin/CapacitorFFmpeg.swift
Implemented audio conversion with AudioExportSessioning/factory, FFmpegConvertedAudio, validation (no in-place, lowercase format, .m4a only), audio-track check, temp export then atomic move, and added FFmpegError.transcodeFailed.
iOS Plugin Binding
ios/Sources/CapacitorFFmpegPlugin/CapacitorFFmpegPlugin.swift
Registered and implemented convertAudio(_:) Capacitor method with param validation, try/resolve handling, and error routing.
iOS Tests
ios/Tests/.../CapacitorFFmpegPluginTests.swift
Added MockAudioExportSession, WAV writer, helper to wait for conversion, tests for unsupported formats/extension mismatch, successful m4a export, and cleanup on failure.
Example App & Tests
example-app/src/js/app.js, example-app/src/js/app.vitest.js
Added convertAudio to FEATURE_ORDER/FEATURE_LABELS and test capability generator.
README API Index
README.md
Inserted convertAudio(...) API docs and updated FFmpegCapabilitiesFeatures documentation.
Misc
src/pluginVersion.ts
Changed quote style for PLUGIN_VERSION (single → double quotes), value unchanged.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Plugin as CapacitorFFmpegPlugin
    participant Impl as CapacitorFFmpeg
    participant Export as AVAssetExportSession
    participant FS as FileSystem

    Client->>Plugin: convertAudio({inputPath, outputPath, format})
    Plugin->>Plugin: validate parameters
    Plugin->>Impl: convertAudio(inputPath, outputPath, format)
    Impl->>Impl: validate format == 'm4a' and input has audio track
    Impl->>FS: ensure output directory exists
    Impl->>FS: create temporary output file
    Impl->>Export: create/configure export session (factory)
    Export->>Export: export asynchronously
    alt Export succeeds
        Export-->>Impl: .completed
        Impl->>FS: move temp -> destination (atomic)
        Impl-->>Plugin: return { outputPath, format }
        Plugin-->>Client: resolve promise with result
    else Export fails
        Export-->>Impl: error
        Impl->>FS: remove partial temp (and don't overwrite existing destination)
        Impl-->>Plugin: throw FFmpegError.transcodeFailed(...)
        Plugin-->>Client: reject promise with error
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰✨ I hopped through docs and native threads,
Brought m4a dreams to iOS beds,
Android and web politely say "not yet",
AVAssetExportSession hums the fret—
Tests hum tones, partials swept away.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and concisely describes the main feature addition: a new convertAudio API with iOS m4a support, which aligns with the primary objective of this changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/add-convert-audio-api

Comment @coderabbitai help to get the list of available commands and usage tips.

@riderx riderx marked this pull request as ready for review April 13, 2026 15:46
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 66069bbbd9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ios/Sources/CapacitorFFmpegPlugin/CapacitorFFmpeg.swift Outdated
@riderx
Copy link
Copy Markdown
Member Author

riderx commented Apr 13, 2026

@coderabbitai review

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/definitions.ts (1)

21-27: ⚠️ Potential issue | 🟠 Major

Avoid making the new v8 surface required.

convertAudio is being added as a required member on two exported interfaces. That is a source-level breaking change for consumers that construct typed capability fixtures or implement plugin test doubles against the current v8 API. Please keep these additions optional in this major, or defer the stricter contract to the next Capacitor-major release.

♻️ Backward-compatible shape for this major
 export interface FFmpegCapabilitiesFeatures {
   getPluginVersion: FFmpegCapability;
   getCapabilities: FFmpegCapability;
   reencodeVideo: FFmpegCapability;
   convertImage: FFmpegCapability;
-  convertAudio: FFmpegCapability;
+  convertAudio?: FFmpegCapability;
   progressEvents: FFmpegCapability;
   probeMedia: FFmpegCapability;
   generateThumbnail: FFmpegCapability;
   extractAudio: FFmpegCapability;
   remux: FFmpegCapability;
   trim: FFmpegCapability;
 }
@@
-  convertAudio(options: ConvertAudioOptions): Promise<ConvertAudioResult>;
+  convertAudio?(options: ConvertAudioOptions): Promise<ConvertAudioResult>;

As per coding guidelines, "Do not introduce breaking changes in src/definitions.ts unless explicitly asked or the current definition is broken or unusable. Breaking changes belong to the matching Capacitor major migration, and all other changes must stay backward compatible".

Also applies to: 125-131

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/definitions.ts` around lines 21 - 27, The review points out that adding
convertAudio as a required member on exported interface
FFmpegCapabilitiesFeatures (and the other exported interface at lines ~125-131)
is a breaking change; make convertAudio optional (e.g., convertAudio?:
FFmpegCapability) on both exported interfaces (including
FFmpegCapabilitiesFeatures and the other exported interface that currently adds
convertAudio) so the v8 surface remains backward-compatible for consumers who
construct typed fixtures or implement plugin test doubles.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/definitions.ts`:
- Around line 21-27: The review points out that adding convertAudio as a
required member on exported interface FFmpegCapabilitiesFeatures (and the other
exported interface at lines ~125-131) is a breaking change; make convertAudio
optional (e.g., convertAudio?: FFmpegCapability) on both exported interfaces
(including FFmpegCapabilitiesFeatures and the other exported interface that
currently adds convertAudio) so the v8 surface remains backward-compatible for
consumers who construct typed fixtures or implement plugin test doubles.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fa0df2e0-2b1a-45d9-bb28-3836f92dfcb3

📥 Commits

Reviewing files that changed from the base of the PR and between e3985b3 and 66069bb.

📒 Files selected for processing (14)
  • FEATURE_MATRIX.md
  • README.md
  • ROADMAP.md
  • android/src/main/java/ee/forgr/capacitor_ffmpeg/CapacitorFFmpegPlugin.java
  • android/src/test/java/ee/forgr/capacitor_ffmpeg/CapacitorFFmpegPluginTest.java
  • example-app/src/js/app.js
  • example-app/src/js/app.vitest.js
  • ios/Sources/CapacitorFFmpegPlugin/CapacitorFFmpeg.swift
  • ios/Sources/CapacitorFFmpegPlugin/CapacitorFFmpegPlugin.swift
  • ios/Tests/CapacitorFFmpegPluginTests/CapacitorFFmpegPluginTests.swift
  • src/definitions.ts
  • src/web.ts
  • test/TEST_INVENTORY.md
  • test/web.test.ts

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 13, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ios/Sources/CapacitorFFmpegPlugin/CapacitorFFmpeg.swift`:
- Around line 528-552: The output URL extension can disagree with the requested
format (normalizedFormat), causing M4A data to be written to a mismatched
filename; update the validation in the conversion path to reject or normalize
output extensions: check outputURL.pathExtension (or
outputURL.lastPathComponent) against normalizedFormat and throw
FFmpegError.invalidArgument if they differ, or else force temporaryOutputURL and
final output filenames to use the requested extension (ensure temporaryOutputURL
is created with extension normalizedFormat and verify any final move/rename uses
that extension) so the file extension always matches the requested format (refer
to normalizedFormat, outputURL, outputDirectory, temporaryOutputURL).
- Around line 564-568: convertAudio currently blocks the bridge thread using
DispatchSemaphore.wait after calling exportSession.exportAsynchronously;
instead, make convertAudio follow the async job pattern used by reencodeVideo:
generate and return a job ID immediately from convertAudio, start
exportSession.exportAsynchronously without waiting, store the job metadata and
the resolver/rejector (or a completion handler map keyed by job ID) so you can
resolve or reject later, and move all resolve/reject calls into the
exportSession.exportAsynchronously completion block (including handling error
cases and updating job status). Remove the DispatchSemaphore usage and add
optional timeout/cancellation handling for the stored job entry so exports can
be cancelled or time out without blocking the main thread.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c30bce30-95b5-45f7-a23d-77bef0a531c0

📥 Commits

Reviewing files that changed from the base of the PR and between 57bb738 and 15bd0c8.

📒 Files selected for processing (2)
  • ios/Sources/CapacitorFFmpegPlugin/CapacitorFFmpeg.swift
  • ios/Tests/CapacitorFFmpegPluginTests/CapacitorFFmpegPluginTests.swift
✅ Files skipped from review due to trivial changes (1)
  • ios/Tests/CapacitorFFmpegPluginTests/CapacitorFFmpegPluginTests.swift

Comment thread ios/Sources/CapacitorFFmpegPlugin/CapacitorFFmpeg.swift
Comment thread ios/Sources/CapacitorFFmpegPlugin/CapacitorFFmpeg.swift Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
ios/Sources/CapacitorFFmpegPlugin/CapacitorFFmpeg.swift (1)

544-548: Consider using !isEmpty instead of extracting an unused track.

The guard extracts audioTrack only to discard it. A cleaner approach checks track presence directly.

♻️ Suggested simplification
-        let asset = AVURLAsset(url: inputURL)
-        guard let audioTrack = asset.tracks(withMediaType: .audio).first else {
+        let asset = AVURLAsset(url: inputURL)
+        guard !asset.tracks(withMediaType: .audio).isEmpty else {
             throw FFmpegError.invalidArgument("The input media does not contain an audio track.")
         }
-        _ = audioTrack
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ios/Sources/CapacitorFFmpegPlugin/CapacitorFFmpeg.swift` around lines 544 -
548, The current code creates an AVURLAsset and binds a discarded audioTrack via
guard let audioTrack = asset.tracks(withMediaType: .audio).first then _ =
audioTrack; replace this with a direct presence check to avoid extracting an
unused variable: call asset.tracks(withMediaType: .audio) and guard that the
resulting array is not empty (using !isEmpty) and otherwise throw the same
FFmpegError.invalidArgument; update the guard to reference AVURLAsset and the
tracks(withMediaType:) call and remove the unused audioTrack binding.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ios/Sources/CapacitorFFmpegPlugin/CapacitorFFmpeg.swift`:
- Around line 559-591: The temporary file removal currently in the top-level
defer runs before AVAssetExportSession finishes—remove the defer that calls
fileManager.removeItem(at: temporaryOutputURL) from convertAudio and instead
perform cleanup inside the exportSession.exportAsynchronously completion handler
(after successful move/replace or in the catch/failure branch) so
temporaryOutputURL is only deleted once the async export and any file operations
complete; locate the exportSession.exportAsynchronously block (created via
audioExportSessionFactory) and add cleanup logic there, using
fileManager.removeItem(at:) safely (try? or handling errors) to ensure the temp
file is removed in all terminal paths without racing the export.

---

Nitpick comments:
In `@ios/Sources/CapacitorFFmpegPlugin/CapacitorFFmpeg.swift`:
- Around line 544-548: The current code creates an AVURLAsset and binds a
discarded audioTrack via guard let audioTrack = asset.tracks(withMediaType:
.audio).first then _ = audioTrack; replace this with a direct presence check to
avoid extracting an unused variable: call asset.tracks(withMediaType: .audio)
and guard that the resulting array is not empty (using !isEmpty) and otherwise
throw the same FFmpegError.invalidArgument; update the guard to reference
AVURLAsset and the tracks(withMediaType:) call and remove the unused audioTrack
binding.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cfcc9ffd-d813-48c6-a05b-3bb67c185e8f

📥 Commits

Reviewing files that changed from the base of the PR and between 15bd0c8 and dd73e3d.

📒 Files selected for processing (3)
  • ios/Sources/CapacitorFFmpegPlugin/CapacitorFFmpeg.swift
  • ios/Sources/CapacitorFFmpegPlugin/CapacitorFFmpegPlugin.swift
  • ios/Tests/CapacitorFFmpegPluginTests/CapacitorFFmpegPluginTests.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • ios/Sources/CapacitorFFmpegPlugin/CapacitorFFmpegPlugin.swift

Comment thread ios/Sources/CapacitorFFmpegPlugin/CapacitorFFmpeg.swift Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dd73e3d060

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ios/Sources/CapacitorFFmpegPlugin/CapacitorFFmpeg.swift Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
ios/Sources/CapacitorFFmpegPlugin/CapacitorFFmpeg.swift (2)

555-557: Simplify unreachable ternary branch.

Since lines 540-542 enforce that outputURL.pathExtension.lowercased() == normalizedFormat, the extension can never be empty at this point. The ternary's first branch is unreachable.

♻️ Optional simplification
         let temporaryOutputURL = outputDirectory
             .appendingPathComponent(".ffmpeg-audio-\(UUID().uuidString)")
-            .appendingPathExtension(outputURL.pathExtension.isEmpty ? "m4a" : outputURL.pathExtension)
+            .appendingPathExtension(normalizedFormat)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ios/Sources/CapacitorFFmpegPlugin/CapacitorFFmpeg.swift` around lines 555 -
557, The ternary in the temporaryOutputURL creation is unnecessary because
outputURL.pathExtension is guaranteed non-empty (enforced earlier), so simplify
the call by removing the conditional branch and always use
outputURL.pathExtension; update the expression that builds temporaryOutputURL
(referencing temporaryOutputURL and outputURL.pathExtension) to append the path
extension directly instead of using the unreachable ternary.

544-547: Consider async track loading for better compatibility with iOS 15+ guidelines.

asset.tracks(withMediaType:) performs synchronous I/O and is discouraged in iOS 15+ in favor of asynchronous loading via loadTracks(withMediaType:completionHandler:) or newer async APIs. While this works reliably for local files, Apple recommends the async approach for future compatibility.

However, a full conversion would require restructuring the entire method to use async/await or completion-based async loading, as the current completion-handler pattern cannot easily wrap the track check in a Task. If your minimum deployment target is iOS 15+, a proper refactor would involve converting convertAudio to an async function:

func convertAudio(
    inputPath: String,
    outputPath: String,
    format: String
) async throws -> FFmpegConvertedAudio {
    let asset = AVURLAsset(url: inputURL)
    let tracks = try await asset.loadTracks(withMediaType: .audio)
    guard !tracks.isEmpty else {
        throw FFmpegError.invalidArgument("The input media does not contain an audio track.")
    }
    // Continue with async export...
}

This is a larger refactoring that would affect the entire method signature and call sites. The current synchronous check is acceptable for local files and does not block noticeably in practice.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ios/Sources/CapacitorFFmpegPlugin/CapacitorFFmpeg.swift` around lines 544 -
547, The synchronous call asset.tracks(withMediaType:) in convertAudio can
perform blocking I/O and should be replaced with the async track-loading API;
refactor convertAudio to be async (convertAudio(...) async throws) and replace
the synchronous check with the async loader (use try await
asset.loadTracks(withMediaType: .audio) and then guard !tracks.isEmpty else
throw FFmpegError.invalidArgument("The input media does not contain an audio
track.")), updating all call sites to await the new signature or provide an
async wrapper if needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@ios/Sources/CapacitorFFmpegPlugin/CapacitorFFmpeg.swift`:
- Around line 555-557: The ternary in the temporaryOutputURL creation is
unnecessary because outputURL.pathExtension is guaranteed non-empty (enforced
earlier), so simplify the call by removing the conditional branch and always use
outputURL.pathExtension; update the expression that builds temporaryOutputURL
(referencing temporaryOutputURL and outputURL.pathExtension) to append the path
extension directly instead of using the unreachable ternary.
- Around line 544-547: The synchronous call asset.tracks(withMediaType:) in
convertAudio can perform blocking I/O and should be replaced with the async
track-loading API; refactor convertAudio to be async (convertAudio(...) async
throws) and replace the synchronous check with the async loader (use try await
asset.loadTracks(withMediaType: .audio) and then guard !tracks.isEmpty else
throw FFmpegError.invalidArgument("The input media does not contain an audio
track.")), updating all call sites to await the new signature or provide an
async wrapper if needed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7a56a5fc-9da0-43ca-8da8-6b5ac8687451

📥 Commits

Reviewing files that changed from the base of the PR and between dd73e3d and 7b2698f.

📒 Files selected for processing (1)
  • ios/Sources/CapacitorFFmpegPlugin/CapacitorFFmpeg.swift

@riderx riderx merged commit c006707 into main Apr 13, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant