Skip to content

Commit 509f49e

Browse files
Merge pull request #2610 from yashranaway/fix/project-cli
fix(project): normalize Windows recording asset paths
2 parents 2493255 + fc74888 commit 509f49e

2 files changed

Lines changed: 136 additions & 3 deletions

File tree

‎crates/project/src/meta.rs‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ impl RecordingMeta {
323323
fn normalize_paths(&mut self) {
324324
let normalize_video = |meta: &mut VideoMeta| normalize_relative_path(&mut meta.path);
325325
let normalize_audio = |meta: &mut AudioMeta| normalize_relative_path(&mut meta.path);
326-
let normalize_cursor = |path: &mut Option<RelativePathBuf>| {
326+
let normalize_optional_path = |path: &mut Option<RelativePathBuf>| {
327327
if let Some(path) = path {
328328
normalize_relative_path(path);
329329
}
@@ -339,7 +339,7 @@ impl RecordingMeta {
339339
if let Some(audio) = &mut segment.audio {
340340
normalize_audio(audio);
341341
}
342-
normalize_cursor(&mut segment.cursor);
342+
normalize_optional_path(&mut segment.cursor);
343343
}
344344
StudioRecordingMeta::MultipleSegments { inner } => {
345345
for segment in &mut inner.segments {
@@ -353,7 +353,8 @@ impl RecordingMeta {
353353
if let Some(system_audio) = &mut segment.system_audio {
354354
normalize_audio(system_audio);
355355
}
356-
normalize_cursor(&mut segment.cursor);
356+
normalize_optional_path(&mut segment.cursor);
357+
normalize_optional_path(&mut segment.keyboard);
357358
}
358359

359360
if let Cursors::Correct(cursors) = &mut inner.cursors {
@@ -376,6 +377,7 @@ fn normalize_relative_path(path: &mut RelativePathBuf) {
376377
|| normalized.starts_with("screenshots/")
377378
|| normalized.starts_with("output/")
378379
{
380+
*path = RelativePathBuf::from(normalized);
379381
return;
380382
}
381383

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
use cap_project::{RecordingMeta, StudioRecordingMeta};
2+
use relative_path::RelativePathBuf;
3+
use serde_json::json;
4+
5+
fn load_project(metadata: serde_json::Value, paths: &[&str]) -> (tempfile::TempDir, RecordingMeta) {
6+
let project = tempfile::tempdir().unwrap();
7+
for path in paths {
8+
let path = project.path().join(path);
9+
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
10+
std::fs::write(path, b"recorded data").unwrap();
11+
}
12+
std::fs::write(
13+
project.path().join("recording-meta.json"),
14+
serde_json::to_vec(&metadata).unwrap(),
15+
)
16+
.unwrap();
17+
let meta = RecordingMeta::load_for_project(project.path()).unwrap();
18+
(project, meta)
19+
}
20+
21+
fn assert_path(meta: &RecordingMeta, path: &RelativePathBuf, expected: &str) {
22+
assert_eq!(path.as_str(), expected);
23+
assert_eq!(std::fs::read(meta.path(path)).unwrap(), b"recorded data");
24+
}
25+
26+
#[test]
27+
fn single_segment_paths_resolve_with_either_separator() {
28+
let paths = [
29+
"content/display.mp4",
30+
"content/camera.mp4",
31+
"content/audio-input.mp3",
32+
"content/cursor.json",
33+
];
34+
for separator in ["/", "\\"] {
35+
let input = paths.map(|path| path.replace('/', separator));
36+
let (_project, meta) = load_project(
37+
json!({
38+
"pretty_name": "Legacy recording",
39+
"display": {"path": input[0]},
40+
"camera": {"path": input[1]},
41+
"audio": {"path": input[2]},
42+
"cursor": input[3],
43+
"segments": [{"start": 0.0, "end": 1.0}]
44+
}),
45+
&paths,
46+
);
47+
let StudioRecordingMeta::SingleSegment { segment } = meta.studio_meta().unwrap() else {
48+
panic!("expected single segment");
49+
};
50+
assert_path(&meta, &segment.display.path, paths[0]);
51+
assert_path(&meta, &segment.camera.as_ref().unwrap().path, paths[1]);
52+
assert_path(&meta, &segment.audio.as_ref().unwrap().path, paths[2]);
53+
assert_path(&meta, segment.cursor.as_ref().unwrap(), paths[3]);
54+
}
55+
}
56+
57+
#[test]
58+
fn multiple_segment_paths_resolve_with_either_separator() {
59+
let paths = [
60+
"content/segments/segment-0/display.mp4",
61+
"content/segments/segment-0/camera.mp4",
62+
"content/segments/segment-0/mic.ogg",
63+
"content/segments/segment-0/system-audio.ogg",
64+
"content/segments/segment-0/cursor.json",
65+
"content/segments/segment-0/keyboard.bin",
66+
"content/cursors/cursor_0.png",
67+
];
68+
for separator in ["/", "\\"] {
69+
let input = paths.map(|path| path.replace('/', separator));
70+
let (_project, meta) = load_project(
71+
json!({
72+
"pretty_name": "Studio recording",
73+
"segments": [{
74+
"display": {"path": input[0]},
75+
"camera": {"path": input[1]},
76+
"mic": {"path": input[2]},
77+
"system_audio": {"path": input[3]},
78+
"cursor": input[4],
79+
"keyboard": input[5]
80+
}],
81+
"cursors": {"0": {"imagePath": input[6], "hotspot": {"x": 0.0, "y": 0.0}}}
82+
}),
83+
&paths,
84+
);
85+
let StudioRecordingMeta::MultipleSegments { inner } = meta.studio_meta().unwrap() else {
86+
panic!("expected multiple segments");
87+
};
88+
let segment = &inner.segments[0];
89+
assert_path(&meta, &segment.display.path, paths[0]);
90+
assert_path(&meta, &segment.camera.as_ref().unwrap().path, paths[1]);
91+
assert_path(&meta, &segment.mic.as_ref().unwrap().path, paths[2]);
92+
assert_path(
93+
&meta,
94+
&segment.system_audio.as_ref().unwrap().path,
95+
paths[3],
96+
);
97+
assert_path(&meta, segment.cursor.as_ref().unwrap(), paths[4]);
98+
assert_path(&meta, segment.keyboard.as_ref().unwrap(), paths[5]);
99+
assert_eq!(
100+
std::fs::read(inner.get_cursor_image(&meta, "0").unwrap().path).unwrap(),
101+
b"recorded data"
102+
);
103+
}
104+
}
105+
106+
#[test]
107+
fn relocated_keyboard_paths_resolve_alongside_media() {
108+
let paths = [
109+
"content/segments/segment-0/display.mp4",
110+
"content/segments/segment-0/keyboard.bin",
111+
];
112+
let (_project, meta) = load_project(
113+
json!({
114+
"pretty_name": "Moved recording",
115+
"segments": [{
116+
"display": {"path": r"C:\Old\Demo.cap\content\segments\segment-0\display.mp4"},
117+
"keyboard": r"C:\Old\Demo.cap\content\segments\segment-0\keyboard.bin"
118+
}]
119+
}),
120+
&paths,
121+
);
122+
let StudioRecordingMeta::MultipleSegments { inner } = meta.studio_meta().unwrap() else {
123+
panic!("expected multiple segments");
124+
};
125+
assert_path(&meta, &inner.segments[0].display.path, paths[0]);
126+
assert_path(
127+
&meta,
128+
inner.segments[0].keyboard.as_ref().unwrap(),
129+
paths[1],
130+
);
131+
}

0 commit comments

Comments
 (0)