### Build the project with Maven Source: https://github.com/kokorin/jaffree/blob/master/README.md Standard command to clean and install the project dependencies. ```shell mvn clean install ``` -------------------------------- ### Produce Video in Pure Java Source: https://github.com/kokorin/jaffree/blob/master/README.md Generate video frames programmatically using a FrameProducer. This example creates a simple colored rectangle video. ```java FrameProducer producer = new FrameProducer() { private long frameCounter = 0; @Override public List produceStreams() { return Collections.singletonList(new Stream() .setType(Stream.Type.VIDEO) .setTimebase(1000L) .setWidth(320) .setHeight(240) ); } @Override public Frame produce() { if (frameCounter > 30) { return null; // return null when End of Stream is reached } BufferedImage image = new BufferedImage(320, 240, BufferedImage.TYPE_3BYTE_BGR); Graphics2D graphics = image.createGraphics(); graphics.setPaint(new Color(frameCounter * 1.0f / 30, 0, 0)); graphics.fillRect(0, 0, 320, 240); long pts = frameCounter * 1000 / 10; // Frame PTS in Stream Timebase Frame videoFrame = Frame.createVideoFrame(0, pts, image); frameCounter++; return videoFrame; } }; FFmpeg.atPath() .addInput(FrameInput.withProducer(producer)) .addOutput(UrlOutput.toUrl(pathToVideo)) .execute(); ``` -------------------------------- ### File Structure - Undamaged File Example Source: https://github.com/kokorin/jaffree/blob/master/src/main/java/com/github/kokorin/jaffree/nut/nut.txt Illustrates the expected structure of an undamaged file, including handling of reserved headers and iterating through stream and info packets. Demuxers must ignore unknown headers. ```C file: file_id_string while(!eof){ packet_header, main_header, packet_footer reserved_headers for(i=0; i 4096) header_checksum u(32) ``` -------------------------------- ### Reserved Headers Structure Source: https://github.com/kokorin/jaffree/blob/master/src/main/java/com/github/kokorin/jaffree/nut/nut.txt Describes the structure for handling reserved headers, which are identified by a specific byte and start code. These are skipped if they are not recognized packet types. ```C reserved_headers while(next_byte == 'N' && next_code != main_startcode && next_code != stream_startcode && next_code != info_startcode && next_code != index_startcode && next_code != syncpoint_startcode){ packet_header reserved_bytes packet_footer } ``` -------------------------------- ### Forcefully Stop FFmpeg via Future Source: https://github.com/kokorin/jaffree/blob/master/README.md Starts an FFmpeg process asynchronously and stops it forcefully using `forceStop()` on the `FFmpegResultFuture`. This may result in corrupted media output. ```java FFmpegResultFuture future = ffmpeg.executeAsync(); Thread.sleep(5_000); future.forceStop(); ``` -------------------------------- ### Create a mosaic video filtergraph in Java Source: https://github.com/kokorin/jaffree/blob/master/README.md Uses FilterGraph and FilterChain to combine four input videos into a single 640x480 mosaic layout. ```java FFmpegResult result = FFmpeg.atPath(BIN) .addInput(UrlInput.fromPath(VIDEO1_MP4).setDuration(10, TimeUnit.SECONDS)) .addInput(UrlInput.fromPath(VIDEO2_MP4).setDuration(10, TimeUnit.SECONDS)) .addInput(UrlInput.fromPath(VIDEO3_MP4).setDuration(10, TimeUnit.SECONDS)) .addInput(UrlInput.fromPath(VIDEO4_MP4).setDuration(10, TimeUnit.SECONDS)) .setComplexFilter(FilterGraph.of( FilterChain.of( Filter.withName("nullsrc") .addArgument("size", "640x480") .addOutputLink("base") ), FilterChain.of( Filter.fromInputLink(StreamSpecifier.withInputIndexAndType(0, StreamType.ALL_VIDEO)) .setName("setpts") .addArgument("PTS-STARTPTS"), Filter.withName("scale") .addArgument("320x240") .addOutputLink("upperleft") ), FilterChain.of( Filter.fromInputLink(StreamSpecifier.withInputIndexAndType(1, StreamType.ALL_VIDEO)) .setName("setpts") .addArgument("PTS-STARTPTS"), Filter.withName("scale") .addArgument("320x240") .addOutputLink("upperright") ), FilterChain.of( Filter.fromInputLink(StreamSpecifier.withInputIndexAndType(2, StreamType.ALL_VIDEO)) .setName("setpts") .addArgument("PTS-STARTPTS"), Filter.withName("scale") .addArgument("320x240") .addOutputLink("lowerleft") ), FilterChain.of( Filter.fromInputLink(StreamSpecifier.withInputIndexAndType(3, StreamType.ALL_VIDEO)) .setName("setpts") .addArgument("PTS-STARTPTS"), Filter.withName("scale") .addArgument("320x240") .addOutputLink("lowerright") ), FilterChain.of( Filter.fromInputLink("base") .addInputLink("upperleft") .setName("overlay") .addArgument("shortest", "1") .addOutputLink("tmp1") ), FilterChain.of( Filter.fromInputLink("tmp1") .addInputLink("upperright") .setName("overlay") //.addArgument("shortest", "1") .addArgument("x", "320") .addOutputLink("tmp2") ), FilterChain.of( Filter.fromInputLink("tmp2") .addInputLink("lowerleft") .setName("overlay") //.addArgument("shortest", "1") .addArgument("y", "240") .addOutputLink("tmp3") ), FilterChain.of( Filter.fromInputLink("tmp3") .addInputLink("lowerright") .setName("overlay") //.addArgument("shortest", "1") .addArgument("x", "320") .addArgument("y", "240") ) )) .addOutput(UrlOutput.toPath(outputPath)) .execute(); ``` -------------------------------- ### Configure HLS Live Streaming Output Source: https://context7.com/kokorin/jaffree/llms.txt Sets up HLS live streaming from a source URL. Configures output format, frame rate, video encoding, and HLS specific options like segment duration and list size. ```java import com.github.kokorin.jaffree.ffmpeg.FFmpeg; import com.github.kokorin.jaffree.ffmpeg.UrlInput; import com.github.kokorin.jaffree.ffmpeg.UrlOutput; import java.nio.file.Path; Path hlsDir = Path.of("/path/to/hls"); FFmpeg.atPath() .addInput( UrlInput.fromUrl("rtmp://live-stream-source") ) .addOutput( UrlOutput.toPath(hlsDir.resolve("stream.m3u8")) .setFormat("hls") .setFrameRate(30) // Video encoding .addArguments("-c:v", "libx264") .addArguments("-preset", "veryfast") .addArguments("-g", "60") // Keyframe every 2 seconds at 30fps // HLS options .addArguments("-hls_time", "2") .addArguments("-hls_list_size", "5") .addArguments("-hls_flags", "delete_segments") .addArguments("-hls_delete_threshold", "5") ) .setOverwriteOutput(true) .execute(); ``` -------------------------------- ### Forcefully Stop FFmpeg via Thread Interrupt Source: https://github.com/kokorin/jaffree/blob/master/README.md Starts an FFmpeg process synchronously and interrupts the thread to forcefully stop it. This may result in corrupted media output. ```java Thread thread = new Thread() { @Override public void run() { ffmpeg.execute(); } }; thread.start(); Thread.sleep(5_000); thread.interrupt(); ``` -------------------------------- ### Implement Byte Read and Write Utilities Source: https://github.com/kokorin/jaffree/blob/master/src/main/java/com/github/kokorin/jaffree/nut/nut.txt Functions to read or write a specified number of bytes from or to a BufferContext. ```c static inline uint64_t get_bytes(BufferContext *bc, int count){ uint64_t val=0; assert(count>0 && count<9); for(i=0; ibuf_ptr++); } return val; } static inline void put_bytes(BufferContext *bc, int count, uint64_t val){ uint64_t val=0; assert(count>0 && count<9); for(i=count-1; i>=0; i--){ *(bc->buf_ptr++)= val >> (8*i); } return val; } ``` -------------------------------- ### Syncpoint and Startcode Constants Source: https://github.com/kokorin/jaffree/blob/master/src/main/java/com/github/kokorin/jaffree/nut/nut.txt Defines syncpoint structure and the hexadecimal startcode constants used for file identification. ```text syncpoint: global_key_pts t back_ptr_div16 v if(main_flags & BROADCAST_MODE) transmit_ts t reserved_bytes ``` ```text main_startcode (f(64)) 0x7A561F5F04ADULL + (((uint64_t)('N'<<8) + 'M')<<48) stream_startcode (f(64)) 0x11405BF2F9DBULL + (((uint64_t)('N'<<8) + 'S')<<48) syncpoint_startcode (f(64)) 0xE4ADEECA4569ULL + (((uint64_t)('N'<<8) + 'K')<<48) index_startcode (f(64)) 0xDD672F23E64EULL + (((uint64_t)('N'<<8) + 'X')<<48) info_startcode (f(64)) 0xAB68B596BA78ULL + (((uint64_t)('N'<<8) + 'I')<<48) ``` -------------------------------- ### Analyze Media File with FFprobe Source: https://context7.com/kokorin/jaffree/llms.txt Use FFprobe to analyze media files and retrieve detailed stream and format information. Ensure FFprobe is accessible via `FFprobe.atPath()`. ```java import com.github.kokorin.jaffree.ffprobe.FFprobe; import com.github.kokorin.jaffree.ffprobe.FFprobeResult; import com.github.kokorin.jaffree.ffprobe.Stream; // Analyze media file and get stream information FFprobeResult result = FFprobe.atPath() .setShowStreams(true) .setShowFormat(true) .setInput("/path/to/video.mp4") .execute(); // Iterate through streams for (Stream stream : result.getStreams()) { System.out.println("Stream #" + stream.getIndex() + " type: " + stream.getCodecType() + " codec: " + stream.getCodecName() + " duration: " + stream.getDuration() + " seconds"); // Video-specific info if (stream.getWidth() != null) { System.out.println(" Resolution: " + stream.getWidth() + "x" + stream.getHeight()); System.out.println(" Frame rate: " + stream.getAvgFrameRate()); } // Audio-specific info if (stream.getSampleRate() != null) { System.out.println(" Sample rate: " + stream.getSampleRate()); System.out.println(" Channels: " + stream.getChannels()); } } // Format information System.out.println("Format: " + result.getFormat().getFormatName()); System.out.println("Duration: " + result.getFormat().getDuration() + " seconds"); System.out.println("Bit rate: " + result.getFormat().getBitRate()); ``` -------------------------------- ### Screen Capture with Jaffree Source: https://context7.com/kokorin/jaffree/llms.txt Capture desktop screen using CaptureInput with automatic OS detection. Supports Windows (GDI Grab, DirectShow), macOS (AVFoundation), and Linux (X11 Grab). ```java import com.github.kokorin.jaffree.ffmpeg.FFmpeg; import com.github.kokorin.jaffree.ffmpeg.CaptureInput; import com.github.kokorin.jaffree.ffmpeg.UrlOutput; import java.util.concurrent.TimeUnit; // Auto-detect capture device based on OS FFmpeg.atPath() .addInput( CaptureInput.captureDesktop() .setCaptureFrameRate(30) .setCaptureCursor(true) ) .addOutput( UrlOutput.toUrl("/path/to/screencast.mp4") .addArguments("-c:v", "libx264") .addArguments("-preset", "ultrafast") // Low CPU usage .addArguments("-crf", "23") .setDuration(60, TimeUnit.SECONDS) ) .setOverwriteOutput(true) .execute(); ``` ```java // Platform-specific: Windows GDI Grab with region FFmpeg.atPath() .addInput( CaptureInput.WindowsGdiGrab.captureDesktop() .setCaptureFrameRate(25) .setCaptureVideoSize(1280, 720) .setCaptureVideoOffset(100, 100) ) .addOutput(UrlOutput.toUrl("/path/to/region.mp4")) .execute(); ``` ```java // Platform-specific: macOS AVFoundation FFmpeg.atPath() .addInput( CaptureInput.MacOsAvFoundation.captureVideoAndAudio("default", "default") .setCaptureFrameRate(30) ) .addOutput(UrlOutput.toUrl("/path/to/screen_with_audio.mp4")) .execute(); ``` -------------------------------- ### Screen Capture and Re-encoding Source: https://github.com/kokorin/jaffree/blob/master/README.md Capture desktop screen and optionally re-encode the recorded video to optimize file size. Ensure to specify capture frame rate and cursor visibility. ```java FFmpeg.atPath() .addInput(CaptureInput .captureDesktop() .setCaptureFrameRate(30) .setCaptureCursor(true) ) .addOutput(UrlOutput .toPath(pathToVideo) // Record with ultrafast to lower CPU usage .addArguments("-preset", "ultrafast") .setDuration(30, TimeUnit.SECONDS) ) .setOverwriteOutput(true) .execute(); //Re-encode when record is completed to optimize file size Path pathToOptimized = pathToVideo.resolveSibling("optimized-" + pathToVideo.getFileName()); FFmpeg.atPath() .addInput(UrlInput.fromPath(pathToVideo)) .addOutput(UrlOutput.toPath(pathToOptimized)) .execute(); Files.move(pathToOptimized, pathToVideo, StandardCopyOption.REPLACE_EXISTING); ``` -------------------------------- ### Compile with J9-module profile Source: https://github.com/kokorin/jaffree/blob/master/README.md Executes the Maven build process using the J9-module profile to enable two-pass compilation. ```shell mvn clean install -PJ9-module ``` -------------------------------- ### Monitor FFmpeg Progress Source: https://context7.com/kokorin/jaffree/llms.txt Use a two-pass approach to calculate total duration with NullOutput, then report percentage progress during transcoding. ```java import com.github.kokorin.jaffree.ffmpeg.FFmpeg; import com.github.kokorin.jaffree.ffmpeg.ProgressListener; import com.github.kokorin.jaffree.ffmpeg.FFmpegProgress; import com.github.kokorin.jaffree.ffmpeg.NullOutput; import java.util.concurrent.atomic.AtomicLong; // First pass: determine total duration using NullOutput final AtomicLong totalDuration = new AtomicLong(); FFmpeg.atPath() .addInput(UrlInput.fromUrl("/path/to/video.mp4")) .addOutput(new NullOutput()) .setOverwriteOutput(true) .setProgressListener(new ProgressListener() { @Override public void onProgress(FFmpegProgress progress) { totalDuration.set(progress.getTimeMillis()); } }) .execute(); // Second pass: transcode with percentage progress FFmpeg.atPath() .addInput(UrlInput.fromUrl("/path/to/video.mp4")) .addOutput(UrlOutput.toUrl("/path/to/output.mp4")) .setOverwriteOutput(true) .setProgressListener(new ProgressListener() { @Override public void onProgress(FFmpegProgress progress) { double percent = 100.0 * progress.getTimeMillis() / totalDuration.get(); System.out.printf("Progress: %.1f%% (frame: %d, fps: %.1f, speed: %.2fx)%n", percent, progress.getFrame(), progress.getFps(), progress.getSpeed()); } }) .execute(); ``` -------------------------------- ### Check Media Streams with FFprobe Source: https://github.com/kokorin/jaffree/blob/master/README.md Use FFprobe to retrieve and iterate over stream information from a media file. ```java FFprobeResult result = FFprobe.atPath() .setShowStreams(true) .setInput(pathToVideo) .execute(); for (Stream stream : result.getStreams()) { System.out.println("Stream #" + stream.getIndex() + " type: " + stream.getCodecType() + " duration: " + stream.getDuration() + " seconds"); } ``` -------------------------------- ### Basic FFmpeg Transcoding Source: https://context7.com/kokorin/jaffree/llms.txt Perform basic video/audio transcoding using FFmpeg. The `setOverwriteOutput(true)` option is used to overwrite existing files. `UrlInput` and `UrlOutput` are suitable for file operations. ```java import com.github.kokorin.jaffree.ffmpeg.FFmpeg; import com.github.kokorin.jaffree.ffmpeg.FFmpegResult; import com.github.kokorin.jaffree.ffmpeg.UrlInput; import com.github.kokorin.jaffree.ffmpeg.UrlOutput; import java.util.concurrent.TimeUnit; // Basic transcoding from one format to another FFmpegResult result = FFmpeg.atPath() .addInput(UrlInput.fromUrl("/path/to/input.avi")) .addOutput(UrlOutput.toUrl("/path/to/output.mp4")) .setOverwriteOutput(true) .execute(); System.out.println("Transcoding completed"); // Cut video segment with position and duration FFmpeg.atPath() .addInput( UrlInput.fromUrl("/path/to/input.mp4") .setPosition(10, TimeUnit.SECONDS) // Start at 10 seconds .setDuration(30, TimeUnit.SECONDS) // Extract 30 seconds ) .addOutput( UrlOutput.toUrl("/path/to/clip.mp4") .addArguments("-c:v", "libx264") .addArguments("-c:a", "aac") ) .setOverwriteOutput(true) .execute(); ``` -------------------------------- ### Apply Video Scale Filter with Fluent API Source: https://context7.com/kokorin/jaffree/llms.txt Uses a fluent API with Filter.withName for more structured filter definition. This approach is useful for filters with multiple arguments. ```java // Filter using fluent API FFmpeg.atPath() .addInput(UrlInput.fromUrl("/path/to/input.mp4")) .setFilter(StreamType.VIDEO, Filter.withName("scale") .addArgument("w", "1280") .addArgument("h", "720")) .addOutput(UrlOutput.toUrl("/path/to/output.mp4")) .execute(); ``` -------------------------------- ### Main Header Structure Source: https://github.com/kokorin/jaffree/blob/master/src/main/java/com/github/kokorin/jaffree/nut/nut.txt Details the main header format, including version information, stream count, time base parameters, and flags for defining stream properties. It also parses up to 256 flags and stream IDs. ```C main_header: version v if (version > 3) minor_version v stream_count v max_distance v time_base_count v for(i=0; i0) tmp_pts s if(tmp_fields>1) tmp_mul v if(tmp_fields>2) tmp_stream v if(tmp_fields>3) tmp_size v else tmp_size=0 if(tmp_fields>4) tmp_res v else tmp_res=0 if(tmp_fields>5) count v else count= tmp_mul - tmp_size if(tmp_fields>6) tmp_match s if(tmp_fields>7) tmp_head_idx v for(j=8; j produceStreams() { return Collections.singletonList( new Stream() .setType(Stream.Type.VIDEO) .setTimebase(1000L) .setWidth(WIDTH) .setHeight(HEIGHT) ); } @Override public Frame produce() { if (frameCounter >= TOTAL_FRAMES) { return null; // End of stream } // Create frame image (TYPE_3BYTE_BGR is required) BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_3BYTE_BGR); Graphics2D g = image.createGraphics(); // Animated gradient float hue = frameCounter / (float) TOTAL_FRAMES; g.setColor(Color.getHSBColor(hue, 1.0f, 1.0f)); g.fillRect(0, 0, WIDTH, HEIGHT); // Draw frame number g.setColor(Color.WHITE); g.drawString("Frame: " + frameCounter, 50, 50); g.dispose(); // PTS in timebase units (1000 = milliseconds) long pts = frameCounter * 1000 / FRAME_RATE; Frame frame = Frame.createVideoFrame(0, pts, image); frameCounter++; return frame; } }; FFmpeg.atPath() .addInput( FrameInput.withProducer(producer) .setFrameRate(25) // Important for performance ) .addOutput( UrlOutput.toUrl("/path/to/generated.mp4") .addArguments("-c:v", "libx264") .addArguments("-preset", "fast") ) .setOverwriteOutput(true) .execute(); ``` -------------------------------- ### Cut and Scale Media Source: https://github.com/kokorin/jaffree/blob/master/README.md Apply input-specific settings like position and duration, and use filters for scaling. ```java FFmpeg.atPath() .addInput( UrlInput.fromUrl(pathToSrc) .setPosition(10, TimeUnit.SECONDS) .setDuration(42, TimeUnit.SECONDS) ) .setFilter(StreamType.VIDEO, "scale=160:-2") .setOverwriteOutput(true) .addArguments("-movflags", "faststart") .addOutput( UrlOutput.toUrl(pathToDst) .setPosition(10, TimeUnit.SECONDS) ) .execute(); ``` -------------------------------- ### Re-encode and Track Progress Source: https://github.com/kokorin/jaffree/blob/master/README.md Calculate total duration first, then perform re-encoding while reporting percentage progress. ```java final AtomicLong duration = new AtomicLong(); FFmpeg.atPath() .addInput(UrlInput.fromUrl(pathToSrc)) .setOverwriteOutput(true) .addOutput(new NullOutput()) .setProgressListener(new ProgressListener() { @Override public void onProgress(FFmpegProgress progress) { duration.set(progress.getTimeMillis()); } }) .execute(); FFmpeg.atPath() .addInput(UrlInput.fromUrl(pathToSrc)) .setOverwriteOutput(true) .addArguments("-movflags", "faststart") .addOutput(UrlOutput.toUrl(pathToDst)) .setProgressListener(new ProgressListener() { @Override public void onProgress(FFmpegProgress progress) { double percents = 100. * progress.getTimeMillis() / duration.get(); System.out.println("Progress: " + percents + "%"); } }) .execute(); ``` -------------------------------- ### Perform SeekableByteChannel I/O Source: https://context7.com/kokorin/jaffree/llms.txt Use ChannelInput and ChannelOutput for custom I/O scenarios requiring seekable streams. ```java import com.github.kokorin.jaffree.ffmpeg.FFmpeg; import com.github.kokorin.jaffree.ffmpeg.ChannelInput; import com.github.kokorin.jaffree.ffmpeg.ChannelOutput; import java.nio.channels.SeekableByteChannel; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; Path sourcePath = Path.of("/path/to/input.mp4"); Path destPath = Path.of("/path/to/output.mkv"); try (SeekableByteChannel inputChannel = Files.newByteChannel(sourcePath, StandardOpenOption.READ); SeekableByteChannel outputChannel = Files.newByteChannel(destPath, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.TRUNCATE_EXISTING)) { FFmpeg.atPath() .addInput(ChannelInput.fromChannel("input.mp4", inputChannel)) .addOutput(ChannelOutput.toChannel("output.mkv", outputChannel)) .setOverwriteOutput(true) .execute(); } ``` -------------------------------- ### Apply Simple Video Scale Filter Source: https://context7.com/kokorin/jaffree/llms.txt Scales video to a specific resolution using the setFilter method. Ensure input and output paths are correctly specified. ```java import com.github.kokorin.jaffree.ffmpeg.FFmpeg; import com.github.kokorin.jaffree.ffmpeg.Filter; import com.github.kokorin.jaffree.ffmpeg.FilterChain; import com.github.kokorin.jaffree.ffmpeg.FilterGraph; import com.github.kokorin.jaffree.StreamType; // Simple filter: scale video FFmpeg.atPath() .addInput(UrlInput.fromUrl("/path/to/input.mp4")) .setFilter(StreamType.VIDEO, "scale=1280:720") .addOutput(UrlOutput.toUrl("/path/to/scaled.mp4")) .setOverwriteOutput(true) .execute(); ``` -------------------------------- ### Read/Write with InputStream/OutputStream Source: https://github.com/kokorin/jaffree/blob/master/README.md Utilize InputStream and OutputStream for data transfer. Note that this uses TCP sockets internally for higher bandwidth. ```java try (InputStream inputStream = Files.newInputStream(pathToSrc); OutputStream outputStream = Files.newOutputStream(pathToDst, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING) ) { FFmpeg.atPath() .addInput(PipeInput.pumpFrom(inputStream)) .addOutput( PipeOutput.pumpTo(outputStream) .setFormat("flv") ) .execute(); } ``` -------------------------------- ### Live Stream Re-Streaming (HLS) Source: https://github.com/kokorin/jaffree/blob/master/README.md Re-stream a live HLS stream. Configure HLS specific options like list size, time, and segment deletion. ```java FFmpeg.atPath() .addInput( UrlInput.fromUrl(liveStream) ) .addOutput( UrlOutput.toPath(dir.resolve("index.m3u8")) .setFrameRate(30) // check all available options: ffmpeg -help muxer=hls .setFormat("hls") // enforce keyframe every 2s - see setFrameRate .addArguments("-x264-params", "keyint=60") .addArguments("-hls_list_size", "5") .addArguments("-hls_delete_threshold", "5") .addArguments("-hls_time", "2") .addArguments("-hls_flags", "delete_segments") ) .setOverwriteOutput(true) .execute(); ``` -------------------------------- ### Programmatic Video Frame Consumption in Java Source: https://context7.com/kokorin/jaffree/llms.txt Consume video frames in Java using FrameOutput with a custom FrameConsumer. Extract frames as BufferedImage objects for processing or analysis. Saves every 30th frame as a PNG. ```java import com.github.kokorin.jaffree.ffmpeg.FFmpeg; import com.github.kokorin.jaffree.ffmpeg.FrameOutput; import com.github.kokorin.jaffree.ffmpeg.FrameConsumer; import com.github.kokorin.jaffree.ffmpeg.Frame; import com.github.kokorin.jaffree.ffmpeg.Stream; import com.github.kokorin.jaffree.ffmpeg.UrlInput; import com.github.kokorin.jaffree.StreamType; import javax.imageio.ImageIO; import java.io.File; import java.util.List; FFmpeg.atPath() .addInput(UrlInput.fromUrl("/path/to/video.mp4")) .addOutput( FrameOutput.withConsumer(new FrameConsumer() { private int frameNumber = 0; @Override public void consumeStreams(List streams) { System.out.println("Streams available: " + streams.size()); for (Stream stream : streams) { System.out.println(" " + stream.getType() + " " + stream.getWidth() + "x" + stream.getHeight()); } } @Override public void consume(Frame frame) { if (frame == null) { System.out.println("End of stream, processed " + frameNumber + " frames"); return; } // Save every 30th frame as PNG if (frameNumber % 30 == 0) { try { String filename = String.format("frame_%04d.png", frameNumber); ImageIO.write(frame.getImage(), "png", new File(filename)); System.out.println("Saved: " + filename); } catch (Exception e) { e.printStackTrace(); } } frameNumber++; } }) .setFrameRate(1) // Extract 1 frame per second .setFrameCount(StreamType.VIDEO, 100L) // Max 100 frames .disableStream(StreamType.AUDIO) .disableStream(StreamType.SUBTITLE) .disableStream(StreamType.DATA) ) .execute(); ``` -------------------------------- ### Enable FFmpeg Error Handling Source: https://github.com/kokorin/jaffree/blob/master/README.md Configures FFmpeg to exit with an error status when a fatal error occurs by adding the '-xerror' argument. This ensures exceptions are raised in Jaffree for such cases. ```java FFmpeg.atPath() .addArgument("-xerror") // ... ``` -------------------------------- ### Parse Custom FFmpeg Output Source: https://github.com/kokorin/jaffree/blob/master/README.md Capture and process raw FFmpeg output lines using an OutputListener. ```java // StringBuffer - because it's thread safe final StringBuffer loudnormReport = new StringBuffer(); FFmpeg.atPath() .addInput(UrlInput.fromUrl(pathToVideo)) .addArguments("-af", "loudnorm=I=-16:TP=-1.5:LRA=11:print_format=json") .addOutput(new NullOutput(false)) .setOutputListener(new OutputListener() { @Override public void onOutput(String line) { loudnormReport.append(line); } }) .execute(); System.out.println("Loudnorm report:\n" + loudnormReport); ``` -------------------------------- ### Implement Variable-Length Integer Utilities Source: https://github.com/kokorin/jaffree/blob/master/src/main/java/com/github/kokorin/jaffree/nut/nut.txt Functions to encode and decode variable-length integers within a buffer context. ```c static inline uint64_t get_v(BufferContext *bc){ uint64_t val= 0; for(; space_left(bc) > 0; ){ int tmp= *(bc->buf_ptr++); if(tmp&0x80) val= (val<<7) + tmp - 0x80; else return (val<<7) + tmp; } return -1; } static inline int put_v(BufferContext *bc, uint64_t val){ int i; if(space_left(bc) < 9) return -1; val &= 0x7FFFFFFFFFFFFFFFULL; // FIXME: Can only encode up to 63 bits ATM. for(i=7; ; i+=7){ if(val>>i == 0) break; } for(i-=7; i>0; i-=7){ *(bc->buf_ptr++)= 0x80 | (val>>i); } *(bc->buf_ptr++)= val&0x7F; return 0; } ``` -------------------------------- ### Consume Video Frames in Java Source: https://github.com/kokorin/jaffree/blob/master/README.md Extracts frames from a video file and saves them as PNG images. Configure frame count, frame rate, and disable specific streams. ```java FFmpeg.atPath() .addInput(UrlInput .fromPath(pathToSrc) ) .addOutput(FrameOutput .withConsumer( new FrameConsumer() { private long num = 1; @Override public void consumeStreams(List streams) { // All stream type except video are disabled. just ignore } @Override public void consume(Frame frame) { // End of Stream if (frame == null) { return; } try { String filename = "frame_" + num++ + ".png"; Path output = pathToDstDir.resolve(filename); ImageIO.write(frame.getImage(), "png", output.toFile()); } catch (Exception e) { e.printStackTrace(); } } } ) // No more then 100 frames .setFrameCount(StreamType.VIDEO, 100L) // 1 frame every 10 seconds .setFrameRate(0.1) // Disable all streams except video .disableStream(StreamType.AUDIO) .disableStream(StreamType.SUBTITLE) .disableStream(StreamType.DATA) ) .execute(); ``` -------------------------------- ### Read/Write with SeekableByteChannel Source: https://github.com/kokorin/jaffree/blob/master/README.md Use SeekableByteChannel for reading from and writing to files. Ensure proper file opening options are used. ```java try (SeekableByteChannel inputChannel = Files.newByteChannel(pathToSrc, StandardOpenOption.READ); SeekableByteChannel outputChannel = Files.newByteChannel(pathToDst, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.TRUNCATE_EXISTING) ) { FFmpeg.atPath() .addInput(ChannelInput.fromChannel(inputChannel)) .addOutput(ChannelOutput.toChannel(filename, outputChannel)) .execute(); } ``` -------------------------------- ### Perform Stream I/O with Pipes Source: https://context7.com/kokorin/jaffree/llms.txt Use PipeInput and PipeOutput to process InputStream and OutputStream data. Note that non-seekable formats like FLV require explicit format setting. ```java import com.github.kokorin.jaffree.ffmpeg.FFmpeg; import com.github.kokorin.jaffree.ffmpeg.PipeInput; import com.github.kokorin.jaffree.ffmpeg.PipeOutput; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; Path inputPath = Path.of("/path/to/input.mp4"); Path outputPath = Path.of("/path/to/output.flv"); try (InputStream inputStream = Files.newInputStream(inputPath); OutputStream outputStream = Files.newOutputStream(outputPath, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) { FFmpeg.atPath() .addInput(PipeInput.pumpFrom(inputStream)) .addOutput( PipeOutput.pumpTo(outputStream) .setFormat("flv") // Format required for non-seekable output ) .execute(); } ``` -------------------------------- ### Index and Info Packet Definitions Source: https://github.com/kokorin/jaffree/blob/master/src/main/java/com/github/kokorin/jaffree/nut/nut.txt Describes the structure for index tables and information packets within the container. ```text index: max_pts t syncpoints v for(i=0; i>=1 n=j if(type){ flag= x & 1 x>>=1 while(x--) has_keyframe[n++][i]=flag has_keyframe[n++][i]=!flag; }else{ while(x != 1){ has_keyframe[n++][i]=x&1; x>>=1; } } for(; j { // get the result of the operation when it is done }) .exceptionally(ex -> { // handle exceptions produced during operation }); ``` -------------------------------- ### NUT Format Specifications Source: https://github.com/kokorin/jaffree/blob/master/src/main/java/com/github/kokorin/jaffree/nut/nut.txt Technical details regarding frame timing, metadata, and container structure. ```APIDOC ## NUT Format Specifications ### Description Technical specifications for the NUT container format, including timing constraints, video/audio metadata, and indexing structures. ### Timing Rules - **DTS Calculation**: Calculated using a decode_delay + 1 sized buffer. - **PTS/DTS Constraint**: PTS of all frames must be >= DTS of all previous frames. DTS of all frames must be >= DTS of all previous frames in the same stream. ### Video Metadata - **width/height (v)**: Coded dimensions in pixels (must not be 0). - **sample_width/sample_height (v)**: Aspect ratio; must be relatively prime if not zero. - **colorspace_type (v)**: Defines color range (0: unknown, 1: Rec 601, 2: Rec 709, 17: Rec 601 full, 18: Rec 709 full). ### Checksum Fields - **headerChecksum (u(32))**: CRC32 using polynomial 0x104C11DB7. Calculated for the area pointed to by forward_ptr. - **header_checksum (u(32))**: Checksum over the startcode and forward pointer. ### Syncpoint Tags - **back_ptr_div16 (v)**: Used to calculate back_ptr (back_ptr = back_ptr_div16 * 16 + 15). - **global_key_pts (t)**: Reference PTS for stream synchronization. - **transmit_ts (t)**: Reference clock value at transmission. ### Index Tags - **max_pts (t)**: Highest PTS in the file. - **syncpoints (v)**: Number of indexed syncpoints. - **index_ptr (u(64))**: Length in bytes of the entire index. ``` -------------------------------- ### Detect Exact Media Duration Source: https://github.com/kokorin/jaffree/blob/master/README.md Transcode to a NullOutput to accurately determine the duration of a media file when FFprobe is insufficient. ```java final AtomicLong durationMillis = new AtomicLong(); FFmpegResult ffmpegResult = FFmpeg.atPath() .addInput( UrlInput.fromUrl(pathToVideo) ) .addOutput(new NullOutput()) .setProgressListener(new ProgressListener() { @Override public void onProgress(FFmpegProgress progress) { durationMillis.set(progress.getTimeMillis()); } }) .execute(); System.out.println("Exact duration: " + durationMillis.get() + " milliseconds"); ``` -------------------------------- ### File Header Structure Source: https://github.com/kokorin/jaffree/blob/master/src/main/java/com/github/kokorin/jaffree/nut/nut.txt Defines the mandatory order of headers in the file. ```APIDOC ## File Header Structure ### Description Headers must appear in a specific order to simplify demuxer design. ### Structure 1. main header 2. stream_header (id=0) 3. stream_header (id=1) ... 4. stream_header (id=n) ``` -------------------------------- ### Add Jaffree Maven Dependency Source: https://github.com/kokorin/jaffree/blob/master/README.md Include the Jaffree library and SLF4J API in your project's pom.xml file. ```xml com.github.kokorin.jaffree jaffree ${jaffree.version} org.slf4j slf4j-api 1.7.25 ``` -------------------------------- ### Parse Custom FFmpeg Output with OutputListener Source: https://context7.com/kokorin/jaffree/llms.txt Captures and processes custom text output from FFmpeg filters, such as loudnorm analysis in JSON format. Requires an OutputListener implementation to handle each line of output. ```java import com.github.kokorin.jaffree.ffmpeg.FFmpeg; import com.github.kokorin.jaffree.ffmpeg.OutputListener; import com.github.kokorin.jaffree.ffmpeg.NullOutput; final StringBuilder loudnormReport = new StringBuilder(); FFmpeg.atPath() .addInput(UrlInput.fromUrl("/path/to/audio.mp4")) .addArguments("-af", "loudnorm=I=-16:TP=-1.5:LRA=11:print_format=json") .addOutput(new NullOutput(false)) // false = don't copy codecs .setOutputListener(new OutputListener() { @Override public void onOutput(String line) { loudnormReport.append(line).append("\n"); } }) .execute(); System.out.println("Loudnorm analysis:\n" + loudnormReport); ``` -------------------------------- ### Create Picture-in-Picture Video Filtergraph Source: https://context7.com/kokorin/jaffree/llms.txt Applies a complex filtergraph for picture-in-picture effect using FilterGraph and FilterChain. Requires two inputs and defines a sequence of filters. ```java // Complex filtergraph: picture-in-picture FFmpeg.atPath() .addInput(UrlInput.fromUrl("/path/to/main.mp4")) .addInput(UrlInput.fromUrl("/path/to/overlay.mp4")) .setComplexFilter(FilterGraph.of( FilterChain.of( Filter.fromInputLink("1:v") .setName("scale") .addArgument("320:-1") .addOutputLink("pip") ), FilterChain.of( Filter.fromInputLink("0:v") .addInputLink("pip") .setName("overlay") .addArgument("x", "main_w-overlay_w-10") .addArgument("y", "10") ) )) .addOutput(UrlOutput.toUrl("/path/to/pip_output.mp4")) .setOverwriteOutput(true) .execute(); ```