Compare commits

..

No commits in common. "96b4cecaec8128187cbfa0400a13abc9a002383f" and "3d76a5ece54e7a2e28b95c0a124c9df571781674" have entirely different histories.

5 changed files with 53 additions and 70 deletions

View File

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

View File

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

View File

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

View File

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

View File

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