Skip to content
Merged
59 changes: 26 additions & 33 deletions apps/desktop/src-tauri/src/captions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ use ffmpeg::{
format::{self as avformat},
software::resampling,
};
use futures::StreamExt;
use serde::{Deserialize, Serialize};
use specta::Type;
use std::fs::File;
use std::io::Read;
use std::path::PathBuf;
use std::process::Command;
use std::sync::Arc;
use tauri::{AppHandle, Emitter, Manager, Window};
use tauri::{AppHandle, Manager};
use tauri_specta::Event;
use tempfile::tempdir;
use tokio::io::AsyncWriteExt;
use tokio::sync::Mutex;
Expand Down Expand Up @@ -1775,11 +1777,14 @@ pub async fn save_captions(
"position".to_string(),
serde_json::Value::String(settings.position.clone()),
);
settings_obj.insert("bold".to_string(), serde_json::Value::Bool(settings.bold));
settings_obj.insert(
"italic".to_string(),
serde_json::Value::Bool(settings.italic),
);
settings_obj.insert(
"fontWeight".to_string(),
serde_json::Value::Number(serde_json::Number::from(settings.font_weight)),
);
settings_obj.insert(
"outline".to_string(),
serde_json::Value::Bool(settings.outline),
Expand Down Expand Up @@ -1912,18 +1917,19 @@ pub fn parse_captions_json(json: &str) -> Result<cap_project::CaptionsData, Stri
.and_then(|v| v.as_str())
.unwrap_or("bottom")
.to_string();
let bold = settings_obj
.get("bold")
.and_then(|v| v.as_bool())
.unwrap_or(false);
let italic = settings_obj
.get("italic")
.and_then(|v| v.as_bool())
.unwrap_or(false);
let font_weight = settings_obj
.get("fontWeight")
.or_else(|| settings_obj.get("font_weight"))
.and_then(|v| v.as_u64())
.unwrap_or(700) as u32;
let outline = settings_obj
.get("outline")
.and_then(|v| v.as_bool())
.unwrap_or(true);
.unwrap_or(false);

let outline_color = settings_obj
.get("outlineColor")
Expand Down Expand Up @@ -1971,8 +1977,8 @@ pub fn parse_captions_json(json: &str) -> Result<cap_project::CaptionsData, Stri
background_color,
background_opacity,
position,
bold,
italic,
font_weight,
outline,
outline_color,
export_with_subtitles,
Expand Down Expand Up @@ -2081,16 +2087,11 @@ pub struct DownloadProgress {
pub message: String,
}

impl DownloadProgress {
const EVENT_NAME: &'static str = "download-progress";
}

#[tauri::command]
#[specta::specta]
#[instrument(skip(window))]
#[instrument(skip(app))]
pub async fn download_whisper_model(
app: AppHandle,
window: Window,
model_name: String,
output_path: String,
) -> Result<(), String> {
Expand Down Expand Up @@ -2128,38 +2129,30 @@ pub async fn download_whisper_model(
.await
.map_err(|e| format!("Failed to create file: {e}"))?;

let mut downloaded = 0;
let mut bytes = response
.bytes()
.await
.map_err(|e| format!("Failed to get response bytes: {e}"))?;
let mut downloaded: u64 = 0;
let mut stream = response.bytes_stream();

const CHUNK_SIZE: usize = 1024 * 1024;
while !bytes.is_empty() {
let chunk_size = std::cmp::min(CHUNK_SIZE, bytes.len());
let chunk = bytes.split_to(chunk_size);
while let Some(chunk_result) = stream.next().await {
let chunk = chunk_result.map_err(|e| format!("Error while downloading: {e}"))?;

file.write_all(&chunk)
.await
.map_err(|e| format!("Error while writing to file: {e}"))?;

downloaded += chunk_size as u64;
downloaded += chunk.len() as u64;

let progress = if total_size > 0 {
(downloaded as f64 / total_size as f64) * 100.0
} else {
0.0
};

window
.emit(
DownloadProgress::EVENT_NAME,
DownloadProgress {
message: format!("Downloading model: {progress:.1}%"),
progress,
},
)
.map_err(|e| format!("Failed to emit progress: {e}"))?;
DownloadProgress {
progress,
message: format!("Downloading model: {progress:.1}%"),
}
.emit(&app)
.ok();
}

file.flush()
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/src-tauri/src/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,10 +182,10 @@ impl CapWindowId {
pub fn min_size(&self) -> Option<(f64, f64)> {
Some(match self {
Self::Setup => (600.0, 600.0),
Self::Main => (300.0, 360.0),
Self::Main => (310.0, 320.0),
Self::Editor { .. } => (1275.0, 800.0),
Self::ScreenshotEditor { .. } => (800.0, 600.0),
Self::Settings => (600.0, 450.0),
Self::Settings => (600.0, 465.0),
Self::Camera => (200.0, 200.0),
Self::Upgrade => (950.0, 850.0),
Self::ModeSelect => (580.0, 340.0),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default function CameraSelect(props: {
<CameraSelectBase
{...props}
PillComponent={InfoPill}
class="flex flex-row gap-2 items-center px-2 w-full h-9 rounded-lg transition-colors cursor-default disabled:opacity-70 bg-gray-3 disabled:text-gray-11 KSelect"
class="flex flex-row gap-2 items-center px-2 w-full h-10 rounded-lg transition-colors cursor-default disabled:opacity-70 bg-gray-3 disabled:text-gray-11 KSelect"
iconClass="text-gray-10 size-4"
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default function MicrophoneSelect(props: {
return (
<MicrophoneSelectBase
{...props}
class="flex overflow-hidden relative z-10 flex-row gap-2 items-center px-2 w-full h-9 rounded-lg transition-colors cursor-default disabled:opacity-70 bg-gray-3 disabled:text-gray-11 KSelect"
class="flex overflow-hidden relative z-10 flex-row gap-2 items-center px-2 w-full h-10 rounded-lg transition-colors cursor-default disabled:opacity-70 bg-gray-3 disabled:text-gray-11 KSelect"
levelIndicatorClass="bg-blue-7"
iconClass="text-gray-10 size-4"
PillComponent={InfoPill}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import InfoPill from "./InfoPill";
export default function SystemAudio() {
return (
<SystemAudioToggleRoot
class="flex flex-row gap-2 items-center px-2 w-full h-9 rounded-lg transition-colors cursor-default disabled:opacity-70 bg-gray-3 disabled:text-gray-11 KSelect"
class="flex flex-row gap-2 items-center px-2 w-full h-10 rounded-lg transition-colors cursor-default disabled:opacity-70 bg-gray-3 disabled:text-gray-11 KSelect"
PillComponent={InfoPill}
icon={<IconPhMonitorBold class="text-gray-10 size-4" />}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default function TargetDropdownButton<
aria-expanded={local.expanded ? "true" : "false"}
data-expanded={local.expanded ? "true" : "false"}
class={cx(
"flex h-[3.75rem] w-5 shrink-0 items-center justify-center rounded-lg bg-gray-4 text-gray-12 transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-9 focus-visible:ring-offset-2 focus-visible:ring-offset-gray-1 hover:bg-gray-5",
"flex h-[4rem] w-5 shrink-0 items-center justify-center rounded-lg bg-gray-4 text-gray-12 transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-9 focus-visible:ring-offset-2 focus-visible:ring-offset-gray-1 hover:bg-gray-5",
local.expanded && "bg-gray-5",
local.disabled && "pointer-events-none opacity-60",
local.class,
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/routes/(window-chrome)/new-main/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ import TargetDropdownButton from "./TargetDropdownButton";
import TargetMenuGrid from "./TargetMenuGrid";
import TargetTypeButton from "./TargetTypeButton";

const WINDOW_SIZE = { width: 290, height: 310 } as const;
const WINDOW_SIZE = { width: 310, height: 320 } as const;

const findCamera = (cameras: CameraInfo[], id: DeviceOrModelID) => {
return cameras.find((c) => {
Expand Down
7 changes: 2 additions & 5 deletions apps/desktop/src/routes/(window-chrome)/settings/general.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,8 @@ function AppearanceSection(props: {

return (
<div class="flex flex-col gap-4">
<div class="flex flex-col pb-4 border-b border-gray-2">
<h2 class="text-lg font-medium text-gray-12">General</h2>
<p class="text-sm text-gray-10">
General settings of your Cap application.
</p>
<div class="flex flex-col border-b border-gray-2">
<h2 class="text-lg font-medium text-gray-12">General Settings</h2>
</div>
<div
class="flex justify-start items-center text-gray-12"
Expand Down
114 changes: 82 additions & 32 deletions apps/desktop/src/routes/editor/CaptionsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import { Toggle } from "~/components/Toggle";
import { defaultCaptionSettings } from "~/store/captions";
import type { CaptionSettings } from "~/utils/tauri";
import { commands, events } from "~/utils/tauri";
import IconCapChevronDown from "~icons/cap/chevron-down";
import IconCapCircleCheck from "~icons/cap/circle-check";
import IconLucideCheck from "~icons/lucide/check";
import IconLucideDownload from "~icons/lucide/download";
import { useEditorContext } from "./context";
Expand All @@ -31,6 +33,7 @@ import {
Slider,
Subfield,
topLeftAnimateClasses,
topSlideAnimateClasses,
} from "./ui";

interface ModelOption {
Expand Down Expand Up @@ -273,16 +276,24 @@ export function CaptionsTab() {
);

createEffect(
on(selectedModel, (model) => {
if (model) localStorage.setItem("selectedTranscriptionModel", model);
}),
on(
selectedModel,
(model) => {
if (model) localStorage.setItem("selectedTranscriptionModel", model);
},
{ defer: true },
),
);

createEffect(
on(selectedLanguage, (language) => {
if (language)
localStorage.setItem("selectedTranscriptionLanguage", language);
}),
on(
selectedLanguage,
(language) => {
if (language)
localStorage.setItem("selectedTranscriptionLanguage", language);
},
{ defer: true },
),
);

const checkModelExists = async (modelName: string) => {
Expand Down Expand Up @@ -778,32 +789,71 @@ export function CaptionsTab() {
</div>
</Field>

<Field name="Style Options" icon={<IconCapMessageBubble />}>
<div class="space-y-3">
<div class="flex flex-col gap-4">
<Subfield name="Outline">
<Toggle
checked={getSetting("outline")}
onChange={(checked) =>
updateCaptionSetting("outline", checked)
}
disabled={!hasCaptions()}
/>
</Subfield>
</div>

<Show when={getSetting("outline")}>
<div class="flex flex-col gap-2">
<span class="text-gray-11 text-sm">Outline Color</span>
<RgbInput
value={getSetting("outlineColor")}
onChange={(value) =>
updateCaptionSetting("outlineColor", value)
}
<Field name="Font Weight" icon={<IconCapMessageBubble />}>
<KSelect
options={[
{ label: "Normal", value: 400 },
{ label: "Medium", value: 500 },
{ label: "Bold", value: 700 },
]}
optionValue="value"
optionTextValue="label"
value={{
label: "Custom",
value: getSetting("fontWeight"),
}}
onChange={(value) => {
if (!value) return;
updateCaptionSetting("fontWeight", value.value);
}}
disabled={!hasCaptions()}
itemComponent={(selectItemProps) => (
<MenuItem<typeof KSelect.Item>
as={KSelect.Item}
item={selectItemProps.item}
>
<KSelect.ItemLabel class="flex-1">
{selectItemProps.item.rawValue.label}
</KSelect.ItemLabel>
<KSelect.ItemIndicator class="ml-auto text-blue-9">
<IconCapCircleCheck />
</KSelect.ItemIndicator>
</MenuItem>
)}
>
<KSelect.Trigger class="flex w-full items-center justify-between rounded-md border border-gray-3 bg-gray-2 px-3 py-2 text-sm text-gray-12 transition-colors hover:border-gray-4 hover:bg-gray-3 focus:border-blue-9 focus:outline-none focus:ring-1 focus:ring-blue-9">
<KSelect.Value<{
label: string;
value: number;
}> class="truncate">
{(state) => {
const selected = state.selectedOption();
if (selected) return selected.label;
const weight = getSetting("fontWeight");
const option = [
{ label: "Normal", value: 400 },
{ label: "Medium", value: 500 },
{ label: "Bold", value: 700 },
].find((o) => o.value === weight);
return option ? option.label : "Bold";
}}
</KSelect.Value>
<KSelect.Icon>
<IconCapChevronDown class="size-4 shrink-0 transform transition-transform ui-expanded:rotate-180 text-[--gray-500]" />
</KSelect.Icon>
</KSelect.Trigger>
<KSelect.Portal>
<PopperContent<typeof KSelect.Content>
as={KSelect.Content}
class={cx(topSlideAnimateClasses, "z-50")}
>
<MenuItemList<typeof KSelect.Listbox>
class="overflow-y-auto max-h-40"
as={KSelect.Listbox}
/>
</div>
</Show>
</div>
</PopperContent>
</KSelect.Portal>
</KSelect>
</Field>

<Field name="Export Options" icon={<IconCapMessageBubble />}>
Expand Down
Loading