package com.cooleshow.ffmpegcmd.util; import android.content.Context; import com.cooleshow.ffmpegcmd.model.VideoLayout; import java.util.List; import java.util.Locale; /** * ffmpeg tool: assemble the complete command * Created by frank on 2018/1/23. */ public class FFmpegUtil { private static String[] insert(String[] cmd, int position, String inputPath) { return insert(cmd, position, inputPath, null); } /** * insert inputPath and outputPath into target array */ private static String[] insert(String[] cmd, int position, String inputPath, String outputPath) { if (cmd == null || inputPath == null || position < 2) { return cmd; } int len = (outputPath != null ? (cmd.length + 2) : (cmd.length + 1)); String[] result = new String[len]; System.arraycopy(cmd, 0, result, 0, position); result[position] = inputPath; System.arraycopy(cmd, position, result, position + 1, cmd.length - position); if (outputPath != null) { result[result.length - 1] = outputPath; } return result; } public static String[] insert(String[] cmd, int position1, String inputPath1, int position2, String inputPath2, String outputPath) { if (cmd == null || inputPath1 == null || position1 < 2 || inputPath2 == null || position2 < 4) { return cmd; } int len = (outputPath != null ? (cmd.length + 3) : (cmd.length + 2)); String[] result = new String[len]; System.arraycopy(cmd, 0, result, 0, position1); result[position1] = inputPath1; System.arraycopy(cmd, position1, result, position1 + 1, position2 - position1 - 1); result[position2] = inputPath2; System.arraycopy(cmd, position2 - 1, result, position2 + 1, cmd.length - (position2 - 1)); if (outputPath != null) { result[result.length - 1] = outputPath; } return result; } /** * transform audio, according to your assigning the output format * * @param inputPath input file * @param outputPath output file * @return transform success or not */ public static String[] transformAudio(String inputPath, String outputPath) { String[] result = new String[4]; result[0] = "ffmpeg"; result[1] = "-i"; result[2] = inputPath; result[3] = outputPath; return result; } public static String[] transformAudio(String inputPath, String acodec, String outputPath) { String transformAudioCmd = "ffmpeg -i -acodec %s -ac 2 -ar 44100"; transformAudioCmd = String.format(transformAudioCmd, acodec); return insert(transformAudioCmd.split(" "), 2, inputPath, outputPath); } /** * cut audio, you could assign the startTime and duration which you want to * * @param inputPath input file * @param startTime start time in the audio(unit is second) * @param duration start time(unit is second) * @param outputPath output file * @return cut success or not */ public static String[] cutAudio(String inputPath, float startTime, float duration, String outputPath) { String cutAudioCmd = "ffmpeg -i -ss %f -t %f -vn"; cutAudioCmd = String.format(Locale.getDefault(), cutAudioCmd, startTime, duration); return insert(cutAudioCmd.split(" "), 2, inputPath, outputPath); } public static String[] getAudioDuration(String input) { // String cmd = "ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 %s"; String cmd = "ffmpeg -i %s -hide_banner -loglevel grep Duration | cut -d ' ' -f 4 | sed 's/,//'"; // String cmd = "ffmpeg -i %s"; cmd = String.format(Locale.getDefault(), cmd, input); return cmd.split(" "); } /** * concat all the audio together * * @param fileList list of files * @param outputPath output file * @return concat success or not */ public static String[] concatAudio(List fileList, String outputPath) { if (fileList == null || fileList.size() == 0) { return null; } StringBuilder concatBuilder = new StringBuilder(); concatBuilder.append("concat:"); for (String file : fileList) { concatBuilder.append(file).append("|"); } String concatStr = concatBuilder.substring(0, concatBuilder.length() - 1); String concatAudioCmd = "ffmpeg -i -acodec copy"; // "ffmpeg -i -acodec libmp3lame" return insert(concatAudioCmd.split(" "), 2, concatStr, outputPath); } /** * mix multiple audio inputs into a single output */ public static String[] mixAudio(String inputPath, String mixPath, String outputPath) { //mixing formula: value = sample1 + sample2 - ((sample1 * sample2) >> 0x10) return mixAudio(inputPath, mixPath, 0, 0, false, outputPath); } public static String[] mixAudio(String inputPath, String mixPath, int weight1, int weight2, boolean disableThumb, String outputPath) { //duration: first shortest longest //weight: adjust weight(volume) of each audio stream //disableThumb:(-vn)if not disable, it may cause incorrect mixing int len = 8; String amix = "amix=inputs=2:duration=shortest"; if (disableThumb) len += 1; String[] mixAudioCmd = new String[len]; mixAudioCmd[0] = "ffmpeg"; mixAudioCmd[1] = "-i"; mixAudioCmd[2] = inputPath; mixAudioCmd[3] = "-i"; mixAudioCmd[4] = mixPath; mixAudioCmd[5] = "-filter_complex"; if (weight1 > 0 && weight2 > 0) { String weight = ":weights=" + "'" + weight1 + " " + weight2 + "'"; amix += weight; } mixAudioCmd[6] = amix; if (disableThumb) mixAudioCmd[7] = "-vn"; mixAudioCmd[len - 1] = outputPath; return mixAudioCmd; } public static String[] mixAudio(String inputPath, String mixPath, int weight1, int weight2, float volume1, float volume2, boolean disableThumb, String outputPath, int delay, int delay2) { //duration: first shortest longest //weight: adjust weight(volume) of each audio stream //disableThumb:(-vn)if not disable, it may cause incorrect mixing int len = 8; // String amix = "[1]adelay=%d|%d[b];[0][b]amix=inputs=2:duration=shortest"; // String amix = ""; // if (isShortestAligning) { // amix = "[0]volume=%f,adelay=%d|%d[a];[1]volume=%f,adelay=%d|%d[b];[a][b]amix=inputs=2:duration=shortest"; // } else { // amix = "[0]volume=%f,adelay=%d|%d[a];[1]volume=%f,adelay=%d|%d[b];[a][b]amix=inputs=2:duration=longest"; // } String amix = "[0]volume=%f,adelay=%d|%d[a];[1]volume=%f,adelay=%d|%d[b];[a][b]amix=inputs=2:duration=first"; amix = String.format(Locale.getDefault(), amix, volume1, delay, delay, volume2, delay2, delay2); if (disableThumb) len += 1; String[] mixAudioCmd = new String[len]; mixAudioCmd[0] = "ffmpeg"; mixAudioCmd[1] = "-i"; mixAudioCmd[2] = inputPath; mixAudioCmd[3] = "-i"; mixAudioCmd[4] = mixPath; mixAudioCmd[5] = "-filter_complex"; if (weight1 > 0 && weight2 > 0) { String weight = ":weights=" + "'" + weight1 + " " + weight2 + "'"; amix += weight; } mixAudioCmd[6] = amix; if (disableThumb) mixAudioCmd[7] = "-vn"; mixAudioCmd[len - 1] = outputPath; return mixAudioCmd; } /** * merge multiple audio streams into a single multi-channel stream */ public static String[] mergeAudio(String inputPath, String mergePath, String outputPath) { String mergeCmd = "ffmpeg -i %s -i %s -filter_complex [0:a][1:a]amerge=inputs=2[aout] -map [aout] %s"; mergeCmd = String.format(mergeCmd, inputPath, mergePath, outputPath); return mergeCmd.split(" "); } /** * Set echo and delay effect * * @param inputPath input file * @param delay delay to play * @param outputPath output file */ public static String[] audioEcho(String inputPath, int delay, String outputPath) { // in_gain (0, 1], Default is 0.6 // out_gain (0, 1], Default is 0.3 // delays (0 - 90000]. Default is 1000 // decays (0 - 1.0]. Default is 0.5 String echoCmd = "ffmpeg -i -af aecho=0.8:0.8:%d:0.5"; echoCmd = String.format(Locale.getDefault(), echoCmd, delay); return insert(echoCmd.split(" "), 2, inputPath, outputPath); } /** * Set tremolo effect, sinusoidal amplitude modulation * * @param inputPath input file * @param frequency frequency * @param depth depth * @param outputPath output file */ public static String[] audioTremolo(String inputPath, int frequency, float depth, String outputPath) { // frequency [0.1, 20000.0], Default is 5 // depth (0, 1], Default is 0.5 String tremoloCmd = "ffmpeg -i -af tremolo=%d:%f"; tremoloCmd = String.format(Locale.getDefault(), tremoloCmd, frequency, depth); return insert(tremoloCmd.split(" "), 2, inputPath, outputPath); } /** * Denoise audio samples with FFT * * @param inputPath input file * @param outputPath output file */ public static String[] audioDenoise(String inputPath, String outputPath) { // nr: noise reduction in dB, [0.01 to 97], Default value is 12 dB // nf: noise floor in dB, [-80 to -20], Default value is -50 dB // nt: noise type {w:white noise v:vinyl noise s:shellac noise} String fftDenoiseCmd = "ffmpeg -i -af afftdn"; return insert(fftDenoiseCmd.split(" "), 2, inputPath, outputPath); } /** * Detect silence of a chunk of audio * * @param inputPath input file */ public static String[] audioSilenceDetect(String inputPath) { // silence_start: 268.978 // silence_end: 271.048 | silence_duration: 2.06975 String silenceCmd = "ffmpeg -i -af silencedetect=noise=0.0001 -f null -"; return insert(silenceCmd.split(" "), 2, inputPath); } /** * Change volume of a chunk of audio * * @param inputPath input file * @param volume volume * @param outputPath output file */ public static String[] audioVolume(String inputPath, float volume, String outputPath) { // output_volume = volume * input_volume String volumeCmd = "ffmpeg -i -af volume=%f"; volumeCmd = String.format(Locale.getDefault(), volumeCmd, volume); return insert(volumeCmd.split(" "), 2, inputPath, outputPath); } /** * Apply 18 band of equalizer into audio * * @param inputPath input file * @param bandList bandList * @param outputPath output file */ public static String[] audioEqualizer(String inputPath, List bandList, String outputPath) { // unit: Hz gain:0-20 /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | 1b | 2b | 3b | 4b | 5b | 6b | 7b | 8b | 9b | | 65 | 92 | 131 | 185 | 262 | 370 | 523 | 740 | 1047 | |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | 10b | 11b | 12b | 13b | 14b | 15b | 16b | 17b | 18b | | 1480 | 2093 | 2960 | 4186 | 5920 | 8372 | 11840 | 16744 | 20000 | |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/ String bands = String.join(":", bandList); String equalizerCmd = "ffmpeg -i -af superequalizer=%s -y"; equalizerCmd = String.format(Locale.getDefault(), equalizerCmd, bands); return insert(equalizerCmd.split(" "), 2, inputPath, outputPath); } /** * mux audio and video together * * @param videoFile the file of pure video * @param audioFile the file of pure audio * @param copy copy codec * @param muxFile output file * @return mux success or not */ public static String[] mediaMux(String videoFile, String audioFile, boolean copy, String muxFile) { String mediaMuxCmd; if (copy) { mediaMuxCmd = "ffmpeg -i %s -i %s -codec copy -shortest -y %s"; } else { mediaMuxCmd = "ffmpeg -i %s -i %s -shortest -y %s"; } mediaMuxCmd = String.format(Locale.getDefault(), mediaMuxCmd, videoFile, audioFile, muxFile); return mediaMuxCmd.split(" "); } /** * mux audio and video together * * @param videoFile the file of pure video * @param audioFile the file of pure audio * @param copy copy codec * @param muxFile output file * @return mux success or not */ public static String[] mediaMux2(String videoFile, String audioFile, boolean copy, String muxFile, float videoDelay) { String mediaMuxCmd; if (copy) { mediaMuxCmd = "ffmpeg -itsoffset %f -i %s -itsoffset 0 -i %s -codec copy -shortest -y %s"; } else { mediaMuxCmd = "ffmpeg -itsoffset %f -i %s -itsoffset 0 -i %s -shortest -y %s"; } mediaMuxCmd = String.format(Locale.getDefault(), mediaMuxCmd, videoDelay, videoFile, audioFile, muxFile); return mediaMuxCmd.split(" "); } public static String[] getVideoFrame(String input, String output) { // String mediaMuxCmd = "ffmpeg -i %s -vf fps=1/1 %simg%03d.jpg"; String mediaMuxCmd = "ffmpeg -i %s -vf fps=1/1 %s"; mediaMuxCmd = String.format(Locale.getDefault(), mediaMuxCmd, input, output); return mediaMuxCmd.split(" "); } /** * extract audio from media file * * @param inputPath input file * @param outputPath output file * @return demux audio success or not */ public static String[] extractAudio(String inputPath, String outputPath) { // -vn: disable video // multi audio track: ffmpeg -i input.mp4 -map 0:1 -vn output.mp3 String extractAudioCmd = "ffmpeg -i -vn"; return insert(extractAudioCmd.split(" "), 2, inputPath, outputPath); } /** * extract pure video from media file * * @param inputPath input file * @param outputPath output file * @return demux video success or not */ public static String[] extractVideo(String inputPath, String outputPath) { //-an: disable audio String extractVideoCmd = "ffmpeg -i -vcodec copy -an"; return insert(extractVideoCmd.split(" "), 2, inputPath, outputPath); } /** * transform video, according to your assigning the output format * * @param inputPath input file * @param outputPath output file * @return transform video success or not */ public static String[] transformVideo(String inputPath, String outputPath) { //just copy codec // String transformVideoCmd = "ffmpeg -i %s -vcodec copy -acodec copy %s"; // assign the frameRate, bitRate and resolution // String transformVideoCmd = "ffmpeg -i %s -r 25 -b 200 -s 1080x720 %s"; // assign the encoder // ffmpeg -i %s -vcodec libx264 -acodec libmp3lame %s String transformVideoCmd = "ffmpeg -i -vcodec libx264 -acodec libmp3lame"; return insert(transformVideoCmd.split(" "), 2, inputPath, outputPath); } /** * Using FFmpeg to transform video, with re-encode * * @param inputPath the source file * @param outputPath target file * @return transform video success or not */ public static String[] transformVideoWithEncode(String inputPath, String outputPath) { return transformVideoWithEncode(inputPath, 0, 0, outputPath); } /** * Using FFmpeg to transform video, with re-encode and specific resolution * * @param inputPath the source file * @param width the width of video * @param height the height of video * @param outputPath target file * @return transform video success or not */ public static String[] transformVideoWithEncode(String inputPath, int width, int height, String outputPath) { String transformVideoCmd; if (width > 0 && height > 0) { String scale = "-vf scale=" + width + ":" + height; transformVideoCmd = "ffmpeg -i -vcodec libx264 -acodec aac " + scale; } else { transformVideoCmd = "ffmpeg -i -vcodec libx264 -acodec aac"; } return insert(transformVideoCmd.split(" "), 2, inputPath, outputPath); } /** * joint every single video together * * @param fileListPath the path file list * @param outputPath output path * @return joint video success or not */ public static String[] jointVideo(String fileListPath, String outputPath) { // ffmpeg -f concat -safe 0 -i %s -c copy %s String jointVideoCmd = "ffmpeg -f concat -safe 0 -i -c copy -y"; return insert(jointVideoCmd.split(" "), 6, fileListPath, outputPath); } /** * cut video, you could assign the startTime and duration which you want to * * @param inputPath input file * @param startTime startTime in the video(unit is second) * @param duration duration * @param outputPath output file * @return cut video success or not */ public static String[] cutVideo(String inputPath, float startTime, float duration, String outputPath) { // -map 0 -codec copy (copy all tracks) // -map 0:v -vcodec copy (copy track of video) // -map 0:a -acodec copy (copy all tracks of audio) // -map 0:s -scodec copy (copy all tracks of subtitle) // ffmpeg -ss %f -accurate_seek -t %f -i %s -map 0 -codec copy -avoid_negative_ts 1 %s String cutVideoCmd = "ffmpeg -ss %f -accurate_seek -t %f -i -map 0 -codec copy -avoid_negative_ts 1"; cutVideoCmd = String.format(Locale.getDefault(), cutVideoCmd, startTime, duration); return insert(cutVideoCmd.split(" "), 7, inputPath, outputPath); } /** * screenshot from video, you could assign the specific time * * @param inputPath input file * @param offset which time you want to shot * @param outputPath output file * @return screenshot success or not */ public static String[] screenShot(String inputPath, float offset, String outputPath) { // ffmpeg -ss %f -i %s -f image2 -vframes 1 -an %s String screenShotCmd = "ffmpeg -ss %f -i -f image2 -vframes 1 -an"; screenShotCmd = String.format(Locale.getDefault(), screenShotCmd, offset); return insert(screenShotCmd.split(" "), 4, inputPath, outputPath); } private static String obtainOverlay(int offsetX, int offsetY, int location) { switch (location) { case 2: return "overlay='(main_w-overlay_w)-" + offsetX + ":" + offsetY + "'"; case 3: return "overlay='" + offsetX + ":(main_h-overlay_h)-" + offsetY + "'"; case 4: return "overlay='(main_w-overlay_w)-" + offsetX + ":(main_h-overlay_h)-" + offsetY + "'"; case 1: default: // move from to right return "overlay=(10+t*20):" + offsetY; } } /** * add watermark with image to video, you could assign the location and bitRate * * @param inputPath input file * @param imgPath the path of the image * @param location the location in the video(1:top left 2:top right 3:bottom left 4:bottom right) * @param bitRate bitRate * @param offsetXY the offset of x and y in the video * @param outputPath output file * @return add watermark success or not */ public static String[] addWaterMarkImg(String inputPath, String imgPath, int location, int bitRate, int offsetXY, String outputPath) { String mBitRate = bitRate + "k"; String overlay = obtainOverlay(offsetXY, offsetXY, location); String waterMarkCmd = "ffmpeg -i -i -b:v %s -filter_complex %s -preset:v superfast -y"; waterMarkCmd = String.format(waterMarkCmd, mBitRate, overlay); return insert(waterMarkCmd.split(" "), 2, inputPath, 4, imgPath, outputPath); } /** * add watermark with gif to video, you could assign the location and bitRate * * @param inputPath input file * @param imgPath the path of the gif * @param location the location in the video(1:top left 2:top right 3:bottom left 4:bottom right) * @param bitRate bitRate * @param offsetXY the offset of x and y in the video * @param outputPath output file * @return add watermark success or not */ public static String[] addWaterMarkGif(Context context, String inputPath, String imgPath, int location, int bitRate, int offsetXY, String outputPath) { String mBitRate = bitRate + "k"; int offset = ScreenUtil.INSTANCE.dp2px(context, offsetXY); String overlay = obtainOverlay(offset, offset, location) + ":shortest=1"; String waterMarkCmd = "ffmpeg -i -ignore_loop 0 -i -b:v %s -filter_complex %s -preset:v superfast"; waterMarkCmd = String.format(waterMarkCmd, mBitRate, overlay); return insert(waterMarkCmd.split(" "), 2, inputPath, 6, imgPath, outputPath); } /** * Remove watermark from video: Suppress logo by a simple interpolation of the surrounding pixels. * On the other hand, it can be used to mosaic video * * @return delogo cmd */ public static String[] removeLogo(String inputPath, int x, int y, int width, int height, String outputPath) { // ffmpeg -i in.mp4 -filter_complex delogo=x=%d:y=%d:w=%d:h=%d out.mp4 String delogoCmd = "ffmpeg -i -filter_complex delogo=x=%d:y=%d:w=%d:h=%d"; delogoCmd = String.format(Locale.getDefault(), delogoCmd, x, y, width, height); return insert(delogoCmd.split(" "), 2, inputPath, outputPath); } /** * generate a palette for gif * * @param inputPath input file * @param frameRate frameRate of the gif * @param startTime startTime in the video * @param duration duration, how long you want to * @param width width * @param outputPath output file * @return generate palette success or not */ public static String[] generatePalette(String inputPath, int startTime, int duration, int frameRate, int width, String outputPath) { String paletteCmd = "ffmpeg -ss %d -accurate_seek -t %d -i -vf fps=%d,scale=%d:-1:flags=lanczos,palettegen"; paletteCmd = String.format(Locale.getDefault(), paletteCmd, startTime, duration, frameRate, width); return insert(paletteCmd.split(" "), 7, inputPath, outputPath); } /** * convert video into gif with palette * * @param inputPath input file * @param palette the palette which will apply to gif * @param startTime startTime in the video * @param duration duration, how long you want to * @param frameRate frameRate of the gif * @param width width * @param outputPath output gif * @return convert gif success or not */ public static String[] generateGifByPalette(String inputPath, String palette, int startTime, int duration, int frameRate, int width, String outputPath) { String paletteGifCmd = "ffmpeg -ss %d -accurate_seek -t %d -i -i -lavfi fps=%d,scale=%d:-1:flags=lanczos[x];[x][1:v]" + "paletteuse=dither=bayer:bayer_scale=3"; paletteGifCmd = String.format(Locale.getDefault(), paletteGifCmd, startTime, duration, frameRate, width); return insert(paletteGifCmd.split(" "), 7, inputPath, 9, palette, outputPath); } /** * convert s series of pictures into video * * @param inputPath input file * @param frameRate frameRate * @param outputPath output file * @return convert success or not */ public static String[] pictureToVideo(String inputPath, int frameRate, String outputPath) { //-f: stand for format String combineVideo = "ffmpeg -f image2 -r %d -i %simg#d.jpg -vcodec mpeg4 %s"; combineVideo = String.format(Locale.getDefault(), combineVideo, frameRate, inputPath, outputPath); combineVideo = combineVideo.replace("#", "%"); return combineVideo.split(" "); } /** * convert resolution * * @param inputPath input file * @param resolution resolution * @param outputPath output file * @return convert success or not */ public static String[] convertResolution(String inputPath, String resolution, String outputPath) { String convertCmd = "ffmpeg -i -s %s -pix_fmt yuv420p"; convertCmd = String.format(Locale.getDefault(), convertCmd, resolution); return insert(convertCmd.split(" "), 2, inputPath, outputPath); } /** * encode audio, you could assign the sampleRate and channel * * @param inputPath pcm raw audio * @param outputPath output file * @param sampleRate sampleRate * @param channel sound channel: mono channel is 1, stereo channel is 2 * @return encode audio success or not */ public static String[] encodeAudio(String inputPath, String outputPath, int sampleRate, int channel) { String encodeAudioCmd = "ffmpeg -f s16le -ar %d -ac %d -i"; encodeAudioCmd = String.format(Locale.getDefault(), encodeAudioCmd, sampleRate, channel); return insert(encodeAudioCmd.split(" "), 8, inputPath, outputPath); } /** * join multi videos together * * @param input1 input one * @param input2 input two * @param videoLayout the layout of video, which could be horizontal or vertical * @param outputPath output file * @return join success or not */ public static String[] multiVideo(String input1, String input2, String outputPath, int videoLayout) { String multiVideo = "ffmpeg -i -i -filter_complex hstack";//hstack: horizontal if (videoLayout == VideoLayout.LAYOUT_VERTICAL) {//vstack: vertical multiVideo = multiVideo.replace("hstack", "vstack"); } return insert(multiVideo.split(" "), 2, input1, 4, input2, outputPath); } /** * reverse video * * @param inputPath input file * @param outputPath output file * @return reverse success or not */ public static String[] reverseVideo(String inputPath, String outputPath) { //-vf reverse: only video reverse, -an: disable audio //tip: reverse will cost a lot of time, only short video are recommended String reverseVideo = "ffmpeg -i -vf reverse -an"; return insert(reverseVideo.split(" "), 2, inputPath, outputPath); } /** * noise reduction with video * * @param inputPath input file * @param outputPath output file * @return noise reduction success or not */ public static String[] denoiseVideo(String inputPath, String outputPath) { String denoiseVideo = "ffmpeg -i -nr 500"; return insert(denoiseVideo.split(" "), 2, inputPath, outputPath); } /** * covert video into a series of pictures * * @param inputPath input file * @param startTime startTime in the video * @param duration duration, how long you want * @param frameRate frameRate * @param outputPath output file * @return convert success or not */ public static String[] videoToImage(String inputPath, int startTime, int duration, int frameRate, String outputPath) { return videoToImageWithScale(inputPath, startTime, duration, frameRate, 0, outputPath); } public static String[] videoToImageWithScale(String inputPath, int startTime, int duration, int frameRate, int width, String outputPath) { String toImage; if (width > 0) { toImage = "ffmpeg -ss %d -accurate_seek -t %d -i %s -an -vf fps=%d,scale=%d:-1 %s"; toImage = String.format(Locale.CHINESE, toImage, startTime, duration, inputPath, frameRate, width, outputPath); } else { toImage = "ffmpeg -ss %d -accurate_seek -t %d -i %s -an -r %d %s"; toImage = String.format(Locale.CHINESE, toImage, startTime, duration, inputPath, frameRate, outputPath); } toImage = toImage + "%3d.png"; return toImage.split(" "); } /** * convert videos into picture-in-picture mode * * @param inputPath1 input one * @param inputPath2 input two * @param outputPath output file * @param x x coordinate point * @param y y coordinate point * @return convert success or not */ public static String[] picInPicVideo(String inputPath1, String inputPath2, int x, int y, String outputPath) { String pipVideo = "ffmpeg -i -i -filter_complex overlay=%d:%d"; pipVideo = String.format(Locale.getDefault(), pipVideo, x, y); return insert(pipVideo.split(" "), 2, inputPath1, 4, inputPath2, outputPath); } /** * convert videos into picture-in-picture mode * * @param whichCorner 0: top-left; 1: top-right; 2: bottom-left; 3: bottom-right * @return convert success or not */ public static String[] picInPicVideoInCorner(String inputPath1, String inputPath2, int whichCorner, String outputPath) { String pipCmd = "ffmpeg -i -i -filter_complex "; switch (whichCorner) { case 1: pipCmd += "overlay=W-w"; break; case 2: pipCmd += "overlay=0:H-h"; break; case 3: pipCmd += "overlay=W-w:H-h"; break; case 0: default: pipCmd += "overlay"; break; } return insert(pipCmd.split(" "), 2, inputPath1, 4, inputPath2, outputPath); } /** * move moov box in the front of mdat box, when moox box is behind mdat box(only mp4) * * @param inputPath inputFile * @param outputPath outputFile * @return move success or not */ public static String[] moveMoovAhead(String inputPath, String outputPath) { String moovCmd = "ffmpeg -i -movflags faststart -acodec copy -vcodec copy"; return insert(moovCmd.split(" "), 2, inputPath, outputPath); } /** * Change Video from RGB to gray(black & white) * * @param inputPath inputPath * @param outputPath outputPath * @return grayCmd */ public static String[] toGrayVideo(String inputPath, String outputPath) { String grayCmd = "ffmpeg -i -vf lutyuv='u=128:v=128'"; return insert(grayCmd.split(" "), 2, inputPath, outputPath); } /** * Photo zoom to video * * @param inputPath inputPath * @param position position * 0:center * 1:left to right * 2:right to left * 3:down to up * 4:up to down * @param outputPath outputPath * @return zoomCmd */ public static String[] photoZoomToVideo(String inputPath, int position, String outputPath) { String zoomCmd = "ffmpeg -i -vf "; switch (position) { case 1: zoomCmd += "zoompan='1.3':x='if(lte(on,1),(iw/zoom)/2,x-0.5)':y='if(lte(on,1),(ih-ih/zoom)/2,y)':d=180"; break; case 2: zoomCmd += "zoompan='1.3':x='if(lte(on,-1),(iw-iw/zoom)/2,x+0.5)':y='if(lte(on,1),(ih-ih/zoom)/2,y)':d=180"; break; case 3: zoomCmd += "zoompan='1.3':x='if(lte(on,1),(iw-iw/zoom)/2,x)':y='if(lte(on,-1),(ih-ih/zoom)/2,y+0.5)':d=180"; break; case 4: zoomCmd += "zoompan='1.3':x='if(lte(on,1),(iw-iw/zoom)/2,x)':y='if(lte(on,1),(ih/zoom)/2,y-0.5)':d=180"; break; case 0: default: zoomCmd += "zoompan=z='min(zoom+0.0015,1.5)':x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':d=180"; break; } return insert(zoomCmd.split(" "), 2, inputPath, outputPath); } /** * using FFprobe to parse the media format * * @param inputPath inputFile * @return probe success or not */ public static String[] probeFormat(String inputPath) { // ffprobe -i hello.mp4 -show_streams -show_format -print_format json" String ffprobeCmd = "ffprobe -i -show_streams -show_format -print_format json"; return insert(ffprobeCmd.split(" "), 2, inputPath); } /** * Changing the speed of playing, speed range at 0.5-2 in audio-video mode. * However, in pure video mode, the speed range at 0.25-4 * * @param inputPath the inputFile of normal speed * @param outputPath the outputFile which you want to change speed * @param speed speed of playing * @param pureVideo whether pure video or not, default false * @return change speed success or not */ public static String[] changeSpeed(String inputPath, String outputPath, float speed, boolean pureVideo) { //audio atempo: 0.5--2 //video pts:0.25--4 if (pureVideo) { if (speed > 4 || speed < 0.25) { throw new IllegalArgumentException("speed range is 0.25--4"); } } else { if (speed > 2 || speed < 0.5) { throw new IllegalArgumentException("speed range is 0.5--2"); } } float ptsFactor = 1 / speed; String speedCmd; if (pureVideo) { speedCmd = "ffmpeg -i -filter_complex [0:v]setpts=%.2f*PTS[v] -map [v]"; speedCmd = String.format(Locale.getDefault(), speedCmd, ptsFactor); } else { speedCmd = "ffmpeg -i -filter_complex [0:v]setpts=%.2f*PTS[v];[0:a]atempo=%.2f[a] -map [v] -map [a]"; speedCmd = String.format(Locale.getDefault(), speedCmd, ptsFactor, speed); } return insert(speedCmd.split(" "), 2, inputPath, outputPath); } /** * Changing the speed of playing, speed range at 0.5-2 in audio. * * @param inputPath the inputFile of normal speed * @param outputPath the outputFile which you want to change speed * @param speed speed of playing * @return change speed success or not */ public static String[] changeAudioSpeed(String inputPath, String outputPath, float speed) { // atempo range [0.5, 100.0] if (speed > 100 || speed < 0.5) { throw new IllegalArgumentException("audio speed range is from 0.5 to 100"); } String speedCmd = "ffmpeg -i -filter_complex atempo=%.2f -c:a copy -b:a 192k"; speedCmd = String.format(Locale.getDefault(), speedCmd, speed); return insert(speedCmd.split(" "), 2, inputPath, outputPath); } public static String[] changeAudioSpeed2(String inputPath, String outputPath, float speed) { // atempo range [0.5, 100.0] if (speed > 100) { throw new IllegalArgumentException("audio speed range is from 0.5 to 100"); } if (speed < 0.5) { String s = buildAtempoFilters(speed); String command = "ffmpeg -i -filter:a %s -b:a 192k"; String format = String.format(Locale.getDefault(), command, s); return insert(format.split(" "), 2, inputPath, outputPath); } else { String speedCmd = "ffmpeg -i -filter:a atempo=%.2f -b:a 192k"; speedCmd = String.format(Locale.getDefault(), speedCmd, speed); return insert(speedCmd.split(" "), 2, inputPath, outputPath); } } public static String[] changeAudioSpeed3(String inputPath, String outputPath, float speed) { if (speed > 100.0F) { throw new IllegalArgumentException("audio speed range is from 0.5 to 100"); } else { String speedCmd; if ((double)speed < 0.5) { speedCmd = buildAtempoFilters((double)speed); String command = "ffmpeg -i -filter:a %s -b:a 192k -ar 44100 -ac 2 -acodec pcm_s16le"; String format = String.format(Locale.getDefault(), command, speedCmd); return insert(format.split(" "), 2, inputPath, outputPath); } else { speedCmd = "ffmpeg -i -filter:a atempo=%.2f -b:a 192k -ar 44100 -ac 2 -acodec pcm_s16le"; speedCmd = String.format(Locale.getDefault(), speedCmd, speed); return insert(speedCmd.split(" "), 2, inputPath, outputPath); } } } private static String buildAtempoFilters(double speed) { StringBuilder filters = new StringBuilder(); double tempSpeed = speed; // 分解速度调整,FFmpeg的atempo值只能在0.5到2.0之间 while (tempSpeed < 0.5) { filters.append("atempo=0.5,"); tempSpeed *= 2; } filters.append(String.format("atempo=%.2f", tempSpeed)); return filters.toString(); } /** * Insert the picture into the header of video, which as a thumbnail * * @param inputPath inputFile * @param picturePath the path of thumbnail * @param outputPath targetFile * @return command of inserting picture */ public static String[] insertPicIntoVideo(String inputPath, String picturePath, String outputPath) { String insertPicCmd = "ffmpeg -i -i -map 0 -map 1 -c copy -c:v:1 png -disposition:v:1 attached_pic"; return insert(insertPicCmd.split(" "), 2, inputPath, 4, picturePath, outputPath); } public static String[] addSubtitleIntoVideo(String inputPath, String subtitlePath, String outputPath) { String subtitleCmd = "ffmpeg -i -i -map 0:v -map 0:a -map 1:s -c copy"; return insert(subtitleCmd.split(" "), 2, inputPath, 4, subtitlePath, outputPath); } /** * Using one input file to push multi streams. * After publish the streams, you could use VLC to play it * Note: if stream is rtmp protocol, need to start your rtmp server * Note: if stream is http protocol, need to start your http server * * @param inputPath inputFile * @param duration how long of inputFile you want to publish * @param streamUrl1 the url of stream1 * @param streamUrl2 the url of stream2 * @return command of build flv index */ public static String[] pushMultiStreams(String inputPath, int duration, String streamUrl1, String streamUrl2) { //ffmpeg -i what.mp4 -vcodec libx264 -acodec aac -t 60 -f flv //"tee:rtmp://192.168.1.102/live/stream1|rtmp://192.168.1.102/live/stream2" String format = "flv"; if (streamUrl1.startsWith("rtmp://")) {//rtmp protocol format = "flv"; } else if (streamUrl1.startsWith("http://")) {//http protocol format = "mpegts"; } String pushStreams = "ffmpeg -i %s -vcodec libx264 -acodec aac -t %d -f %s \"tee:%s|%s\""; pushStreams = String.format(Locale.getDefault(), pushStreams, inputPath, duration, format, streamUrl1, streamUrl2); return pushStreams.split(" "); } public static String[] rotateVideo(String inputPath, int rotateDegree, String outputPath) { String rotateCmd = "ffmpeg -i -c copy -metadata:s:v:0 rotate=%d"; rotateCmd = String.format(Locale.getDefault(), rotateCmd, rotateDegree); return insert(rotateCmd.split(" "), 2, inputPath, outputPath); } /** * Trim one or more segments from a single video * * @param inputPath the path of input file * @param start1 start time of the first segment * @param end1 end time of the first segment * @param start2 start time of the second segment * @param end2 end time of the second segment * @param outputPath the path of output file * @return the command of trim video */ public static String[] trimVideo(String inputPath, int start1, int end1, int start2, int end2, String outputPath) { String trimCmd = "ffmpeg -i -filter_complex " + "[0:v]trim=start=%d:end=%d,setpts=PTS-STARTPTS[v0];" + "[0:a]atrim=start=%d:end=%d,asetpts=PTS-STARTPTS[a0];" + "[0:v]trim=start=%d:end=%d,setpts=PTS-STARTPTS[v1];" + "[0:a]atrim=start=%d:end=%d,asetpts=PTS-STARTPTS[a1];" + "[v0][a0][v1][a1]concat=n=2:v=1:a=1[out] -map [out]"; trimCmd = String.format(Locale.getDefault(), trimCmd, start1, end1, start1, end1, start2, end2, start2, end2); return insert(trimCmd.split(" "), 2, inputPath, outputPath); } public static String[] showAudioWaveform(String inputPath, String resolution, int splitChannels, String outputPath) { String waveformCmd = "ffmpeg -i -filter_complex showwavespic=s=%s:split_channels=%d"; waveformCmd = String.format(Locale.getDefault(), waveformCmd, resolution, splitChannels); return insert(waveformCmd.split(" "), 2, inputPath, outputPath); } /** * Convert video into stereo3d mode(VR video) * * @param inputPath inputPath * @param outputPath outputPath * @return the command of stereo3d */ public static String[] videoStereo3D(String inputPath, String outputPath) { /* * * sbsl: side by side parallel (left eye left, right eye right) * sbsr: side by side crosseye (right eye left, left eye right) * abl : above-below (left eye above, right eye below) * al : alternating frames (left eye first, right eye second) * irl : interleaved rows (left eye has top row, right eye next) * icl : interleaved columns, left eye first */ String stereo3dCmd = "ffmpeg -i -filter_complex stereo3d=sbsl:arbg"; return insert(stereo3dCmd.split(" "), 2, inputPath, outputPath); } public static String[] videoTransition(String inputPath1, int width, int height, int offset, String inputPath2, String outputPath) { String transitionCmd = "ffmpeg -i -i -filter_complex " + "[0]settb=AVTB,fps=24000/1001[v0];[1]settb=AVTB,fps=24000/1001,scale=%d:%d[v1];" + "[v0][v1]xfade=transition=radial:duration=1:offset=%d"; transitionCmd = String.format(Locale.getDefault(), transitionCmd, width, height, offset); return insert(transitionCmd.split(" "), 2, inputPath1, 4, inputPath2, outputPath); } }