transavormer
Version:
Transform (transcode, transmux, etc) audio/video formats using libav.js and WebCodecs
387 lines (335 loc) • 11.7 kB
text/typescript
/*
* Copyright (c) 2024, 2025 Yahweasel
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
import type * as LibAVT from "@libav.js/types";
import type * as wcp from "libavjs-webcodecs-polyfill";
import * as demuxer from "./demuxer";
import * as packetSel from "./packet-selector";
import * as decoder from "./decoder";
import * as frameSel from "./frame-selector";
import * as norm from "./normalizer";
import * as pNorm from "./play-normalizer";
import * as filter from "./filter";
import * as encoder from "./encoder";
import * as muxer from "./muxer";
import * as frameStream from "./frame-stream";
import * as cmdsM from "./commands";
import * as ifs from "./interfaces";
export function build(libav: LibAVT.LibAV, init: ifs.InitDemuxer): Promise<ifs.PacketStream>;
export function build(libav: LibAVT.LibAV, init: ifs.InitDemuxerPtr): Promise<ifs.PacketStreamPtr>;
export function build(libav: LibAVT.LibAV, init: ifs.InitPacketSelector): Promise<ifs.PacketStreamAny>;
export function build(libav: LibAVT.LibAV, init: ifs.InitDecoder): Promise<ifs.FrameStream>;
export function build(libav: LibAVT.LibAV, init: ifs.InitDecoderPtr): Promise<ifs.FrameStreamPtr>;
export function build(libav: LibAVT.LibAV, init: ifs.InitFrameSelector): Promise<ifs.FrameStreamAny>;
export function build(libav: LibAVT.LibAV, init: ifs.InitFrameNormalizer): Promise<ifs.LibAVFrameStream>;
export function build(libav: LibAVT.LibAV, init: ifs.InitFrameNormalizerPtr): Promise<ifs.LibAVFrameStreamPtr>;
export function build(libav: LibAVT.LibAV, init: ifs.InitPlaybackNormalizer): Promise<ifs.FrameStream>;
export function build(libav: LibAVT.LibAV, init: ifs.InitEncoder): Promise<ifs.PacketStream>;
export function build(libav: LibAVT.LibAV, init: ifs.InitEncoderPtr): Promise<ifs.PacketStreamPtr>;
export function build(libav: LibAVT.LibAV, init: ifs.InitMuxer): Promise<ifs.FileStream>;
export function build(libav: LibAVT.LibAV, init: ifs.InitUserPacketStream): Promise<ifs.PacketStreamAny>;
export function build(libav: LibAVT.LibAV, init: ifs.InitUserFrameStream): Promise<ifs.FrameStreamAny>;
export function build(libav: LibAVT.LibAV, init: ifs.InitUserMonoFrameStream): Promise<ifs.FrameStreamAny>;
/**
* Create a transavormer for the requested task.
* @param init Task description (initializer).
*/
export function build(libav: LibAVT.LibAV, init: any): Promise<any> {
switch (init.type) {
case "demuxer":
return buildDemuxer(libav, init, !!init.ptr);
case "packet-selector":
return buildPacketSelector(libav, init);
case "decoder":
return buildDecoder(libav, init, !!init.ptr);
case "frame-selector":
return buildFrameSelector(libav, init);
case "frame-normalizer":
return buildNormalizer(libav, init, !!init.ptr);
case "play-normalizer":
return buildPlayNormalizer(libav, init);
case "la-filter":
return buildLAFilter(libav, init, !!init.ptr);
case "encoder":
return buildEncoder(libav, init, !!init.ptr);
case "muxer":
return buildMuxer(libav, init);
case "packet-stream":
return buildUserPacketStream(init);
case "frame-stream":
return buildUserFrameStream(libav, init);
case "mono-frame-stream":
return buildUserMonoFrameStream(libav, init);
}
throw new Error(`Unrecognized initializer type ${(<any> init).type}`);
}
function buildDemuxer(
libav: LibAVT.LibAV, init: any, ptr: boolean
): Promise<ifs.PacketStream> {
if (init.then)
return init;
if (init.streamType === "packet")
return Promise.resolve(init);
if (init.type !== "demuxer") {
return buildDemuxer(libav, <ifs.InitDemuxer> {
type: "demuxer",
input: init
}, ptr);
}
if (init.libav) {
libav = init.libav;
init.ptr = false;
} else {
init.ptr = ptr;
}
return demuxer.Demuxer.build(libav, init);
}
function buildPacketSelector(
libav: LibAVT.LibAV, init: any
): Promise<ifs.PacketStreamAny> {
if (init.then)
return init;
if (init.streamType === "packet")
return Promise.resolve(init);
return packetSel.PacketSelector.build(
libav, init, buildPacketStream(libav, init.input, true)
);
}
async function buildDecoder(
libav: LibAVT.LibAV,
init: any, ptr: boolean
): Promise<ifs.FrameStream> {
if (init.then)
return init;
if (
init.streamType === "frame" ||
init.streamType === "libav-frame" ||
init.streamType === "webcodecs-frame"
)
return Promise.resolve(init);
if (init.type !== "decoder") {
return buildDecoder(libav, <ifs.InitDecoder> {
type: "decoder",
input: init
}, ptr);
}
let packetStreamP: Promise<ifs.PacketStream> = buildPacketStream(
libav, init.input, true
);
init.ptr = ptr;
if (init.libav) {
libav = init.libav;
init.ptr = false;
}
if (init.LibAV)
init.ptr = false;
return decoder.Decoder.build(
libav, init, packetStreamP
);
}
function buildFrameSelector(
libav: LibAVT.LibAV, init: any
): Promise<ifs.FrameStreamAny> {
if (init.then)
return init;
if (init.streamType === "packet")
return Promise.resolve(init);
return frameSel.FrameSelector.build(
libav, init, buildFrameStream(libav, init.input, true)
);
}
function buildNormalizer(
libav: LibAVT.LibAV,
init: any, ptr: boolean
): Promise<ifs.LibAVFrameStream> {
if (init.then)
return init;
if (init.streamType === "libav-frame")
return Promise.resolve(init);
if (init.type !== "frame-normalizer") {
return buildNormalizer(libav, <ifs.InitFrameNormalizer> {
type: "frame-normalizer",
input: init
}, ptr);
}
init.ptr = ptr;
return norm.FrameNormalizer.build(
libav, init, buildDecoder(libav, init.input, true)
);
}
function buildPlayNormalizer(
libav: LibAVT.LibAV,
init: any
): Promise<ifs.FrameStream> {
if (init.then)
return init;
if (init.streamType === "frame")
return Promise.resolve(init);
return pNorm.PlaybackNormalizer.build(
libav, init, buildDecoder(libav, init.input, true)
);
}
function buildLAFilter(
libav: LibAVT.LibAV,
init: any, ptr: boolean
): Promise<ifs.LibAVFrameStream> {
if (init.then)
return init;
if (init.type !== "la-filter") {
return buildLAFilter(libav, <ifs.InitLAFilter> {
type: "la-filter",
input: init
}, ptr);
}
init.ptr = ptr;
return filter.LAFilter.build(
libav, init, buildFrameStream(libav, init.input, true)
);
}
function buildFrameStream(
libav: LibAVT.LibAV,
init: any, ptr: boolean
): Promise<ifs.FrameStream> {
if (init.then)
return init;
if (
init.streamType === "frame" ||
init.streamType === "libav-frame" ||
init.streamType === "webcodecs-frame"
)
return Promise.resolve(init);
if (init.type === "la-filter") {
return buildLAFilter(libav, init, ptr || !!init.ptr);
} else if (init.type === "frame-normalizer") {
return buildNormalizer(libav, init, ptr || !!init.ptr);
} else if (init.type === "frame-stream") {
return <any> buildUserFrameStream(libav, init);
} else if (init.type === "mono-frame-stream") {
return <any> buildUserMonoFrameStream(libav, init);
} else {
return buildDecoder(libav, init, ptr || !!init.ptr);
}
}
function buildEncoder(
libav: LibAVT.LibAV,
init: any, ptr: boolean
): Promise<ifs.PacketStream> {
if (init.then)
return init;
if (init.streamType === "packet")
return Promise.resolve(init);
if (init.type !== "encoder") {
return buildEncoder(libav, <ifs.InitEncoder> {
type: "encoder",
videoConfig: <wcp.VideoEncoderConfig> {
codec: "vp09.00.10.08.03.1.1.1.0",
width: 0,
height: 0
},
audioConfig: <wcp.AudioEncoderConfig> {
codec: "opus"
},
input: init
}, ptr);
}
init.ptr = ptr;
return encoder.Encoder.build(
libav, init, buildFrameStream(libav, init.input, true)
);
}
function buildPacketStream(
libav: LibAVT.LibAV,
init: any, ptr: boolean
): Promise<ifs.PacketStream> {
if (init.then)
return init;
if (init.streamType === "packet")
return Promise.resolve(init);
if (init.type === "filter" ||
init.type === "frame-normalizer" ||
init.type === "encoder") {
return buildEncoder(libav, init, ptr || !!init.ptr);
} else if (init.type === "packet-stream") {
return <any> buildUserPacketStream(init);
} else {
return buildDemuxer(libav, init, ptr || !!init.ptr);
}
}
function buildMuxer(
libav: LibAVT.LibAV,
init: any
): Promise<ifs.FileStream> {
if (init.then)
return init;
if (init.streamType === "file")
return Promise.resolve(init);
if (init.type !== "muxer") {
init.ptr = true;
return buildMuxer(libav, <ifs.InitMuxer> {
type: "muxer",
format: "matroska",
input: init
});
}
return muxer.Muxer.build(
libav, init, buildPacketStream(libav, init.input, true)
);
}
export function buildUserPacketStream(
init: ifs.InitUserPacketStream
): Promise<ifs.PacketStreamAny> {
return Promise.resolve({
component: "packet-stream",
ptr: false,
streams: Promise.resolve(init.streams),
streamType: "packet",
stream: <any> init.input,
sendCommands(cmds) {
return Promise.resolve(cmdsM.addResults(cmds));
}
});
}
export function buildUserFrameStream(
libav: LibAVT.LibAV,
init: ifs.InitUserFrameStream
): Promise<ifs.FrameStreamAny> {
return Promise.resolve(new frameStream.UserFrameStream(
libav, init
));
}
export function buildUserMonoFrameStream(
libav: LibAVT.LibAV,
init: ifs.InitUserMonoFrameStream
): Promise<ifs.FrameStreamAny> {
const rdr = init.input.getReader();
const rs = new ReadableStream<ifs.StreamFrame[]>({
pull: async (controller) => {
const rd = await rdr.read();
if (rd.done) {
controller.close();
} else {
controller.enqueue(rd.value!.map(x => ({
streamIndex: 0,
frame: <any> x
})));
}
}
});
return buildUserFrameStream(libav, {
type: "frame-stream",
streams: [init.stream],
input: rs
});
}