Skip to content

Commit 880a12c

Browse files
committed
[*] refactor: video-editor
1 parent 1a1bcfa commit 880a12c

27 files changed

+328
-30
lines changed

lib/video-editor/src/preview/renderer.rs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -233,13 +233,10 @@ impl PreviewRenderer {
233233
self.mixer_iter = None;
234234
}
235235
} else {
236+
// 视频结束时,暂停播放控制器并清空音频缓冲区
236237
self.controller.pause();
237238
self.mixer_iter = None;
238-
if let Some(sink) = &self.audio_sink
239-
&& let Ok(sink) = sink.lock()
240-
{
241-
sink.pause();
242-
}
239+
self.clear_audio_sink();
243240
}
244241
}
245242

@@ -482,13 +479,10 @@ impl PreviewRenderer {
482479

483480
self.current_frame = video_frame;
484481
} else {
482+
// 视频结束时,暂停播放控制器并清空音频缓冲区
485483
self.controller.pause();
486484
self.mixer_iter = None;
487-
if let Some(sink) = &self.audio_sink
488-
&& let Ok(sink) = sink.lock()
489-
{
490-
sink.pause();
491-
}
485+
self.clear_audio_sink();
492486
}
493487
}
494488

lib/video-editor/src/tracks.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ pub use frame_position::{FramePosition, FrameRange, TimeToFrameConverter};
1515
pub use manager::Manager;
1616
pub use track::Track;
1717
pub use unified_mixer::{UnifiedFrame, UnifiedMixerConfig, UnifiedTracksMixerIterator};
18+
pub use video_frame_cache::set_global_cache_max_frames;

lib/video-editor/src/tracks/video_frame_cache.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@ use std::{
55
hash::{Hash, Hasher},
66
num::NonZeroUsize,
77
path::Path,
8-
sync::{Mutex, OnceLock},
8+
sync::{
9+
Mutex, OnceLock,
10+
atomic::{AtomicUsize, Ordering},
11+
},
912
};
1013

14+
static CONFIGURED_MAX_FRAMES: AtomicUsize = AtomicUsize::new(100);
1115
static GLOBAL_FRAME_CACHE: OnceLock<GlobalFrameCache> = OnceLock::new();
1216

1317
#[derive(Clone, Debug)]
@@ -27,9 +31,15 @@ impl VideoImage {
2731
}
2832
}
2933

34+
pub fn set_global_cache_max_frames(max_frames: usize) {
35+
CONFIGURED_MAX_FRAMES.store(max_frames, Ordering::SeqCst);
36+
}
37+
3038
pub(crate) fn get_global_video_cache() -> &'static GlobalFrameCache {
31-
// 100 frames @ 1080p = ~830MB
32-
GLOBAL_FRAME_CACHE.get_or_init(|| GlobalFrameCache::new(100))
39+
GLOBAL_FRAME_CACHE.get_or_init(|| {
40+
let max_frames = CONFIGURED_MAX_FRAMES.load(Ordering::SeqCst);
41+
GlobalFrameCache::new(max_frames)
42+
})
3343
}
3444

3545
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
@@ -60,7 +70,7 @@ impl GlobalFrameCache {
6070
pub(crate) fn new(max_frames: usize) -> Self {
6171
Self {
6272
cache: Mutex::new(LruCache::new(
63-
NonZeroUsize::new(max_frames).unwrap_or_else(|| NonZeroUsize::new(1000).unwrap()),
73+
NonZeroUsize::new(max_frames).unwrap_or_else(|| NonZeroUsize::new(100).unwrap()),
6474
)),
6575
}
6676
}

todo.md

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,16 @@
11
## 问题
2-
- 预览播放到视频结尾是,有时音频声音会发生明显扭曲
32

43

54
## 待验证
6-
- 如果将segment分割成每一个1秒左右,播放segment会黑屏
7-
- 两个相近的segment,中间有gap时,从前一个segment播放,到后一个segment开始部分画面会出现错乱
5+
- 添加立即缩放到可视范围的按钮,改变缩放值,让所有segment都可见
86

97
- 视频轨道分离字幕
108

119

1210
## 待实现功能
13-
- 添加一个快速切换是否显示segment thumbnail的按钮
1411
- 不同轨道可以使用不同的背景进行区分
15-
- 支持全局缓存图片数量缓存设置
16-
- 预览缓存时长设置
1712
- playlist和library支持编辑移除
18-
- 添加吸附功能,功能是将选中的segment吸附到上一个segment末尾,和移除左边gap功能一致。需要添加一个图标
19-
- 按上下按钮能够改变声音大小,按左右能够移动到上一帧和下一帧
20-
- 添加立即缩放到可视范围的按钮,改变缩放值,让所有segment都可见
2113
- 添加录音功能,录音文件需要添加到playlist中,有选中音频轨道,就添加到音频轨道,没有就添加新的音频轨道
22-
- 轨道头中添加:移动到最上层和最下层两个popup action选项
2314
- view -> metadata弹框需要实现
2415

2516
- 需要重新思考所有轨道联动功能:1. 联动分割

wayshot/src/logic/popup_action.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,14 @@ pub fn init(ui: &AppWindow) {
159159
let index = user_data.parse::<i32>().unwrap();
160160
global_logic!(ui).invoke_video_editor_track_move_down(index);
161161
}
162+
"video-editor-track-move-to-top" => {
163+
let index = user_data.parse::<i32>().unwrap();
164+
global_logic!(ui).invoke_video_editor_track_move_to_top(index);
165+
}
166+
"video-editor-track-move-to-bottom" => {
167+
let index = user_data.parse::<i32>().unwrap();
168+
global_logic!(ui).invoke_video_editor_track_move_to_bottom(index);
169+
}
162170
"video-editor-insert-video-track" => {
163171
let index = user_data.parse::<i32>().unwrap();
164172
global_logic!(ui).invoke_video_editor_insert_video_track(index);

wayshot/src/logic/video_editor/common_type.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::slint_generatedAppWindow::{
55
Resolution as UIResolution, SubtitleType as UISubtitleType,
66
VideoEditorExportAudioConfig as UIVideoEditorExportAudioConfig,
77
VideoEditorExportVideoConfig as UIVideoEditorExportVideoConfig,
8+
VideoEditorPreferenceCacheConfig as UIVideoEditorPreferenceCacheConfig,
89
VideoEditorPreferenceConfig as UIVideoEditorPreferenceConfig,
910
VideoEditorPreferenceTrackConfig as UIVideoEditorPreferenceTrackConfig,
1011
VideoEditorPreviewConfig as UIVideoEditorPreviewConfig,
@@ -86,11 +87,24 @@ pub struct VideoEditorPreferenceTrackConfig {
8687
pub show_filename: bool,
8788
}
8889

90+
#[derive(Serialize, Deserialize, Debug, Clone, Derivative, SlintFromConvert)]
91+
#[derivative(Default)]
92+
#[from("UIVideoEditorPreferenceCacheConfig")]
93+
pub struct VideoEditorPreferenceCacheConfig {
94+
#[derivative(Default(value = "100"))]
95+
pub max_frames: i32,
96+
97+
#[derivative(Default(value = "5"))]
98+
pub max_cache_duration: i32,
99+
}
100+
89101
#[derive(Serialize, Deserialize, Debug, Clone, Derivative, SlintFromConvert)]
90102
#[derivative(Default)]
91103
#[from("UIVideoEditorPreferenceConfig")]
104+
#[serde(default)]
92105
pub struct VideoEditorPreferenceConfig {
93106
pub track: VideoEditorPreferenceTrackConfig,
107+
pub cache: VideoEditorPreferenceCacheConfig,
94108
}
95109

96110
#[derive(Serialize, Deserialize, Debug, Clone, Derivative, SlintFromConvert)]

wayshot/src/logic/video_editor/preview.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,11 +224,15 @@ fn video_editor_preview_size(
224224

225225
fn create_renderer(ui: &AppWindow, position: Duration, volume: f32) -> Option<PreviewRenderer> {
226226
let manager = with_history_manager(|state| Arc::new(state.tracks_manager.clone()));
227-
let mixer_config: UnifiedMixerConfig = global_store!(ui)
227+
let mut mixer_config: UnifiedMixerConfig = global_store!(ui)
228228
.get_video_editor_new_project_config()
229229
.preview_config
230230
.into();
231231

232+
let cache_config = global_store!(ui).get_video_editor_preference_config().cache;
233+
mixer_config.max_cache_duration =
234+
Duration::from_secs(cache_config.max_cache_duration.max(3) as u64);
235+
232236
let mut renderer = PreviewRenderer::new(manager, mixer_config.into());
233237
renderer.set_volume(volume);
234238

@@ -261,11 +265,15 @@ pub fn seek_to_position(ui: &AppWindow, position: Duration) {
261265
let seek_id = PENDING_SEEK_ID.fetch_add(1, Ordering::SeqCst) + 1;
262266
let thread_id = PLAYBACK_THREAD_ID.fetch_add(1, Ordering::SeqCst) + 1;
263267
let manager = with_history_manager(|state| Arc::new(state.tracks_manager.clone()));
264-
let mixer_config: UnifiedMixerConfig = global_store!(ui)
268+
let mut mixer_config: UnifiedMixerConfig = global_store!(ui)
265269
.get_video_editor_new_project_config()
266270
.preview_config
267271
.into();
268272

273+
let cache_config = global_store!(ui).get_video_editor_preference_config().cache;
274+
mixer_config.max_cache_duration =
275+
Duration::from_secs(cache_config.max_cache_duration.max(3) as u64);
276+
269277
let ui_state = global_store!(ui).get_video_editor_ui_state();
270278
let volume = ui_state.preview_volumn.clamp(0.0, 100.0) / 100.0;
271279

wayshot/src/logic/video_editor/project.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use crate::{
1515
slint_generatedAppWindow::{
1616
AppWindow, VideoEditorNewProjectConfig as UIVideoEditorNewProjectConfig,
1717
VideoEditorPlaylistItem as UIVideoEditorPlaylistItem,
18+
VideoEditorPreferenceCacheConfig as UIVideoEditorPreferenceCacheConfig,
1819
VideoEditorPreferenceTrackConfig as UIVideoEditorPreferenceTrackConfig,
1920
VideoEditorRecentEntry as UIVideoEditorRecentEntry,
2021
VideoEditorRecoveryInfo as UIVideoEditorRecoveryInfo,
@@ -39,7 +40,7 @@ use video_editor::{
3940
project::{ManagerData, ProjectPreviewConfig},
4041
recent::RecentFilesManager,
4142
},
42-
tracks::{manager::Manager, track::Track},
43+
tracks::{manager::Manager, set_global_cache_max_frames, track::Track},
4344
};
4445

4546
pub const PROJECT_EXT: &str = "wayshot";
@@ -122,6 +123,7 @@ pub fn init(ui: &AppWindow) {
122123
logic_cb!(video_editor_quit, ui);
123124
logic_cb!(video_editor_show_preference_setting_dialog, ui);
124125
logic_cb!(video_editor_update_preference_track_config, ui, setting);
126+
logic_cb!(video_editor_update_preference_cache_config, ui, setting);
125127
logic_cb!(video_editor_recover_from_autosave, ui);
126128
logic_cb!(video_editor_ignore_recovery, ui);
127129
logic_cb!(video_editor_cancel_recovery, ui);
@@ -206,6 +208,8 @@ fn inner_init(ui: &AppWindow) {
206208
}
207209
};
208210

211+
set_global_cache_max_frames(preference_config.cache.max_frames.max(10) as usize);
212+
209213
_ = ui_weak_for_preference.upgrade_in_event_loop(move |ui| {
210214
global_store!(ui).set_video_editor_preference_config(preference_config.into());
211215
});
@@ -531,6 +535,20 @@ fn video_editor_update_preference_track_config(
531535
crate::toast_success!(ui, "Save config successfully");
532536
}
533537

538+
fn video_editor_update_preference_cache_config(
539+
ui: &AppWindow,
540+
setting: UIVideoEditorPreferenceCacheConfig,
541+
) {
542+
let current = global_store!(ui).get_video_editor_preference_config();
543+
let config = VideoEditorPreferenceConfig {
544+
cache: setting.into(),
545+
..current.into()
546+
};
547+
global_store!(ui).set_video_editor_preference_config(config.clone().into());
548+
db_update_preference_config(ui.as_weak(), config);
549+
crate::toast_success!(ui, "Save config successfully");
550+
}
551+
534552
fn video_editor_recover_from_autosave(ui: &AppWindow) {
535553
let ui_weak = ui.as_weak();
536554
let recovery_info: RecoveryInfo = global_store!(ui).get_video_editor_recovery_info().into();

wayshot/src/logic/video_editor/segment.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use super::{
33
track::{get_selected_segment_indices, is_track_locked},
44
};
55
use crate::{
6-
global_store, logic_cb,
6+
global_logic, global_store, logic_cb,
77
slint_generatedAppWindow::{
88
AppWindow, SelectedSegmentIndex as UISelectedSegmentIndex,
99
VideoEditorTrackSegment as UIVideoEditorTrackSegment,
@@ -56,6 +56,7 @@ pub fn init(ui: &AppWindow) {
5656
logic_cb!(video_editor_segment_remove_gap, ui, index);
5757
logic_cb!(video_editor_segment_remove_left_gap, ui, index);
5858
logic_cb!(video_editor_segment_remove_right_gap, ui, index);
59+
logic_cb!(video_editor_segments_snap_to_previous, ui);
5960
logic_cb!(
6061
video_editor_video_segment_thumbnail,
6162
ui,
@@ -900,6 +901,18 @@ fn video_editor_segment_remove_left_gap(ui: &AppWindow, index: UISelectedSegment
900901
}
901902
}
902903

904+
fn video_editor_segments_snap_to_previous(ui: &AppWindow) {
905+
let idx = store_video_editor_selected_segments_index!(ui).row_count();
906+
if idx == 0 {
907+
return crate::toast_warn!(ui, "No segments selected");
908+
}
909+
910+
match store_video_editor_selected_segments_index!(ui).row_data(idx - 1) {
911+
Some(index) => global_logic!(ui).invoke_video_editor_segment_remove_left_gap(index),
912+
_ => crate::toast_warn!(ui, "No segments selected"),
913+
}
914+
}
915+
903916
fn video_editor_segment_remove_right_gap(ui: &AppWindow, index: UISelectedSegmentIndex) {
904917
let shift_timeline = global_store!(ui)
905918
.get_video_editor_ui_state()

wayshot/src/logic/video_editor/track.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ pub fn init(ui: &AppWindow) {
6060
logic_cb!(video_editor_remove_tracks, ui);
6161
logic_cb!(video_editor_track_move_up, ui, index);
6262
logic_cb!(video_editor_track_move_down, ui, index);
63+
logic_cb!(video_editor_track_move_to_top, ui, index);
64+
logic_cb!(video_editor_track_move_to_bottom, ui, index);
6365
logic_cb!(video_editor_insert_video_track, ui, index);
6466
logic_cb!(video_editor_insert_audio_track, ui, index);
6567
logic_cb!(video_editor_insert_subtitle_track, ui, index);
@@ -257,6 +259,83 @@ fn video_editor_track_move_down(ui: &AppWindow, index: i32) {
257259
}
258260
}
259261

262+
fn video_editor_track_move_to_top(ui: &AppWindow, index: i32) {
263+
if index <= 0 {
264+
return;
265+
}
266+
267+
if is_track_locked(ui, index) {
268+
crate::toast_warn!(ui, "Cannot move a locked track");
269+
return;
270+
}
271+
272+
let idx = index as usize;
273+
274+
let result = with_history_manager(|state| {
275+
if idx >= state.tracks_manager.len() {
276+
return Err(
277+
video_editor::Error::IndexOutOfBounds(idx, state.tracks_manager.len()).into(),
278+
);
279+
}
280+
281+
let command = MoveTrackCommand::new(idx, 0);
282+
state
283+
.history_manager
284+
.execute(&mut state.tracks_manager, Box::new(command))
285+
});
286+
287+
match result {
288+
Ok(execute_result) => {
289+
sync_manager_to_ui(ui);
290+
if execute_result.affected_segments.tracks_changed {
291+
refresh_preview(ui);
292+
}
293+
crate::toast_success!(ui, format!("Moved track from {} to top", index));
294+
}
295+
Err(e) => crate::toast_warn!(ui, e.to_string()),
296+
}
297+
}
298+
299+
fn video_editor_track_move_to_bottom(ui: &AppWindow, index: i32) {
300+
if is_track_locked(ui, index) {
301+
crate::toast_warn!(ui, "Cannot move a locked track");
302+
return;
303+
}
304+
305+
let idx = index as usize;
306+
307+
let result = with_history_manager(|state| {
308+
if idx >= state.tracks_manager.len() {
309+
return Err(
310+
video_editor::Error::IndexOutOfBounds(idx, state.tracks_manager.len()).into(),
311+
);
312+
}
313+
314+
let last_idx = state.tracks_manager.len() - 1;
315+
if idx == last_idx {
316+
return Ok(ExecuteResult {
317+
affected_segments: Default::default(),
318+
});
319+
}
320+
321+
let command = MoveTrackCommand::new(idx, last_idx);
322+
state
323+
.history_manager
324+
.execute(&mut state.tracks_manager, Box::new(command))
325+
});
326+
327+
match result {
328+
Ok(execute_result) => {
329+
sync_manager_to_ui(ui);
330+
if execute_result.affected_segments.tracks_changed {
331+
refresh_preview(ui);
332+
}
333+
crate::toast_success!(ui, format!("Moved track from {} to bottom", index));
334+
}
335+
Err(e) => crate::toast_warn!(ui, e.to_string()),
336+
}
337+
}
338+
260339
fn video_editor_insert_video_track(ui: &AppWindow, index: i32) {
261340
insert_track_by_type(ui, index, UIVideoEditorTrackType::Video);
262341
}

0 commit comments

Comments
 (0)