001package net.bramp.ffmpeg.job;
002
003import static com.google.common.base.Preconditions.checkNotNull;
004
005import com.google.common.base.Throwables;
006import java.io.IOException;
007import java.nio.file.DirectoryStream;
008import java.nio.file.Files;
009import java.nio.file.Path;
010import java.nio.file.Paths;
011import java.util.List;
012import java.util.UUID;
013import javax.annotation.Nullable;
014import net.bramp.ffmpeg.FFmpeg;
015import net.bramp.ffmpeg.builder.FFmpegBuilder;
016import net.bramp.ffmpeg.progress.ProgressListener;
017
018/** An FFmpeg job that performs two encoding passes for improved quality. */
019public class TwoPassFFmpegJob extends FFmpegJob {
020
021  final String passlogPrefix;
022  final FFmpegBuilder builder;
023
024  /** Constructs a new two-pass FFmpeg job. */
025  public TwoPassFFmpegJob(FFmpeg ffmpeg, FFmpegBuilder builder) {
026    this(ffmpeg, builder, null);
027  }
028
029  /** Creates a new two-pass FFmpeg job with the given progress listener. */
030  public TwoPassFFmpegJob(
031      FFmpeg ffmpeg, FFmpegBuilder builder, @Nullable ProgressListener listener) {
032    super(ffmpeg, listener);
033
034    // Random prefix so multiple runs don't clash
035    this.passlogPrefix = UUID.randomUUID().toString();
036    this.builder = checkNotNull(builder).setPassPrefix(passlogPrefix);
037
038    // Build the args now (but throw away the results). This allows the illegal arguments to be
039    // caught early, but also allows the ffmpeg command to actually alter the arguments when
040    // running.
041    @SuppressWarnings("unused")
042    List<String> unused = this.builder.setPass(1).build();
043  }
044
045  /** Deletes the pass log files created during multi-pass encoding. */
046  protected void deletePassLog() throws IOException {
047    final Path cwd = Paths.get(builder.getPassDirectory());
048    try (DirectoryStream<Path> stream = Files.newDirectoryStream(cwd, passlogPrefix + "*.log*")) {
049      for (Path p : stream) {
050        Files.deleteIfExists(p);
051      }
052    }
053  }
054
055  @Override
056  public void run() {
057    state = State.RUNNING;
058
059    try {
060      try {
061        // Two pass
062        final boolean override = builder.getOverrideOutputFiles();
063
064        FFmpegBuilder b1 = builder.setPass(1).overrideOutputFiles(true);
065        ffmpeg.run(b1, listener);
066
067        FFmpegBuilder b2 = builder.setPass(2).overrideOutputFiles(override);
068        ffmpeg.run(b2, listener);
069
070      } finally {
071        deletePassLog();
072      }
073      state = State.FINISHED;
074
075    } catch (Throwable t) {
076      state = State.FAILED;
077
078      Throwables.throwIfUnchecked(t);
079      throw new RuntimeException(t);
080    }
081  }
082}