diff --git a/Cargo.toml b/Cargo.toml index 922ab0344a..38a6fbdcc6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,11 +64,7 @@ dump_lookahead_data = ["byteorder", "image"] arg_enum_proc_macro = "0.3" bitstream-io = "1" cfg-if = "1.0" -clap = { version = "3.1", optional = true, default-features = false, features = [ - "color", - "std", - "wrap_help", -] } +clap = { version = "3.2.6", optional = true, default-features = false, features = ["color", "std", "wrap_help", "derive"] } clap_complete = { version = "3", optional = true } libc = "0.2" y4m = { version = "0.7", optional = true } @@ -101,6 +97,7 @@ arrayref = "0.3.6" const_fn_assert = "0.1.2" nom = { version = "7.0.0", optional = true } new_debug_unreachable = "1.0.4" +once_cell = "1.13.0" [dependencies.image] version = "0.23" diff --git a/ivf/src/lib.rs b/ivf/src/lib.rs index 4a78527fcf..6d8e4b9669 100644 --- a/ivf/src/lib.rs +++ b/ivf/src/lib.rs @@ -36,7 +36,8 @@ #![warn(clippy::doc_markdown)] #![warn(clippy::missing_errors_doc)] #![warn(clippy::missing_panics_doc)] -#![warn(clippy::undocumented_unsafe_blocks)] +// FIXME: Temporarily disabled due to https://github.com/rust-lang/rust-clippy/issues/9142 +#![allow(clippy::undocumented_unsafe_blocks)] /// Simple ivf muxer /// diff --git a/src/api/config/encoder.rs b/src/api/config/encoder.rs index f8acf6aa64..2a84d625bc 100644 --- a/src/api/config/encoder.rs +++ b/src/api/config/encoder.rs @@ -118,7 +118,7 @@ pub struct EncoderConfig { /// [`with_speed_preset()`]: struct.EncoderConfig.html#method.with_speed_preset impl Default for EncoderConfig { fn default() -> Self { - const DEFAULT_SPEED: usize = 6; + const DEFAULT_SPEED: u8 = 6; Self::with_speed_preset(DEFAULT_SPEED) } } @@ -130,7 +130,7 @@ impl EncoderConfig { /// than 10, it will result in the same settings as 10. /// /// [`from_preset()`]: struct.SpeedSettings.html#method.from_preset - pub fn with_speed_preset(speed: usize) -> Self { + pub fn with_speed_preset(speed: u8) -> Self { EncoderConfig { width: 640, height: 480, diff --git a/src/api/config/speedsettings.rs b/src/api/config/speedsettings.rs index fa7e2bbe39..03851e66a0 100644 --- a/src/api/config/speedsettings.rs +++ b/src/api/config/speedsettings.rs @@ -112,7 +112,7 @@ impl Default for SpeedSettings { impl SpeedSettings { /// Set the speed setting according to a numeric speed preset. - pub fn from_preset(speed: usize) -> Self { + pub fn from_preset(speed: u8) -> Self { // The default settings are equivalent to speed 0 let mut settings = SpeedSettings::default(); diff --git a/src/api/test.rs b/src/api/test.rs index 6c998901a7..c058cc8a47 100644 --- a/src/api/test.rs +++ b/src/api/test.rs @@ -15,7 +15,7 @@ use std::sync::Arc; use interpolate_name::interpolate_test; fn setup_config( - w: usize, h: usize, speed: usize, quantizer: usize, bit_depth: usize, + w: usize, h: usize, speed: u8, quantizer: usize, bit_depth: usize, chroma_sampling: ChromaSampling, min_keyint: u64, max_keyint: u64, bitrate: i32, low_latency: bool, switch_frame_interval: u64, no_scene_detection: bool, rdo_lookahead_frames: usize, @@ -44,7 +44,7 @@ fn setup_config( } fn setup_encoder( - w: usize, h: usize, speed: usize, quantizer: usize, bit_depth: usize, + w: usize, h: usize, speed: u8, quantizer: usize, bit_depth: usize, chroma_sampling: ChromaSampling, min_keyint: u64, max_keyint: u64, bitrate: i32, low_latency: bool, switch_frame_interval: u64, no_scene_detection: bool, rdo_lookahead_frames: usize, diff --git a/src/bin/common.rs b/src/bin/common.rs index 3af9502545..dabb6cb6c7 100644 --- a/src/bin/common.rs +++ b/src/bin/common.rs @@ -13,16 +13,241 @@ use crate::grain_synth::parse_grain_table; use crate::muxer::{create_muxer, Muxer}; use crate::stats::MetricsEnabled; use crate::{ColorPrimaries, MatrixCoefficients, TransferCharacteristics}; -use clap::{AppSettings, Arg, ArgMatches, Command}; +use clap::{ArgMatches, IntoApp, Parser as Clap, Subcommand}; use clap_complete::{generate, Shell}; use rav1e::prelude::*; -use rav1e::version; use scan_fmt::scan_fmt; -use std::ffi::OsString; use std::fs::File; use std::io; use std::io::prelude::*; +use std::path::PathBuf; + +#[derive(Clap)] +#[clap(name = "rav1e", version, about = "AV1 video encoder", long_about = None)] +pub struct CliOptions { + /// Uncompressed YUV4MPEG2 video input + #[clap(value_parser, help_heading = "INPUT/OUTPUT")] + pub input: PathBuf, + /// Compressed AV1 in IVF video output + #[clap(long, short, value_parser, help_heading = "INPUT/OUTPUT")] + pub output: PathBuf, + /// Overwrite output file. + #[clap(short = 'y', help_heading = "INPUT/OUTPUT")] + pub overwrite: bool, + + /// Set the threadpool size + #[clap(long, value_parser, default_value_t = 0, help_heading = "THREADING")] + pub threads: usize, + /// Number of tile rows. Must be a power of 2. rav1e may override this based on video resolution. + #[clap(long, value_parser, default_value_t = 0, help_heading = "THREADING")] + pub tile_rows: usize, + /// Number of tile columns. Must be a power of 2. rav1e may override this based on video resolution. + #[clap(long, value_parser, default_value_t = 0, help_heading = "THREADING")] + pub tile_cols: usize, + /// Number of tiles. Tile-cols and tile-rows are overridden + /// so that the video has at least this many tiles. + #[clap( + long, + value_parser, + conflicts_with = "tile-rows", + conflicts_with = "tile-cols", + help_heading = "THREADING" + )] + pub tiles: Option, + /// Maximum number of GOPs that can be encoded in parallel + #[cfg(feature = "unstable")] + #[clap(long, value_parser, default_value_t = 0, help_heading = "THREADING")] + pub slots: usize, + + /// Perform the first pass of a two-pass encode, + /// saving the pass data to the specified file for future passes + #[clap( + long, + value_parser, + value_name = "STATS_FILE", + help_heading = "ENCODE SETTINGS" + )] + pub first_pass: Option, + /// Perform the second pass of a two-pass encode, + /// reading the pass data saved from a previous pass from the specified file + #[clap( + long, + value_parser, + value_name = "STATS_FILE", + conflicts_with = "first-pass", + help_heading = "ENCODE SETTINGS" + )] + pub second_pass: Option, + /// Maximum number of frames to encode + #[clap( + long, + short, + value_parser, + default_value_t = 0, + help_heading = "ENCODE SETTINGS" + )] + pub limit: usize, + /// Skip n number of frames and encode + #[clap( + long, + value_parser, + default_value_t = 0, + help_heading = "ENCODE SETTINGS" + )] + pub skip: usize, + /// Quantizer (0-255), smaller values are higher quality [default: 100] + #[clap(long, value_parser, help_heading = "ENCODE SETTINGS")] + pub quantizer: Option, + /// Minimum quantizer (0-255) to use in bitrate mode [default: 0] + #[clap(long, value_parser, help_heading = "ENCODE SETTINGS")] + pub min_quantizer: Option, + /// Bitrate (kbps) + #[clap(long, short, value_parser, help_heading = "ENCODE SETTINGS")] + pub bitrate: Option, + /// Speed level (0 is best quality, 10 is fastest). + /// Speeds 10 and 0 are extremes and are generally not recommended. + #[clap(long, short, value_parser = clap::value_parser!(u8).range(0..=10), default_value_t = 6, help_heading = "ENCODE SETTINGS", long_help = build_speed_long_help())] + pub speed: u8, + /// Speed level for scene-change detection, 0: best quality, 1: fastest mode. + /// [default: 0 for s0-s9, 1 for s10] + #[clap(long, value_parser = clap::value_parser!(u8).range(0..=1), help_heading = "ENCODE SETTINGS")] + pub scd_speed: Option, + /// Minimum interval between keyframes + #[clap( + long, + short = 'i', + value_parser, + default_value_t = 12, + help_heading = "ENCODE SETTINGS" + )] + pub min_keyint: u64, + /// Maximum interval between keyframes. When set to 0, disables fixed-interval keyframes. + #[clap( + long, + short = 'I', + value_parser, + default_value_t = 240, + help_heading = "ENCODE SETTINGS" + )] + pub keyint: u64, + /// Maximum interval between switch frames. When set to 0, disables switch frames. + #[clap( + long, + short = 'S', + value_parser, + default_value_t = 0, + help_heading = "ENCODE SETTINGS" + )] + pub switch_frame_interval: u64, + /// "Number of frames over which rate control should distribute the reservoir + /// [default: min(240, 1.5x keyint)] + /// A minimum value of 12 is enforced. + #[clap(long, value_parser = clap::value_parser!(i32).range(12..), help_heading = "ENCODE SETTINGS")] + pub reservoir_frame_delay: Option, + /// Low latency mode; disables frame reordering. + /// Has a significant speed-to-quality trade-off + #[clap(long, help_heading = "ENCODE SETTINGS")] + pub low_latency: bool, + /// Disables scene detection entirely. + /// Has a significant speed-to-quality trade-off in full encodes. + /// Useful for chunked encoding. + #[clap(long, help_heading = "ENCODE SETTINGS")] + pub no_scene_detection: bool, + /// Number of frames encoder should lookahead for RDO purposes\n\ + /// [default value for speed levels: 10,9 - 10; 8,7,6 - 20; 5,4,3 - 30; 2,1,0 - 40] + #[clap(long, value_parser, help_heading = "ENCODE SETTINGS")] + pub rdo_lookahead_frames: Option, + /// Quality tuning + #[clap(long, value_parser, default_value_t = Tune::Psychovisual, help_heading = "ENCODE SETTINGS")] + pub tune: Tune, + /// Still picture mode + #[clap(long, help_heading = "ENCODE SETTINGS")] + pub still_picture: bool, + /// Uses grain synthesis to add photon noise to the resulting encode. + /// Takes a strength value 0-64. + #[cfg(feature = "unstable")] + #[clap(long, value_parser = clap::value_parser!(u8).range(0..=64), default_value_t = 0, help_heading = "ENCODE SETTINGS")] + pub photon_noise: u8, + /// Uses a film grain table file to apply grain synthesis to the encode. + /// Uses the same table file format as aomenc and svt-av1. + #[cfg(feature = "unstable")] + #[clap( + long, + value_parser, + conflicts_with = "photon-noise", + help_heading = "ENCODE SETTINGS" + )] + pub photon_noise_table: Option, + + /// Pixel range + #[clap(long, value_parser, help_heading = "VIDEO METADATA")] + pub range: Option, + /// Color primaries used to describe color parameters + #[clap(long, value_parser, help_heading = "VIDEO METADATA")] + pub primaries: Option, + /// Transfer characteristics used to describe color parameters + #[clap(long, value_parser, help_heading = "VIDEO METADATA")] + pub transfer: Option, + /// Matrix coefficients used to describe color parameters + #[clap(long, value_parser, help_heading = "VIDEO METADATA")] + pub matrix: Option, + /// Mastering display primaries in the form of G(x,y)B(x,y)R(x,y)WP(x,y)L(max,min) + #[clap(long, help_heading = "VIDEO METADATA")] + pub mastering_display: Option, + /// Content light level used to describe content luminosity (cll,fall) + #[clap(long, help_heading = "VIDEO METADATA")] + pub content_light: Option, + /// Constant frame rate to set at the output (inferred from input when omitted) + #[clap(long, value_parser, help_heading = "VIDEO METADATA")] + pub frame_rate: Option, + /// The time scale associated with the frame rate if provided (ignored otherwise) + #[clap( + long, + value_parser, + default_value_t = 0, + help_heading = "VIDEO METADATA" + )] + pub time_scale: u64, + + /// Provide a benchmark report at the end of the encoding + #[clap(long, help_heading = "DEBUGGING")] + pub benchmark: bool, + /// Verbose logging; outputs info for every frame + #[clap(long, help_heading = "DEBUGGING")] + pub verbose: bool, + /// Do not output any status message + #[clap(long, conflicts_with = "verbose", help_heading = "DEBUGGING")] + pub quiet: bool, + /// Calculate and display PSNR metrics + #[clap(long, help_heading = "DEBUGGING")] + pub psnr: bool, + /// Calculate and display several metrics including PSNR, SSIM, CIEDE2000 etc + #[clap(long, conflicts_with = "psnr", help_heading = "DEBUGGING")] + pub metrics: bool, + /// Outputs a Y4M file containing the output from the decoder + #[clap(long, short, value_parser, help_heading = "DEBUGGING")] + pub reconstruction: Option, + + #[clap(subcommand)] + pub command: Option, +} + +#[derive(Subcommand)] +pub enum Commands { + /// Advanced features + Advanced { + /// Output to stdout the completion definition for the shell + #[clap(long, short, value_parser)] + completion: Option, + /// Save the current configuration in a toml file + #[clap(long, short, value_parser)] + save_config: Option, + /// Load the encoder configuration from a toml file + #[clap(long, short, value_parser, conflicts_with = "save-config")] + load_config: Option, + }, +} pub struct EncoderIO { pub input: Box, @@ -37,7 +262,7 @@ pub enum Verboseness { Verbose, } -pub struct CliOptions { +pub struct ParsedCliOptions { pub io: EncoderIO, pub enc: EncoderConfig, pub limit: usize, @@ -48,447 +273,97 @@ pub struct CliOptions { pub benchmark: bool, pub threads: usize, pub metrics_enabled: MetricsEnabled, - pub pass1file_name: Option, - pub pass2file_name: Option, - pub save_config: Option, + pub pass1file_name: Option, + pub pass2file_name: Option, + pub save_config: Option, + #[cfg(feature = "unstable")] pub slots: usize, #[cfg(feature = "unstable")] pub generate_grain_strength: u8, } #[cfg(feature = "serialize")] -fn build_speed_long_help() -> String { - let levels = (0..=10) - .map(|speed| { - let s = SpeedSettings::from_preset(speed); - let o = crate::kv::to_string(&s).unwrap().replace(", ", "\n "); - format!("{:2} :\n {}", speed, o) - }) - .collect::>() - .join("\n"); +static HELP_TEXT: once_cell::sync::OnceCell = + once_cell::sync::OnceCell::new(); - format!( - "Speed level (0 is best quality, 10 is fastest)\n\ - Speeds 10 and 0 are extremes and are generally not recommended\n\ - {}", - levels - ) +#[cfg(feature = "serialize")] +fn build_speed_long_help() -> Option<&'static str> { + let help = HELP_TEXT.get_or_init(|| { + let levels = (0..=10) + .map(|speed| { + let s = SpeedSettings::from_preset(speed); + let o = crate::kv::to_string(&s).unwrap().replace(", ", "\n "); + format!("{:2} :\n {}", speed, o) + }) + .collect::>() + .join("\n"); + + format!( + "Speed level (0 is best quality, 10 is fastest)\n\ + Speeds 10 and 0 are extremes and are generally not recommended\n\ + {}", + levels + ) + }); + + Some(&help) } #[cfg(not(feature = "serialize"))] -fn build_speed_long_help() -> String { - "Speed level (0 is best quality, 10 is fastest)\n\ - Speeds 10 and 0 are extremes and are generally not recommended" - .into() +#[allow(clippy::missing_const_for_fn)] +fn build_speed_long_help() -> Option<&'static str> { + Some( + "Speed level (0 is best quality, 10 is fastest)\n\ + Speeds 10 and 0 are extremes and are generally not recommended", + ) } #[allow(unused_mut)] /// Only call this once at the start of the app, /// otherwise bad things will happen. -pub fn parse_cli() -> Result { - let profile = env!("PROFILE"); - let ver_short = format!("{} ({})", version::short(), profile); - let ver_long = format!("{} ({})", version::full(), profile); - let speed_long_help = build_speed_long_help(); - let mut app = Command::new("rav1e") - .version(ver_short.as_str()) - .long_version(ver_long.as_str()) - .about("AV1 video encoder") - .setting(AppSettings::DeriveDisplayOrder) - .subcommand_negates_reqs(true) - .arg(Arg::new("FULLHELP") - .help("Prints more detailed help information") - .long("fullhelp")) - // THREADS - .arg( - Arg::new("THREADS") - .help("Set the threadpool size") - .long("threads") - .takes_value(true) - .default_value("0") - ) - // INPUT/OUTPUT - .arg( - Arg::new("INPUT") - .help("Uncompressed YUV4MPEG2 video input") - .required_unless_present("FULLHELP") - .index(1) - .allow_invalid_utf8(true) - ) - .arg( - Arg::new("OUTPUT") - .help("Compressed AV1 in IVF video output") - .short('o') - .long("output") - .required_unless_present("FULLHELP") - .takes_value(true) - .allow_invalid_utf8(true) - ) - // ENCODING SETTINGS - .arg( - Arg::new("FIRST_PASS") - .help("Perform the first pass of a two-pass encode, saving the pass data to the specified file for future passes") - .long("first-pass") - .takes_value(true) - .allow_invalid_utf8(true) - ) - .arg( - Arg::new("SECOND_PASS") - .help("Perform the second pass of a two-pass encode, reading the pass data saved from a previous pass from the specified file") - .long("second-pass") - .takes_value(true) - .allow_invalid_utf8(true) - ) - .arg( - Arg::new("LIMIT") - .help("Maximum number of frames to encode") - .short('l') - .long("limit") - .takes_value(true) - .default_value("0") - ) - .arg( - Arg::new("SKIP") - .help("Skip n number of frames and encode") - .long("skip") - .takes_value(true) - .default_value("0") - ) - .arg( - Arg::new("QP") - .help("Quantizer (0-255), smaller values are higher quality [default: 100]") - .long("quantizer") - .takes_value(true) - ) - .arg( - Arg::new("MINQP") - .help("Minimum quantizer (0-255) to use in bitrate mode [default: 0]") - .long("min-quantizer") - .alias("min_quantizer") - .takes_value(true) - ) - .arg( - Arg::new("BITRATE") - .help("Bitrate (kbps)") - .short('b') - .long("bitrate") - .takes_value(true) - ) - .arg( - Arg::new("SPEED") - .help("Speed level (0 is best quality, 10 is fastest)\n\ - Speeds 10 and 0 are extremes and are generally not recommended") - .long_help(speed_long_help.as_str()) - .short('s') - .long("speed") - .takes_value(true) - .default_value("6") - ) - .arg( - Arg::new("SCENE_CHANGE_DETECTION_SPEED") - .help("Speed level for scene-change detection, 0: best quality, 1: fastest mode\n\ - [default: 0 for s0-s9, 1 for s10]") - .long("scd_speed") - .takes_value(true) - .default_value("1") - ) - .arg( - Arg::new("MIN_KEYFRAME_INTERVAL") - .help("Minimum interval between keyframes") - .short('i') - .long("min-keyint") - .takes_value(true) - .default_value("12") - ) - .arg( - Arg::new("KEYFRAME_INTERVAL") - .help("Maximum interval between keyframes. When set to 0, disables fixed-interval keyframes.") - .short('I') - .long("keyint") - .takes_value(true) - .default_value("240") - ) - .arg( - Arg::new("SWITCH_FRAME_INTERVAL") - .help("Maximum interval between switch frames. When set to 0, disables switch frames.") - .short('S') - .long("switch-frame-interval") - .takes_value(true) - .default_value("0") - ) - .arg( - Arg::new("RESERVOIR_FRAME_DELAY") - .help("Number of frames over which rate control should distribute the reservoir [default: min(240, 1.5x keyint)]\n\ - A minimum value of 12 is enforced.") - .long("reservoir-frame-delay") - .alias("reservoir_frame_delay") - .takes_value(true) - ) - .arg( - Arg::new("LOW_LATENCY") - .help("Low latency mode; disables frame reordering\n\ - Has a significant speed-to-quality trade-off") - .long("low-latency") - .alias("low_latency") - ) - .arg( - Arg::new("NO_SCENE_DETECTION") - .help("Disables scene detection entirely\n\ - Has a significant speed-to-quality trade-off in full encodes.\n\ - Useful for chunked encoding.") - .long("no-scene-detection") - .alias("no_scene_detection") - ) - .arg( - Arg::new("RDO_LOOKAHEAD_FRAMES") - .help("Number of frames encoder should lookahead for RDO purposes\n\ - [default value for speed levels: 10,9 - 10; 8,7,6 - 20; 5,4,3 - 30; 2,1,0 - 40]\n") - .long("rdo-lookahead-frames") - .takes_value(true) - ) - .arg( - Arg::new("TUNE") - .help("Quality tuning") - .long("tune") - .possible_values(Tune::variants()) - .default_value("Psychovisual") - .ignore_case(true) - ) - .arg( - Arg::new("TILE_ROWS") - .help("Number of tile rows. Must be a power of 2. rav1e may override this based on video resolution.") - .long("tile-rows") - .takes_value(true) - .default_value("0") - ) - .arg( - Arg::new("TILE_COLS") - .help("Number of tile columns. Must be a power of 2. rav1e may override this based on video resolution.") - .long("tile-cols") - .takes_value(true) - .default_value("0") - ) - .arg( - Arg::new("TILES") - .help("Number of tiles. Tile-cols and tile-rows are overridden\n\ - so that the video has at least this many tiles.") - .long("tiles") - .takes_value(true) - .default_value("0") - ) - // MASTERING - .arg( - Arg::new("PIXEL_RANGE") - .help("Pixel range") - .long("range") - .possible_values(PixelRange::variants()) - .default_value("limited") - .ignore_case(true) - ) - .arg( - Arg::new("COLOR_PRIMARIES") - .help("Color primaries used to describe color parameters") - .long("primaries") - .possible_values(ColorPrimaries::variants()) - .default_value("unspecified") - .ignore_case(true) - ) - .arg( - Arg::new("TRANSFER_CHARACTERISTICS") - .help("Transfer characteristics used to describe color parameters") - .long("transfer") - .possible_values(TransferCharacteristics::variants()) - .default_value("unspecified") - .ignore_case(true) - ) - .arg( - Arg::new("MATRIX_COEFFICIENTS") - .help("Matrix coefficients used to describe color parameters") - .long("matrix") - .possible_values(MatrixCoefficients::variants()) - .default_value("unspecified") - .ignore_case(true) - ) - .arg( - Arg::new("MASTERING_DISPLAY") - .help("Mastering display primaries in the form of G(x,y)B(x,y)R(x,y)WP(x,y)L(max,min)") - .long("mastering-display") - .alias("mastering_display") - .default_value("unspecified") - .ignore_case(true) - ) - .arg( - Arg::new("CONTENT_LIGHT") - .help("Content light level used to describe content luminosity (cll,fall)") - .long("content-light") - .alias("content_light") - .default_value("0,0") - .ignore_case(true) - ) - // TIMING INFO - .arg( - Arg::new("FRAME_RATE") - .help("Constant frame rate to set at the output (inferred from input when omitted)") - .long("frame-rate") - .alias("frame_rate") - .takes_value(true) - ) - .arg( - Arg::new("TIME_SCALE") - .help("The time scale associated with the frame rate if provided (ignored otherwise)") - .long("time-scale") - .alias("time_scale") - .default_value("1") - .takes_value(true) - ) - // STILL PICTURE - .arg( - Arg::new("STILL_PICTURE") - .help("Still picture mode") - .long("still-picture") - .alias("still_picture") - ) - // DEBUGGING - .arg( - Arg::new("BENCHMARK") - .help("Provide a benchmark report at the end of the encoding") - .long("benchmark") - ) - .arg( - Arg::new("VERBOSE") - .help("Verbose logging; outputs info for every frame") - .long("verbose") - .short('v') - ) - .arg( - Arg::new("QUIET") - .help("Do not output any status message") - .long("quiet") - .short('q') - ) - .arg( - Arg::new("PSNR") - .help("Calculate and display PSNR metrics") - .long("psnr") - ) - .arg( - Arg::new("METRICS") - .help("Calculate and display several metrics including PSNR, SSIM, CIEDE2000 etc") - .long("metrics") - ) - .arg( - Arg::new("RECONSTRUCTION") - .help("Outputs a Y4M file containing the output from the decoder") - .long("reconstruction") - .short('r') - .takes_value(true) - .allow_invalid_utf8(true) - ) - .arg( - Arg::new("OVERWRITE") - .help("Overwrite output file.") - .short('y') - ) - .subcommand(Command::new("advanced") - .hide(true) - .about("Advanced features") - .arg(Arg::new("SHELL") - .help("Output to stdout the completion definition for the shell") - .short('c') - .long("completion") - .takes_value(true) - .possible_values(Shell::possible_values().collect::>()) - ) - .arg(Arg::new("SAVE_CONFIG") - .help("Save the current configuration in a toml file") - .short('s') - .long("save_config") - .takes_value(true) - ) - .arg(Arg::new("LOAD_CONFIG") - .help("Load the encoder configuration from a toml file") - .short('l') - .long("load_config") - .takes_value(true) - ) - ); - - if cfg!(feature = "unstable") { - app = app.arg( - Arg::new("SLOTS") - .help("Maximum number of GOPs that can be encoded in parallel") - .long("parallel_gops") - .long("slots") - .takes_value(true) - .default_value("0"), - ).arg( - Arg::new("PHOTON_NOISE") - .help("Uses grain synthesis to add photon noise to the resulting encode.\n\ - Takes a strength value 0-64.") - .long("photon-noise") - .takes_value(true) - .conflicts_with("PHOTON_NOISE_TABLE") - ).arg( - Arg::new("PHOTON_NOISE_TABLE") - .help("Uses a film grain table file to apply grain synthesis to the encode.\n\ - Uses the same table file format as aomenc and svt-av1.") - .long("photon-noise-table") - .takes_value(true) - .conflicts_with("PHOTON_NOISE") - ); - } - - let matches = app.clone().get_matches(); +pub fn parse_cli() -> Result { + let matches = CliOptions::parse(); - if matches.is_present("FULLHELP") { - app.print_long_help().unwrap(); - std::process::exit(0); - } - - let threads = matches - .value_of("THREADS") - .map(|v| v.parse().expect("Threads must be an integer")) - .unwrap(); - - let mut save_config = None; + let mut save_config_path = None; let mut enc = None; - if let Some(matches) = matches.subcommand_matches("advanced") { - if let Some(shell) = - matches.value_of("SHELL").map(|v| v.parse::().unwrap()) - { - let name = app.get_name().to_string(); - generate(shell, &mut app, name, &mut std::io::stdout()); - std::process::exit(0); - } - - #[cfg(feature = "serialize")] - { - save_config = matches.value_of("SAVE_CONFIG").map(|v| v.to_owned()); - if let Some(load_config) = matches.value_of("LOAD_CONFIG") { - let mut config = String::new(); - File::open(load_config) - .and_then(|mut f| f.read_to_string(&mut config)) - .map_err(|e| e.context("Cannot open the configuration file"))?; - - enc = Some(toml::from_str(&config).unwrap()); - } - } - #[cfg(not(feature = "serialize"))] - { - if matches.value_of("SAVE_CONFIG").is_some() - || matches.value_of("LOAD_CONFIG").is_some() - { - let e: io::Error = io::ErrorKind::InvalidInput.into(); - return Err(e.context( - "The load/save config advanced option requires the + if let Some(command) = matches.command.as_ref() { + match command { + Commands::Advanced { completion, save_config, load_config } => { + if let Some(shell) = completion { + let mut app = CliOptions::command(); + let app_name = app.get_name().to_string(); + generate(*shell, &mut app, app_name, &mut std::io::stdout()); + std::process::exit(0); + } + + #[cfg(feature = "serialize")] + { + save_config_path = save_config.clone(); + if let Some(load_config) = load_config { + let mut config = String::new(); + File::open(load_config) + .and_then(|mut f| f.read_to_string(&mut config)) + .map_err(|e| e.context("Cannot open the configuration file"))?; + + enc = Some(toml::from_str(&config).unwrap()); + } + } + #[cfg(not(feature = "serialize"))] + { + if save_config.is_some() || load_config.is_some() { + let e: io::Error = io::ErrorKind::InvalidInput.into(); + return Err(e.context( + "The load/save config advanced option requires the `serialize` feature, rebuild adding it.", - )); + )); + } + } } } } - let rec = match matches.value_of_os("RECONSTRUCTION") { + let rec = match matches.reconstruction.as_ref() { Some(f) => Some(Box::new( File::create(&f) .map_err(|e| e.context("Cannot create reconstruction file"))?, @@ -496,7 +371,7 @@ pub fn parse_cli() -> Result { None => None, }; - let os_input = matches.value_of_os("INPUT").unwrap(); + let os_input = &matches.input; let io = EncoderIO { input: match os_input.to_str() { Some("-") => Box::new(io::stdin()) as Box, @@ -505,64 +380,54 @@ pub fn parse_cli() -> Result { .map_err(|e| e.context("Cannot open input file"))?, ) as Box, }, - output: create_muxer( - matches.value_of_os("OUTPUT").unwrap(), - matches.is_present("OVERWRITE"), - )?, + output: create_muxer(&matches.output, matches.overwrite)?, rec, }; let enc = enc.map_or_else(|| parse_config(&matches), Ok)?; - let verbose = if matches.is_present("QUIET") { + let verbose = if matches.quiet { Verboseness::Quiet - } else if matches.is_present("VERBOSE") { + } else if matches.verbose { Verboseness::Verbose } else { Verboseness::Normal }; - let metrics_enabled = if matches.is_present("METRICS") { + let metrics_enabled = if matches.metrics { MetricsEnabled::All - } else if matches.is_present("PSNR") { + } else if matches.psnr { MetricsEnabled::Psnr } else { MetricsEnabled::None }; - let limit = matches.value_of("LIMIT").unwrap().parse().unwrap(); + let limit = matches.limit; if enc.still_picture && limit > 1 { panic!("A limit cannot be set above 1 in still picture mode"); } - let slots = if cfg!(feature = "unstable") { - matches.value_of("SLOTS").unwrap().parse().unwrap() - } else { - 0 - }; + #[cfg(feature = "unstable")] + let slots = matches.slots; - Ok(CliOptions { + Ok(ParsedCliOptions { io, enc, limit, - // Use `occurrences_of()` because `is_present()` is always true - // if a parameter has a default value. - color_range_specified: matches.occurrences_of("PIXEL_RANGE") > 0, - override_time_base: matches.is_present("FRAME_RATE"), + color_range_specified: matches.range.is_some(), + override_time_base: matches.frame_rate.is_some(), metrics_enabled, - skip: matches.value_of("SKIP").unwrap().parse().unwrap(), - benchmark: matches.is_present("BENCHMARK"), + skip: matches.skip, + benchmark: matches.benchmark, verbose, - threads, - pass1file_name: matches.value_of_os("FIRST_PASS").map(|s| s.to_owned()), - pass2file_name: matches.value_of_os("SECOND_PASS").map(|s| s.to_owned()), - save_config, + threads: matches.threads, + pass1file_name: matches.first_pass.clone(), + pass2file_name: matches.second_pass.clone(), + save_config: save_config_path, + #[cfg(feature = "unstable")] slots, #[cfg(feature = "unstable")] - generate_grain_strength: matches - .value_of("PHOTON_NOISE") - .map(|g| g.parse::().unwrap()) - .unwrap_or(0), + generate_grain_strength: matches.photon_noise, }) } @@ -578,22 +443,22 @@ impl MatchGet for ArgMatches { } } -fn parse_config(matches: &ArgMatches) -> Result { - let maybe_quantizer = matches.value_of_int("QP"); - let maybe_bitrate = matches.value_of_int("BITRATE"); +fn parse_config(matches: &CliOptions) -> Result { + let maybe_quantizer = matches.quantizer; + let maybe_bitrate = matches.bitrate; let quantizer = maybe_quantizer.unwrap_or_else(|| { if maybe_bitrate.is_some() { // If a bitrate is specified, the quantizer is the maximum allowed (e.g., // the minimum quality allowed), which by default should be // unconstrained. - Ok(255) + 255 } else { - Ok(100) + 100 } - })? as usize; - let bitrate: i32 = maybe_bitrate.unwrap_or(Ok(0))?; + }) as usize; + let bitrate: i32 = maybe_bitrate.unwrap_or(0); if bitrate <= 0 - && (matches.is_present("FIRST_PASS") || matches.is_present("SECOND_PASS")) + && (matches.first_pass.is_some() || matches.second_pass.is_some()) { panic!("A target bitrate must be specified when using passes"); } @@ -604,44 +469,25 @@ fn parse_config(matches: &ArgMatches) -> Result { panic!("Quantizer must be between 0-255"); } - let speed = matches.value_of("SPEED").unwrap().parse().unwrap(); - let scene_detection_speed: u32 = - matches.value_of("SCENE_CHANGE_DETECTION_SPEED").unwrap().parse().unwrap(); - let max_interval: u64 = - matches.value_of("KEYFRAME_INTERVAL").unwrap().parse().unwrap(); - let mut min_interval: u64 = - matches.value_of("MIN_KEYFRAME_INTERVAL").unwrap().parse().unwrap(); - - if matches.occurrences_of("MIN_KEYFRAME_INTERVAL") == 0 { - min_interval = min_interval.min(max_interval); - } + let speed = matches.speed; + let scene_detection_speed = matches.scd_speed; + let max_interval = matches.keyint; + let min_interval = matches.min_keyint.min(max_interval); if speed > 10 { panic!("Speed must be between 0-10"); } else if min_interval > max_interval { panic!("Maximum keyframe interval must be greater than or equal to minimum keyframe interval"); } - if scene_detection_speed > 2 { - panic!("Scene change detection speed must be between 0-2"); - } - let color_primaries = - matches.value_of("COLOR_PRIMARIES").unwrap().parse().unwrap_or_default(); - let transfer_characteristics = matches - .value_of("TRANSFER_CHARACTERISTICS") - .unwrap() - .parse() - .unwrap_or_default(); - let matrix_coefficients = matches - .value_of("MATRIX_COEFFICIENTS") - .unwrap() - .parse() - .unwrap_or_default(); + let color_primaries = matches.primaries.unwrap_or_default(); + let transfer_characteristics = matches.transfer.unwrap_or_default(); + let matrix_coefficients = matches.matrix.unwrap_or_default(); let mut cfg = EncoderConfig::with_speed_preset(speed); - if matches.occurrences_of("SCENE_CHANGE_DETECTION_SPEED") != 0 { - cfg.speed_settings.scene_detection_mode = if scene_detection_speed == 0 { + if let Some(scd_speed) = scene_detection_speed { + cfg.speed_settings.scene_detection_mode = if scd_speed == 0 { SceneDetectionSpeed::Standard } else { SceneDetectionSpeed::Fast @@ -649,11 +495,9 @@ fn parse_config(matches: &ArgMatches) -> Result { } cfg.set_key_frame_interval(min_interval, max_interval); - cfg.switch_frame_interval = - matches.value_of("SWITCH_FRAME_INTERVAL").unwrap().parse().unwrap(); + cfg.switch_frame_interval = matches.switch_frame_interval; - cfg.pixel_range = - matches.value_of("PIXEL_RANGE").unwrap().parse().unwrap_or_default(); + cfg.pixel_range = matches.range.unwrap_or_default(); cfg.color_description = if color_primaries == ColorPrimaries::Unspecified && transfer_characteristics == TransferCharacteristics::Unspecified && matrix_coefficients == MatrixCoefficients::Unspecified @@ -668,13 +512,10 @@ fn parse_config(matches: &ArgMatches) -> Result { }) }; - let mastering_display_opt = matches.value_of("MASTERING_DISPLAY").unwrap(); - cfg.mastering_display = if mastering_display_opt == "unspecified" { - None - } else { + cfg.mastering_display = matches.mastering_display.as_ref().map(|mastering_display| { let (g_x, g_y, b_x, b_y, r_x, r_y, wp_x, wp_y, max_lum, min_lum) = scan_fmt!( - mastering_display_opt, + mastering_display, "G({},{})B({},{})R({},{})WP({},{})L({},{})", f64, f64, @@ -708,7 +549,7 @@ fn parse_config(matches: &ArgMatches) -> Result { ); } - Some(MasteringDisplay { + MasteringDisplay { primaries: [ ChromaticityPoint { x: (r_x * ((1 << 16) as f64)).round() as u16, @@ -729,48 +570,44 @@ fn parse_config(matches: &ArgMatches) -> Result { }, max_luminance: (max_lum * ((1 << 8) as f64)).round() as u32, min_luminance: (min_lum * ((1 << 14) as f64)).round() as u32, - }) - }; - - let content_light_opt = matches.value_of("CONTENT_LIGHT").unwrap(); - let (cll, fall) = scan_fmt!(content_light_opt, "{},{}", u16, u16) - .expect("Cannot parse the content light option"); - cfg.content_light = if cll == 0 && fall == 0 { - None - } else { - Some(ContentLight { - max_content_light_level: cll, - max_frame_average_light_level: fall, - }) - }; + } + }); + + cfg.content_light = + matches.content_light.as_ref().and_then(|content_light| { + let (cll, fall) = scan_fmt!(content_light, "{},{}", u16, u16) + .expect("Cannot parse the content light option"); + if cll == 0 && fall == 0 { + None + } else { + Some(ContentLight { + max_content_light_level: cll, + max_frame_average_light_level: fall, + }) + } + }); - cfg.still_picture = matches.is_present("STILL_PICTURE"); + cfg.still_picture = matches.still_picture; cfg.quantizer = quantizer; - cfg.min_quantizer = - matches.value_of("MINQP").unwrap_or("0").parse().unwrap(); + cfg.min_quantizer = matches.min_quantizer.unwrap_or(0); cfg.bitrate = bitrate.checked_mul(1000).expect("Bitrate too high"); - cfg.reservoir_frame_delay = matches - .value_of("RESERVOIR_FRAME_DELAY") - .map(|reservior_frame_delay| reservior_frame_delay.parse().unwrap()); - - // rdo-lookahead-frames - let maybe_rdo = matches.value_of("RDO_LOOKAHEAD_FRAMES"); - if maybe_rdo.is_some() { - cfg.speed_settings.rdo_lookahead_frames = - matches.value_of("RDO_LOOKAHEAD_FRAMES").unwrap().parse().unwrap(); + cfg.reservoir_frame_delay = matches.reservoir_frame_delay; + + if let Some(rdo_frames) = matches.rdo_lookahead_frames { + cfg.speed_settings.rdo_lookahead_frames = rdo_frames; } - cfg.tune = matches.value_of("TUNE").unwrap().parse().unwrap(); + cfg.tune = matches.tune; if cfg.tune == Tune::Psychovisual { cfg.speed_settings.transform.tx_domain_distortion = false; } - cfg.tile_cols = matches.value_of("TILE_COLS").unwrap().parse().unwrap(); - cfg.tile_rows = matches.value_of("TILE_ROWS").unwrap().parse().unwrap(); + cfg.tile_cols = matches.tile_cols; + cfg.tile_rows = matches.tile_rows; - cfg.tiles = matches.value_of("TILES").unwrap().parse().unwrap(); + cfg.tiles = matches.tiles.unwrap_or(0); if cfg.tile_cols > 64 || cfg.tile_rows > 64 { panic!("Tile columns and rows may not be greater than 64"); @@ -778,17 +615,14 @@ fn parse_config(matches: &ArgMatches) -> Result { #[cfg(feature = "unstable")] { - let grain_str = matches - .value_of("PHOTON_NOISE") - .map(|g| g.parse::().unwrap()) - .unwrap_or(0); + let grain_str = matches.photon_noise; if grain_str > 0 { if grain_str > 64 { panic!("Film grain strength must be between 0-64"); } // We have to know the video resolution before we can generate a table, // so we must handle that elsewhere. - } else if let Some(table_file) = matches.value_of("PHOTON_NOISE_TABLE") { + } else if let Some(table_file) = matches.photon_noise_table.as_ref() { let contents = std::fs::read_to_string(table_file) .expect("Failed to read film grain table file"); let table = parse_grain_table(&contents) @@ -799,16 +633,13 @@ fn parse_config(matches: &ArgMatches) -> Result { } } - if let Some(frame_rate) = matches.value_of("FRAME_RATE") { - cfg.time_base = Rational::new( - matches.value_of("TIME_SCALE").unwrap().parse().unwrap(), - frame_rate.parse().unwrap(), - ); + if let Some(frame_rate) = matches.frame_rate { + cfg.time_base = Rational::new(matches.time_scale, frame_rate); } - cfg.low_latency = matches.is_present("LOW_LATENCY"); + cfg.low_latency = matches.low_latency; // Disables scene_detection - if matches.is_present("NO_SCENE_DETECTION") { + if matches.no_scene_detection { cfg.speed_settings.scene_detection_mode = SceneDetectionSpeed::None; } diff --git a/src/bin/rav1e-ch.rs b/src/bin/rav1e-ch.rs index e6d30568d2..40fa4cb0fb 100644 --- a/src/bin/rav1e-ch.rs +++ b/src/bin/rav1e-ch.rs @@ -35,7 +35,8 @@ #![warn(clippy::doc_markdown)] #![warn(clippy::missing_errors_doc)] #![warn(clippy::missing_panics_doc)] -#![warn(clippy::undocumented_unsafe_blocks)] +// FIXME: Temporarily disabled due to https://github.com/rust-lang/rust-clippy/issues/9142 +#![allow(clippy::undocumented_unsafe_blocks)] #[macro_use] extern crate log; diff --git a/src/bin/rav1e.rs b/src/bin/rav1e.rs index c68d118465..fd5ae72bce 100644 --- a/src/bin/rav1e.rs +++ b/src/bin/rav1e.rs @@ -35,7 +35,8 @@ #![warn(clippy::doc_markdown)] #![warn(clippy::missing_errors_doc)] #![warn(clippy::missing_panics_doc)] -#![warn(clippy::undocumented_unsafe_blocks)] +// FIXME: Temporarily disabled due to https://github.com/rust-lang/rust-clippy/issues/9142 +#![allow(clippy::undocumented_unsafe_blocks)] #[macro_use] extern crate log; @@ -100,6 +101,7 @@ impl Source { Self { limit, input, count: 0, exit_requested, } } } else { + #[allow(clippy::missing_const_for_fn)] fn new(limit: usize, input: D) -> Self { Self { limit, input, count: 0, } } diff --git a/src/fuzzing.rs b/src/fuzzing.rs index 6c4c04991b..b56e73adc2 100644 --- a/src/fuzzing.rs +++ b/src/fuzzing.rs @@ -300,7 +300,7 @@ pub fn fuzz_encode(arbitrary: ArbitraryEncoder) { pub struct DecodeTestParameters { w: usize, h: usize, - speed: usize, + speed: u8, q: usize, limit: usize, bit_depth: usize, diff --git a/src/lib.rs b/src/lib.rs index e9fd2deee3..0bc54ce71b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,7 +66,8 @@ #![warn(clippy::doc_markdown)] #![warn(clippy::missing_errors_doc)] #![warn(clippy::missing_panics_doc)] -#![warn(clippy::undocumented_unsafe_blocks)] +// FIXME: Temporarily disabled due to https://github.com/rust-lang/rust-clippy/issues/9142 +#![allow(clippy::undocumented_unsafe_blocks)] // Override assert! and assert_eq! in tests #[cfg(test)] diff --git a/src/test_encode_decode/mod.rs b/src/test_encode_decode/mod.rs index 229beb71f7..1788c52fef 100644 --- a/src/test_encode_decode/mod.rs +++ b/src/test_encode_decode/mod.rs @@ -68,12 +68,11 @@ pub(crate) trait TestDecoder { where Self: Sized; fn encode_decode( - &mut self, verify: bool, w: usize, h: usize, speed: usize, - quantizer: usize, limit: usize, bit_depth: usize, - chroma_sampling: ChromaSampling, min_keyint: u64, max_keyint: u64, - switch_frame_interval: u64, low_latency: bool, error_resilient: bool, - bitrate: i32, tile_cols_log2: usize, tile_rows_log2: usize, - still_picture: bool, + &mut self, verify: bool, w: usize, h: usize, speed: u8, quantizer: usize, + limit: usize, bit_depth: usize, chroma_sampling: ChromaSampling, + min_keyint: u64, max_keyint: u64, switch_frame_interval: u64, + low_latency: bool, error_resilient: bool, bitrate: i32, + tile_cols_log2: usize, tile_rows_log2: usize, still_picture: bool, #[cfg(feature = "unstable")] grain_table: Option>, ) { let mut ra = ChaChaRng::from_seed([0; 32]); @@ -177,7 +176,7 @@ pub fn compare_plane( } fn setup_encoder( - w: usize, h: usize, speed: usize, quantizer: usize, bit_depth: usize, + w: usize, h: usize, speed: u8, quantizer: usize, bit_depth: usize, chroma_sampling: ChromaSampling, min_keyint: u64, max_keyint: u64, switch_frame_interval: u64, low_latency: bool, error_resilient: bool, bitrate: i32, tile_cols_log2: usize, tile_rows_log2: usize, @@ -216,7 +215,7 @@ fn setup_encoder( static DIMENSION_OFFSETS: &[(usize, usize)] = &[(0, 0), (4, 4), (8, 8), (16, 16)]; -fn speed(s: usize, decoder: &str) { +fn speed(s: u8, decoder: &str) { let quantizer = 100; let limit = 5; let w = 64; diff --git a/v_frame/src/lib.rs b/v_frame/src/lib.rs index 2e0eebc285..672a4b95e1 100644 --- a/v_frame/src/lib.rs +++ b/v_frame/src/lib.rs @@ -34,7 +34,8 @@ #![warn(clippy::doc_markdown)] #![warn(clippy::missing_errors_doc)] #![warn(clippy::missing_panics_doc)] -#![warn(clippy::undocumented_unsafe_blocks)] +// FIXME: Temporarily disabled due to https://github.com/rust-lang/rust-clippy/issues/9142 +#![allow(clippy::undocumented_unsafe_blocks)] pub mod frame; pub mod math;