Compare commits

..

3 Commits

Author SHA1 Message Date
96b4cecaec
fix: incorrect logic when removing files 2025-01-21 03:40:40 +08:00
94bd14db52
update: remove maxInterval in scheduler
fix: unlinking a file without handling the case of file not exist
2025-01-21 02:12:17 +08:00
fb0c60a71e
add: blurred background in rewind page when ratio mismatch 2025-01-21 01:37:20 +08:00
5 changed files with 70 additions and 53 deletions

View File

@ -34,7 +34,7 @@ function Image({ src }: { src: string }) {
<img
src={src}
alt="Current frame"
className="w-full h-full object-cover absolute inset-0"
className="w-full h-full object-contain absolute inset-0"
/>
);
}
@ -181,8 +181,6 @@ export default function RewindPage() {
const newIndex = Math.min(Math.max(currentIndex - delta, 0), timeline.length - 1);
const newFrameId = timeline[newIndex].id;
console.log(currentFrameId, lastAvaliableFrameId);
if (newFrameId !== currentFrameId) {
setCurrentFrameId(newFrameId);
// Preload adjacent images
@ -212,9 +210,19 @@ export default function RewindPage() {
return (
<div
ref={containerRef}
className="w-screen h-screen relative dark:text-white overflow-hidden"
className="w-screen h-screen relative dark:text-white overflow-hidden bg-black"
onWheel={handleScroll}
>
<img
src={currentFrameId
? images[currentFrameId] ||
(lastAvaliableFrameId.current ? images[lastAvaliableFrameId.current] : "")
: ""}
alt="background"
className="w-full h-full object-cover absolute inset-0 blur-lg"
/>
{/* Current image */}
<Image
src={
@ -225,6 +233,8 @@ export default function RewindPage() {
}
/>
{/* Time capsule */}
<div
className="absolute bottom-8 left-8 bg-zinc-800 text-white bg-opacity-80 backdrop-blur-lg

View File

@ -19,9 +19,10 @@ export function checkFramesForEncoding() {
const stmt = db.prepare(`
SELECT id, imgFilename, createdAt
FROM frame
WHERE encodeStatus = 0 AND imgFilename IS NOT NULL
ORDER BY createdAt ASC;
`);
WHERE encodeStatus = 0
AND imgFilename IS NOT NULL
ORDER BY createdAt;
`);
const frames = stmt.all() as Frame[];
const buffer: Frame[] = [];
@ -52,20 +53,24 @@ export function checkFramesForEncoding() {
if (chunkConditionSatisfied) {
// Create new encoding task
const taskStmt = db.prepare(`
INSERT INTO encoding_task (status) VALUES (0);
INSERT INTO encoding_task (status)
VALUES (0);
`);
const taskId = taskStmt.run().lastInsertRowid;
// Insert frames into encoding_task_data
const insertStmt = db.prepare(`
INSERT INTO encoding_task_data (encodingTaskID, frame) VALUES (?, ?);
INSERT INTO encoding_task_data (encodingTaskID, frame)
VALUES (?, ?);
`);
for (const frame of buffer) {
insertStmt.run(taskId, frame.id);
db.prepare(
`
UPDATE frame SET encodeStatus = 1 WHERE id = ?;
`
UPDATE frame
SET encodeStatus = 1
WHERE id = ?;
`
).run(frame.id);
}
console.log(`Created encoding task ${taskId} with ${buffer.length} frames`);
@ -78,14 +83,21 @@ function deleteEncodedScreenshots() {
const db = getDatabase();
// TODO: double-check that the frame was really encoded into the video
const stmt = db.prepare(`
SELECT * FROM frame WHERE encodeStatus = 2 AND imgFilename IS NOT NULL;
SELECT *
FROM frame
WHERE encodeStatus = 2
AND imgFilename IS NOT NULL;
`);
const frames = stmt.all() as Frame[];
for (const frame of frames) {
if (!frame.imgFilename) continue;
fs.unlinkSync(path.join(getScreenshotsDir(), frame.imgFilename));
const imgPath = path.join(getScreenshotsDir(), frame.imgFilename!);
if (fs.existsSync(imgPath)) {
fs.unlinkSync(imgPath);
}
const updateStmt = db.prepare(`
UPDATE frame SET imgFilename = NULL WHERE id = ?;
UPDATE frame
SET imgFilename = NULL
WHERE id = ?;
`);
updateStmt.run(frame.id);
}
@ -97,7 +109,9 @@ function _deleteNonExistentScreenshots() {
const filesInDir = new Set(fs.readdirSync(screenshotDir));
const dbStmt = db.prepare(`
SELECT imgFilename FROM frame WHERE imgFilename IS NOT NULL;
SELECT imgFilename
FROM frame
WHERE imgFilename IS NOT NULL;
`);
const dbFiles = dbStmt.all() as { imgFilename: string }[];
const dbFileSet = new Set(dbFiles.map((f) => f.imgFilename));
@ -118,7 +132,9 @@ export async function deleteUnnecessaryScreenshots() {
export function deleteFrameFromDB(id: number) {
const db = getDatabase();
const deleteStmt = db.prepare(`
DELETE FROM frame WHERE id = ?;
DELETE
FROM frame
WHERE id = ?;
`);
deleteStmt.run(id);
console.log(`Deleted frame ${id} from database`);
@ -146,11 +162,10 @@ export function processEncodingTasks() {
if (tasksPerforming.length >= CONCURRENCY) return;
const stmt = db.prepare(`
SELECT id, status
FROM encoding_task
WHERE status = 0
LIMIT ?
`);
SELECT id, status
FROM encoding_task
WHERE status = 0 LIMIT ?
`);
const tasks = stmt.all(CONCURRENCY - tasksPerforming.length) as EncodingTask[];
@ -161,17 +176,19 @@ export function processEncodingTasks() {
// Update task status as processing (1)
const updateStmt = db.prepare(`
UPDATE encoding_task SET status = 1 WHERE id = ?
`);
UPDATE encoding_task
SET status = 1
WHERE id = ?
`);
updateStmt.run(taskId);
const framesStmt = db.prepare(`
SELECT frame.imgFilename, frame.id
FROM encoding_task_data
JOIN frame ON encoding_task_data.frame = frame.id
JOIN frame ON encoding_task_data.frame = frame.id
WHERE encoding_task_data.encodingTaskID = ?
ORDER BY frame.createdAt ASC
`);
ORDER BY frame.createdAt
`);
const frames = framesStmt.all(taskId) as Frame[];
const metaFilePath = path.join(getEncodingTempDir(), `${taskId}_meta.txt`);
@ -191,13 +208,19 @@ export function processEncodingTasks() {
console.log(`Video ${videoPath} created successfully`);
// Update task status to complete (2)
const completeStmt = db.prepare(`
UPDATE encoding_task SET status = 2 WHERE id = ?
`);
UPDATE encoding_task
SET status = 2
WHERE id = ?
`);
completeStmt.run(taskId);
for (let frameIndex = 0; frameIndex < frames.length; frameIndex++) {
const frame = frames[frameIndex];
const updateFrameStmt = db.prepare(`
UPDATE frame SET videoPath = ?, videoFrameIndex = ?, encodeStatus = 2 WHERE id = ?
UPDATE frame
SET videoPath = ?,
videoFrameIndex = ?,
encodeStatus = 2
WHERE id = ?
`);
updateFrameStmt.run(`${taskId}.mp4`, frameIndex, frame.id);
}

View File

@ -142,7 +142,7 @@ function init(db: Database) {
export async function initDatabase() {
const dbPath = getDatabaseDir();
const db = new DB(dbPath, { verbose: console.log });
const db = new DB(dbPath);
const libSimpleExtensionPath = getLibSimpleExtensionPath();
db.loadExtension(libSimpleExtensionPath);

View File

@ -5,7 +5,6 @@ interface Task {
id: TaskId;
func: TaskFunction;
interval?: number;
maxInterval?: number;
lastRun?: number;
nextRun?: number;
isPaused: boolean;
@ -21,8 +20,6 @@ export interface TaskStatus {
export class Scheduler {
private tasks: Map<TaskId, Task> = new Map();
private timer: NodeJS.Timeout | null = null;
private nextTickTime: number | null = null;
constructor(private readonly minTickInterval: number = 500) {
this.start();
}
@ -76,16 +73,6 @@ export class Scheduler {
task.nextRun = now + task.interval!;
}
const isTaskReadyForMaxIntervalRun =
task.maxInterval && task.lastRun && now - task.lastRun >= task.maxInterval;
if (isTaskReadyForMaxIntervalRun) {
task.func();
task.lastRun = now;
if (task.interval) {
task.nextRun = now + task.interval;
}
}
const isTaskNextRunEarlierThanNextTick = task.nextRun && task.nextRun < getNextTick();
if (isTaskNextRunEarlierThanNextTick) {
updateNextTick(task.nextRun!);
@ -113,15 +100,12 @@ export class Scheduler {
* @param id A unique string identifier for the task.
* @param func The function to be executed by the task.
* @param interval The interval (in milliseconds) between task executions.
* @param maxInterval The maximum time (in milliseconds) that a task can wait before being executed.
* If a task has not been executed in this amount of time, it will be executed immediately.
*/
addTask(id: TaskId, func: TaskFunction, interval?: number, maxInterval?: number): void {
addTask(id: TaskId, func: TaskFunction, interval?: number): void {
this.tasks.set(id, {
id,
func,
interval,
maxInterval,
isPaused: false,
lastRun: undefined,
nextRun: interval ? Date.now() + interval : undefined
@ -165,7 +149,7 @@ export class Scheduler {
}
/**
* Resume a paused task, so that it can be executed according to its interval and maxInterval.
* Resume a paused task, so that it can be executed according to its interval.
*
* @param id The unique string identifier for the task.
*/

View File

@ -110,10 +110,10 @@ app.on("ready", () => {
});
});
initDatabase().then((db) => {
scheduler.addTask("screenshot", takeScreenshot, 2000, 2000);
scheduler.addTask("check-encoding", checkFramesForEncoding, 5000, 10000);
scheduler.addTask("process-encoding", processEncodingTasks, 10000, 30000);
scheduler.addTask("delete-screenshots", deleteUnnecessaryScreenshots, 20000, 60000);
scheduler.addTask("screenshot", takeScreenshot, 2000);
scheduler.addTask("check-encoding", checkFramesForEncoding, 5000);
scheduler.addTask("process-encoding", processEncodingTasks, 10000);
scheduler.addTask("delete-screenshots", deleteUnnecessaryScreenshots, 20000);
dbConnection = db;
cache.put("server:dbConnection", dbConnection);
});