///|
/// Declarative CLI request model shared by examples and application wrappers.
pub(all) enum GifCommand {
Inspect
Encode
Decode
Optimize
Preview
Benchmark
} derive(Eq, Debug)
///|
pub(all) struct GifCliRequest {
command : GifCommand
input : String
output : String
width : Int?
height : Int?
colors : Int
delay_cs : Int
profile : ExportProfile
} derive(Debug)
///|
pub fn GifCliRequest::default(command : GifCommand) -> GifCliRequest {
{
command,
input: "",
output: "",
width: None,
height: None,
colors: 256,
delay_cs: 6,
profile: Balanced,
}
}
///|
pub fn GifCliRequest::input(
self : GifCliRequest,
path : String,
) -> GifCliRequest {
{ ..self, input: path }
}
///|
pub fn GifCliRequest::output(
self : GifCliRequest,
path : String,
) -> GifCliRequest {
{ ..self, output: path }
}
///|
pub fn GifCliRequest::size(
self : GifCliRequest,
width : Int,
height : Int,
) -> GifCliRequest {
{ ..self, width: Some(width), height: Some(height) }
}
///|
pub fn GifCliRequest::colors(
self : GifCliRequest,
count : Int,
) -> GifCliRequest {
{ ..self, colors: clamp_palette_size(count) }
}
///|
pub fn GifCliRequest::delay(
self : GifCliRequest,
delay_cs : Int,
) -> GifCliRequest {
{
..self,
delay_cs: if delay_cs < 0 {
0
} else if delay_cs > 65535 {
65535
} else {
delay_cs
},
}
}
///|
pub fn GifCliRequest::profile(
self : GifCliRequest,
profile : ExportProfile,
) -> GifCliRequest {
{ ..self, profile, }
}
///|
pub(all) struct GifCliPlan {
command : GifCommand
animation : Animation?
encode_options : EncodeOptions
optimization_options : OptimizationOptions
preview_size : (Int, Int)?
} derive(Debug)
///|
pub fn GifCliPlan::command(self : GifCliPlan) -> GifCommand {
self.command
}
///|
pub fn GifCliPlan::animation(self : GifCliPlan) -> Animation? {
self.animation
}
///|
pub fn GifCliPlan::encode_options(self : GifCliPlan) -> EncodeOptions {
self.encode_options
}
///|
pub fn GifCliPlan::optimization_options(
self : GifCliPlan,
) -> OptimizationOptions {
self.optimization_options
}
///|
pub fn GifCliPlan::preview_size(self : GifCliPlan) -> (Int, Int)? {
self.preview_size
}
///|
/// Convert a request and source animation into an executable plan.
pub fn make_cli_plan(
request : GifCliRequest,
animation : Animation?,
) -> GifCliPlan raise GifError {
let source = match animation {
None => None
Some(value) => Some(value)
}
let optimization = match source {
None => OptimizationOptions::default().colors(request.colors)
Some(value) => value.plan_export(request.profile).optimization()
}
let encode = EncodeOptions::new(
max_colors=request.colors,
default_delay_cs=request.delay_cs,
)
GifCliPlan::{
command: request.command,
animation: source,
encode_options: encode,
optimization_options: optimization,
preview_size: match (request.width, request.height) {
(Some(width), Some(height)) => Some((width, height))
_ => None
},
}
}
///|
/// Return a short command description for shell help.
pub fn command_description(command : GifCommand) -> String {
match command {
Inspect => "inspect GIF structure and metadata"
Encode => "encode a frame sequence as GIF89a"
Decode => "decode GIF89a into RGBA frames"
Optimize => "estimate and apply palette/delta optimizations"
Preview => "render a contact sheet or thumbnail strip"
Benchmark => "run deterministic encode/decode measurements"
}
}
///|
/// Validate request values before opening an input resource.
pub fn validate_cli_request(request : GifCliRequest) -> Array[String] {
let issues : Array[String] = []
if request.command != Benchmark && request.input.length() == 0 {
issues.push("input path is required")
}
if request.command == Encode && request.output.length() == 0 {
issues.push("output path is required for encode")
}
match (request.width, request.height) {
(Some(width), Some(height)) if width <= 0 || height <= 0 =>
issues.push("preview dimensions must be positive")
_ => ()
}
issues
}