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": 161 out_time_ns = fromTimecode(value); 162 return false; 163 164 case "dup_frames": 165 dup_frames = Long.parseLong(value); 166 return false; 167 168 case "drop_frames": 169 drop_frames = Long.parseLong(value); 170 return false; 171 172 case "speed": 173 if (value.equals("N/A")) { 174 speed = -1; 175 } else { 176 speed = Float.parseFloat(value.replace("x", "")); 177 } 178 return false; 179 180 case "progress": 181 // TODO After "end" stream is closed 182 status = Status.of(value); 183 return true; // The status field is always last in the record 184 185 default: 186 if (key.startsWith("stream_")) { 187 // TODO handle stream_0_0_q=0.0: 188 // stream_%d_%d_q= file_index, index, quality 189 // stream_%d_%d_psnr_%c=%2.2f, file_index, index, type{Y, U, V}, quality // Enable with 190 // AV_CODEC_FLAG_PSNR 191 // stream_%d_%d_psnr_all 192 } else { 193 LOG.warn("skipping unhandled key: {} = {}", key, value); 194 } 195 196 return false; // Either way, not supported 197 } 198 } 199 200 @CheckReturnValue 201 public boolean isEnd() { 202 return status == Status.END; 203 } 204 205 @Override 206 public boolean equals(Object o) { 207 if (this == o) return true; 208 if (o == null || getClass() != o.getClass()) return false; 209 Progress progress1 = (Progress) o; 210 return frame == progress1.frame 211 && bitrate == progress1.bitrate 212 && total_size == progress1.total_size 213 && out_time_ns == progress1.out_time_ns 214 && dup_frames == progress1.dup_frames 215 && drop_frames == progress1.drop_frames 216 && Float.compare(progress1.speed, speed) == 0 217 && Objects.equals(fps, progress1.fps) 218 && Objects.equals(status, progress1.status); 219 } 220 221 @Override 222 public int hashCode() { 223 return Objects.hash( 224 frame, fps, bitrate, total_size, out_time_ns, dup_frames, drop_frames, speed, status); 225 } 226 227 @Override 228 public String toString() { 229 return MoreObjects.toStringHelper(this) 230 .add("frame", frame) 231 .add("fps", fps) 232 .add("bitrate", bitrate) 233 .add("total_size", total_size) 234 .add("out_time_ns", out_time_ns) 235 .add("dup_frames", dup_frames) 236 .add("drop_frames", drop_frames) 237 .add("speed", speed) 238 .add("status", status) 239 .toString(); 240 } 241}