Skip to content
Merged
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
2 changes: 2 additions & 0 deletions i18n/en/camera.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ settings-video = Video
settings-device = Device
settings-format = Format
settings-microphone = Microphone
settings-record-audio = Record audio
settings-audio-encoder = Audio encoder
settings-encoder = Encoder
settings-quality = Quality
settings-video-encoder = Video encoder
Expand Down
12 changes: 8 additions & 4 deletions src/app/handlers/capture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -732,10 +732,14 @@ impl AppModel {
let framerate = format.framerate.unwrap_or(30);
let pixel_format = format.pixel_format.clone();

let audio_device = self
.available_audio_devices
.get(self.current_audio_device_index)
.map(|dev| format!("pipewire-serial-{}", dev.serial));
// Only get audio device if audio recording is enabled in settings
let audio_device = if self.config.record_audio {
self.available_audio_devices
.get(self.current_audio_device_index)
.map(|dev| format!("pipewire-serial-{}", dev.serial))
} else {
None
};

let selected_encoder = self
.available_video_encoders
Expand Down
38 changes: 38 additions & 0 deletions src/app/handlers/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,44 @@ impl AppModel {
Task::none()
}

pub(crate) fn handle_toggle_record_audio(&mut self) -> Task<cosmic::Action<Message>> {
use cosmic::cosmic_config::CosmicConfigEntry;

self.config.record_audio = !self.config.record_audio;
info!(
record_audio = self.config.record_audio,
"Toggled record audio"
);

if let Some(handler) = self.config_handler.as_ref() {
if let Err(err) = self.config.write_entry(handler) {
error!(?err, "Failed to save record audio setting");
}
}
Task::none()
}

pub(crate) fn handle_select_audio_encoder(
&mut self,
index: usize,
) -> Task<cosmic::Action<Message>> {
use crate::config::AudioEncoder;
use cosmic::cosmic_config::CosmicConfigEntry;

if index < AudioEncoder::ALL.len() {
let encoder = AudioEncoder::ALL[index];
info!(?encoder, "Selected audio encoder");
self.config.audio_encoder = encoder;

if let Some(handler) = self.config_handler.as_ref() {
if let Err(err) = self.config.write_entry(handler) {
error!(?err, "Failed to save audio encoder selection");
}
}
}
Task::none()
}

pub(crate) fn handle_toggle_save_burst_raw(&mut self) -> Task<cosmic::Action<Message>> {
self.config.save_burst_raw = !self.config.save_burst_raw;
info!(
Expand Down
4 changes: 4 additions & 0 deletions src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,10 @@ impl cosmic::Application for AppModel {
.iter()
.map(|f| f.display_name().to_string())
.collect(),
audio_encoder_dropdown_options: crate::config::AudioEncoder::ALL
.iter()
.map(|e| e.display_name().to_string())
.collect(),
device_info_visible: false,
transition_state: crate::app::state::TransitionState::default(),
// QR detection enabled by default
Expand Down
42 changes: 33 additions & 9 deletions src/app/settings/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! Settings drawer view

use crate::app::state::{AppModel, Message};
use crate::config::{AppTheme, PhotoOutputFormat};
use crate::config::{AppTheme, AudioEncoder, PhotoOutputFormat};
use crate::constants::BitratePreset;
use crate::fl;
use cosmic::Element;
Expand Down Expand Up @@ -91,8 +91,14 @@ impl AppModel {
)),
);

// Audio encoder index
let current_audio_encoder_index = AudioEncoder::ALL
.iter()
.position(|e| *e == self.config.audio_encoder)
.unwrap_or(0); // Default to Opus (index 0)

// Video section
let video_section = widget::settings::section()
let mut video_section = widget::settings::section()
.title(fl!("settings-video"))
.add(
widget::settings::item::builder(fl!("settings-encoder")).control(widget::dropdown(
Expand All @@ -109,15 +115,33 @@ impl AppModel {
)),
)
.add(
widget::settings::item::builder(fl!("settings-microphone")).control(
widget::dropdown(
&self.audio_dropdown_options,
Some(self.current_audio_device_index),
Message::SelectAudioDevice,
),
),
widget::settings::item::builder(fl!("settings-record-audio"))
.toggler(self.config.record_audio, |_| Message::ToggleRecordAudio),
);

// Only show audio encoder and microphone selection when audio is enabled
if self.config.record_audio {
video_section = video_section
.add(
widget::settings::item::builder(fl!("settings-audio-encoder")).control(
widget::dropdown(
&self.audio_encoder_dropdown_options,
Some(current_audio_encoder_index),
Message::SelectAudioEncoder,
),
),
)
.add(
widget::settings::item::builder(fl!("settings-microphone")).control(
widget::dropdown(
&self.audio_dropdown_options,
Some(self.current_audio_device_index),
Message::SelectAudioDevice,
),
),
);
}

// Photo section (output format and HDR+ settings)
use crate::config::BurstModeSetting;
// Index 0 = Off, 1 = Auto, 2 = 4 frames, 3 = 6 frames, 4 = 8 frames, 5 = 50 frames
Expand Down
6 changes: 6 additions & 0 deletions src/app/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,8 @@ pub struct AppModel {
pub burst_mode_frame_count_dropdown_options: Vec<String>,
/// Photo output format dropdown options (JPEG, PNG, DNG)
pub photo_output_format_dropdown_options: Vec<String>,
/// Audio encoder dropdown options (Opus, AAC)
pub audio_encoder_dropdown_options: Vec<String>,
/// Whether the device info panel is visible
pub device_info_visible: bool,

Expand Down Expand Up @@ -1218,6 +1220,10 @@ pub enum Message {
SelectVideoEncoder(usize),
/// Select photo output format (JPEG, PNG, DNG)
SelectPhotoOutputFormat(usize),
/// Toggle recording audio with video
ToggleRecordAudio,
/// Select audio encoder (Opus, AAC)
SelectAudioEncoder(usize),
/// Toggle saving raw burst frames as DNG (debugging feature)
ToggleSaveBurstRaw,
/// Toggle virtual camera feature enabled
Expand Down
2 changes: 2 additions & 0 deletions src/app/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,8 @@ impl AppModel {
Message::SelectPhotoOutputFormat(index) => {
self.handle_select_photo_output_format(index)
}
Message::ToggleRecordAudio => self.handle_toggle_record_audio(),
Message::SelectAudioEncoder(index) => self.handle_select_audio_encoder(index),
Message::ToggleSaveBurstRaw => self.handle_toggle_save_burst_raw(),

// ===== System & Recovery =====
Expand Down
31 changes: 30 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,29 @@ impl BurstModeSetting {
];
}

/// Audio encoder preference
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub enum AudioEncoder {
/// Opus codec (preferred - best quality)
#[default]
Opus,
/// AAC codec (fallback - good compatibility)
AAC,
}

impl AudioEncoder {
/// Get display name for this encoder
pub fn display_name(&self) -> &'static str {
match self {
AudioEncoder::Opus => "Opus",
AudioEncoder::AAC => "AAC",
}
}

/// Get all available encoders
pub const ALL: [AudioEncoder; 2] = [AudioEncoder::Opus, AudioEncoder::AAC];
}

/// Application theme preference
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub enum AppTheme {
Expand Down Expand Up @@ -140,7 +163,7 @@ pub struct FormatSettings {
pub type VideoSettings = FormatSettings;

#[derive(Debug, Clone, CosmicConfigEntry, Eq, PartialEq, Serialize, Deserialize)]
#[version = 11]
#[version = 12]
pub struct Config {
/// Application theme preference (System, Dark, Light)
pub app_theme: AppTheme,
Expand Down Expand Up @@ -170,6 +193,10 @@ pub struct Config {
pub save_burst_raw: bool,
/// Burst mode setting (Off, Auto, or fixed frame count)
pub burst_mode_setting: BurstModeSetting,
/// Record audio with video
pub record_audio: bool,
/// Audio encoder preference (Opus or AAC)
pub audio_encoder: AudioEncoder,
}

impl Default for Config {
Expand All @@ -191,6 +218,8 @@ impl Default for Config {
photo_output_format: PhotoOutputFormat::default(), // Default to JPEG
save_burst_raw: false, // Disabled by default (debugging feature)
burst_mode_setting: BurstModeSetting::default(), // Default to Auto
record_audio: true, // Enable audio recording by default
audio_encoder: AudioEncoder::default(), // Default to Opus
}
}
}