Files
oak-editor/crates/oak-cli/src/main.rs
T
Mike-Solar a45a7af2ac refactor(cli,worker): cut liboakengine, link module rlibs directly (M14 R2)
- oak-cli: new engine.rs assembly layer maps every facade call to
  module Rust APIs (oaknode graph/serializer, oaktimeline commands,
  oakrender ticket arena, oaktask ExportTask, oakcommon config); the
  ffi/optional/host layers and build.rs link config are gone
- oak-worker: the worker session + POSIX shm transport moved into the
  crate (oakrender backend + serde_json control plane); no dylib
- both binaries carry zero liboakengine references (otool verified);
  tests green (30 cli / 41 worker)
2026-08-16 21:16:27 +08:00

185 lines
5.7 KiB
Rust

// Oak Video Editor - Non-Linear Video Editor
// Copyright (C) 2026 Oak Team
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//! oak-cli: headless command-line consumer of the oak editor modules.
//!
//! Rust rewrite of `cli/main.cpp` (which stays in the tree until cutover).
//! Same subcommands, same output format, same exit codes:
//!
//! ```text
//! oak-cli info <project.ove> <start> <end> <out_dir> (project info)
//! oak-cli render <project.ove> <start_seconds> <end_seconds> <out_dir>
//! oak-cli probe <mediafile>
//! oak-cli transcode <input_media> <out> [width] [--format ppm|mp4]
//! ```
//!
//! Exit codes: 0 success, 1 general error, 2 rendering unavailable,
//! 64 usage error.
//!
//! Since M14 R2 this crate links the oak* module rlibs directly
//! (oaknode / oaktimeline / oakcodec / oakrender / oaktask / oakcommon) —
//! no liboakengine dylib, no C ABI, no host shims. [`engine`] is the
//! CLI's own assembly layer over the modules; the subcommands in
//! `src/cmd/` call it (and the modules) directly. [`fmt`], [`ppm`] and
//! [`wav`] are pure-Rust formatting/writing helpers (exact ports of the
//! C++ `printf`/writers).
mod cmd;
mod engine;
mod fmt;
mod ppm;
mod wav;
use std::process::exit;
use clap::{Parser, Subcommand};
/// The exact usage text of `cli/main.cpp`'s `print_usage()` (also the
/// `--help` output).
const USAGE: &str = "oak-cli - headless consumer of the oak editor modules (direct Rust ABI)\n\
\n\
Usage:\n\
oak-cli info <project.ove>\n\
Print project name, sequences and footage.\n\
\n\
oak-cli render <project.ove> <start_seconds> <end_seconds> <out_dir>\n\
Render the first sequence to PPM frames (P6, 8-bit RGB) and the\n\
audio range to a PCM s16 WAV file in <out_dir>.\n\
\n\
oak-cli probe <mediafile>\n\
Probe a media file: decoder, duration, video and audio streams.\n\
\n\
oak-cli transcode <input_media> <out> [width] [--format ppm|mp4]\n\
Transcode a media file end to end: import it into a temporary\n\
project, place it as clips, and render the whole duration.\n\
Default output is a single H.264/AAC MP4 file (encoder default\n\
bit rate); --format ppm renders PPM frames + a WAV instead.\n\
[width] defaults to the source width; the height follows the\n\
source aspect ratio. <out> is the MP4 file path, or the\n\
output directory with --format ppm.\n\
\n\
oak-cli --help\n\
Show this text.\n\
\n\
Exit codes:\n\
0 success\n\
1 general error (bad project/media file, no sequence, I/O failure)\n\
2 rendering unavailable or failed (e.g. no render backend)\n\
64 usage error\n";
/// CLI surface. `--help`/`-h` are handled before clap so the C++ usage text
/// is reproduced exactly; clap still enforces the argument shapes.
#[derive(Parser, Debug)]
#[command(
name = "oak-cli",
disable_help_flag = true,
disable_version_flag = true,
subcommand_required = true
)]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand, Debug)]
enum Command {
/// Print project name, sequences and footage.
Info {
/// Path to the project file (.ove).
project: String,
},
/// Render the first sequence to PPM frames (P6, 8-bit RGB) and the
/// audio range to a PCM s16 WAV file in <out_dir>.
Render {
/// Path to the project file (.ove).
project: String,
/// Start of the rendered range, in seconds.
start_seconds: String,
/// End of the rendered range, in seconds (must be > start).
end_seconds: String,
/// Directory the PPM frames and audio.wav are written into.
out_dir: String,
},
/// Probe a media file: decoder, duration, video and audio streams.
Probe {
/// Media file to probe.
mediafile: String,
},
/// Transcode a media file end to end.
Transcode {
/// Source media file.
input_media: String,
/// Output MP4 path, or the output directory with --format ppm.
out: String,
/// Output width (defaults to the source width).
width: Option<String>,
/// Output format: "mp4" (default) or "ppm".
#[arg(long = "format")]
format: Option<String>,
},
}
fn main() {
let args: Vec<String> = std::env::args().skip(1).collect();
// argv[1] handling that mirrors the C++ main() exactly.
if let Some(first) = args.first() {
if first == "--help" || first == "-h" {
print!("{USAGE}");
exit(cmd::EXIT_OK);
}
}
if let Some(first) = args.first() {
if !matches!(first.as_str(), "info" | "render" | "probe" | "transcode") {
eprintln!("error: unknown command \"{first}\"");
eprint_usage();
exit(cmd::EXIT_USAGE);
}
}
let cli = match Cli::try_parse() {
Ok(cli) => cli,
Err(e) => {
// clap's own arity/format message, then the C++ usage text.
let _ = e.print();
eprint_usage();
exit(cmd::EXIT_USAGE);
}
};
let code = match cli.command {
Command::Info { project } => cmd::info::run(project),
Command::Render {
project,
start_seconds,
end_seconds,
out_dir,
} => cmd::render::run(project, &start_seconds, &end_seconds, &out_dir),
Command::Probe { mediafile } => cmd::probe::run(mediafile),
Command::Transcode {
input_media,
out,
width,
format,
} => cmd::transcode::run(input_media, out, width, format),
};
exit(code);
}
fn eprint_usage() {
eprint!("{USAGE}");
}