001package net.bramp.ffmpeg; 002 003import static com.google.common.base.Preconditions.checkArgument; 004import static java.util.concurrent.TimeUnit.*; 005import static net.bramp.ffmpeg.Preconditions.checkNotEmpty; 006 007import com.google.common.base.CharMatcher; 008import com.google.gson.Gson; 009import com.google.gson.GsonBuilder; 010import java.util.concurrent.TimeUnit; 011import java.util.regex.Matcher; 012import java.util.regex.Pattern; 013import net.bramp.commons.lang3.math.gson.FractionAdapter; 014import net.bramp.ffmpeg.gson.LowercaseEnumTypeAdapterFactory; 015import net.bramp.ffmpeg.gson.NamedBitsetAdapter; 016import net.bramp.ffmpeg.probe.FFmpegDisposition; 017import org.apache.commons.lang3.math.Fraction; 018 019/** Helper class with commonly used methods */ 020public final class FFmpegUtils { 021 022 static final Gson gson = FFmpegUtils.setupGson(); 023 static final Pattern BITRATE_REGEX = Pattern.compile("(\\d+(?:\\.\\d+)?)kbits/s"); 024 static final Pattern TIME_REGEX = Pattern.compile("(\\d+):(\\d+):(\\d+(?:\\.\\d+)?)"); 025 static final CharMatcher ZERO = CharMatcher.is('0'); 026 027 FFmpegUtils() { 028 throw new AssertionError("No instances for you!"); 029 } 030 031 /** 032 * Convert milliseconds to "hh:mm:ss.ms" String representation. 033 * 034 * @param milliseconds time duration in milliseconds 035 * @return time duration in human-readable format 036 * @deprecated please use #toTimecode() instead. 037 */ 038 @Deprecated 039 public static String millisecondsToString(long milliseconds) { 040 return toTimecode(milliseconds, MILLISECONDS); 041 } 042 043 /** 044 * Convert the duration to "hh:mm:ss" timecode representation, where ss (seconds) can be decimal. 045 * 046 * @param duration the duration. 047 * @param units the unit the duration is in. 048 * @return the timecode representation. 049 */ 050 public static String toTimecode(long duration, TimeUnit units) { 051 // FIXME Negative durations are also supported. 052 // https://www.ffmpeg.org/ffmpeg-utils.html#Time-duration 053 checkArgument(duration >= 0, "duration must be positive"); 054 055 long nanoseconds = units.toNanos(duration); // TODO This will clip at Long.MAX_VALUE 056 long seconds = units.toSeconds(duration); 057 long ns = nanoseconds - SECONDS.toNanos(seconds); 058 059 long minutes = SECONDS.toMinutes(seconds); 060 seconds -= MINUTES.toSeconds(minutes); 061 062 long hours = MINUTES.toHours(minutes); 063 minutes -= HOURS.toMinutes(hours); 064 065 if (ns == 0) { 066 return String.format("%02d:%02d:%02d", hours, minutes, seconds); 067 } 068 069 return ZERO.trimTrailingFrom(String.format("%02d:%02d:%02d.%09d", hours, minutes, seconds, ns)); 070 } 071 072 /** 073 * Returns the number of nanoseconds this timecode represents. The string is expected to be in the 074 * format "hour:minute:second", where second can be a decimal number. 075 * 076 * @param time the timecode to parse. 077 * @return the number of nanoseconds. 078 */ 079 public static long fromTimecode(String time) { 080 checkNotEmpty(time, "time must not be empty string"); 081 Matcher m = TIME_REGEX.matcher(time); 082 if (!m.find()) { 083 throw new IllegalArgumentException("invalid time '" + time + "'"); 084 } 085 086 long hours = Long.parseLong(m.group(1)); 087 long mins = Long.parseLong(m.group(2)); 088 double secs = Double.parseDouble(m.group(3)); 089 090 return HOURS.toNanos(hours) + MINUTES.toNanos(mins) + (long) (SECONDS.toNanos(1) * secs); 091 } 092 093 /** 094 * Converts a string representation of bitrate to a long of bits per second 095 * 096 * @param bitrate in the form of 12.3kbits/s 097 * @return the bitrate in bits per second or -1 if bitrate is 'N/A' 098 */ 099 public static long parseBitrate(String bitrate) { 100 if ("N/A".equals(bitrate)) { 101 return -1; 102 } 103 Matcher m = BITRATE_REGEX.matcher(bitrate); 104 if (!m.find()) { 105 throw new IllegalArgumentException("Invalid bitrate '" + bitrate + "'"); 106 } 107 108 return (long) (Float.parseFloat(m.group(1)) * 1000); 109 } 110 111 static Gson getGson() { 112 return gson; 113 } 114 115 private static Gson setupGson() { 116 GsonBuilder builder = new GsonBuilder(); 117 118 builder.registerTypeAdapterFactory(new LowercaseEnumTypeAdapterFactory()); 119 builder.registerTypeAdapter(Fraction.class, new FractionAdapter()); 120 builder.registerTypeAdapter( 121 FFmpegDisposition.class, new NamedBitsetAdapter<>(FFmpegDisposition.class)); 122 123 return builder.create(); 124 } 125}