fix: address final review findings (clip reset, codecs, paths, timeline)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1
src-tauri/Cargo.lock
generated
1
src-tauri/Cargo.lock
generated
@@ -3297,6 +3297,7 @@ dependencies = [
|
||||
name = "tauri-app"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"dirs",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
|
||||
@@ -29,6 +29,7 @@ tokio = { version = "1.53.1", features = ["full"] }
|
||||
thiserror = "2.0.20"
|
||||
uuid = { version = "1.26.1", features = ["v4"] }
|
||||
tempfile = "3.27.0"
|
||||
dirs = "6.0.0"
|
||||
|
||||
|
||||
# Read the optimization guideline for more details: https://tauri.app/concept/size/#cargo-configuration
|
||||
|
||||
@@ -25,6 +25,7 @@ pub async fn export_clips(
|
||||
on_event: Channel<ExportEvent>,
|
||||
) -> Result<Vec<String>, String> {
|
||||
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);
|
||||
|
||||
match config.export_scope {
|
||||
@@ -40,7 +41,7 @@ pub async fn export_clips(
|
||||
});
|
||||
|
||||
let output = clip_exporter::generate_output_path(
|
||||
&config.output_directory,
|
||||
&output_directory,
|
||||
&config.video_title,
|
||||
&clip.label,
|
||||
&ext,
|
||||
@@ -69,7 +70,7 @@ pub async fn export_clips(
|
||||
});
|
||||
|
||||
let output = clip_exporter::generate_output_path(
|
||||
&config.output_directory,
|
||||
&output_directory,
|
||||
&config.video_title,
|
||||
"Merged",
|
||||
&ext,
|
||||
|
||||
@@ -23,11 +23,20 @@ pub fn build_ffmpeg_args(
|
||||
args.extend(["-c".to_string(), "copy".to_string()]);
|
||||
}
|
||||
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([
|
||||
"-c:v".to_string(),
|
||||
"libx264".to_string(),
|
||||
video_codec.to_string(),
|
||||
"-c:a".to_string(),
|
||||
"aac".to_string(),
|
||||
audio_codec.to_string(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -36,6 +45,20 @@ pub fn build_ffmpeg_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(
|
||||
output_dir: &str,
|
||||
video_title: &str,
|
||||
@@ -206,6 +229,18 @@ mod tests {
|
||||
let args = build_ffmpeg_args(&clip, "input.mp4", "output.mp4", &mode);
|
||||
assert!(args.contains(&"-c:v".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]
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
import ExportDialog from '$lib/components/ExportDialog.svelte';
|
||||
import { loadPreferences } from '$lib/stores/preferences.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';
|
||||
|
||||
let showSetupWizard = $state(true);
|
||||
@@ -24,6 +25,13 @@
|
||||
loadPreferences();
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (session.status === 'resolving') {
|
||||
shuttleRate = 1;
|
||||
resetShuttleRate();
|
||||
}
|
||||
});
|
||||
|
||||
function dispatchTransport(action: TransportKeyAction) {
|
||||
transportControls?.handleKeyAction(action);
|
||||
}
|
||||
|
||||
@@ -31,7 +31,9 @@
|
||||
|
||||
$effect(() => {
|
||||
if (session.duration > 0) {
|
||||
timelineState.visibleStart = 0;
|
||||
timelineState.visibleEnd = session.duration;
|
||||
timelineState.zoom = 1;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
extractWaveform,
|
||||
} from '$lib/bindings/mediaAnalysis';
|
||||
import { clearThumbnailCache } from '$lib/timeline/thumbnailRenderer';
|
||||
import { clearAll as clearAllClips } from '$lib/stores/clips.svelte';
|
||||
|
||||
export type SessionStatus = 'idle' | 'resolving' | 'ready' | 'error';
|
||||
export type DownloadStatus = 'idle' | 'downloading' | 'complete' | 'failed';
|
||||
@@ -32,6 +33,7 @@ export const session = $state({
|
||||
});
|
||||
|
||||
export function setMetadata(meta: VideoMetadata) {
|
||||
clearAllClips();
|
||||
session.url = meta.url;
|
||||
session.title = meta.title;
|
||||
session.duration = meta.duration;
|
||||
@@ -73,6 +75,7 @@ export function setError(msg: string) {
|
||||
}
|
||||
|
||||
export function setResolving() {
|
||||
clearAllClips();
|
||||
clearMediaFields();
|
||||
session.status = 'resolving';
|
||||
session.error = null;
|
||||
|
||||
@@ -17,6 +17,13 @@ export function computeZoom(
|
||||
duration: number
|
||||
): { visibleStart: number; visibleEnd: number; zoom: number } {
|
||||
const currentRange = state.visibleEnd - state.visibleStart;
|
||||
if (currentRange <= 0) {
|
||||
return {
|
||||
visibleStart: state.visibleStart,
|
||||
visibleEnd: state.visibleEnd,
|
||||
zoom: state.zoom,
|
||||
};
|
||||
}
|
||||
const newRange = currentRange / factor;
|
||||
const clampedRange = Math.max(MIN_VISIBLE_RANGE, Math.min(newRange, duration));
|
||||
|
||||
|
||||
@@ -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 {
|
||||
const videoEl = document.querySelector('video');
|
||||
if (!videoEl) return shuttleRate;
|
||||
|
||||
Reference in New Issue
Block a user