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}