transcode

expect suspend fun transcode(input: String, output: String, spec: VideoEncoderSpec? = null, videoFilter: String? = null, videoCopy: Boolean = false, audioSpec: AudioEncoderSpec? = null, audioFilter: String? = null, audioCopy: Boolean = false, subtitleCopy: Boolean = false, subtitleCodec: CodecId? = null, startMicros: Long = 0, endMicros: Long = Long.MAX_VALUE, metadata: Map<String, String> = emptyMap(), dispatcher: CoroutineDispatcher? = null, onProgress: (TranscodeProgress) -> Unit? = null)

Run a transcode end-to-end. Suspends until done.

The work runs on dispatcher, not on the caller's dispatcher: every step is a blocking call into FFmpeg, and running them on a UI thread would freeze it until the whole file is written. Cancelling the caller stops the work before the next input packet or encoded frame, and this function throws kotlinx.coroutines.CancellationException only after every decoder, encoder, filter graph and file the work opened is closed. An exception from onProgress stops the work the same way, and this function then throws it. A read that blocks inside FFmpeg, such as a stalled network input, is not interrupted; it ends when the read returns.

The constant frame rate fills every gap in the input's timestamps, so a file with two frames a day apart makes a day of video, and a videoFilter that never stops producing, such as tpad=stop=-1, makes video for ever once the input ends. For a file or a filter you did not make, bound the work: call this inside withTimeout, or throw from onProgress once TranscodeProgress.framesEncoded passes your limit.

Parameters

input

input file path

output

output file path

spec

video encoder spec. Null (with videoCopy false) gives audio-only output and drops any input video. When set but the input has no video stream, this throws. The video is written at a constant VideoEncoderSpec.frameRate: frames are dropped or repeated against the input timeline the way FFmpeg's fps filter does, so a rate change keeps the duration and never the speed. The colour, pixel shape and HDR metadata the spec leaves null are copied from the first frame the encoder receives, after videoFilter, so a filter that changes them decides what the output declares. Only what that frame declares is copied, never a guess. To read that frame the transcode opens input a second time and decodes until the frame comes out; a spec that sets all three skips that.

videoFilter

filter graph description applied to the video stream. Null passes decoded frames straight into the encoder. Requires spec.

videoCopy

stream-copy the video instead of re-encoding (-c:v copy): bit-exact and near-free. Combine it with audioSpec to keep the video and re-encode only the audio. Mutually exclusive with spec/videoFilter. Trimming a copied video stream is keyframe-snapped, not frame-exact.

audioSpec

audio encoder spec. Null (with audioCopy false) drops audio. When set but the input has no audio stream, the output is silently video-only. A null AudioEncoderSpec.channelLayoutMask copies the input stream's layout when it has AudioEncoderSpec.channels channels.

audioFilter

filter chain for the audio stream (e.g. volume=0.5,atempo=1.25). Null means plain resample and reformat to what the encoder needs.

audioCopy

stream-copy the audio instead of re-encoding (-c:a copy): bit-exact, near-free. Mutually exclusive with audioSpec/audioFilter.

subtitleCopy

stream-copy every subtitle stream into the output. Works for subtitle codecs the output container accepts (mkv: almost all; mp4: mov_text). Otherwise the muxer raises a typed error.

subtitleCodec

convert every subtitle stream to this codec instead of copying it, such as CodecId.MovText, the one text codec MP4 stores. A text subtitle converts to any text codec this build encodes. An image subtitle, such as a DVD or Blu-ray track, cannot become text, and the transcode fails with a typed error. Mutually exclusive with subtitleCopy.

startMicros

trim start, a position in the input relative to the start of the content (see MediaSource.startTimeMicros). Re-encoded video keeps the frames that start at or after it, and re-encoded audio keeps the samples from it on. Copied streams keep whole packets: copied video from the keyframe at or before it, without the pictures that show before that keyframe, and copied audio and subtitles from the first packet at or after it. The output starts at zero where its media starts to show.

endMicros

trim end, a position in the input on the same scale. Re-encoded streams exclude it: video keeps the frames that start before it, and audio is cut to the sample, the way FFmpeg's atrim filter cuts it, so a window from 1 s to 2 s holds exactly one second. Copied streams drop the packets whose decode time is after it. Demuxing stops once the lead stream reaches it. The trim selects decoded input before any filter runs, so a filter that moves time, such as setpts=2*PTS or atempo=0.5, makes the output longer or shorter than the selection, and every frame the filter makes from the selection is encoded.

metadata

container tags written into the output header (title, artist, …)

dispatcher

where the blocking work runs. Null runs it on Dispatchers.IO, the pool for blocking calls. Pass your own to choose the threads, for example Dispatchers.IO.limitedParallelism(2) to cap how many transcodes run at once.

onProgress

invoked about every 30 encoded video frames (or 100 audio frames when audio-only) with a TranscodeProgress, in the caller's own coroutine context and never on dispatcher, so a UI caller may update its views from it. A caller that is busy when a report arrives gets only the newest one, and the last report arrives before this function returns.

Throws

when the input cannot be opened, this build lacks an encoder or a filter, or a decode, filter, encode or write fails. Swift and Objective-C receive it as an error.


expect suspend fun transcode(input: String, inputOptions: Map<String, String>, output: String, spec: VideoEncoderSpec? = null, videoFilter: String? = null, videoCopy: Boolean = false, audioSpec: AudioEncoderSpec? = null, audioFilter: String? = null, audioCopy: Boolean = false, subtitleCopy: Boolean = false, subtitleCodec: CodecId? = null, startMicros: Long = 0, endMicros: Long = Long.MAX_VALUE, metadata: Map<String, String> = emptyMap(), dispatcher: CoroutineDispatcher? = null, onProgress: (TranscodeProgress) -> Unit? = null)

transcode with pre-open options for input, applied the way MediaSource.open applies them. On Android this reads a file the user picked without copying it: pass "fd:" as input and the descriptor number as the fd option. Every other parameter is as in the path overload.

Throws

as the path overload does. Swift and Objective-C receive it as an error.


expect suspend fun transcode(input: () -> MediaByteSource, output: MediaByteSink, format: String, outputOptions: Map<String, String> = emptyMap(), spec: VideoEncoderSpec? = null, videoFilter: String? = null, videoCopy: Boolean = false, audioSpec: AudioEncoderSpec? = null, audioFilter: String? = null, audioCopy: Boolean = false, subtitleCopy: Boolean = false, subtitleCodec: CodecId? = null, startMicros: Long = 0, endMicros: Long = Long.MAX_VALUE, metadata: Map<String, String> = emptyMap(), dispatcher: CoroutineDispatcher? = null, onProgress: (TranscodeProgress) -> Unit? = null)

transcode from bytes the caller supplies into bytes the caller keeps, with no path at either end. Every other parameter is as in the path overload.

Parameters

input

opens a fresh source at position zero each time it is called. The transcode calls it twice when the encoder must see the first frame, and closes each source it opened.

output

receives the file. The transcode owns it and closes it, as MediaSink.open does.

format

the output container, such as mp4 or matroska, because there is no file name to infer it from.

outputOptions

muxer options, as MediaSink.open takes them. An output that cannot seek needs a layout that never goes back, such as MP4 with movflags set to frag_keyframe+empty_moov.

Throws

as the path overload does. Swift and Objective-C receive it as an error.

actual suspend fun transcode(input: String, output: String, spec: VideoEncoderSpec?, videoFilter: String?, videoCopy: Boolean, audioSpec: AudioEncoderSpec?, audioFilter: String?, audioCopy: Boolean, subtitleCopy: Boolean, subtitleCodec: CodecId?, startMicros: Long, endMicros: Long, metadata: Map<String, String>, dispatcher: CoroutineDispatcher?, onProgress: (TranscodeProgress) -> Unit?)
actual suspend fun transcode(input: String, inputOptions: Map<String, String>, output: String, spec: VideoEncoderSpec?, videoFilter: String?, videoCopy: Boolean, audioSpec: AudioEncoderSpec?, audioFilter: String?, audioCopy: Boolean, subtitleCopy: Boolean, subtitleCodec: CodecId?, startMicros: Long, endMicros: Long, metadata: Map<String, String>, dispatcher: CoroutineDispatcher?, onProgress: (TranscodeProgress) -> Unit?)
actual suspend fun transcode(input: () -> MediaByteSource, output: MediaByteSink, format: String, outputOptions: Map<String, String>, spec: VideoEncoderSpec?, videoFilter: String?, videoCopy: Boolean, audioSpec: AudioEncoderSpec?, audioFilter: String?, audioCopy: Boolean, subtitleCopy: Boolean, subtitleCodec: CodecId?, startMicros: Long, endMicros: Long, metadata: Map<String, String>, dispatcher: CoroutineDispatcher?, onProgress: (TranscodeProgress) -> Unit?)
actual suspend fun transcode(input: String, output: String, spec: VideoEncoderSpec?, videoFilter: String?, videoCopy: Boolean, audioSpec: AudioEncoderSpec?, audioFilter: String?, audioCopy: Boolean, subtitleCopy: Boolean, subtitleCodec: CodecId?, startMicros: Long, endMicros: Long, metadata: Map<String, String>, dispatcher: CoroutineDispatcher?, onProgress: (TranscodeProgress) -> Unit?)

Run a transcode end-to-end. Suspends until done.

The work runs on dispatcher, not on the caller's dispatcher: every step is a blocking call into FFmpeg, and running them on a UI thread would freeze it until the whole file is written. Cancelling the caller stops the work before the next input packet or encoded frame, and this function throws kotlinx.coroutines.CancellationException only after every decoder, encoder, filter graph and file the work opened is closed. An exception from onProgress stops the work the same way, and this function then throws it. A read that blocks inside FFmpeg, such as a stalled network input, is not interrupted; it ends when the read returns.

The constant frame rate fills every gap in the input's timestamps, so a file with two frames a day apart makes a day of video, and a videoFilter that never stops producing, such as tpad=stop=-1, makes video for ever once the input ends. For a file or a filter you did not make, bound the work: call this inside withTimeout, or throw from onProgress once TranscodeProgress.framesEncoded passes your limit.

Parameters

input

input file path

output

output file path

spec

video encoder spec. Null (with videoCopy false) gives audio-only output and drops any input video. When set but the input has no video stream, this throws. The video is written at a constant VideoEncoderSpec.frameRate: frames are dropped or repeated against the input timeline the way FFmpeg's fps filter does, so a rate change keeps the duration and never the speed. The colour, pixel shape and HDR metadata the spec leaves null are copied from the first frame the encoder receives, after videoFilter, so a filter that changes them decides what the output declares. Only what that frame declares is copied, never a guess. To read that frame the transcode opens input a second time and decodes until the frame comes out; a spec that sets all three skips that.

videoFilter

filter graph description applied to the video stream. Null passes decoded frames straight into the encoder. Requires spec.

videoCopy

stream-copy the video instead of re-encoding (-c:v copy): bit-exact and near-free. Combine it with audioSpec to keep the video and re-encode only the audio. Mutually exclusive with spec/videoFilter. Trimming a copied video stream is keyframe-snapped, not frame-exact.

audioSpec

audio encoder spec. Null (with audioCopy false) drops audio. When set but the input has no audio stream, the output is silently video-only. A null AudioEncoderSpec.channelLayoutMask copies the input stream's layout when it has AudioEncoderSpec.channels channels.

audioFilter

filter chain for the audio stream (e.g. volume=0.5,atempo=1.25). Null means plain resample and reformat to what the encoder needs.

audioCopy

stream-copy the audio instead of re-encoding (-c:a copy): bit-exact, near-free. Mutually exclusive with audioSpec/audioFilter.

subtitleCopy

stream-copy every subtitle stream into the output. Works for subtitle codecs the output container accepts (mkv: almost all; mp4: mov_text). Otherwise the muxer raises a typed error.

subtitleCodec

convert every subtitle stream to this codec instead of copying it, such as CodecId.MovText, the one text codec MP4 stores. A text subtitle converts to any text codec this build encodes. An image subtitle, such as a DVD or Blu-ray track, cannot become text, and the transcode fails with a typed error. Mutually exclusive with subtitleCopy.

startMicros

trim start, a position in the input relative to the start of the content (see MediaSource.startTimeMicros). Re-encoded video keeps the frames that start at or after it, and re-encoded audio keeps the samples from it on. Copied streams keep whole packets: copied video from the keyframe at or before it, without the pictures that show before that keyframe, and copied audio and subtitles from the first packet at or after it. The output starts at zero where its media starts to show.

endMicros

trim end, a position in the input on the same scale. Re-encoded streams exclude it: video keeps the frames that start before it, and audio is cut to the sample, the way FFmpeg's atrim filter cuts it, so a window from 1 s to 2 s holds exactly one second. Copied streams drop the packets whose decode time is after it. Demuxing stops once the lead stream reaches it. The trim selects decoded input before any filter runs, so a filter that moves time, such as setpts=2*PTS or atempo=0.5, makes the output longer or shorter than the selection, and every frame the filter makes from the selection is encoded.

metadata

container tags written into the output header (title, artist, …)

dispatcher

where the blocking work runs. Null runs it on Dispatchers.IO, the pool for blocking calls. Pass your own to choose the threads, for example Dispatchers.IO.limitedParallelism(2) to cap how many transcodes run at once.

onProgress

invoked about every 30 encoded video frames (or 100 audio frames when audio-only) with a TranscodeProgress, in the caller's own coroutine context and never on dispatcher, so a UI caller may update its views from it. A caller that is busy when a report arrives gets only the newest one, and the last report arrives before this function returns.

Throws

when the input cannot be opened, this build lacks an encoder or a filter, or a decode, filter, encode or write fails. Swift and Objective-C receive it as an error.


actual suspend fun transcode(input: String, inputOptions: Map<String, String>, output: String, spec: VideoEncoderSpec?, videoFilter: String?, videoCopy: Boolean, audioSpec: AudioEncoderSpec?, audioFilter: String?, audioCopy: Boolean, subtitleCopy: Boolean, subtitleCodec: CodecId?, startMicros: Long, endMicros: Long, metadata: Map<String, String>, dispatcher: CoroutineDispatcher?, onProgress: (TranscodeProgress) -> Unit?)

transcode with pre-open options for input, applied the way MediaSource.open applies them. On Android this reads a file the user picked without copying it: pass "fd:" as input and the descriptor number as the fd option. Every other parameter is as in the path overload.

Throws

as the path overload does. Swift and Objective-C receive it as an error.


actual suspend fun transcode(input: () -> MediaByteSource, output: MediaByteSink, format: String, outputOptions: Map<String, String>, spec: VideoEncoderSpec?, videoFilter: String?, videoCopy: Boolean, audioSpec: AudioEncoderSpec?, audioFilter: String?, audioCopy: Boolean, subtitleCopy: Boolean, subtitleCodec: CodecId?, startMicros: Long, endMicros: Long, metadata: Map<String, String>, dispatcher: CoroutineDispatcher?, onProgress: (TranscodeProgress) -> Unit?)

transcode from bytes the caller supplies into bytes the caller keeps, with no path at either end. Every other parameter is as in the path overload.

Parameters

input

opens a fresh source at position zero each time it is called. The transcode calls it twice when the encoder must see the first frame, and closes each source it opened.

output

receives the file. The transcode owns it and closes it, as MediaSink.open does.

format

the output container, such as mp4 or matroska, because there is no file name to infer it from.

outputOptions

muxer options, as MediaSink.open takes them. An output that cannot seek needs a layout that never goes back, such as MP4 with movflags set to frag_keyframe+empty_moov.

Throws

as the path overload does. Swift and Objective-C receive it as an error.

actual suspend fun transcode(input: String, output: String, spec: VideoEncoderSpec?, videoFilter: String?, videoCopy: Boolean, audioSpec: AudioEncoderSpec?, audioFilter: String?, audioCopy: Boolean, subtitleCopy: Boolean, subtitleCodec: CodecId?, startMicros: Long, endMicros: Long, metadata: Map<String, String>, dispatcher: CoroutineDispatcher?, onProgress: (TranscodeProgress) -> Unit?)

Run a transcode end-to-end. Suspends until done.

The work runs on dispatcher, not on the caller's dispatcher: every step is a blocking call into FFmpeg, and running them on a UI thread would freeze it until the whole file is written. Cancelling the caller stops the work before the next input packet or encoded frame, and this function throws kotlinx.coroutines.CancellationException only after every decoder, encoder, filter graph and file the work opened is closed. An exception from onProgress stops the work the same way, and this function then throws it. A read that blocks inside FFmpeg, such as a stalled network input, is not interrupted; it ends when the read returns.

The constant frame rate fills every gap in the input's timestamps, so a file with two frames a day apart makes a day of video, and a videoFilter that never stops producing, such as tpad=stop=-1, makes video for ever once the input ends. For a file or a filter you did not make, bound the work: call this inside withTimeout, or throw from onProgress once TranscodeProgress.framesEncoded passes your limit.

Parameters

input

input file path

output

output file path

spec

video encoder spec. Null (with videoCopy false) gives audio-only output and drops any input video. When set but the input has no video stream, this throws. The video is written at a constant VideoEncoderSpec.frameRate: frames are dropped or repeated against the input timeline the way FFmpeg's fps filter does, so a rate change keeps the duration and never the speed. The colour, pixel shape and HDR metadata the spec leaves null are copied from the first frame the encoder receives, after videoFilter, so a filter that changes them decides what the output declares. Only what that frame declares is copied, never a guess. To read that frame the transcode opens input a second time and decodes until the frame comes out; a spec that sets all three skips that.

videoFilter

filter graph description applied to the video stream. Null passes decoded frames straight into the encoder. Requires spec.

videoCopy

stream-copy the video instead of re-encoding (-c:v copy): bit-exact and near-free. Combine it with audioSpec to keep the video and re-encode only the audio. Mutually exclusive with spec/videoFilter. Trimming a copied video stream is keyframe-snapped, not frame-exact.

audioSpec

audio encoder spec. Null (with audioCopy false) drops audio. When set but the input has no audio stream, the output is silently video-only. A null AudioEncoderSpec.channelLayoutMask copies the input stream's layout when it has AudioEncoderSpec.channels channels.

audioFilter

filter chain for the audio stream (e.g. volume=0.5,atempo=1.25). Null means plain resample and reformat to what the encoder needs.

audioCopy

stream-copy the audio instead of re-encoding (-c:a copy): bit-exact, near-free. Mutually exclusive with audioSpec/audioFilter.

subtitleCopy

stream-copy every subtitle stream into the output. Works for subtitle codecs the output container accepts (mkv: almost all; mp4: mov_text). Otherwise the muxer raises a typed error.

subtitleCodec

convert every subtitle stream to this codec instead of copying it, such as CodecId.MovText, the one text codec MP4 stores. A text subtitle converts to any text codec this build encodes. An image subtitle, such as a DVD or Blu-ray track, cannot become text, and the transcode fails with a typed error. Mutually exclusive with subtitleCopy.

startMicros

trim start, a position in the input relative to the start of the content (see MediaSource.startTimeMicros). Re-encoded video keeps the frames that start at or after it, and re-encoded audio keeps the samples from it on. Copied streams keep whole packets: copied video from the keyframe at or before it, without the pictures that show before that keyframe, and copied audio and subtitles from the first packet at or after it. The output starts at zero where its media starts to show.

endMicros

trim end, a position in the input on the same scale. Re-encoded streams exclude it: video keeps the frames that start before it, and audio is cut to the sample, the way FFmpeg's atrim filter cuts it, so a window from 1 s to 2 s holds exactly one second. Copied streams drop the packets whose decode time is after it. Demuxing stops once the lead stream reaches it. The trim selects decoded input before any filter runs, so a filter that moves time, such as setpts=2*PTS or atempo=0.5, makes the output longer or shorter than the selection, and every frame the filter makes from the selection is encoded.

metadata

container tags written into the output header (title, artist, …)

dispatcher

where the blocking work runs. Null runs it on Dispatchers.IO, the pool for blocking calls. Pass your own to choose the threads, for example Dispatchers.IO.limitedParallelism(2) to cap how many transcodes run at once.

onProgress

invoked about every 30 encoded video frames (or 100 audio frames when audio-only) with a TranscodeProgress, in the caller's own coroutine context and never on dispatcher, so a UI caller may update its views from it. A caller that is busy when a report arrives gets only the newest one, and the last report arrives before this function returns.

Throws

when the input cannot be opened, this build lacks an encoder or a filter, or a decode, filter, encode or write fails. Swift and Objective-C receive it as an error.


actual suspend fun transcode(input: String, inputOptions: Map<String, String>, output: String, spec: VideoEncoderSpec?, videoFilter: String?, videoCopy: Boolean, audioSpec: AudioEncoderSpec?, audioFilter: String?, audioCopy: Boolean, subtitleCopy: Boolean, subtitleCodec: CodecId?, startMicros: Long, endMicros: Long, metadata: Map<String, String>, dispatcher: CoroutineDispatcher?, onProgress: (TranscodeProgress) -> Unit?)

transcode with pre-open options for input, applied the way MediaSource.open applies them. On Android this reads a file the user picked without copying it: pass "fd:" as input and the descriptor number as the fd option. Every other parameter is as in the path overload.

Throws

as the path overload does. Swift and Objective-C receive it as an error.


actual suspend fun transcode(input: () -> MediaByteSource, output: MediaByteSink, format: String, outputOptions: Map<String, String>, spec: VideoEncoderSpec?, videoFilter: String?, videoCopy: Boolean, audioSpec: AudioEncoderSpec?, audioFilter: String?, audioCopy: Boolean, subtitleCopy: Boolean, subtitleCodec: CodecId?, startMicros: Long, endMicros: Long, metadata: Map<String, String>, dispatcher: CoroutineDispatcher?, onProgress: (TranscodeProgress) -> Unit?)

transcode from bytes the caller supplies into bytes the caller keeps, with no path at either end. Every other parameter is as in the path overload.

Parameters

input

opens a fresh source at position zero each time it is called. The transcode calls it twice when the encoder must see the first frame, and closes each source it opened.

output

receives the file. The transcode owns it and closes it, as MediaSink.open does.

format

the output container, such as mp4 or matroska, because there is no file name to infer it from.

outputOptions

muxer options, as MediaSink.open takes them. An output that cannot seek needs a layout that never goes back, such as MP4 with movflags set to frag_keyframe+empty_moov.

Throws

as the path overload does. Swift and Objective-C receive it as an error.