001package net.bramp.ffmpeg.progress; 002 003import static com.google.common.base.Preconditions.checkNotNull; 004import static net.bramp.ffmpeg.FFmpegUtils.fromTimecode; 005 006import com.google.common.base.MoreObjects; 007import java.util.Objects; 008import javax.annotation.CheckReturnValue; 009import net.bramp.ffmpeg.FFmpegUtils; 010import org.apache.commons.lang3.math.Fraction; 011import org.slf4j.Logger; 012import org.slf4j.LoggerFactory; 013 014// TODO Change to be immutable 015public class Progress { 016 017 static final Logger LOG = LoggerFactory.getLogger(Progress.class); 018 019 public enum Status { 020 CONTINUE("continue"), 021 END("end"); 022 023 private final String status; 024 025 Status(String status) { 026 this.status = status; 027 } 028 029 @Override 030 public String toString() { 031 return status; 032 } 033 034 /** 035 * Returns the canonical status for this String or throws a IllegalArgumentException. 036 * 037 * @param status the status to convert to a Status enum. 038 * @return the Status enum. 039 * @throws IllegalArgumentException if the status is unknown. 040 */ 041 public static Status of(String status) { 042 for (Status s : Status.values()) { 043 if (status.equalsIgnoreCase(s.status)) { 044 return s; 045 } 046 } 047 048 throw new IllegalArgumentException("invalid progress status '" + status + "'"); 049 } 050 } 051 052 /** The frame number being processed */ 053 public long frame = 0; 054 055 /** The current frames per second */ 056 public Fraction fps = Fraction.ZERO; 057 058 /** Current bitrate */ 059 public long bitrate = 0; 060 061 /** Output file size (in bytes) */ 062 public long total_size = 0; 063 064 /** Output time (in nanoseconds) */ 065 // TODO Change this to a java.time.Duration 066 public long out_time_ns = 0; 067 068 public long dup_frames = 0; 069 070 /** Number of frames dropped */ 071 public long drop_frames = 0; 072 073 /** Speed of transcoding. 1 means realtime, 2 means twice realtime. */ 074 public float speed = 0; 075 076 /** Current status, can be one of "continue", or "end" */ 077 public Status status = null; 078 079 public Progress() { 080 // Nothing 081 } 082 083 public Progress( 084 long frame, 085 float fps, 086 long bitrate, 087 long total_size, 088 long out_time_ns, 089 long dup_frames, 090 long drop_frames, 091 float speed, 092 Status status) { 093 this.frame = frame; 094 this.fps = Fraction.getFraction(fps); 095 this.bitrate = bitrate; 096 this.total_size = total_size; 097 this.out_time_ns = out_time_ns; 098 this.dup_frames = dup_frames; 099 this.drop_frames = drop_frames; 100 this.speed = speed; 101 this.status = status; 102 } 103 104 /** 105 * Parses values from the line, into this object. 106 * 107 * <p>The value options are defined in ffmpeg.c's print_report function 108 * https://github.com/FFmpeg/FFmpeg/blob/master/ffmpeg.c 109 * 110 * @param line A single line of output from ffmpeg 111 * @return true if the record is finished 112 */ 113 protected boolean parseLine(String line) { 114 line = checkNotNull(line).trim(); 115 if (line.isEmpty()) { 116 return false; // Skip empty lines 117 } 118 119 final String[] args = line.split("=", 2); 120 if (args.length != 2) { 121 // invalid argument, so skip 122 return false; 123 } 124 125 final String key = checkNotNull(args[0]); 126 final String value = checkNotNull(args[1]); 127 128 switch (key) { 129 case "frame": 130 frame = Long.parseLong(value); 131 return false; 132 133 case "fps": 134 fps = Fraction.getFraction(value); 135 return false; 136 137 case "bitrate": 138 if (value.equals("N/A")) { 139 bitrate = -1; 140 } else { 141 bitrate = FFmpegUtils.parseBitrate(value); 142 } 143 return false; 144 145 case "total_size": 146 if (value.equals("N/A")) { 147 total_size = -1; 148 } else { 149 total_size = Long.parseLong(value); 150 } 151 return false; 152 153 case "out_time_ms": 154 // This is a duplicate of the "out_time" field, but expressed as a int instead of string. 155 // Note this value is in microseconds, not milliseconds, and is based on AV_TIME_BASE which 156 // could change. 157 // out_time_ns = Long.parseLong(value) * 1000; 158 return false; 159 160 case "out_time_us": 161 return false; 162 163 case "out_time": 164 out_time_ns = fromTimecode(value); 165 return false; 166 167 case "dup_frames": 168 dup_frames = Long.parseLong(value); 169 return false; 170 171 case "drop_frames": 172 drop_frames = Long.parseLong(value); 173 return false; 174 175 case "speed": 176 if (value.equals("N/A")) { 177 speed = -1; 178 } else { 179 speed = Float.parseFloat(value.replace("x", "")); 180 } 181 return false; 182 183 case "progress": 184 // TODO After "end" stream is closed 185 status = Status.of(value); 186 return true; // The status field is always last in the record 187 188 default: 189 if (key.startsWith("stream_")) { 190 // TODO handle stream_0_0_q=0.0: 191 // stream_%d_%d_q= file_index, index, quality 192 // stream_%d_%d_psnr_%c=%2.2f, file_index, index, type{Y, U, V}, quality // Enable with 193 // AV_CODEC_FLAG_PSNR 194 // stream_%d_%d_psnr_all 195 } else { 196 LOG.warn("skipping unhandled key: {} = {}", key, value); 197 } 198 199 return false; // Either way, not supported 200 } 201 } 202 203 @CheckReturnValue 204 public boolean isEnd() { 205 return status == Status.END; 206 } 207 208 @Override 209 public boolean equals(Object o) { 210 if (this == o) return true; 211 if (o == null || getClass() != o.getClass()) return false; 212 Progress progress1 = (Progress) o; 213 return frame == progress1.frame 214 && bitrate == progress1.bitrate 215 && total_size == progress1.total_size 216 && out_time_ns == progress1.out_time_ns 217 && dup_frames == progress1.dup_frames 218 && drop_frames == progress1.drop_frames 219 && Float.compare(progress1.speed, speed) == 0 220 && Objects.equals(fps, progress1.fps) 221 && Objects.equals(status, progress1.status); 222 } 223 224 @Override 225 public int hashCode() { 226 return Objects.hash( 227 frame, fps, bitrate, total_size, out_time_ns, dup_frames, drop_frames, speed, status); 228 } 229 230 @Override 231 public String toString() { 232 return MoreObjects.toStringHelper(this) 233 .add("frame", frame) 234 .add("fps", fps) 235 .add("bitrate", bitrate) 236 .add("total_size", total_size) 237 .add("out_time_ns", out_time_ns) 238 .add("dup_frames", dup_frames) 239 .add("drop_frames", drop_frames) 240 .add("speed", speed) 241 .add("status", status) 242 .toString(); 243 } 244}