Skip to content

Commit c719e8a

Browse files
committed
[*] refactor
1 parent 3c1308e commit c719e8a

File tree

17 files changed

+457
-41
lines changed

17 files changed

+457
-41
lines changed

lib/video-editor/src/font.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,20 @@ pub fn get_fonts_info() -> Result<Vec<(String, PathBuf)>> {
2222

2323
Ok(fonts)
2424
}
25+
26+
pub fn get_font_family_from_file(path: &PathBuf) -> Result<String> {
27+
let mut db = fontdb::Database::new();
28+
db.load_font_file(path.clone())?;
29+
30+
for face in db.faces() {
31+
if let Some(family) = face.families.first() {
32+
return Ok(family.0.clone());
33+
}
34+
}
35+
36+
Ok(path
37+
.file_stem()
38+
.and_then(|s| s.to_str())
39+
.unwrap_or("Unknown")
40+
.to_string())
41+
}

wayshot/src/db.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::slint_generatedAppWindow::{
2-
FileType as UIFileType, HistoryEntry as UIHistoryEntry, SettingPlayer as UISettingPlayer,
3-
Subtitle as UISubtitle, Transcribe as UITranscribe,
2+
FileType as UIFileType, FontEntry as UIFontEntry, HistoryEntry as UIHistoryEntry,
3+
SettingPlayer as UISettingPlayer, Subtitle as UISubtitle, Transcribe as UITranscribe,
44
};
55
use pmacro::SlintFromConvert;
66
use serde::{Deserialize, Serialize};
@@ -10,6 +10,7 @@ pub const HISTORY_TABLE: &str = "history";
1010
pub const PLAYER_SETTING_TABLE: &str = "player_setting";
1111
pub const TRANSCRIBE_TABLE: &str = "transcribe";
1212
pub const VIDEO_EDITOR_TABLE: &str = "video_editor";
13+
pub const FONT_TABLE: &str = "font";
1314

1415
pub async fn init(db_path: &str) {
1516
sqldb::create_db(db_path).await.expect("create db");
@@ -29,6 +30,10 @@ pub async fn init(db_path: &str) {
2930
sqldb::entry::new(VIDEO_EDITOR_TABLE)
3031
.await
3132
.expect("video editor table failed");
33+
34+
sqldb::entry::new(FONT_TABLE)
35+
.await
36+
.expect("font table failed");
3237
}
3338

3439
#[macro_export]
@@ -207,3 +212,14 @@ pub struct Transcribe {
207212
#[vec(from = "subtitles")]
208213
pub subtitles: Vec<Subtitle>,
209214
}
215+
216+
#[derive(Serialize, Deserialize, Debug, Clone, Derivative, SlintFromConvert)]
217+
#[derivative(Default)]
218+
#[serde(default)]
219+
#[from("UIFontEntry")]
220+
pub struct FontEntry {
221+
pub id: String,
222+
pub family: String,
223+
pub path: String,
224+
pub marked: bool,
225+
}

wayshot/src/logic/video_editor/filters/subtitle.rs

Lines changed: 116 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::{
77
project::FONT_STYLE_ID,
88
segment::refresh_affected_segments,
99
},
10+
logic_cb,
1011
slint_generatedAppWindow::{
1112
AppWindow, BackgroundColorDetail as UIBackgroundColorDetail,
1213
BorderRadiusDetail as UIBorderRadiusDetail, FontPathDetail as UIFontPathDetail,
@@ -18,18 +19,25 @@ use crate::{
1819
ve_filter_cb,
1920
};
2021
use slint::{ComponentHandle, SharedString};
22+
use std::sync::Arc;
2123
use video_editor::{
2224
commands::{BatchCommand, filter::InsertFilterCommand},
23-
filters::subtitle::style::{
24-
BackgroundColorFilter, BorderRadiusFilter, FontPathFilter, FontSizeFilter,
25-
MarginHorizontalFilter, MarginVerticalFilter, OutlineColorFilter, OutlineWidthFilter,
26-
PaddingFilter, PrimaryColorFilter,
25+
filters::{
26+
subtitle::style::{
27+
BackgroundColorFilter, BorderRadiusFilter, FontPathFilter, FontSizeFilter,
28+
MarginHorizontalFilter, MarginVerticalFilter, OutlineColorFilter, OutlineWidthFilter,
29+
PaddingFilter, PrimaryColorFilter,
30+
},
31+
traits::SubtitleFilterWrapper,
2732
},
33+
tracks::track::Track,
2834
};
2935

3036
pub fn init(ui: &AppWindow) {
3137
load_font_style_from_db(ui);
3238

39+
logic_cb!(video_editor_update_font_style_from_track, ui, track_index);
40+
3341
ve_filter_cb!(from_font_size_json, ui, json);
3442
ve_filter_cb!(from_padding_json, ui, json);
3543
ve_filter_cb!(from_margin_vertical_json, ui, json);
@@ -244,3 +252,107 @@ fn load_font_style_from_db(ui: &AppWindow) {
244252
});
245253
});
246254
}
255+
256+
fn video_editor_update_font_style_from_track(ui: &AppWindow, track_index: i32) {
257+
if track_index < 0 {
258+
return;
259+
}
260+
261+
let track_idx = track_index as usize;
262+
let is_subtitle = with_history_manager(|state| {
263+
state
264+
.tracks_manager
265+
.get(track_idx)
266+
.map(|t| matches!(t, Track::Subtitle(_)))
267+
.unwrap_or(false)
268+
});
269+
270+
if !is_subtitle {
271+
return;
272+
}
273+
274+
let font_style = with_history_manager(|state| {
275+
state.tracks_manager.get(track_idx).and_then(|track| {
276+
if let Track::Subtitle(inner) = track {
277+
inner
278+
.track
279+
.segments
280+
.first()
281+
.map(|seg| extract_font_style_from_filters(&seg.subtitle_filters))
282+
} else {
283+
None
284+
}
285+
})
286+
})
287+
.unwrap_or_default();
288+
289+
crate::global_ve_filter!(ui).set_font_style(font_style.into());
290+
}
291+
292+
fn extract_font_style_from_filters(filters: &[Arc<SubtitleFilterWrapper>]) -> FontStyleConfig {
293+
let mut config = FontStyleConfig::default();
294+
295+
for filter_wrapper in filters {
296+
let filter = &filter_wrapper.inner;
297+
let filter_name = filter.name();
298+
299+
if filter_name == FontPathFilter::NAME {
300+
if let Some(f) = filter.as_any().downcast_ref::<FontPathFilter>() {
301+
config.font_path = f.font_path.to_string_lossy().to_string();
302+
}
303+
} else if filter_name == FontSizeFilter::NAME {
304+
if let Some(f) = filter.as_any().downcast_ref::<FontSizeFilter>() {
305+
config.font_size = f.font_size as i32;
306+
}
307+
} else if filter_name == PaddingFilter::NAME {
308+
if let Some(f) = filter.as_any().downcast_ref::<PaddingFilter>() {
309+
config.padding = f.padding.unwrap_or(4) as i32;
310+
}
311+
} else if filter_name == MarginVerticalFilter::NAME {
312+
if let Some(f) = filter.as_any().downcast_ref::<MarginVerticalFilter>() {
313+
config.margin_vertical = f.margin.unwrap_or(30) as i32;
314+
}
315+
} else if filter_name == MarginHorizontalFilter::NAME {
316+
if let Some(f) = filter.as_any().downcast_ref::<MarginHorizontalFilter>() {
317+
config.margin_horizontal = f.margin.unwrap_or(0) as i32;
318+
}
319+
} else if filter_name == OutlineWidthFilter::NAME {
320+
if let Some(f) = filter.as_any().downcast_ref::<OutlineWidthFilter>() {
321+
config.outline_width = f.width.unwrap_or(2) as i32;
322+
}
323+
} else if filter_name == BorderRadiusFilter::NAME {
324+
if let Some(f) = filter.as_any().downcast_ref::<BorderRadiusFilter>() {
325+
config.border_radius = f.radius.unwrap_or(0) as i32;
326+
}
327+
} else if filter_name == PrimaryColorFilter::NAME {
328+
if let Some(f) = filter.as_any().downcast_ref::<PrimaryColorFilter>()
329+
&& let Some(color) = f.color
330+
{
331+
config.primary_color_r = color[0] as i32;
332+
config.primary_color_g = color[1] as i32;
333+
config.primary_color_b = color[2] as i32;
334+
config.primary_color_a = color[3] as i32;
335+
}
336+
} else if filter_name == OutlineColorFilter::NAME {
337+
if let Some(f) = filter.as_any().downcast_ref::<OutlineColorFilter>()
338+
&& let Some(color) = f.color
339+
{
340+
config.outline_color_r = color[0] as i32;
341+
config.outline_color_g = color[1] as i32;
342+
config.outline_color_b = color[2] as i32;
343+
config.outline_color_a = color[3] as i32;
344+
}
345+
} else if filter_name == BackgroundColorFilter::NAME {
346+
if let Some(f) = filter.as_any().downcast_ref::<BackgroundColorFilter>()
347+
&& let Some(color) = f.color
348+
{
349+
config.background_color_r = color[0] as i32;
350+
config.background_color_g = color[1] as i32;
351+
config.background_color_b = color[2] as i32;
352+
config.background_color_a = color[3] as i32;
353+
}
354+
}
355+
}
356+
357+
config
358+
}
Lines changed: 163 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,173 @@
11
use crate::{
2-
global_store, global_ve_filter,
3-
logic::{toast, tr::tr},
2+
db::{FONT_TABLE as DB_TABLE, FontEntry},
3+
db_add, db_remove_all, db_select_all, db_update,
4+
logic::{toast, tr::tr, video_editor::playlist::picker_files},
45
logic_cb,
5-
slint_generatedAppWindow::{AppWindow, FontInfo as UIFontInfo},
6+
slint_generatedAppWindow::{AppWindow, FontEntry as UIFontEntry},
67
};
8+
use slint::{ComponentHandle, Model, VecModel, Weak};
9+
use std::{collections::HashSet, path::PathBuf};
10+
use video_editor::font::get_font_family_from_file;
11+
12+
db_add!(DB_TABLE, FontEntry);
13+
db_remove_all!(DB_TABLE);
14+
db_update!(DB_TABLE, FontEntry);
715

816
#[macro_export]
9-
macro_rules! store_font_infos {
17+
macro_rules! store_font_entries {
1018
($ui:expr) => {
11-
crate::global_store!($ui)
12-
.get_font_infos()
19+
$ui.global::<crate::slint_generatedAppWindow::Store>()
20+
.get_font_entries()
1321
.as_any()
14-
.downcast_ref::<VecModel<UIFontInfo>>()
15-
.expect("We know we set a VecModel<UIFontInfo> earlier")
22+
.downcast_ref::<VecModel<UIFontEntry>>()
23+
.expect("We know we set a VecModel<UIFontEntry> earlier")
1624
};
1725
}
1826

19-
fn init(ui: &AppWindow) {}
27+
pub fn init(ui: &AppWindow) {
28+
store_font_entries!(ui).set_vec(vec![]);
29+
30+
logic_cb!(init_font_dialog, ui);
31+
logic_cb!(refresh_font_entries, ui);
32+
logic_cb!(import_font_files, ui);
33+
logic_cb!(toggle_font_marked, ui, index);
34+
}
35+
36+
fn init_font_dialog(ui: &AppWindow) {
37+
let ui_weak = ui.as_weak();
38+
tokio::spawn(async move {
39+
let db_fonts = db_select_all!(DB_TABLE, FontEntry);
40+
if db_fonts.is_empty() {
41+
refresh_font_entries_impl(ui_weak.clone()).await;
42+
} else {
43+
sync_fonts_to_ui(ui_weak, db_fonts);
44+
}
45+
});
46+
}
47+
48+
fn refresh_font_entries(ui: &AppWindow) {
49+
let ui_weak = ui.as_weak();
50+
tokio::spawn(async move {
51+
refresh_font_entries_impl(ui_weak).await;
52+
});
53+
}
54+
55+
async fn refresh_font_entries_impl(ui_weak: Weak<AppWindow>) {
56+
let marked_fonts: HashSet<String> = {
57+
let db_fonts = db_select_all!(DB_TABLE, FontEntry);
58+
db_fonts
59+
.into_iter()
60+
.filter(|f| f.marked)
61+
.map(|f| f.path)
62+
.collect()
63+
};
64+
65+
if let Err(e) = sqldb::entry::delete_all(DB_TABLE).await {
66+
log::warn!("Failed to clear font table: {}", e);
67+
}
68+
69+
let system_fonts = match video_editor::font::get_fonts_info() {
70+
Ok(fonts) => fonts,
71+
Err(e) => {
72+
toast::async_toast_warn(ui_weak.clone(), format!("Failed to get fonts: {}", e));
73+
return;
74+
}
75+
};
76+
77+
let mut font_entries: Vec<FontEntry> = Vec::new();
78+
for (family, path) in system_fonts {
79+
let path_str = path.to_string_lossy().to_string();
80+
let marked = marked_fonts.contains(&path_str);
81+
82+
let entry = FontEntry {
83+
id: path_str.clone(),
84+
family,
85+
path: path_str,
86+
marked,
87+
};
88+
89+
let data = serde_json::to_string(&entry).expect("Serialize font entry");
90+
if let Err(e) = sqldb::entry::insert(DB_TABLE, entry.id.as_str(), &data).await {
91+
log::warn!("Failed to insert font {}: {}", entry.family, e);
92+
}
93+
94+
font_entries.push(entry);
95+
}
96+
97+
sync_fonts_to_ui(ui_weak, font_entries);
98+
}
99+
100+
// todo: 需要排序,marked在前
101+
fn sync_fonts_to_ui(ui_weak: Weak<AppWindow>, fonts: Vec<FontEntry>) {
102+
let ui_fonts: Vec<UIFontEntry> = fonts.into_iter().map(|f| f.into()).collect();
103+
_ = ui_weak.upgrade_in_event_loop(move |ui| {
104+
store_font_entries!(ui).set_vec(ui_fonts);
105+
});
106+
}
107+
108+
fn import_font_files(ui: &AppWindow) {
109+
let ui_weak = ui.as_weak();
110+
tokio::spawn(async move {
111+
let file_paths = match picker_files(
112+
ui_weak.clone(),
113+
&tr("Select font files"),
114+
&tr("Font Files"),
115+
&["ttf", "otf", "woff", "woff2"],
116+
) {
117+
Some(paths) => paths,
118+
None => return,
119+
};
120+
121+
for file_path in file_paths {
122+
import_font_to_db(ui_weak.clone(), file_path).await;
123+
}
124+
125+
refresh_font_entries_impl(ui_weak).await;
126+
});
127+
}
128+
129+
async fn import_font_to_db(ui_weak: Weak<AppWindow>, file_path: PathBuf) {
130+
let family = match get_font_family_from_file(&file_path) {
131+
Ok(f) => f,
132+
Err(e) => {
133+
toast::async_toast_warn(
134+
ui_weak.clone(),
135+
format!("Failed to read font file {}: {}", file_path.display(), e),
136+
);
137+
return;
138+
}
139+
};
140+
141+
let path_str = file_path.to_string_lossy().to_string();
142+
143+
let entry = FontEntry {
144+
id: path_str.clone(),
145+
family,
146+
path: path_str,
147+
marked: true,
148+
};
149+
150+
let data = serde_json::to_string(&entry).expect("Serialize font entry");
151+
if let Err(e) = sqldb::entry::insert(DB_TABLE, entry.id.as_str(), &data).await {
152+
toast::async_toast_warn(
153+
ui_weak.clone(),
154+
format!("Failed to import font {}: {}", entry.family, e),
155+
);
156+
return;
157+
}
158+
159+
toast::async_toast_success(ui_weak.clone(), format!("Imported font: {}", entry.family));
160+
}
161+
162+
// TODO: 需要重新排序
163+
fn toggle_font_marked(ui: &AppWindow, index: i32) {
164+
let idx = index as usize;
165+
166+
if idx < store_font_entries!(ui).row_count()
167+
&& let Some(mut font_info) = store_font_entries!(ui).row_data(idx)
168+
{
169+
font_info.marked = !font_info.marked;
170+
store_font_entries!(ui).set_row_data(idx, font_info.clone());
171+
db_update(ui.as_weak(), font_info.into());
172+
}
173+
}

0 commit comments

Comments
 (0)