001package net.bramp.ffmpeg.builder;
002
003import static com.google.common.base.Preconditions.checkArgument;
004import static com.google.common.base.Preconditions.checkNotNull;
005import static net.bramp.ffmpeg.FFmpegUtils.millisToSeconds;
006import static net.bramp.ffmpeg.Preconditions.checkNotEmpty;
007
008import com.google.common.base.Strings;
009import com.google.common.collect.ImmutableList;
010import java.util.ArrayList;
011import java.util.List;
012import java.util.concurrent.TimeUnit;
013import javax.annotation.CheckReturnValue;
014
015/** Builder for FFmpeg HLS (HTTP Live Streaming) output arguments. */
016public class FFmpegHlsOutputBuilder extends AbstractFFmpegOutputBuilder<FFmpegHlsOutputBuilder> {
017
018  public Long hls_time;
019  public String hls_segment_filename;
020  public Long hls_init_time;
021  public Integer hls_list_size;
022  public String hls_base_url;
023  public String hls_playlist_type;
024  public String master_pl_name;
025  public String var_stream_map;
026
027  private final List<HlsVariant> variants = new ArrayList<>();
028
029  /** Constructs a new HLS output builder with the given filename. */
030  protected FFmpegHlsOutputBuilder(FFmpegBuilder parent, String filename) {
031    super(parent, filename);
032    this.format = "hls";
033  }
034
035  @Override
036  public FFmpegHlsOutputBuilder setFormat(String format) {
037    if (format == null || !format.equals("hls")) {
038      throw new IllegalArgumentException(
039          "Format cannot be set to anything else except 'hls' for FFmpegHlsOutputBuilder");
040    }
041    super.setFormat(format);
042
043    return this;
044  }
045
046  /**
047   * Set the target segment length. Default value is 2 seconds.
048   *
049   * @param duration hls_time to set
050   * @param units The units the offset is in
051   * @return {@link FFmpegHlsOutputBuilder}
052   */
053  public FFmpegHlsOutputBuilder setHlsTime(long duration, TimeUnit units) {
054    checkNotNull(units);
055    this.hls_time = units.toMillis(duration);
056
057    return this;
058  }
059
060  /**
061   * hls_segment_filename Examples <br>
062   * <br>
063   * "file%03d.ts" segment files: file000.ts, file001.ts, file002.ts, etc.
064   *
065   * @param filename hls_segment_file_name to set
066   * @return {@link FFmpegHlsOutputBuilder}
067   */
068  public FFmpegHlsOutputBuilder setHlsSegmentFileName(String filename) {
069    this.hls_segment_filename = checkNotEmpty(filename, "filename must not be empty");
070
071    return this;
072  }
073
074  /**
075   * <strong>Segment will be cut on the next key frame after this time has passed on the first m3u8
076   * list.</strong> <br>
077   *
078   * @param duration hls_init_time to set
079   * @param units The units the offset is in
080   * @return {@link FFmpegHlsOutputBuilder}
081   */
082  public FFmpegHlsOutputBuilder setHlsInitTime(long duration, TimeUnit units) {
083    checkNotNull(units);
084    this.hls_init_time = units.toMillis(duration);
085
086    return this;
087  }
088
089  /**
090   * <strong>Set the maximum number of playlist entries. If set to 0 the list file will contain all
091   * the segments .</strong> <br>
092   * Default value is 5 <br>
093   *
094   * @param size hls_time to set
095   * @return {@link FFmpegHlsOutputBuilder}
096   */
097  public FFmpegHlsOutputBuilder setHlsListSize(int size) {
098    checkArgument(size >= 0, "Size cannot be less than 0.");
099    this.hls_list_size = size;
100
101    return this;
102  }
103
104  /**
105   * <strong>Append baseurl to every entry in the playlist. Useful to generate playlists with
106   * absolute paths. <br>
107   * Note that the playlist sequence number must be unique for each segment and it is not to be
108   * confused with the segment filename sequence number which can be cyclic, for example if the wrap
109   * option is specified.</strong> <br>
110   *
111   * @param baseurl hls_base_url to set
112   * @return {@link FFmpegHlsOutputBuilder}
113   */
114  public FFmpegHlsOutputBuilder setHlsBaseUrl(String baseurl) {
115    this.hls_base_url = checkNotEmpty(baseurl, "baseurl must not be empty");
116
117    return this;
118  }
119
120  /**
121   * Set the playlist type.
122   *
123   * @param type The playlist type (e.g. "event", "vod")
124   * @return {@link FFmpegHlsOutputBuilder}
125   */
126  public FFmpegHlsOutputBuilder setHlsPlaylistType(String type) {
127    this.hls_playlist_type = checkNotEmpty(type, "type must not be empty");
128    return this;
129  }
130
131  /**
132   * Set the master playlist name.
133   *
134   * @param name The master playlist name
135   * @return {@link FFmpegHlsOutputBuilder}
136   */
137  public FFmpegHlsOutputBuilder setMasterPlName(String name) {
138    this.master_pl_name = checkNotEmpty(name, "name must not be empty");
139    return this;
140  }
141
142  /**
143   * Set the variant stream map string manually.
144   *
145   * <p>Prefer using {@link #addVariant(HlsVariant)} for a cleaner API.
146   *
147   * @param map The variant stream map (e.g. "v:0,a:0 v:1,a:1")
148   * @return {@link FFmpegHlsOutputBuilder}
149   */
150  public FFmpegHlsOutputBuilder setVarStreamMap(String map) {
151    this.var_stream_map = checkNotEmpty(map, "map must not be empty");
152    return this;
153  }
154
155  /**
156   * Adds an HLS variant to this output.
157   *
158   * @param variant The variant configuration.
159   * @return {@link FFmpegHlsOutputBuilder}
160   */
161  public FFmpegHlsOutputBuilder addVariant(HlsVariant variant) {
162    this.variants.add(checkNotNull(variant));
163    return this;
164  }
165
166  @Override
167  protected void addFormatArgs(ImmutableList.Builder<String> args) {
168    super.addFormatArgs(args);
169    if (hls_time != null) {
170      args.add("-hls_time", millisToSeconds(hls_time));
171    }
172
173    if (!Strings.isNullOrEmpty(hls_segment_filename)) {
174      args.add("-hls_segment_filename", hls_segment_filename);
175    }
176
177    if (hls_init_time != null) {
178      args.add("-hls_init_time", millisToSeconds(hls_init_time));
179    }
180
181    if (hls_list_size != null) {
182      args.add("-hls_list_size", hls_list_size.toString());
183    }
184
185    if (!Strings.isNullOrEmpty(hls_base_url)) {
186      args.add("-hls_base_url", hls_base_url);
187    }
188
189    if (!Strings.isNullOrEmpty(hls_playlist_type)) {
190      args.add("-hls_playlist_type", hls_playlist_type);
191    }
192
193    if (!Strings.isNullOrEmpty(master_pl_name)) {
194      args.add("-master_pl_name", master_pl_name);
195    }
196
197    if (!Strings.isNullOrEmpty(var_stream_map)) {
198      args.add("-var_stream_map", var_stream_map);
199    } else if (!variants.isEmpty()) {
200      StringBuilder sb = new StringBuilder();
201      for (int i = 0; i < variants.size(); i++) {
202        if (i > 0) {
203          sb.append(" ");
204        }
205        sb.append(variants.get(i).toString());
206      }
207      args.add("-var_stream_map", sb.toString());
208    }
209  }
210
211  @CheckReturnValue
212  @Override
213  protected FFmpegHlsOutputBuilder getThis() {
214    return this;
215  }
216}