Skip to content

Commit ec04041

Browse files
committed
massive optimizations
1 parent b3786e0 commit ec04041

File tree

18 files changed

+1033
-247
lines changed

18 files changed

+1033
-247
lines changed

electron/main.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import fs from "node:fs";
1010
import path from "node:path";
1111
import { fileURLToPath } from "node:url";
1212
import { pathToFileURL } from "node:url";
13-
import * as ffmpegInstaller from "@ffmpeg-installer/ffmpeg";
14-
import * as ffprobeInstaller from "@ffprobe-installer/ffprobe";
13+
import ffmpegInstaller from "@ffmpeg-installer/ffmpeg";
14+
import ffprobeInstaller from "@ffprobe-installer/ffprobe";
1515
import puppeteer from "puppeteer";
1616

1717
const __filename = fileURLToPath(import.meta.url);
@@ -82,6 +82,8 @@ type RenderStartPayload = {
8282
workers: number;
8383
encode: "H264" | "H265";
8484
preset: string;
85+
ffmpegThreads: number;
86+
ffmpegLowMemory: boolean;
8587
};
8688

8789
function getPlatformKey() {
@@ -150,7 +152,7 @@ function startBackend(): Promise<void> {
150152
if (!fs.existsSync(info.path)) {
151153
throw new Error(
152154
`Backend binary not found for platform "${info.platformKey}". Tried:\n` +
153-
info.candidates.map((p) => `- ${p}`).join("\n"),
155+
info.candidates.map((p) => `- ${p}`).join("\n"),
154156
);
155157
}
156158

@@ -266,7 +268,8 @@ function getRenderBinaryInfo() {
266268
}
267269

268270
function startRenderProcess(payload: RenderStartPayload) {
269-
const argsString = `${payload.width}:${payload.height}:${payload.fps}:${payload.totalFrames}:${payload.workers}:${payload.encode}:${payload.preset}`;
271+
const lowMemoryFlag = payload.ffmpegLowMemory ? 1 : 0;
272+
const argsString = `${payload.width}:${payload.height}:${payload.fps}:${payload.totalFrames}:${payload.workers}:${payload.encode}:${payload.preset}:${payload.ffmpegThreads}:${lowMemoryFlag}`;
270273

271274
if (renderChild && !renderChild.killed) {
272275
console.log("[render] terminating previous render process");
@@ -307,7 +310,7 @@ function startRenderProcess(payload: RenderStartPayload) {
307310
const info = getRenderBinaryInfo();
308311
throw new Error(
309312
`Render binary not found for platform "${platformKey}". Tried:\n` +
310-
info.candidates.map((p) => `- ${p}`).join("\n"),
313+
info.candidates.map((p) => `- ${p}`).join("\n"),
311314
);
312315
}
313316

@@ -375,7 +378,7 @@ function createRenderSettingsWindow() {
375378

376379
renderSettingsWindow = new BrowserWindow({
377380
width: 640,
378-
height: 750,
381+
height: 800,
379382
resizable: false,
380383
minimizable: false,
381384
maximizable: false,
@@ -475,6 +478,8 @@ function setupRenderIpc() {
475478
const workers = Math.max(1, Number(payload.workers) || 1);
476479
const encode = payload.encode === "H265" ? "H265" : "H264";
477480
const preset = payload.preset || "medium";
481+
const ffmpegThreads = Math.max(1, Number(payload.ffmpegThreads) || 1);
482+
const ffmpegLowMemory = Boolean(payload.ffmpegLowMemory);
478483

479484
if (width <= 0 || height <= 0 || fps <= 0 || totalFrames <= 0) {
480485
throw new Error("Invalid render payload");
@@ -488,6 +493,8 @@ function setupRenderIpc() {
488493
workers,
489494
encode,
490495
preset,
496+
ffmpegThreads,
497+
ffmpegLowMemory,
491498
});
492499
});
493500
}

electron/render-settings-preload.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ type RenderStartPayload = {
88
workers: number;
99
encode: "H264" | "H265";
1010
preset: string;
11+
ffmpegThreads: number;
12+
ffmpegLowMemory: boolean;
1113
};
1214

1315
contextBridge.exposeInMainWorld("renderAPI", {

render/src/ffmpeg.rs

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::{
2-
error::Error,
32
collections::BTreeMap,
3+
error::Error,
44
io,
55
path::{Path, PathBuf},
66
process::Stdio,
@@ -69,6 +69,8 @@ impl SegmentWriter {
6969
encode: &str,
7070
preset: Option<&str>,
7171
gop: Option<u32>,
72+
ffmpeg_threads: Option<u32>,
73+
low_memory: bool,
7274
) -> Result<Self, Box<dyn std::error::Error>> {
7375
let vcodec = match encode {
7476
"H264" => "libx264",
@@ -107,6 +109,25 @@ impl SegmentWriter {
107109
.arg("-movflags")
108110
.arg("+faststart");
109111

112+
if let Some(threads) = ffmpeg_threads {
113+
cmd.arg("-threads").arg(threads.max(1).to_string());
114+
}
115+
116+
if low_memory {
117+
cmd.arg("-tune").arg("zerolatency").arg("-bf").arg("0");
118+
match vcodec {
119+
"libx264" => {
120+
cmd.arg("-x264-params")
121+
.arg("bframes=0:rc-lookahead=0:sync-lookahead=0:ref=1");
122+
}
123+
"libx265" => {
124+
cmd.arg("-x265-params")
125+
.arg("bframes=0:rc-lookahead=0:ref=1");
126+
}
127+
_ => {}
128+
}
129+
}
130+
110131
if let Some(g) = gop {
111132
cmd.arg("-g")
112133
.arg(g.to_string())
@@ -189,8 +210,7 @@ pub async fn concat_segments_mp4(
189210

190211
let mut lines = String::new();
191212
for seg in segments {
192-
let abs_path = tokio::task::spawn_blocking(move || std::fs::canonicalize(seg))
193-
.await??;
213+
let abs_path = tokio::task::spawn_blocking(move || std::fs::canonicalize(seg)).await??;
194214
let rel_path = match abs_path.strip_prefix(&list_dir_abs) {
195215
Ok(rel) => rel.to_path_buf(),
196216
Err(_) => abs_path,
@@ -283,8 +303,16 @@ pub async fn mux_audio_plan_into_mp4(
283303
return Ok(());
284304
}
285305

286-
let fps = if fps.is_finite() && fps > 0.0 { fps } else { plan.fps };
287-
let fps = if fps.is_finite() && fps > 0.0 { fps } else { 60.0 };
306+
let fps = if fps.is_finite() && fps > 0.0 {
307+
fps
308+
} else {
309+
plan.fps
310+
};
311+
let fps = if fps.is_finite() && fps > 0.0 {
312+
fps
313+
} else {
314+
60.0
315+
};
288316
let duration_sec = (total_frames as f64) / fps;
289317

290318
let mut sources: BTreeMap<String, usize> = BTreeMap::new();

render/src/main.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ struct CancelResponse {
3131

3232
static CHROMIUM_EXECUTABLE: OnceLock<Option<PathBuf>> = OnceLock::new();
3333

34+
fn parse_bool_token(value: &str) -> Option<bool> {
35+
match value.trim().to_ascii_lowercase().as_str() {
36+
"1" | "true" | "yes" | "on" => Some(true),
37+
"0" | "false" | "no" | "off" => Some(false),
38+
_ => None,
39+
}
40+
}
41+
3442
fn resolve_chromium_executable() -> Option<PathBuf> {
3543
CHROMIUM_EXECUTABLE
3644
.get_or_init(|| {
@@ -195,7 +203,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
195203

196204
let splited = args[1].split(":").collect::<Vec<_>>();
197205

198-
if splited.len() != 7 {
206+
if splited.len() != 7 && splited.len() != 9 {
199207
return Err("Invalid command(split).".into());
200208
}
201209

@@ -206,6 +214,15 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
206214
let workers = splited[4].parse::<usize>()?;
207215
let encode = splited[5].to_string();
208216
let preset = splited[6].to_string();
217+
let ffmpeg_threads = splited
218+
.get(7)
219+
.and_then(|raw| raw.parse::<u32>().ok())
220+
.map(|value| value.max(1))
221+
.or(Some(1));
222+
let ffmpeg_low_memory = splited
223+
.get(8)
224+
.and_then(|raw| parse_bool_token(raw))
225+
.unwrap_or(true);
209226

210227
let worker_count = workers.max(1);
211228
let base_chunk = total_frames / worker_count;
@@ -311,6 +328,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
311328
for (worker_id, (start, end)) in ranges.into_iter().enumerate() {
312329
let encode_clone = encode.clone();
313330
let preset_clone = preset.clone();
331+
let ffmpeg_threads_clone = ffmpeg_threads;
332+
let ffmpeg_low_memory_clone = ffmpeg_low_memory;
314333

315334
let page_url = url.clone();
316335
let completed_clone = completed.clone();
@@ -333,6 +352,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
333352
&encode_clone,
334353
Some(&preset_clone),
335354
Some(fps as u32),
355+
ffmpeg_threads_clone,
356+
ffmpeg_low_memory_clone,
336357
)
337358
.await
338359
.unwrap();

src/StudioApp.tsx

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ export const StudioApp = () => {
2424
const timelineMinHeight = 200;
2525
const [previewViewport, setPreviewViewport] = useState<{ width: number; height: number }>({ width: 0, height: 0 });
2626
const hasPreviewViewport = previewViewport.width > 0 && previewViewport.height > 0;
27+
const [mountUiPanels, setMountUiPanels] = useState(false);
28+
const [mountProjectPreview, setMountProjectPreview] = useState(false);
2729

2830
const clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));
2931

@@ -74,6 +76,21 @@ export const StudioApp = () => {
7476
window.addEventListener("pointerup", up);
7577
}, [onHorizontalDrag]);
7678

79+
useEffect(() => {
80+
let raf1: number | null = null;
81+
let raf2: number | null = null;
82+
raf1 = requestAnimationFrame(() => {
83+
setMountUiPanels(true);
84+
raf2 = requestAnimationFrame(() => {
85+
setMountProjectPreview(true);
86+
});
87+
});
88+
return () => {
89+
if (raf1 != null) cancelAnimationFrame(raf1);
90+
if (raf2 != null) cancelAnimationFrame(raf2);
91+
};
92+
}, []);
93+
7794
useEffect(() => {
7895
const target = previewRef.current;
7996
if (!target) return;
@@ -161,8 +178,12 @@ export const StudioApp = () => {
161178
minWidth: 0,
162179
}}
163180
>
164-
<div style={{ flexBasis: `${horizontalRatio * 100}%`, minWidth: 220 }}>
165-
<ClipVisibilityPanel />
181+
<div style={{ flexBasis: `${horizontalRatio * 100}%`, minWidth: 220, minHeight: 0 }}>
182+
{mountUiPanels ? (
183+
<ClipVisibilityPanel />
184+
) : (
185+
<div style={{ width: "100%", height: "100%", borderRadius: 8, background: "#0f172a", border: "1px solid #1f2937" }} />
186+
)}
166187
</div>
167188
<div
168189
onPointerDown={startHorizontalDrag}
@@ -208,7 +229,23 @@ export const StudioApp = () => {
208229
transformOrigin: "top left",
209230
}}
210231
>
211-
<PROJECT />
232+
{mountProjectPreview ? (
233+
<PROJECT />
234+
) : (
235+
<div
236+
style={{
237+
width: "100%",
238+
height: "100%",
239+
display: "grid",
240+
placeItems: "center",
241+
color: "#94a3b8",
242+
fontSize: 13,
243+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace",
244+
}}
245+
>
246+
Loading preview...
247+
</div>
248+
)}
212249
</div>
213250
</div>
214251
</div>
@@ -228,7 +265,11 @@ export const StudioApp = () => {
228265

229266
<div style={{ flex: 1, minHeight: 160, display: "flex", minWidth: 0 }}>
230267
<div style={{ flex: 1, minHeight: 0 }}>
231-
<TimelineUI />
268+
{mountUiPanels ? (
269+
<TimelineUI />
270+
) : (
271+
<div style={{ width: "100%", height: "100%", borderRadius: 8, background: "#0f172a", border: "1px solid #1f2937" }} />
272+
)}
232273
</div>
233274
</div>
234275
</div>

0 commit comments

Comments
 (0)