Repository navigation
EN US 5 Codecs
Warning
Experimental: decode, reader, sniff and encoder can change in any update, so don't use them in your mods yet
(see public and internal packages).
Note
Configurable: codecs.maxImageSourceSize, codecs.png.failOnCorruptedData, codecs.png.useBKGDChunk,
codecs.svg.maxSize and codecs.vp8.brokenTokens. See their comments in config/watermedia.toml.
CodecsAPI hands out a decoder (ImageReader) and an encoder (Encoder). Images use our pure-Java codecs, with no
startup or FFmpeg; video goes through FFmpeg.
PNG, JPEG, WebP, SVG and Netpbm.
ImageData image = CodecsAPI.decode(bytes); // A byte[], A ByteBuffer OR AN InputStream
ByteBuffer pixels = image.frames()[0]; // TIGHTLY PACKED; JPEG AND WEBP COME ROTATED TO THEIR EXIF ORIENTATION
PixelFormat format = image.format(); // BGRA (GRAY FOR GRAYSCALE JPEGS)
int width = image.width();
int height = image.height();
ImageData rgba = CodecsAPI.decode(bytes, PixelFormat.RGBA); // ASK FOR BGRA, RGBA OR GRAY AND IT CONVERTSGIF, APNG and animated WebP. A reader walks the frames one by one instead of holding them all in memory.
try (ImageReader reader = CodecsAPI.reader(buffer)) { // DON'T CHANGE buffer UNTIL THE READER CLOSES
int frames = reader.frameCount();
boolean forever = reader.repeat() == ImageData.REPEAT_FOREVER;
reader.addFilter(new DownscaleFilter(0.5f)); // OPTIONAL: SHRINKS EVERY FRAME, NEVER ENLARGES
while (reader.hasNext()) {
ByteBuffer frame = reader.next(); // REUSED ON THE NEXT CALL: COPY IT TO KEEP IT
long delayMs = reader.delay(); // HOW LONG THIS FRAME STAYS ON SCREEN
}
boolean rewound = reader.reset(); // BACK TO THE FIRST FRAME; false WHEN IT CAN'T
ImageData all = reader.readAll(); // OR EVERY FRAME LEFT AT ONCE, AS COPIES
}MediaType type = CodecsAPI.sniff(buffer); // IMAGE, VIDEO, AUDIO... FROM THE FIRST BYTES
ImageMetadata info = reader.metadata(); // ON AN OPEN READER: title(), authors()... AND value(CodecsAPI.METAKEY_EXIF)
try {
ImageData image = CodecsAPI.decode(bytes);
} catch (UnsupportedFormatException unsupported) {
// A VALID FILE USING A FEATURE THE DECODER LACKS: FALL BACK TO ANOTHER DECODER
} catch (CodecException broken) {
// A BROKEN OR OVERSIZED FILE
}Some formats sniff recognizes as images (BMP, TIFF, AVIF...) have no decoder yet. For your own engines,
new BcReader(buffer) reads the compressed BC1, BC3 and BC7 blocks of a DDS texture; the built-in engines don't draw
them yet.
Video and audio decode inside the players: create one with PlayerAPI.player (see playing media).
Set<VideoCodec> videoCodecs = Container.MP4.video(); // WHAT EACH CONTAINER ACCEPTS; A TRACK THAT
Set<AudioCodec> audioCodecs = Container.MP4.audio(); // DOESN'T FIT IS REFUSED BEFORE THE FILE IS TOUCHEDEvery encoder takes one frame per call, in the Encoder.Video format, with timestamps in microseconds that keep
increasing; each frame lasts until the next one. Feed each encoder from a single thread.
PNG (written as APNG when it gets more than one frame) and GIF, without FFmpeg. They take RGBA, BGRA, RGB or GRAY frames.
// STATIC: ONE FRAME AND NO AUDIO TRACK
Encoder.Video png = new Encoder.Video(VideoCodec.PNG, 512, 512, PixelFormat.RGBA, 1);
try (Encoder encoder = CodecsAPI.encoder(Container.PNG, Path.of("logo.png"), png, null)) {
encoder.video(pixels, 0);
}
// ANIMATED: MORE FRAMES; THE FULL CONSTRUCTOR ADDS QUALITY (0 TO 100, 50 BY DEFAULT) AND PLAYS (0 LOOPS FOREVER)
Encoder.Video gif = new Encoder.Video(VideoCodec.GIF, 320, 240, PixelFormat.BGRA, 30, 80, 0);
try (Encoder encoder = CodecsAPI.encoder(Container.GIF, Path.of("clip.gif"), gif, null)) {
for (int i = 0; i < frames.length; i++) encoder.video(frames[i], i * 33_333L);
}MP4, MOV, MKV and WebM through FFmpeg, plus Ogg for audio only. They take any raw PixelFormat.
Encoder.Video video = new Encoder.Video(VideoCodec.H264, 1280, 720, PixelFormat.BGRA, 60); // THE FULL CONSTRUCTOR SETS QUALITY, LIKE ON IMAGES
Encoder.Audio audio = new Encoder.Audio(AudioCodec.AAC, 48_000, 2, SampleType.S16);
try (Encoder encoder = CodecsAPI.encoder(Container.MP4, Path.of("clip.mp4"), video, audio)) { // IOException WITHOUT FFMPEG
encoder.video(frame, ptsUs); // EXACTLY video.frameBytes() BYTES
encoder.audio(pcm); // INTERLEAVED SAMPLES FROM TIME ZERO: PAD A LATE START WITH SILENCE
} // close() FINISHES THE FILE, WRITTEN AS <file>.part UNTIL THEN