/* * opsu! - an open-source osu! client * Copyright (C) 2014-2017 Jeffrey Han * * opsu! is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * opsu! is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with opsu!. If not, see <http://www.gnu.org/licenses/>. */ package itdelatrisu.opsu.video; import itdelatrisu.opsu.options.Options; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.regex.Pattern; import craterstudio.io.Streams; import craterstudio.streams.NullOutputStream; import craterstudio.text.RegexUtil; import craterstudio.text.TextValues; import net.indiespot.media.impl.Extractor; import net.indiespot.media.impl.VideoMetadata; /** * FFmpeg utilities. * * @author Riven (base) */ public class FFmpeg { /** The default file name of the FFmpeg shared library. */ public static final String DEFAULT_NATIVE_FILENAME = getDefaultNativeFilename(); /** The FFmpeg shared library location. */ private static File FFMPEG_PATH = null; /** Whether to print FFmpeg errors to stderr. */ private static final boolean FFMPEG_VERBOSE = false; /** * Returns the expected file name of the FFmpeg shared library, based on * the current operating system and architecture. */ private static String getDefaultNativeFilename() { String resourceName = "ffmpeg"; if (Extractor.isMac) resourceName += "-mac"; else { resourceName += Extractor.is64bit ? "64" : "32"; if (Extractor.isWindows) resourceName += ".exe"; } return resourceName; } /** Returns the FFmpeg shared library location. */ private static File getNativeLocation() { File customLocation = Options.getFFmpegLocation(); if (customLocation != null && customLocation.isFile()) return customLocation; else return FFMPEG_PATH; } /** Sets the directory in which to look for the FFmpeg shared library. */ public static void setNativeDir(File dir) { FFMPEG_PATH = new File(dir, DEFAULT_NATIVE_FILENAME); } /** Returns whether the FFmpeg shared library could be found. */ public static boolean exists() { File location = getNativeLocation(); return location != null && location.isFile(); } /** * Extracts the metadata for a video file. * @param srcMovieFile the source video file */ public static VideoMetadata extractMetadata(File srcMovieFile) throws IOException { Process process = new ProcessBuilder().command( getNativeLocation().getAbsolutePath(), "-i", srcMovieFile.getAbsolutePath(), "-f", "null" ).start(); Streams.asynchronousTransfer(process.getInputStream(), System.out, true, false); int width = -1, height = -1; float framerate = -1; try { InputStream stderr = process.getErrorStream(); BufferedReader br = new BufferedReader(new InputStreamReader(stderr)); for (String line; (line = br.readLine()) != null;) { // Look for: // " Stream #0:0: Video: vp6f, yuv420p, 320x240, 314 kb/s, 30 tbr, 1k tbn, 1k tbc" // ----------------------------------------------------------^ if (line.trim().startsWith("Stream #") && line.contains("Video:")) { framerate = Float.parseFloat(RegexUtil.findFirst(line, Pattern.compile("\\s(\\d+(\\.\\d+)?)\\stbr,"), 1)); int[] wh = TextValues.parseInts(RegexUtil.find(line, Pattern.compile("\\s(\\d+)x(\\d+)[\\s,]"), 1, 2)); width = wh[0]; height = wh[1]; } } if (framerate == -1) throw new IOException("Failed to find framerate of video."); return new VideoMetadata(width, height, framerate); } finally { Streams.safeClose(process); } } /** * Returns an RGB24 video stream. * @param srcMovieFile the source video file * @param msec the time offset (in milliseconds) */ public static InputStream extractVideoAsRGB24(File srcMovieFile, int msec) throws IOException { return streamData(new ProcessBuilder().command( getNativeLocation().getAbsolutePath(), "-ss", String.format("%d.%d", msec / 1000, msec % 1000), "-i", srcMovieFile.getAbsolutePath(), "-f", "rawvideo", "-pix_fmt", "rgb24", "-" )); } /** Returns a stream. */ private static InputStream streamData(ProcessBuilder pb) throws IOException { Process process = pb.start(); Streams.asynchronousTransfer(process.getErrorStream(), FFMPEG_VERBOSE ? System.err : new NullOutputStream(), true, false); return process.getInputStream(); } }