Skip to content

Commit c690653

Browse files
committed
[*] refactor video editor
1 parent 51dca24 commit c690653

File tree

5 files changed

+111
-144
lines changed

5 files changed

+111
-144
lines changed

todo.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
## 问题
2-
- 添加item 到轨道,如果是新建轨道,新建轨道是插到相同优先级轨道的末尾。而不是开头,所以获取track_index有问题。
32

43
## 待验证
5-
- 添加item到轨道,如果选中了轨道,会添加到最后。现在需要添加到当前playhead处,如果有segment则先分割segment,再添加item
64

75
- 视频轨道分离字幕
86

wayshot/src/logic/video_editor/playlist.rs

Lines changed: 105 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use super::{
22
command::{refresh_preview, sync_manager_to_ui, with_history_manager},
33
project::PROJECT_STATE,
4-
segment::{async_load_segment_audio, async_load_segment_thumbnail},
4+
segment::refresh_affected_segments,
55
};
66
use crate::{
77
global_store,
@@ -19,8 +19,10 @@ use std::{path::PathBuf, sync::Arc, time::Duration};
1919
use uuid::Uuid;
2020
use video_editor::{
2121
commands::{
22-
BatchCommand, ExecuteResult,
23-
segment::{InsertSegmentAtTimeCommand, SplitSegmentCommand},
22+
AffectedSegment, BatchCommand, ExecuteResult,
23+
segment::{
24+
InsertSegmentAtTimeCommand, ShiftSubsequentSegmentsCommand, SplitSegmentCommand,
25+
},
2426
track::AddTrackCommand,
2527
},
2628
metadata::{Metadata, MetadataType, get_metadata},
@@ -153,168 +155,132 @@ pub fn async_add_item_to_track(ui_weak: Weak<AppWindow>, name: String, file_path
153155
let timeline_offset_ms = global_store!(ui).get_video_editor_timeline_offset();
154156
let timeline_offset = Duration::from_millis(timeline_offset_ms as u64);
155157

156-
let result: Result<(ExecuteResult, usize, usize, String)> =
157-
with_history_manager(|state| {
158-
// Check if current track matches metadata type
159-
if current_track_index >= 0
160-
&& let Some(current_track) =
161-
state.tracks_manager.get(current_track_index as usize)
162-
&& track_matches_metadata_type(current_track, &metadata)
163-
{
164-
// Add segment to existing track at playhead position
165-
let track_index = current_track_index as usize;
166-
let track = state.tracks_manager.get(track_index).expect("track exists");
167-
168-
let segment_duration = if metadata.is_image() && metadata.duration.is_zero()
169-
{
170-
Duration::from_secs(5)
171-
} else {
172-
metadata.duration
173-
};
174-
175-
// Determine insertion point and whether we need to split
176-
let mut need_split = false;
177-
let mut split_segment_index = 0;
178-
let mut insert_index = track.segments_count();
179-
let mut split_time = Duration::ZERO;
180-
181-
for (i, segment) in track.segments().iter().enumerate() {
182-
let segment_start = segment.timeline_offset;
183-
let segment_end = segment.timeline_offset + segment.duration;
184-
185-
if timeline_offset >= segment_start && timeline_offset < segment_end {
186-
// Playhead is within this segment, need to split
187-
insert_index = i + 1;
188-
need_split = true;
189-
split_segment_index = i;
190-
split_time = timeline_offset - segment_start;
191-
break;
192-
} else if timeline_offset < segment_start {
193-
// Playhead is before this segment
194-
insert_index = i;
195-
break;
196-
}
197-
}
158+
let result: Result<ExecuteResult> = with_history_manager(|state| {
159+
// Check if current track matches metadata type
160+
if current_track_index >= 0
161+
&& let Some(current_track) =
162+
state.tracks_manager.get(current_track_index as usize)
163+
&& track_matches_metadata_type(current_track, &metadata)
164+
{
165+
// Add segment to existing track at playhead position
166+
let track_index = current_track_index as usize;
167+
let track = state.tracks_manager.get(track_index).expect("track exists");
168+
169+
let segment_duration = if metadata.is_image() && metadata.duration.is_zero() {
170+
Duration::from_secs(5)
171+
} else {
172+
metadata.duration
173+
};
198174

199-
// Create the new segment at playhead position
200-
let mut segment =
201-
Segment::new(timeline_offset, segment_duration, metadata.clone());
202-
segment.uuid = Uuid::new_v4().to_string();
203-
let segment = Arc::new(segment);
175+
// Determine insertion point and whether we need to split
176+
let mut need_split = false;
177+
let mut split_segment_index = 0;
178+
let mut insert_index = track.segments_count();
179+
let mut split_time = Duration::ZERO;
180+
181+
for (i, segment) in track.segments().iter().enumerate() {
182+
let segment_start = segment.timeline_offset;
183+
let segment_end = segment.timeline_offset + segment.duration;
184+
185+
if timeline_offset >= segment_start && timeline_offset < segment_end {
186+
// Playhead is within this segment, need to split
187+
insert_index = i + 1;
188+
need_split = true;
189+
split_segment_index = i;
190+
split_time = timeline_offset - segment_start;
191+
break;
192+
} else if timeline_offset < segment_start {
193+
// Playhead is before this segment
194+
insert_index = i;
195+
break;
196+
}
197+
}
204198

205-
let mut batch_command = BatchCommand::new(format!("Add {} to track", name));
199+
// Create the new segment at playhead position
200+
let mut segment =
201+
Segment::new(timeline_offset, segment_duration, metadata.clone());
202+
segment.uuid = Uuid::new_v4().to_string();
203+
let segment = Arc::new(segment);
206204

207-
if need_split {
208-
batch_command.add_command(Box::new(SplitSegmentCommand::new(
209-
track_index,
210-
split_segment_index,
211-
split_time,
212-
)));
213-
}
205+
let mut batch_command = BatchCommand::new(format!("Add {} to track", name));
214206

215-
batch_command.add_command(Box::new(InsertSegmentAtTimeCommand::new(
207+
if need_split {
208+
batch_command.add_command(Box::new(SplitSegmentCommand::new(
216209
track_index,
217-
insert_index,
218-
segment,
219-
need_split, // shift timeline if we split
210+
split_segment_index,
211+
split_time,
220212
)));
221213

222-
let execute_result = state
223-
.history_manager
224-
.execute(&mut state.tracks_manager, Box::new(batch_command))?;
225-
226-
// Determine segment index after insertion
227-
// If we split, the right split part will be at insert_index + 1 after our insert
228-
// So our inserted segment is at insert_index
229-
let segment_index = insert_index;
230-
let segment_uuid = state
231-
.tracks_manager
232-
.get(track_index)
233-
.and_then(|t| t.segments().get(segment_index).map(|s| s.uuid.clone()))
234-
.unwrap_or_default();
235-
236-
return Ok((execute_result, track_index, segment_index, segment_uuid));
237-
}
214+
batch_command.add_extra_affected_segment(
215+
AffectedSegment::with_both_thumbnails(
216+
track_index,
217+
split_segment_index, // 被分割的左边
218+
),
219+
);
238220

239-
let tracks = Track::new(&metadata.path)
240-
.map_err(|e| video_editor::Error::InvalidConfig(e.to_string()))?;
221+
batch_command.add_extra_affected_segment(
222+
AffectedSegment::with_both_thumbnails(
223+
track_index,
224+
split_segment_index + 2, // 被分割的右边
225+
),
226+
);
227+
}
241228

242-
let mut batch_command = BatchCommand::new(format!("Add {} to track", name));
229+
batch_command.add_command(Box::new(InsertSegmentAtTimeCommand::new(
230+
track_index,
231+
insert_index,
232+
segment.clone(),
233+
need_split, // shift inside this command only when splitting
234+
)));
243235

244-
for track in tracks {
245-
let command = AddTrackCommand::new(track);
246-
batch_command.add_command(Box::new(command));
236+
// When inserting at a gap, shift all subsequent segments
237+
if !need_split && insert_index < track.segments_count() {
238+
batch_command.add_command(Box::new(ShiftSubsequentSegmentsCommand::new(
239+
track_index,
240+
insert_index + 1, // Shift segments after the newly inserted one
241+
segment_duration,
242+
)));
247243
}
248244

249245
let execute_result = state
250246
.history_manager
251247
.execute(&mut state.tracks_manager, Box::new(batch_command))?;
252248

253-
// Find the inserted track index by looking for the main track type
254-
// based on metadata. Track::new creates tracks with matching types.
255-
let (track_index, segment_index, segment_uuid) = {
256-
let track_index = state
257-
.tracks_manager
258-
.iter()
259-
.enumerate()
260-
.find(|(_, t)| track_matches_metadata_type(t, &metadata))
261-
.map(|(i, _)| i)
262-
.unwrap_or_else(|| state.tracks_manager.len().saturating_sub(1));
263-
264-
let track = state.tracks_manager.get(track_index).unwrap();
265-
let segment_index = track.segments_count() - 1;
266-
let segment_uuid = track
267-
.segments()
268-
.last()
269-
.map(|s| s.uuid.clone())
270-
.unwrap_or_default();
271-
272-
(track_index, segment_index, segment_uuid)
273-
};
249+
return Ok(execute_result);
250+
}
274251

275-
Ok((execute_result, track_index, segment_index, segment_uuid))
276-
});
252+
let tracks = Track::new(&metadata.path)
253+
.map_err(|e| video_editor::Error::InvalidConfig(e.to_string()))?;
277254

278-
match result {
279-
Ok((execute_result, inserted_track_index, segment_index, segment_uuid)) => {
280-
let tracks_manager = with_history_manager(|state| state.tracks_manager.clone());
255+
let mut batch_command = BatchCommand::new(format!("Add {} to track", name));
256+
257+
for track in tracks {
258+
let command = AddTrackCommand::new(track);
259+
batch_command.add_command(Box::new(command));
260+
}
261+
262+
let execute_result = state
263+
.history_manager
264+
.execute(&mut state.tracks_manager, Box::new(batch_command))?;
281265

266+
Ok(execute_result)
267+
});
268+
269+
match result {
270+
Ok(execute_result) => {
282271
sync_manager_to_ui(&ui);
283272

284-
if execute_result.affected_segments.tracks_changed
273+
let needs_preview_refresh = execute_result.affected_segments.tracks_changed
285274
&& (matches!(metadata.get_type(), MetadataType::Video)
286-
|| metadata.is_image())
287-
{
288-
refresh_preview(&ui);
289-
}
275+
|| metadata.is_image());
290276

291-
crate::toast_success!(ui, format!("Added {} to track", name));
277+
refresh_affected_segments(&ui, execute_result.affected_segments);
292278

293-
// Load thumbnails and audio in background
294-
if matches!(metadata.get_type(), MetadataType::Video) || metadata.is_image() {
295-
for index in 0..2 {
296-
async_load_segment_thumbnail(
297-
ui.as_weak(),
298-
tracks_manager.clone(),
299-
inserted_track_index,
300-
segment_index,
301-
segment_uuid.clone(),
302-
index % 2 == 0,
303-
);
304-
}
279+
if needs_preview_refresh {
280+
refresh_preview(&ui);
305281
}
306282

307-
if matches!(metadata.get_type(), MetadataType::Video)
308-
|| matches!(metadata.get_type(), MetadataType::Audio)
309-
{
310-
async_load_segment_audio(
311-
ui.as_weak(),
312-
tracks_manager.clone(),
313-
inserted_track_index,
314-
segment_index,
315-
segment_uuid,
316-
);
317-
}
283+
crate::toast_success!(ui, format!("Added {} to track", name));
318284
}
319285
Err(e) => crate::toast_warn!(ui, e.to_string()),
320286
}

wayshot/ui/base/icon.slint

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@ export global Icons {
349349
out property <image> move-to-end-light: @image-url("../images/icons/move-to-end-light.svg");
350350
out property <image> image-light: @image-url("../images/icons/image-light.svg");
351351
out property <image> grid-light: @image-url("../images/icons/grid-light.svg");
352+
out property <image> grid-fill: @image-url("../images/icons/grid-fill.svg");
352353
out property <image> left-alignment-light: @image-url("../images/icons/left-alignment-light.svg");
353354

354355
out property <image> landing-account: @image-url("../images/landing/landing-account.svg");
@@ -363,4 +364,5 @@ export global Icons {
363364
out property <image> metamask-pay: @image-url("../images/png/metamask-pay.png");
364365
out property <image> wechat-pay: @image-url("../images/png/wechat-pay.png");
365366
out property <image> brand: @image-url("../images/png/brand.png");
366-
}
367+
}
368+
Lines changed: 1 addition & 0 deletions
Loading

wayshot/ui/panel/desktop/video-editor/preview/preview.slint

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,8 @@ component Btns inherits Rectangle {
112112
}
113113

114114
grid-btn := IconBtn {
115-
icon: Icons.grid-light;
116-
icon-size: Theme.icon-size * 0.9;
115+
icon: Icons.grid-fill;
116+
// icon-size: Theme.icon-size * 0.9;
117117

118118
clicked => {
119119
// todo

0 commit comments

Comments
 (0)