Overview
A comprehensive Video Thumbnail Extractor that can generate thumbnails from various video formats using Java libraries like FFmpeg, Xuggler, or JavaCV.
Dependencies
Maven Dependencies
<dependencies> <!-- FFmpeg wrapper --> <dependency> <groupId>net.bramp.ffmpeg</groupId> <artifactId>ffmpeg</artifactId> <version>0.7.0</version> </dependency> <!-- JavaCV (OpenCV wrapper) --> <dependency> <groupId>org.bytedeco</groupId> <artifactId>javacv-platform</artifactId> <version>1.5.7</version> </dependency> <!-- Apache Commons IO --> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.11.0</version> </dependency> <!-- Image scaling --> <dependency> <groupId>org.imgscalr</groupId> <artifactId>imgscalr-lib</artifactId> <version>4.2</version> </dependency> </dependencies>
Core Implementation
1. Thumbnail Configuration Class
import java.awt.*; import java.io.File; public class ThumbnailConfig { private int width = 320; private int height = 240; private String format = "jpg"; private int quality = 90; private double timeOffset = 10.0; // seconds private boolean maintainAspectRatio = true; private Color backgroundColor = Color.BLACK; private ThumbnailStrategy strategy = ThumbnailStrategy.SINGLE_FRAME; public enum ThumbnailStrategy { SINGLE_FRAME, // Extract single frame at specific time MULTIPLE_FRAMES, // Extract multiple frames SCENE_DETECTION, // Use scene detection for best frame MIDDLE_FRAME, // Extract frame from middle of video RANDOM_FRAMES // Extract random frames } // Builder pattern for configuration public static class Builder { private final ThumbnailConfig config; public Builder() { config = new ThumbnailConfig(); } public Builder width(int width) { config.width = width; return this; } public Builder height(int height) { config.height = height; return this; } public Builder size(int width, int height) { config.width = width; config.height = height; return this; } public Builder format(String format) { config.format = format; return this; } public Builder quality(int quality) { config.quality = Math.max(1, Math.min(100, quality)); return this; } public Builder timeOffset(double seconds) { config.timeOffset = seconds; return this; } public Builder maintainAspectRatio(boolean maintain) { config.maintainAspectRatio = maintain; return this; } public Builder backgroundColor(Color color) { config.backgroundColor = color; return this; } public Builder strategy(ThumbnailStrategy strategy) { config.strategy = strategy; return this; } public ThumbnailConfig build() { return config; } } // Getters public int getWidth() { return width; } public int getHeight() { return height; } public String getFormat() { return format; } public int getQuality() { return quality; } public double getTimeOffset() { return timeOffset; } public boolean isMaintainAspectRatio() { return maintainAspectRatio; } public Color getBackgroundColor() { return backgroundColor; } public ThumbnailStrategy getStrategy() { return strategy; } public Dimension getSize() { return new Dimension(width, height); } public static Builder builder() { return new Builder(); } } 2. Video Information Extractor
import java.io.File; import java.time.Duration; import java.util.HashMap; import java.util.Map; public class VideoInfo { private final File videoFile; private final Duration duration; private final double frameRate; private final int width; private final int height; private final long fileSize; private final String format; private final String codec; private final Map<String, String> metadata; public VideoInfo(File videoFile, Duration duration, double frameRate, int width, int height, long fileSize, String format, String codec, Map<String, String> metadata) { this.videoFile = videoFile; this.duration = duration; this.frameRate = frameRate; this.width = width; this.height = height; this.fileSize = fileSize; this.format = format; this.codec = codec; this.metadata = new HashMap<>(metadata); } // Getters public File getVideoFile() { return videoFile; } public Duration getDuration() { return duration; } public double getFrameRate() { return frameRate; } public int getWidth() { return width; } public int getHeight() { return height; } public long getFileSize() { return fileSize; } public String getFormat() { return format; } public String getCodec() { return codec; } public Map<String, String> getMetadata() { return metadata; } public double getDurationInSeconds() { return duration.getSeconds() + duration.getNano() / 1_000_000_000.0; } public String getResolution() { return width + "x" + height; } public double getAspectRatio() { return (double) width / height; } public long getTotalFrames() { return (long) (getDurationInSeconds() * frameRate); } @Override public String toString() { return String.format( "VideoInfo{file=%s, duration=%.2fs, resolution=%dx%d, fps=%.2f, size=%dMB}", videoFile.getName(), getDurationInSeconds(), width, height, frameRate, fileSize / (1024 * 1024) ); } } 3. Main Thumbnail Extractor Interface
import java.io.File; import java.io.IOException; import java.util.List; public interface ThumbnailExtractor { /** * Extract a single thumbnail from video */ ThumbnailResult extractThumbnail(File videoFile, ThumbnailConfig config) throws ThumbnailException; /** * Extract multiple thumbnails from video */ List<ThumbnailResult> extractThumbnails(File videoFile, ThumbnailConfig config, int count) throws ThumbnailException; /** * Get video information */ VideoInfo getVideoInfo(File videoFile) throws ThumbnailException; /** * Check if video format is supported */ boolean isFormatSupported(String format); /** * Get supported formats */ List<String> getSupportedFormats(); } 4. FFmpeg-based Thumbnail Extractor
import net.bramp.ffmpeg.FFmpeg; import net.bramp.ffmpeg.FFmpegExecutor; import net.bramp.ffmpeg.FFprobe; import net.bramp.ffmpeg.builder.FFmpegBuilder; import net.bramp.ffmpeg.probe.FFmpegProbeResult; import net.bramp.ffmpeg.probe.FFmpegStream; import org.apache.commons.io.FileUtils; import org.imgscalr.Scalr; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; import java.util.*; import java.util.List; public class FFmpegThumbnailExtractor implements ThumbnailExtractor { private final FFmpeg ffmpeg; private final FFprobe ffprobe; private final File tempDirectory; private static final Set<String> SUPPORTED_FORMATS = Set.of( "mp4", "avi", "mkv", "mov", "wmv", "flv", "webm", "m4v", "3gp", "mpg", "mpeg" ); public FFmpegThumbnailExtractor(String ffmpegPath, String ffprobePath) throws IOException { this.ffmpeg = new FFmpeg(ffmpegPath); this.ffprobe = new FFprobe(ffprobePath); this.tempDirectory = createTempDirectory(); } public FFmpegThumbnailExtractor() throws IOException { this("ffmpeg", "ffprobe"); // Use system PATH } @Override public ThumbnailResult extractThumbnail(File videoFile, ThumbnailConfig config) throws ThumbnailException { validateInput(videoFile, config); try { VideoInfo videoInfo = getVideoInfo(videoFile); double extractTime = calculateExtractTime(videoInfo, config); File outputFile = createTempFile(config.getFormat()); switch (config.getStrategy()) { case SINGLE_FRAME: return extractSingleFrame(videoFile, outputFile, extractTime, config); case MIDDLE_FRAME: double middleTime = videoInfo.getDurationInSeconds() / 2; return extractSingleFrame(videoFile, outputFile, middleTime, config); case SCENE_DETECTION: return extractWithSceneDetection(videoFile, outputFile, config); default: return extractSingleFrame(videoFile, outputFile, extractTime, config); } } catch (Exception e) { throw new ThumbnailException("Failed to extract thumbnail: " + e.getMessage(), e); } } @Override public List<ThumbnailResult> extractThumbnails(File videoFile, ThumbnailConfig config, int count) throws ThumbnailException { validateInput(videoFile, config); if (count <= 0) { throw new IllegalArgumentException("Count must be positive"); } try { VideoInfo videoInfo = getVideoInfo(videoFile); List<ThumbnailResult> results = new ArrayList<>(); switch (config.getStrategy()) { case MULTIPLE_FRAMES: results = extractMultipleFrames(videoFile, videoInfo, config, count); break; case RANDOM_FRAMES: results = extractRandomFrames(videoFile, videoInfo, config, count); break; default: // Fallback to multiple frames at equal intervals results = extractMultipleFrames(videoFile, videoInfo, config, count); } return results; } catch (Exception e) { throw new ThumbnailException("Failed to extract multiple thumbnails: " + e.getMessage(), e); } } @Override public VideoInfo getVideoInfo(File videoFile) throws ThumbnailException { try { FFmpegProbeResult probeResult = ffprobe.probe(videoFile.getAbsolutePath()); FFmpegStream videoStream = getVideoStream(probeResult); Map<String, String> metadata = new HashMap<>(); if (probeResult.getFormat().tags != null) { probeResult.getFormat().tags.forEach(metadata::put); } return new VideoInfo( videoFile, Duration.ofMillis((long) (probeResult.getFormat().duration * 1000)), videoStream.avg_frame_rate.eval(), videoStream.width, videoStream.height, probeResult.getFormat().size, probeResult.getFormat().format_name, videoStream.codec_name, metadata ); } catch (Exception e) { throw new ThumbnailException("Failed to get video info: " + e.getMessage(), e); } } @Override public boolean isFormatSupported(String format) { return SUPPORTED_FORMATS.contains(format.toLowerCase()); } @Override public List<String> getSupportedFormats() { return new ArrayList<>(SUPPORTED_FORMATS); } private ThumbnailResult extractSingleFrame(File videoFile, File outputFile, double timeOffset, ThumbnailConfig config) throws IOException { FFmpegBuilder builder = new FFmpegBuilder() .setInput(videoFile.getAbsolutePath()) .overrideOutputFiles(true) .addOutput(outputFile.getAbsolutePath()) .setFormat("image2") .addExtraArgs("-ss", String.valueOf(timeOffset)) .addExtraArgs("-vframes", "1") .addExtraArgs("-qscale:v", "2") .done(); FFmpegExecutor executor = new FFmpegExecutor(ffmpeg, ffprobe); executor.createJob(builder).run(); // Process the extracted image File processedFile = processImage(outputFile, config); return new ThumbnailResult(processedFile, timeOffset, config); } private List<ThumbnailResult> extractMultipleFrames(File videoFile, VideoInfo videoInfo, ThumbnailConfig config, int count) throws IOException { List<ThumbnailResult> results = new ArrayList<>(); double duration = videoInfo.getDurationInSeconds(); double interval = duration / (count + 1); // Avoid very beginning and end for (int i = 1; i <= count; i++) { double timeOffset = interval * i; File tempOutput = createTempFile(config.getFormat()); File outputFile = new File(tempDirectory, String.format("thumb_%d_%s", i, tempOutput.getName())); ThumbnailResult result = extractSingleFrame(videoFile, outputFile, timeOffset, config); results.add(result); } return results; } private List<ThumbnailResult> extractRandomFrames(File videoFile, VideoInfo videoInfo, ThumbnailConfig config, int count) throws IOException { List<ThumbnailResult> results = new ArrayList<>(); double duration = videoInfo.getDurationInSeconds(); Random random = new Random(); for (int i = 0; i < count; i++) { double timeOffset = random.nextDouble() * duration; File tempOutput = createTempFile(config.getFormat()); File outputFile = new File(tempDirectory, String.format("thumb_random_%d_%s", i, tempOutput.getName())); ThumbnailResult result = extractSingleFrame(videoFile, outputFile, timeOffset, config); results.add(result); } return results; } private ThumbnailResult extractWithSceneDetection(File videoFile, File outputFile, ThumbnailConfig config) throws IOException { // Simple scene detection: extract frames at different points and choose the one with most contrast VideoInfo videoInfo = getVideoInfo(videoFile); double duration = videoInfo.getDurationInSeconds(); // Extract frames at 25%, 50%, 75% of video double[] timePoints = {duration * 0.25, duration * 0.5, duration * 0.75}; List<File> tempFiles = new ArrayList<>(); List<Double> contrasts = new ArrayList<>(); for (double time : timePoints) { File tempFile = createTempFile(config.getFormat()); extractSingleFrame(videoFile, tempFile, time, ThumbnailConfig.builder().width(320).height(240).build()); tempFiles.add(tempFile); double contrast = calculateImageContrast(tempFile); contrasts.add(contrast); } // Find frame with highest contrast (likely most interesting) int bestIndex = 0; double maxContrast = contrasts.get(0); for (int i = 1; i < contrasts.size(); i++) { if (contrasts.get(i) > maxContrast) { maxContrast = contrasts.get(i); bestIndex = i; } } // Process the best frame with original config File bestFrame = tempFiles.get(bestIndex); File processedFile = processImage(bestFrame, config); // Cleanup temp files for (File tempFile : tempFiles) { if (tempFile != bestFrame) { // Keep the best frame tempFile.delete(); } } return new ThumbnailResult(processedFile, timePoints[bestIndex], config); } private File processImage(File inputImage, ThumbnailConfig config) throws IOException { BufferedImage original = ImageIO.read(inputImage); if (original == null) { throw new IOException("Failed to read extracted image"); } BufferedImage processed; if (config.isMaintainAspectRatio()) { processed = resizeMaintainAspectRatio(original, config); } else { processed = resizeImage(original, config); } File outputFile = createTempFile(config.getFormat()); ImageIO.write(processed, config.getFormat(), outputFile); // Cleanup original extracted file if (!inputImage.equals(outputFile)) { inputImage.delete(); } return outputFile; } private BufferedImage resizeMaintainAspectRatio(BufferedImage original, ThumbnailConfig config) { int originalWidth = original.getWidth(); int originalHeight = original.getHeight(); double aspectRatio = (double) originalWidth / originalHeight; int newWidth, newHeight; if ((double) config.getWidth() / config.getHeight() > aspectRatio) { newHeight = config.getHeight(); newWidth = (int) (newHeight * aspectRatio); } else { newWidth = config.getWidth(); newHeight = (int) (newWidth / aspectRatio); } BufferedImage resized = Scalr.resize(original, Scalr.Method.QUALITY, Scalr.Mode.FIT_EXACT, newWidth, newHeight); // Create canvas with background color if dimensions don't match if (newWidth != config.getWidth() || newHeight != config.getHeight()) { BufferedImage canvas = new BufferedImage( config.getWidth(), config.getHeight(), BufferedImage.TYPE_INT_RGB); Graphics2D g = canvas.createGraphics(); g.setColor(config.getBackgroundColor()); g.fillRect(0, 0, config.getWidth(), config.getHeight()); int x = (config.getWidth() - newWidth) / 2; int y = (config.getHeight() - newHeight) / 2; g.drawImage(resized, x, y, null); g.dispose(); return canvas; } return resized; } private BufferedImage resizeImage(BufferedImage original, ThumbnailConfig config) { return Scalr.resize(original, Scalr.Method.QUALITY, Scalr.Mode.FIT_EXACT, config.getWidth(), config.getHeight()); } private double calculateImageContrast(File imageFile) throws IOException { BufferedImage image = ImageIO.read(imageFile); long totalLuminance = 0; long totalPixels = image.getWidth() * image.getHeight(); for (int x = 0; x < image.getWidth(); x++) { for (int y = 0; y < image.getHeight(); y++) { Color color = new Color(image.getRGB(x, y)); // Calculate relative luminance double luminance = 0.2126 * color.getRed() + 0.7152 * color.getGreen() + 0.0722 * color.getBlue(); totalLuminance += (long) luminance; } } double averageLuminance = (double) totalLuminance / totalPixels; // Calculate variance (simplified contrast measure) long varianceSum = 0; for (int x = 0; x < image.getWidth(); x++) { for (int y = 0; y < image.getHeight(); y++) { Color color = new Color(image.getRGB(x, y)); double luminance = 0.2126 * color.getRed() + 0.7152 * color.getGreen() + 0.0722 * color.getBlue(); double diff = luminance - averageLuminance; varianceSum += (long) (diff * diff); } } return Math.sqrt((double) varianceSum / totalPixels); } private double calculateExtractTime(VideoInfo videoInfo, ThumbnailConfig config) { double duration = videoInfo.getDurationInSeconds(); double requestedTime = config.getTimeOffset(); // Ensure time is within video bounds return Math.max(0, Math.min(duration - 1, requestedTime)); } private FFmpegStream getVideoStream(FFmpegProbeResult probeResult) throws ThumbnailException { return probeResult.getStreams().stream() .filter(stream -> "video".equals(stream.codec_type)) .findFirst() .orElseThrow(() -> new ThumbnailException("No video stream found")); } private void validateInput(File videoFile, ThumbnailConfig config) throws ThumbnailException { if (videoFile == null || !videoFile.exists()) { throw new ThumbnailException("Video file does not exist: " + videoFile); } if (!videoFile.isFile()) { throw new ThumbnailException("Path is not a file: " + videoFile); } if (!isFormatSupported(getFileExtension(videoFile))) { throw new ThumbnailException("Unsupported video format: " + getFileExtension(videoFile)); } if (config.getWidth() <= 0 || config.getHeight() <= 0) { throw new ThumbnailException("Invalid thumbnail dimensions"); } } private String getFileExtension(File file) { String name = file.getName(); int lastDot = name.lastIndexOf('.'); return lastDot > 0 ? name.substring(lastDot + 1).toLowerCase() : ""; } private File createTempFile(String format) throws IOException { return File.createTempFile("thumbnail_", "." + format, tempDirectory); } private File createTempDirectory() throws IOException { Path tempDir = Files.createTempDirectory("video_thumbnails_"); return tempDir.toFile(); } public void cleanup() { if (tempDirectory.exists()) { try { FileUtils.deleteDirectory(tempDirectory); } catch (IOException e) { System.err.println("Warning: Failed to cleanup temp directory: " + e.getMessage()); } } } @Override protected void finalize() throws Throwable { cleanup(); super.finalize(); } } 5. JavaCV-based Thumbnail Extractor
import org.bytedeco.javacv.FFmpegFrameGrabber; import org.bytedeco.javacv.Frame; import org.bytedeco.javacv.Java2DFrameConverter; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; public class JavaCVThumbnailExtractor implements ThumbnailExtractor { private final Java2DFrameConverter converter; public JavaCVThumbnailExtractor() { this.converter = new Java2DFrameConverter(); } @Override public ThumbnailResult extractThumbnail(File videoFile, ThumbnailConfig config) throws ThumbnailException { try (FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(videoFile)) { grabber.start(); double duration = grabber.getLengthInTime() / 1_000_000.0; // Convert to seconds double extractTime = calculateExtractTime(duration, config); // Seek to the desired time long targetFrame = (long) (extractTime * grabber.getFrameRate()); grabber.setFrameNumber((int) targetFrame); Frame frame = grabber.grabImage(); if (frame == null) { throw new ThumbnailException("Could not extract frame at time: " + extractTime); } BufferedImage image = converter.convert(frame); File outputFile = processAndSaveImage(image, config); return new ThumbnailResult(outputFile, extractTime, config); } catch (Exception e) { throw new ThumbnailException("Failed to extract thumbnail with JavaCV: " + e.getMessage(), e); } } @Override public List<ThumbnailResult> extractThumbnails(File videoFile, ThumbnailConfig config, int count) throws ThumbnailException { List<ThumbnailResult> results = new ArrayList<>(); try (FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(videoFile)) { grabber.start(); double duration = grabber.getLengthInTime() / 1_000_000.0; double interval = duration / (count + 1); for (int i = 1; i <= count; i++) { double timeOffset = interval * i; long targetFrame = (long) (timeOffset * grabber.getFrameRate()); grabber.setFrameNumber((int) targetFrame); Frame frame = grabber.grabImage(); if (frame != null) { BufferedImage image = converter.convert(frame); File outputFile = processAndSaveImage(image, config); results.add(new ThumbnailResult(outputFile, timeOffset, config)); } } } catch (Exception e) { throw new ThumbnailException("Failed to extract multiple thumbnails: " + e.getMessage(), e); } return results; } @Override public VideoInfo getVideoInfo(File videoFile) throws ThumbnailException { try (FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(videoFile)) { grabber.start(); return new VideoInfo( videoFile, java.time.Duration.ofMillis((long) (grabber.getLengthInTime() / 1000)), grabber.getFrameRate(), grabber.getImageWidth(), grabber.getImageHeight(), videoFile.length(), grabber.getFormat(), grabber.getVideoCodecName(), new java.util.HashMap<>() ); } catch (Exception e) { throw new ThumbnailException("Failed to get video info: " + e.getMessage(), e); } } @Override public boolean isFormatSupported(String format) { // JavaCV supports most formats via FFmpeg return true; } @Override public List<String> getSupportedFormats() { return List.of("mp4", "avi", "mkv", "mov", "wmv", "flv", "webm"); } private File processAndSaveImage(BufferedImage image, ThumbnailConfig config) throws IOException { BufferedImage processed = resizeImage(image, config); File outputFile = File.createTempFile("javacv_thumb_", "." + config.getFormat()); ImageIO.write(processed, config.getFormat(), outputFile); return outputFile; } private BufferedImage resizeImage(BufferedImage original, ThumbnailConfig config) { // Simple resize implementation - in practice, use better scaling library java.awt.Image scaled = original.getScaledInstance( config.getWidth(), config.getHeight(), java.awt.Image.SCALE_SMOOTH); BufferedImage result = new BufferedImage( config.getWidth(), config.getHeight(), BufferedImage.TYPE_INT_RGB); result.getGraphics().drawImage(scaled, 0, 0, null); return result; } private double calculateExtractTime(double duration, ThumbnailConfig config) { double requestedTime = config.getTimeOffset(); return Math.max(0, Math.min(duration - 1, requestedTime)); } } 6. Result and Exception Classes
import java.io.File; public class ThumbnailResult { private final File thumbnailFile; private final double extractTime; private final ThumbnailConfig config; private final long fileSize; public ThumbnailResult(File thumbnailFile, double extractTime, ThumbnailConfig config) { this.thumbnailFile = thumbnailFile; this.extractTime = extractTime; this.config = config; this.fileSize = thumbnailFile.length(); } // Getters public File getThumbnailFile() { return thumbnailFile; } public double getExtractTime() { return extractTime; } public ThumbnailConfig getConfig() { return config; } public long getFileSize() { return fileSize; } public String getFormat() { return config.getFormat(); } public String getDimensions() { return config.getWidth() + "x" + config.getHeight(); } @Override public String toString() { return String.format( "ThumbnailResult{file=%s, time=%.2fs, size=%s, dimensions=%s, format=%s}", thumbnailFile.getName(), extractTime, formatFileSize(fileSize), getDimensions(), getFormat() ); } private String formatFileSize(long bytes) { if (bytes < 1024) return bytes + " B"; if (bytes < 1024 * 1024) return String.format("%.1f KB", bytes / 1024.0); return String.format("%.1f MB", bytes / (1024.0 * 1024.0)); } } public class ThumbnailException extends Exception { public ThumbnailException(String message) { super(message); } public ThumbnailException(String message, Throwable cause) { super(message, cause); } } 7. Thumbnail Generator Factory
import java.io.IOException; import java.util.HashMap; import java.util.Map; public class ThumbnailGeneratorFactory { public enum GeneratorType { FFMPEG, JAVACV, AUTO } private static final Map<GeneratorType, ThumbnailExtractor> instances = new HashMap<>(); public static ThumbnailExtractor createGenerator(GeneratorType type) throws ThumbnailException { if (instances.containsKey(type)) { return instances.get(type); } try { ThumbnailExtractor generator; switch (type) { case FFMPEG: generator = new FFmpegThumbnailExtractor(); break; case JAVACV: generator = new JavaCVThumbnailExtractor(); break; case AUTO: default: generator = createAutoGenerator(); break; } instances.put(type, generator); return generator; } catch (Exception e) { throw new ThumbnailException("Failed to create thumbnail generator: " + e.getMessage(), e); } } private static ThumbnailExtractor createAutoGenerator() { try { return new FFmpegThumbnailExtractor(); } catch (Exception e) { try { return new JavaCVThumbnailExtractor(); } catch (Exception ex) { throw new RuntimeException("No suitable thumbnail generator found"); } } } public static void cleanup() { instances.values().forEach(generator -> { if (generator instanceof FFmpegThumbnailExtractor) { ((FFmpegThumbnailExtractor) generator).cleanup(); } }); instances.clear(); } } 8. Usage Examples and Main Class
import java.io.File; import java.util.List; public class ThumbnailExtractorDemo { public static void main(String[] args) { try { // Create thumbnail generator ThumbnailExtractor extractor = ThumbnailGeneratorFactory.createGenerator( ThumbnailGeneratorFactory.GeneratorType.AUTO); // Example video file (replace with actual path) File videoFile = new File("example.mp4"); if (!videoFile.exists()) { System.out.println("Please provide a valid video file path"); return; } // Get video information VideoInfo videoInfo = extractor.getVideoInfo(videoFile); System.out.println("Video Info: " + videoInfo); // Example 1: Extract single thumbnail System.out.println("\n--- Extracting Single Thumbnail ---"); ThumbnailConfig singleConfig = ThumbnailConfig.builder() .width(640) .height(480) .format("jpg") .quality(85) .timeOffset(30.0) // Extract at 30 seconds .maintainAspectRatio(true) .strategy(ThumbnailConfig.ThumbnailStrategy.SINGLE_FRAME) .build(); ThumbnailResult singleResult = extractor.extractThumbnail(videoFile, singleConfig); System.out.println("Single Thumbnail: " + singleResult); // Example 2: Extract multiple thumbnails System.out.println("\n--- Extracting Multiple Thumbnails ---"); ThumbnailConfig multiConfig = ThumbnailConfig.builder() .width(320) .height(240) .format("png") .strategy(ThumbnailConfig.ThumbnailStrategy.MULTIPLE_FRAMES) .build(); List<ThumbnailResult> multiResults = extractor.extractThumbnails(videoFile, multiConfig, 5); System.out.println("Extracted " + multiResults.size() + " thumbnails:"); multiResults.forEach(System.out::println); // Example 3: Extract using scene detection System.out.println("\n--- Extracting with Scene Detection ---"); ThumbnailConfig sceneConfig = ThumbnailConfig.builder() .width(800) .height(600) .strategy(ThumbnailConfig.ThumbnailStrategy.SCENE_DETECTION) .build(); ThumbnailResult sceneResult = extractor.extractThumbnail(videoFile, sceneConfig); System.out.println("Scene Detection Thumbnail: " + sceneResult); // Example 4: Batch processing System.out.println("\n--- Batch Processing ---"); processVideoDirectory(new File("videos"), extractor); } catch (Exception e) { System.err.println("Error: " + e.getMessage()); e.printStackTrace(); } finally { ThumbnailGeneratorFactory.cleanup(); } } private static void processVideoDirectory(File directory, ThumbnailExtractor extractor) { if (!directory.exists() || !directory.isDirectory()) { System.out.println("Directory not found: " + directory); return; } File[] videoFiles = directory.listFiles((dir, name) -> name.toLowerCase().matches(".*\\.(mp4|avi|mkv|mov|wmv)$")); if (videoFiles == null || videoFiles.length == 0) { System.out.println("No video files found in directory"); return; } ThumbnailConfig batchConfig = ThumbnailConfig.builder() .width(256) .height(144) .format("jpg") .quality(75) .build(); for (File videoFile : videoFiles) { try { System.out.println("Processing: " + videoFile.getName()); ThumbnailResult result = extractor.extractThumbnail(videoFile, batchConfig); System.out.println(" → " + result.getThumbnailFile().getName()); } catch (Exception e) { System.err.println(" Failed to process " + videoFile.getName() + ": " + e.getMessage()); } } } } Key Features
- Multiple Extraction Strategies:
- Single frame at specific time
- Multiple frames at intervals
- Scene detection for best frame
- Middle frame extraction
- Random frame extraction
- Format Support:
- MP4, AVI, MKV, MOV, WMV, FLV, WebM, and more
- Output in JPG, PNG, BMP formats
- Image Processing:
- Maintain aspect ratio
- Custom background colors
- Quality settings
- Multiple resize methods
- Error Handling:
- Comprehensive exception handling
- Input validation
- Resource cleanup
- Flexible Configuration:
- Builder pattern for easy configuration
- Multiple generator implementations
- Extensible architecture
This Video Thumbnail Extractor provides a robust solution for generating thumbnails from videos with various customization options and error handling.