fix: address final review findings (clip reset, codecs, paths, timeline)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-21 11:25:28 -04:00
parent 169d3d5592
commit 149ce3da21
9 changed files with 70 additions and 5 deletions

1
src-tauri/Cargo.lock generated
View File

@@ -3297,6 +3297,7 @@ dependencies = [
name = "tauri-app" name = "tauri-app"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"dirs",
"serde", "serde",
"serde_json", "serde_json",
"tauri", "tauri",

View File

@@ -29,6 +29,7 @@ tokio = { version = "1.53.1", features = ["full"] }
thiserror = "2.0.20" thiserror = "2.0.20"
uuid = { version = "1.26.1", features = ["v4"] } uuid = { version = "1.26.1", features = ["v4"] }
tempfile = "3.27.0" tempfile = "3.27.0"
dirs = "6.0.0"
# Read the optimization guideline for more details: https://tauri.app/concept/size/#cargo-configuration # Read the optimization guideline for more details: https://tauri.app/concept/size/#cargo-configuration

View File

@@ -25,6 +25,7 @@ pub async fn export_clips(
on_event: Channel<ExportEvent>, on_event: Channel<ExportEvent>,
) -> Result<Vec<String>, String> { ) -> Result<Vec<String>, String> {
tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || {
let output_directory = clip_exporter::expand_tilde_path(&config.output_directory);
let ext = clip_exporter::get_extension(&config.source_file_path, &config.cut_mode); let ext = clip_exporter::get_extension(&config.source_file_path, &config.cut_mode);
match config.export_scope { match config.export_scope {
@@ -40,7 +41,7 @@ pub async fn export_clips(
}); });
let output = clip_exporter::generate_output_path( let output = clip_exporter::generate_output_path(
&config.output_directory, &output_directory,
&config.video_title, &config.video_title,
&clip.label, &clip.label,
&ext, &ext,
@@ -69,7 +70,7 @@ pub async fn export_clips(
}); });
let output = clip_exporter::generate_output_path( let output = clip_exporter::generate_output_path(
&config.output_directory, &output_directory,
&config.video_title, &config.video_title,
"Merged", "Merged",
&ext, &ext,

View File

@@ -23,11 +23,20 @@ pub fn build_ffmpeg_args(
args.extend(["-c".to_string(), "copy".to_string()]); args.extend(["-c".to_string(), "copy".to_string()]);
} }
CutMode::Precise { format: _ } => { CutMode::Precise { format: _ } => {
let ext = Path::new(output)
.extension()
.map(|e| e.to_string_lossy().to_ascii_lowercase())
.unwrap_or_default();
let (video_codec, audio_codec) = match ext.as_str() {
"webm" => ("libvpx-vp9", "libopus"),
"mp4" | "mkv" => ("libx264", "aac"),
_ => ("libx264", "aac"),
};
args.extend([ args.extend([
"-c:v".to_string(), "-c:v".to_string(),
"libx264".to_string(), video_codec.to_string(),
"-c:a".to_string(), "-c:a".to_string(),
"aac".to_string(), audio_codec.to_string(),
]); ]);
} }
} }
@@ -36,6 +45,20 @@ pub fn build_ffmpeg_args(
args args
} }
pub fn expand_tilde_path(path: &str) -> String {
if path == "~" {
return dirs::home_dir()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|| path.to_string());
}
if let Some(rest) = path.strip_prefix("~/") {
if let Some(home) = dirs::home_dir() {
return home.join(rest).to_string_lossy().to_string();
}
}
path.to_string()
}
pub fn generate_output_path( pub fn generate_output_path(
output_dir: &str, output_dir: &str,
video_title: &str, video_title: &str,
@@ -206,6 +229,18 @@ mod tests {
let args = build_ffmpeg_args(&clip, "input.mp4", "output.mp4", &mode); let args = build_ffmpeg_args(&clip, "input.mp4", "output.mp4", &mode);
assert!(args.contains(&"-c:v".to_string())); assert!(args.contains(&"-c:v".to_string()));
assert!(args.contains(&"libx264".to_string())); assert!(args.contains(&"libx264".to_string()));
assert!(args.contains(&"aac".to_string()));
let webm_args = build_ffmpeg_args(&clip, "input.mp4", "output.webm", &mode);
assert!(webm_args.contains(&"libvpx-vp9".to_string()));
assert!(webm_args.contains(&"libopus".to_string()));
}
#[test]
fn test_expand_tilde_path() {
let expanded = expand_tilde_path("~/Videos");
assert!(!expanded.starts_with("~/"));
assert!(expanded.ends_with("Videos") || expanded.contains("Videos"));
} }
#[test] #[test]

View File

@@ -10,7 +10,8 @@
import ExportDialog from '$lib/components/ExportDialog.svelte'; import ExportDialog from '$lib/components/ExportDialog.svelte';
import { loadPreferences } from '$lib/stores/preferences.svelte'; import { loadPreferences } from '$lib/stores/preferences.svelte';
import { getSelectedClipId, removeClip } from '$lib/stores/clips.svelte'; import { getSelectedClipId, removeClip } from '$lib/stores/clips.svelte';
import { adjustShuttle } from '$lib/transport/playback'; import { adjustShuttle, resetShuttleRate } from '$lib/transport/playback';
import { session } from '$lib/stores/videoSession.svelte';
import type { TransportKeyAction } from '$lib/transport/playback'; import type { TransportKeyAction } from '$lib/transport/playback';
let showSetupWizard = $state(true); let showSetupWizard = $state(true);
@@ -24,6 +25,13 @@
loadPreferences(); loadPreferences();
}); });
$effect(() => {
if (session.status === 'resolving') {
shuttleRate = 1;
resetShuttleRate();
}
});
function dispatchTransport(action: TransportKeyAction) { function dispatchTransport(action: TransportKeyAction) {
transportControls?.handleKeyAction(action); transportControls?.handleKeyAction(action);
} }

View File

@@ -31,7 +31,9 @@
$effect(() => { $effect(() => {
if (session.duration > 0) { if (session.duration > 0) {
timelineState.visibleStart = 0;
timelineState.visibleEnd = session.duration; timelineState.visibleEnd = session.duration;
timelineState.zoom = 1;
} }
}); });

View File

@@ -8,6 +8,7 @@ import {
extractWaveform, extractWaveform,
} from '$lib/bindings/mediaAnalysis'; } from '$lib/bindings/mediaAnalysis';
import { clearThumbnailCache } from '$lib/timeline/thumbnailRenderer'; import { clearThumbnailCache } from '$lib/timeline/thumbnailRenderer';
import { clearAll as clearAllClips } from '$lib/stores/clips.svelte';
export type SessionStatus = 'idle' | 'resolving' | 'ready' | 'error'; export type SessionStatus = 'idle' | 'resolving' | 'ready' | 'error';
export type DownloadStatus = 'idle' | 'downloading' | 'complete' | 'failed'; export type DownloadStatus = 'idle' | 'downloading' | 'complete' | 'failed';
@@ -32,6 +33,7 @@ export const session = $state({
}); });
export function setMetadata(meta: VideoMetadata) { export function setMetadata(meta: VideoMetadata) {
clearAllClips();
session.url = meta.url; session.url = meta.url;
session.title = meta.title; session.title = meta.title;
session.duration = meta.duration; session.duration = meta.duration;
@@ -73,6 +75,7 @@ export function setError(msg: string) {
} }
export function setResolving() { export function setResolving() {
clearAllClips();
clearMediaFields(); clearMediaFields();
session.status = 'resolving'; session.status = 'resolving';
session.error = null; session.error = null;

View File

@@ -17,6 +17,13 @@ export function computeZoom(
duration: number duration: number
): { visibleStart: number; visibleEnd: number; zoom: number } { ): { visibleStart: number; visibleEnd: number; zoom: number } {
const currentRange = state.visibleEnd - state.visibleStart; const currentRange = state.visibleEnd - state.visibleStart;
if (currentRange <= 0) {
return {
visibleStart: state.visibleStart,
visibleEnd: state.visibleEnd,
zoom: state.zoom,
};
}
const newRange = currentRange / factor; const newRange = currentRange / factor;
const clampedRange = Math.max(MIN_VISIBLE_RANGE, Math.min(newRange, duration)); const clampedRange = Math.max(MIN_VISIBLE_RANGE, Math.min(newRange, duration));

View File

@@ -44,6 +44,13 @@ export function jumpKeyframe(direction: 1 | -1) {
} }
} }
export function resetShuttleRate() {
const videoEl = document.querySelector('video');
if (videoEl) {
videoEl.playbackRate = 1;
}
}
export function adjustShuttle(dir: 1 | -1, shuttleRate: number): number { export function adjustShuttle(dir: 1 | -1, shuttleRate: number): number {
const videoEl = document.querySelector('video'); const videoEl = document.querySelector('video');
if (!videoEl) return shuttleRate; if (!videoEl) return shuttleRate;