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.toTimecode; 006import static net.bramp.ffmpeg.Preconditions.checkNotEmpty; 007import static net.bramp.ffmpeg.Preconditions.checkValidStream; 008import static net.bramp.ffmpeg.builder.MetadataSpecifier.checkValidKey; 009 010import com.google.common.base.Preconditions; 011import com.google.common.base.Strings; 012import com.google.common.collect.ImmutableList; 013import java.io.File; 014import java.net.URI; 015import java.nio.file.Path; 016import java.util.ArrayList; 017import java.util.List; 018import java.util.concurrent.TimeUnit; 019import net.bramp.ffmpeg.modelmapper.Mapper; 020import net.bramp.ffmpeg.options.AudioEncodingOptions; 021import net.bramp.ffmpeg.options.EncodingOptions; 022import net.bramp.ffmpeg.options.MainEncodingOptions; 023import net.bramp.ffmpeg.options.VideoEncodingOptions; 024import org.apache.commons.lang3.SystemUtils; 025import org.apache.commons.lang3.math.Fraction; 026 027/** 028 * This abstract class holds flags that are both applicable to input and output streams in the 029 * ffmpeg command, while flags that apply to a particular direction (input/output) are located in 030 * {@link FFmpegOutputBuilder}. <br> 031 * <br> 032 * All possible flags can be found in the <a href="https://ffmpeg.org/ffmpeg.html#Options">official 033 * ffmpeg page</a> The discrimination criteria for flag location are the specifiers for each command 034 * 035 * <ul> 036 * <li>AbstractFFmpegStreamBuilder 037 * <ul> 038 * <li>(input/output): <code>-t duration (input/output)</code> 039 * <li>(input/output,per-stream): <code> 040 * -codec[:stream_specifier] codec (input/output,per-stream)</code> 041 * <li>(global): <code>-filter_threads nb_threads (global)</code> 042 * </ul> 043 * <li>FFmpegInputBuilder 044 * <ul> 045 * <li>(input): <code>-muxdelay seconds (input)</code> 046 * <li>(input,per-stream): <code>-guess_layout_max channels (input,per-stream)</code> 047 * </ul> 048 * <li>FFmpegOutputBuilder 049 * <ul> 050 * <li>(output): <code>-atag fourcc/tag (output)</code> 051 * <li>(output,per-stream): <code> 052 * -bsf[:stream_specifier] bitstream_filters (output,per-stream)</code> 053 * </ul> 054 * </ul> 055 * 056 * @param <T> A concrete class that extends from the AbstractFFmpegStreamBuilder 057 */ 058public abstract class AbstractFFmpegStreamBuilder<T extends AbstractFFmpegStreamBuilder<T>> { 059 060 protected static final String DEVNULL = SystemUtils.IS_OS_WINDOWS ? "NUL" : "/dev/null"; 061 062 final FFmpegBuilder parent; 063 064 /** Output filename or uri. Only one may be set. */ 065 public String filename; 066 067 public URI uri; 068 069 public String format; 070 071 public Long startOffset; // in milliseconds 072 public Long duration; // in milliseconds 073 074 public final List<String> meta_tags = new ArrayList<>(); 075 public final List<String> maps = new ArrayList<>(); 076 077 public boolean audio_enabled = true; 078 public String audio_codec; 079 public int audio_channels; 080 public int audio_sample_rate; 081 public String audio_preset; 082 083 public boolean video_enabled = true; 084 public String video_codec; 085 public boolean video_copyinkf; 086 public Fraction video_frame_rate; 087 public int video_width; 088 public int video_height; 089 public String video_size; 090 public String video_movflags; 091 public Integer video_frames; 092 public String video_pixel_format; 093 094 public boolean subtitle_enabled = true; 095 public String subtitle_preset; 096 private String subtitle_codec; 097 098 public String preset; 099 public String presetFilename; 100 public final List<String> extra_args = new ArrayList<>(); 101 102 public Strict strict = Strict.NORMAL; 103 104 public long targetSize = 0; // in bytes 105 public long pass_padding_bitrate = 1024; // in bits per second 106 107 public boolean throwWarnings = true; // TODO Either delete this, or apply it consistently 108 109 /** Constructs a stream builder with no parent. */ 110 protected AbstractFFmpegStreamBuilder() { 111 this.parent = null; 112 } 113 114 /** Constructs a stream builder with the given parent and output filename. */ 115 protected AbstractFFmpegStreamBuilder(FFmpegBuilder parent, String filename) { 116 this.parent = checkNotNull(parent); 117 this.filename = checkNotEmpty(filename, "filename must not be empty"); 118 } 119 120 /** Constructs a stream builder with the given parent and output URI. */ 121 protected AbstractFFmpegStreamBuilder(FFmpegBuilder parent, URI uri) { 122 this.parent = checkNotNull(parent); 123 this.uri = checkValidStream(uri); 124 } 125 126 /** Returns this instance for fluent API chaining. */ 127 protected abstract T getThis(); 128 129 /** Applies the given encoding options to this builder. */ 130 public T useOptions(EncodingOptions opts) { 131 Mapper.map(opts, this); 132 return getThis(); 133 } 134 135 /** Applies the given main encoding options to this builder. */ 136 public T useOptions(MainEncodingOptions opts) { 137 Mapper.map(opts, this); 138 return getThis(); 139 } 140 141 /** Applies the given audio encoding options to this builder. */ 142 public T useOptions(AudioEncodingOptions opts) { 143 Mapper.map(opts, this); 144 return getThis(); 145 } 146 147 /** Applies the given video encoding options to this builder. */ 148 public T useOptions(VideoEncodingOptions opts) { 149 Mapper.map(opts, this); 150 return getThis(); 151 } 152 153 /** Disables video output. */ 154 public T disableVideo() { 155 this.video_enabled = false; 156 return getThis(); 157 } 158 159 /** Disables audio output. */ 160 public T disableAudio() { 161 this.audio_enabled = false; 162 return getThis(); 163 } 164 165 /** Disables subtitle output. */ 166 public T disableSubtitle() { 167 this.subtitle_enabled = false; 168 return getThis(); 169 } 170 171 /** 172 * Sets a file to use containing presets. 173 * 174 * <p>Uses `-fpre`. 175 * 176 * @param presetFilename the preset by filename 177 * @return this 178 */ 179 public T setPresetFilename(String presetFilename) { 180 this.presetFilename = checkNotEmpty(presetFilename, "file preset must not be empty"); 181 return getThis(); 182 } 183 184 /** 185 * Sets a file to use containing presets. 186 * 187 * <p>Uses `-fpre`. 188 * 189 * @param presetFile the preset by file 190 * @return this 191 */ 192 public T setPresetFilename(File presetFile) { 193 return setPresetFilename(checkNotNull(presetFile).getPath()); 194 } 195 196 /** 197 * Sets a file to use containing presets. 198 * 199 * <p>Uses `-fpre`. 200 * 201 * @param presetPath the preset by path 202 * @return this 203 */ 204 public T setPresetFilename(Path presetPath) { 205 return setPresetFilename(checkNotNull(presetPath).toString()); 206 } 207 208 /** 209 * Sets a preset by name (this only works with some codecs). 210 * 211 * <p>Uses `-preset`. 212 * 213 * @param preset the preset 214 * @return this 215 */ 216 public T setPreset(String preset) { 217 this.preset = checkNotEmpty(preset, "preset must not be empty"); 218 return getThis(); 219 } 220 221 /** Sets the output filename. */ 222 public T setFilename(String filename) { 223 this.filename = checkNotEmpty(filename, "filename must not be empty"); 224 return getThis(); 225 } 226 227 /** 228 * Sets the output filename. 229 * 230 * @param file The file 231 * @return this 232 */ 233 public T setFilename(File file) { 234 return setFilename(checkNotNull(file).getPath()); 235 } 236 237 /** 238 * Sets the output filename. 239 * 240 * @param path The path 241 * @return this 242 */ 243 public T setFilename(Path path) { 244 return setFilename(checkNotNull(path).toString()); 245 } 246 247 /** Returns the output filename. */ 248 public String getFilename() { 249 return filename; 250 } 251 252 /** Sets the output URI. */ 253 public T setUri(URI uri) { 254 this.uri = checkValidStream(uri); 255 return getThis(); 256 } 257 258 public URI getUri() { 259 return uri; 260 } 261 262 /** Sets the output format. */ 263 public T setFormat(String format) { 264 this.format = checkNotEmpty(format, "format must not be empty"); 265 return getThis(); 266 } 267 268 /** Sets the video codec. */ 269 public T setVideoCodec(String codec) { 270 this.video_enabled = true; 271 this.video_codec = checkNotEmpty(codec, "codec must not be empty"); 272 return getThis(); 273 } 274 275 /** Sets whether to copy initial non-keyframes. */ 276 public T setVideoCopyInkf(boolean copyinkf) { 277 this.video_enabled = true; 278 this.video_copyinkf = copyinkf; 279 return getThis(); 280 } 281 282 /** Sets the MOV muxer flags. */ 283 public T setVideoMovFlags(String movflags) { 284 this.video_enabled = true; 285 this.video_movflags = checkNotEmpty(movflags, "movflags must not be empty"); 286 return getThis(); 287 } 288 289 /** 290 * Sets the video's frame rate. 291 * 292 * @param frame_rate Frames per second 293 * @return this 294 * @see net.bramp.ffmpeg.FFmpeg#FPS_30 295 * @see net.bramp.ffmpeg.FFmpeg#FPS_29_97 296 * @see net.bramp.ffmpeg.FFmpeg#FPS_24 297 * @see net.bramp.ffmpeg.FFmpeg#FPS_23_976 298 */ 299 public T setVideoFrameRate(Fraction frame_rate) { 300 this.video_enabled = true; 301 this.video_frame_rate = checkNotNull(frame_rate); 302 return getThis(); 303 } 304 305 /** 306 * Set the video frame rate in terms of frames per interval. For example 24fps would be 24/1, 307 * however NTSC TV at 23.976fps would be 24000 per 1001. 308 * 309 * @param frames The number of frames within the given seconds 310 * @param per The number of seconds 311 * @return this 312 */ 313 public T setVideoFrameRate(int frames, int per) { 314 return setVideoFrameRate(Fraction.getFraction(frames, per)); 315 } 316 317 /** Sets the video frame rate. */ 318 public T setVideoFrameRate(double frame_rate) { 319 return setVideoFrameRate(Fraction.getFraction(frame_rate)); 320 } 321 322 /** 323 * Set the number of video frames to record. 324 * 325 * @param frames The number of frames 326 * @return this 327 */ 328 public T setFrames(int frames) { 329 this.video_enabled = true; 330 this.video_frames = frames; 331 return getThis(); 332 } 333 334 /** Checks if the given width or height value is valid. */ 335 protected static boolean isValidSize(int widthOrHeight) { 336 return widthOrHeight > 0 || widthOrHeight == -1; 337 } 338 339 /** Sets the video width in pixels. */ 340 public T setVideoWidth(int width) { 341 checkArgument(isValidSize(width), "Width must be -1 or greater than zero"); 342 343 this.video_enabled = true; 344 this.video_width = width; 345 return getThis(); 346 } 347 348 /** Sets the video height in pixels. */ 349 public T setVideoHeight(int height) { 350 checkArgument(isValidSize(height), "Height must be -1 or greater than zero"); 351 352 this.video_enabled = true; 353 this.video_height = height; 354 return getThis(); 355 } 356 357 /** Sets the video resolution by width and height in pixels. */ 358 public T setVideoResolution(int width, int height) { 359 checkArgument( 360 isValidSize(width) && isValidSize(height), 361 "Both width and height must be -1 or greater than zero"); 362 363 this.video_enabled = true; 364 this.video_width = width; 365 this.video_height = height; 366 return getThis(); 367 } 368 369 /** 370 * Sets video resolution based on an abbreviation, e.g. "ntsc" for 720x480, or "vga" for 640x480 371 * 372 * @see <a href="https://www.ffmpeg.org/ffmpeg-utils.html#Video-size">ffmpeg video size</a> 373 * @param abbreviation The abbreviation size. No validation is done, instead the value is passed 374 * as is to ffmpeg. 375 * @return this 376 */ 377 public T setVideoResolution(String abbreviation) { 378 this.video_enabled = true; 379 this.video_size = checkNotEmpty(abbreviation, "video abbreviation must not be empty"); 380 return getThis(); 381 } 382 383 /** Sets the video pixel format. */ 384 public T setVideoPixelFormat(String format) { 385 this.video_enabled = true; 386 this.video_pixel_format = checkNotEmpty(format, "format must not be empty"); 387 return getThis(); 388 } 389 390 /** 391 * Add metadata on output streams. Which keys are possible depends on the used codec. 392 * 393 * @param key Metadata key, e.g. "comment" 394 * @param value Value to set for key 395 * @return this 396 */ 397 public T addMetaTag(String key, String value) { 398 checkValidKey(key); 399 checkNotEmpty(value, "value must not be empty"); 400 meta_tags.add("-metadata"); 401 meta_tags.add(key + "=" + value); 402 return getThis(); 403 } 404 405 /** 406 * Add metadata on output streams. Which keys are possible depends on the used codec. 407 * 408 * <pre>{@code 409 * import static net.bramp.ffmpeg.builder.MetadataSpecifier.*; 410 * import static net.bramp.ffmpeg.builder.StreamSpecifier.*; 411 * import static net.bramp.ffmpeg.builder.StreamSpecifierType.*; 412 * 413 * new FFmpegBuilder() 414 * .addMetaTag("title", "Movie Title") // Annotate whole file 415 * .addMetaTag(chapter(0), "author", "Bob") // Annotate first chapter 416 * .addMetaTag(program(0), "comment", "Awesome") // Annotate first program 417 * .addMetaTag(stream(0), "copyright", "Megacorp") // Annotate first stream 418 * .addMetaTag(stream(Video), "framerate", "24fps") // Annotate all video streams 419 * .addMetaTag(stream(Video, 0), "artist", "Joe") // Annotate first video stream 420 * .addMetaTag(stream(Audio, 0), "language", "eng") // Annotate first audio stream 421 * .addMetaTag(stream(Subtitle, 0), "language", "fre") // Annotate first subtitle stream 422 * .addMetaTag(usable(), "year", "2010") // Annotate all streams with a usable configuration 423 * }</pre> 424 * 425 * @param spec Metadata specifier, e.g `MetadataSpec.stream(Audio, 0)` 426 * @param key Metadata key, e.g. "comment" 427 * @param value Value to set for key 428 * @return this 429 */ 430 public T addMetaTag(MetadataSpecifier spec, String key, String value) { 431 checkValidKey(key); 432 checkNotEmpty(value, "value must not be empty"); 433 meta_tags.add("-metadata:" + spec.spec()); 434 meta_tags.add(key + "=" + value); 435 return getThis(); 436 } 437 438 /** Adds a stream mapping by input index. */ 439 public T addMap(int inputIndex) { 440 checkArgument(inputIndex >= 0, "inputIndex must be greater or equal to zero"); 441 this.maps.add(String.valueOf(inputIndex)); 442 return getThis(); 443 } 444 445 /** Adds a stream mapping by input index and stream specifier. */ 446 public T addMap(int inputIndex, StreamSpecifier spec) { 447 checkArgument(inputIndex >= 0, "inputIndex must be greater or equal to zero"); 448 this.maps.add(inputIndex + ":" + spec.spec()); 449 return getThis(); 450 } 451 452 /** Sets the audio codec. */ 453 public T setAudioCodec(String codec) { 454 this.audio_enabled = true; 455 this.audio_codec = checkNotEmpty(codec, "codec must not be empty"); 456 return getThis(); 457 } 458 459 /** Sets the subtitle codec. */ 460 public T setSubtitleCodec(String codec) { 461 this.subtitle_enabled = true; 462 this.subtitle_codec = checkNotEmpty(codec, "codec must not be empty"); 463 return getThis(); 464 } 465 466 /** 467 * Sets the number of audio channels. 468 * 469 * @param channels Number of channels 470 * @return this 471 * @see net.bramp.ffmpeg.FFmpeg#AUDIO_MONO 472 * @see net.bramp.ffmpeg.FFmpeg#AUDIO_STEREO 473 */ 474 public T setAudioChannels(int channels) { 475 checkArgument(channels > 0, "channels must be positive"); 476 this.audio_enabled = true; 477 this.audio_channels = channels; 478 return getThis(); 479 } 480 481 /** 482 * Sets the Audio sample rate, for example 44_000. 483 * 484 * @param sample_rate Samples measured in Hz 485 * @return this 486 * @see net.bramp.ffmpeg.FFmpeg#AUDIO_SAMPLE_8000 487 * @see net.bramp.ffmpeg.FFmpeg#AUDIO_SAMPLE_11025 488 * @see net.bramp.ffmpeg.FFmpeg#AUDIO_SAMPLE_12000 489 * @see net.bramp.ffmpeg.FFmpeg#AUDIO_SAMPLE_16000 490 * @see net.bramp.ffmpeg.FFmpeg#AUDIO_SAMPLE_22050 491 * @see net.bramp.ffmpeg.FFmpeg#AUDIO_SAMPLE_32000 492 * @see net.bramp.ffmpeg.FFmpeg#AUDIO_SAMPLE_44100 493 * @see net.bramp.ffmpeg.FFmpeg#AUDIO_SAMPLE_48000 494 * @see net.bramp.ffmpeg.FFmpeg#AUDIO_SAMPLE_96000 495 */ 496 public T setAudioSampleRate(int sample_rate) { 497 checkArgument(sample_rate > 0, "sample rate must be positive"); 498 this.audio_enabled = true; 499 this.audio_sample_rate = sample_rate; 500 return getThis(); 501 } 502 503 /** 504 * Target output file size (in bytes). 505 * 506 * @param targetSize The target size in bytes 507 * @return this 508 */ 509 public T setTargetSize(long targetSize) { 510 checkArgument(targetSize > 0, "target size must be positive"); 511 this.targetSize = targetSize; 512 return getThis(); 513 } 514 515 /** 516 * Decodes but discards input until the offset. 517 * 518 * @param offset The offset 519 * @param units The units the offset is in 520 * @return this 521 */ 522 public T setStartOffset(long offset, TimeUnit units) { 523 checkNotNull(units); 524 525 this.startOffset = units.toMillis(offset); 526 527 return getThis(); 528 } 529 530 /** 531 * Stop writing the output after duration is reached. 532 * 533 * @param duration The duration 534 * @param units The units the duration is in 535 * @return this 536 */ 537 public T setDuration(long duration, TimeUnit units) { 538 checkNotNull(units); 539 540 this.duration = units.toMillis(duration); 541 542 return getThis(); 543 } 544 545 /** Sets the strict mode for standards compliance. */ 546 public T setStrict(Strict strict) { 547 this.strict = checkNotNull(strict); 548 return getThis(); 549 } 550 551 /** 552 * When doing multi-pass we add a little extra padding, to ensure we reach our target. 553 * 554 * @param bitrate bit rate 555 * @return this 556 */ 557 public T setPassPaddingBitrate(long bitrate) { 558 checkArgument(bitrate > 0, "bitrate must be positive"); 559 this.pass_padding_bitrate = bitrate; 560 return getThis(); 561 } 562 563 /** 564 * Sets a audio preset to use. 565 * 566 * <p>Uses `-apre`. 567 * 568 * @param preset the preset 569 * @return this 570 */ 571 public T setAudioPreset(String preset) { 572 this.audio_enabled = true; 573 this.audio_preset = checkNotEmpty(preset, "audio preset must not be empty"); 574 return getThis(); 575 } 576 577 /** 578 * Sets a subtitle preset to use. 579 * 580 * <p>Uses `-spre`. 581 * 582 * @param preset the preset 583 * @return this 584 */ 585 public T setSubtitlePreset(String preset) { 586 this.subtitle_enabled = true; 587 this.subtitle_preset = checkNotEmpty(preset, "subtitle preset must not be empty"); 588 return getThis(); 589 } 590 591 /** 592 * Add additional output arguments (for flags which aren't currently supported). 593 * 594 * @param values The extra arguments 595 * @return this 596 */ 597 public T addExtraArgs(String... values) { 598 checkArgument(values.length > 0, "one or more values must be supplied"); 599 checkNotEmpty(values[0], "first extra arg may not be empty"); 600 601 for (String value : values) { 602 extra_args.add(checkNotNull(value)); 603 } 604 return getThis(); 605 } 606 607 /** 608 * Finished with this output. 609 * 610 * @return the parent FFmpegBuilder 611 */ 612 public FFmpegBuilder done() { 613 Preconditions.checkState(parent != null, "Can not call done without parent being set"); 614 return parent; 615 } 616 617 /** 618 * Returns a representation of this Builder that can be safely serialised. 619 * 620 * <p>NOTE: This method is horribly out of date, and its use should be rethought. 621 * 622 * @return A new EncodingOptions capturing this Builder's state 623 */ 624 public abstract EncodingOptions buildOptions(); 625 626 /** Builds the command-line arguments for the given pass using the parent builder. */ 627 protected List<String> build(int pass) { 628 Preconditions.checkState(parent != null, "Can not build without parent being set"); 629 return build(parent, pass); 630 } 631 632 /** 633 * Builds the arguments. 634 * 635 * @param parent The parent FFmpegBuilder 636 * @param pass The particular pass. For one-pass this value will be zero, for multi-pass, it will 637 * be 1 for the first pass, 2 for the second, and so on. 638 * @return The arguments 639 */ 640 protected List<String> build(FFmpegBuilder parent, int pass) { 641 checkNotNull(parent); 642 643 ImmutableList.Builder<String> args = new ImmutableList.Builder<>(); 644 645 addGlobalFlags(parent, args); 646 647 if (video_enabled) { 648 addVideoFlags(parent, args); 649 } else { 650 args.add("-vn"); 651 } 652 653 if (audio_enabled && pass != 1) { 654 addAudioFlags(args); 655 } else { 656 args.add("-an"); 657 } 658 659 if (subtitle_enabled) { 660 if (!Strings.isNullOrEmpty(subtitle_codec)) { 661 args.add("-scodec", subtitle_codec); 662 } 663 if (!Strings.isNullOrEmpty(subtitle_preset)) { 664 args.add("-spre", subtitle_preset); 665 } 666 } else { 667 args.add("-sn"); 668 } 669 670 addFormatArgs(args); 671 672 args.addAll(extra_args); 673 674 addSourceTarget(pass, args); 675 676 return args.build(); 677 } 678 679 /** Adds source and target specific arguments for the given pass. */ 680 protected abstract void addSourceTarget(int pass, ImmutableList.Builder<String> args); 681 682 /** Adds global flags such as format, preset, and time options to the arguments. */ 683 protected void addGlobalFlags(FFmpegBuilder parent, ImmutableList.Builder<String> args) { 684 if (strict != Strict.NORMAL) { 685 args.add("-strict", strict.toString()); 686 } 687 688 if (!Strings.isNullOrEmpty(format)) { 689 args.add("-f", format); 690 } 691 692 if (!Strings.isNullOrEmpty(preset)) { 693 args.add("-preset", preset); 694 } 695 696 if (!Strings.isNullOrEmpty(presetFilename)) { 697 args.add("-fpre", presetFilename); 698 } 699 700 if (startOffset != null) { 701 args.add("-ss", toTimecode(startOffset, TimeUnit.MILLISECONDS)); 702 } 703 704 if (duration != null) { 705 args.add("-t", toTimecode(duration, TimeUnit.MILLISECONDS)); 706 } 707 708 args.addAll(meta_tags); 709 } 710 711 /** Adds audio-related flags such as codec, channels, and sample rate to the arguments. */ 712 protected void addAudioFlags(ImmutableList.Builder<String> args) { 713 if (!Strings.isNullOrEmpty(audio_codec)) { 714 args.add("-acodec", audio_codec); 715 } 716 717 if (audio_channels > 0) { 718 args.add("-ac", String.valueOf(audio_channels)); 719 } 720 721 if (audio_sample_rate > 0) { 722 args.add("-ar", String.valueOf(audio_sample_rate)); 723 } 724 725 if (!Strings.isNullOrEmpty(audio_preset)) { 726 args.add("-apre", audio_preset); 727 } 728 } 729 730 /** Adds video-related flags such as codec, frame rate, and resolution to the arguments. */ 731 protected void addVideoFlags(FFmpegBuilder parent, ImmutableList.Builder<String> args) { 732 if (video_frames != null) { 733 args.add("-vframes", video_frames.toString()); 734 } 735 736 if (!Strings.isNullOrEmpty(video_codec)) { 737 args.add("-vcodec", video_codec); 738 } 739 740 if (!Strings.isNullOrEmpty(video_pixel_format)) { 741 args.add("-pix_fmt", video_pixel_format); 742 } 743 744 if (video_copyinkf) { 745 args.add("-copyinkf"); 746 } 747 748 if (!Strings.isNullOrEmpty(video_movflags)) { 749 args.add("-movflags", video_movflags); 750 } 751 752 if (video_size != null) { 753 checkArgument( 754 video_width == 0 && video_height == 0, 755 "Can not specific width or height, as well as an abbreviatied video size"); 756 args.add("-s", video_size); 757 758 } else if (video_width != 0 && video_height != 0) { 759 args.add("-s", String.format("%dx%d", video_width, video_height)); 760 } 761 762 // TODO What if width is set but heigh isn't. We don't seem to do anything 763 764 if (video_frame_rate != null) { 765 args.add("-r", video_frame_rate.toString()); 766 } 767 } 768 769 /** Adds format-related arguments such as stream mappings. */ 770 protected void addFormatArgs(ImmutableList.Builder<String> args) { 771 for (String map : maps) { 772 args.add("-map", map); 773 } 774 } 775}