Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions default_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
],
"flip_vertical": false,
"flip_horizontal": false,
"use_hardware_acceleration": true,
"export_start_ms": null,
"export_end_ms": null,
"speed_source": "Auto"
Expand Down
75 changes: 58 additions & 17 deletions src/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,24 +57,64 @@ pub fn export_video(
let temp_path = output_path.with_extension("temp.mp4");
let mut output_ctx = ffmpeg::format::output(&temp_path)?;

let encoder = ffmpeg::encoder::find(ffmpeg::codec::Id::H264)
.ok_or_else(|| anyhow!("H264 encoder not found"))?;

let mut output_stream = output_ctx.add_stream(encoder)?;

let encoder_ctx = ffmpeg::codec::context::Context::new_with_codec(encoder);

let mut encoder_ctx_video = encoder_ctx.encoder().video()?;
encoder_ctx_video.set_width(width);
encoder_ctx_video.set_height(height);
encoder_ctx_video.set_format(ffmpeg::format::Pixel::YUV420P);
encoder_ctx_video.set_time_base(time_base);
encoder_ctx_video.set_frame_rate(Some(frame_rate));
let mut active_encoder = None;
let mut target_pix_fmt = ffmpeg::format::Pixel::YUV420P;

if config.use_hardware_acceleration {
let hw_encoders = ["h264_nvenc", "h264_amf", "h264_qsv", "h264_videotoolbox"];
for hw_name in hw_encoders.iter() {
if let Some(enc) = ffmpeg::encoder::find_by_name(hw_name) {
let encoder_ctx = ffmpeg::codec::context::Context::new_with_codec(enc);
let mut encoder_ctx_video = encoder_ctx.encoder().video()?;
encoder_ctx_video.set_width(width);
encoder_ctx_video.set_height(height);

let pix_fmt = if *hw_name == "h264_qsv" {
ffmpeg::format::Pixel::NV12
} else {
ffmpeg::format::Pixel::YUV420P
};

encoder_ctx_video.set_format(pix_fmt);
encoder_ctx_video.set_time_base(time_base);
encoder_ctx_video.set_frame_rate(Some(frame_rate));

let opts = ffmpeg::Dictionary::new();
if let Ok(opened) = encoder_ctx_video.open_as_with(enc, opts) {
println!("Successfully opened hardware encoder: {}", hw_name);
active_encoder = Some((enc, opened));
target_pix_fmt = pix_fmt;
break;
} else {
println!("Failed to open hardware encoder: {}", hw_name);
}
}
}
}

let mut opts = ffmpeg::Dictionary::new();
opts.set("preset", "medium");
let mut encoder = encoder_ctx_video.open_as_with(encoder, opts)?;
let (codec, mut encoder) = if let Some(opened_enc) = active_encoder {
opened_enc
} else {
println!("Using default software H264 encoder");
let enc = ffmpeg::encoder::find(ffmpeg::codec::Id::H264)
.ok_or_else(|| anyhow!("H264 encoder not found"))?;

let encoder_ctx = ffmpeg::codec::context::Context::new_with_codec(enc);
let mut encoder_ctx_video = encoder_ctx.encoder().video()?;
encoder_ctx_video.set_width(width);
encoder_ctx_video.set_height(height);
encoder_ctx_video.set_format(ffmpeg::format::Pixel::YUV420P);
target_pix_fmt = ffmpeg::format::Pixel::YUV420P;
encoder_ctx_video.set_time_base(time_base);
encoder_ctx_video.set_frame_rate(Some(frame_rate));

let mut opts = ffmpeg::Dictionary::new();
opts.set("preset", "medium");
let opened = encoder_ctx_video.open_as_with(enc, opts)?;
(enc, opened)
};

let mut output_stream = output_ctx.add_stream(codec)?;
output_stream.set_parameters(&encoder);

output_ctx.write_header()?;
Expand All @@ -93,7 +133,7 @@ pub fn export_video(
ffmpeg::format::Pixel::RGBA,
width,
height,
ffmpeg::format::Pixel::YUV420P,
target_pix_fmt,
width,
height,
ffmpeg::software::scaling::flag::Flags::FAST_BILINEAR,
Expand All @@ -102,6 +142,7 @@ pub fn export_video(
let mut decoded = ffmpeg::frame::Video::empty();
let mut rgba_frame = ffmpeg::frame::Video::empty();
let mut yuv_frame = ffmpeg::frame::Video::empty();
yuv_frame.set_format(target_pix_fmt);

// Create telemetry view to handle bounding and laps cleanly
let telemetry_view = crate::telemetry::TelemetryView::new(
Expand Down
4 changes: 4 additions & 0 deletions src/gui/controls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ fn render_settings_section(app: &mut MyApp, ui: &mut egui::Ui) {
ui.heading("Settings");
ui.checkbox(&mut app.config.flip_vertical, "Flip Video Vertically");
ui.checkbox(&mut app.config.flip_horizontal, "Flip Video Horizontally");
ui.checkbox(
&mut app.config.use_hardware_acceleration,
"Use Hardware Acceleration (if available)",
);

let mut speed_source = app.config.speed_source.clone();
let mut changed = false;
Expand Down
6 changes: 6 additions & 0 deletions src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ pub struct SyncState {
pub max_auto_sync_offset_ms: i64,
}

fn default_true() -> bool {
true
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ProjectConfig {
pub video_path: PathBuf,
Expand All @@ -52,6 +56,8 @@ pub struct ProjectConfig {
pub elements: Vec<OverlayElement>,
pub flip_vertical: bool,
pub flip_horizontal: bool,
#[serde(default = "default_true")]
pub use_hardware_acceleration: bool,
#[serde(default)]
pub export_start_ms: Option<i64>,
#[serde(default)]
Expand Down
2 changes: 2 additions & 0 deletions tests/project_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ fn test_custom_config_serialization() {
},
flip_vertical: true,
flip_horizontal: false,
use_hardware_acceleration: true,
elements: vec![
OverlayElement {
enabled: true,
Expand Down Expand Up @@ -60,6 +61,7 @@ fn test_config_save_and_load() {
},
flip_vertical: false,
flip_horizontal: true,
use_hardware_acceleration: true,
elements: vec![OverlayElement {
enabled: true,
kind: OverlayKind::TrackMap,
Expand Down
Loading