more_fps/
cli.rs

1use crate::ResetData;
2use std::num::NonZeroUsize;
3use std::path::PathBuf;
4
5use crate::NonZeroDecimal;
6use clap::Parser;
7
8use crate::FPS;
9
10#[derive(Debug, Parser)]
11pub struct Cli {
12    /// Path to the file for which we'll increase the frame rate
13    #[arg(value_parser=is_file)]
14    pub input: PathBuf,
15
16    /// final output path
17    /// if it exists, we'll try to build on-top of it
18    #[arg(value_parser=output_dne)]
19    pub output: PathBuf,
20    ///
21    /// AI Model used to generate intermediate frames
22    #[arg(value_parser=is_file, env)]
23    pub ai_binary: PathBuf,
24
25    #[arg(value_parser=is_dir, env)]
26    pub ai_model: PathBuf,
27
28    /// The target frame count for the ai binary
29    /// The default will have the ai binary change your (most likely 24fps) video to
30    /// 60fps
31    #[arg(long, value_enum, default_value_t=FPS::default())]
32    pub fps: FPS,
33
34    /// Path to put temporary/intermediate data
35    /// like ffmpeg generated frames and ai generated frames
36    /// If the path doesn't exist, it will be created
37    /// Perferably a fast m.2 ssd or ramdisk because they are fast
38    #[arg(short, value_parser=dne_or_is_dir)]
39    pub temp_dir: PathBuf,
40
41    /// Maximum number of seconds to extract (assuming the scene splits are too big)
42    #[arg(short='m', default_value_t = NonZeroUsize::new(50).unwrap())]
43    pub max_step_size: NonZeroUsize,
44
45    /// Extra args you may want to pass to the ai binary
46    #[arg(long, default_value_t = default_ai_args())]
47    pub ai_args: String,
48
49    /// Clears cached data
50    #[arg(short='r', default_value_t = ResetData::default())]
51    pub reset: ResetData,
52
53    /// Defines how we should split the video up before generating frames
54    /// If there is a big difference between frames, the ai will generate
55    /// bad frames.
56    #[arg(short='s', default_value_t = String::from(".1"), value_parser=can_be_decimal)]
57    pub scene_gt: String,
58
59    /// https://trac.ffmpeg.org/wiki/Encode/H.264#a1.ChooseaCRFvalue
60    #[arg(long, default_value_t = NonZeroUsize::new(18).unwrap())]
61    pub crf: NonZeroUsize,
62}
63
64fn can_be_decimal(scene_gt: &str) -> Result<String, String> {
65    NonZeroDecimal::try_from(scene_gt)
66        .map_err(|e| format!("scene_gt should be a non-zero decimal: {e}"))?;
67    Ok(scene_gt.to_owned())
68}
69
70/// Confirm the path exists + is a file
71fn is_file(path: &str) -> Result<PathBuf, String> {
72    let path = PathBuf::from(path);
73    if !path.is_file() {
74        return Err(format!("path doesn't exist or isn't a file: {path:?}"));
75    }
76    Ok(path)
77}
78
79fn is_dir(path: &str) -> Result<PathBuf, String> {
80    let path = PathBuf::from(path);
81    if !path.is_dir() {
82        return Err(format!("path doesn't exist is isn't a directory: {path:?}"));
83    }
84    Ok(path)
85}
86
87fn dne_or_is_dir(path: &str) -> Result<PathBuf, String> {
88    let path = PathBuf::from(path);
89    if path.exists() && !path.is_dir() {
90        return Err(format!(
91            "Path should not exist or should be a folder: {path:?}"
92        ));
93    }
94    Ok(path)
95}
96
97fn output_dne(path: &str) -> Result<PathBuf, String> {
98    let path = PathBuf::from(path);
99    if path.exists() {
100        return Err(format!(
101            "Output path already exist. Please delete the file to continue: {path:?}"
102        ));
103    }
104    Ok(path)
105}
106
107fn default_ai_args() -> String {
108    let cpu_count = num_cpus::get();
109    format!("-g 0,-1 -j {cpu_count}:{cpu_count},16:32:16")
110}