Compare commits
3 Commits
3d76a5ece5
...
96b4cecaec
Author | SHA1 | Date | |
---|---|---|---|
96b4cecaec | |||
94bd14db52 | |||
fb0c60a71e |
@ -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-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 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
|
||||||
@ -212,9 +210,19 @@ export default function RewindPage() {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
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}
|
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={
|
||||||
@ -225,6 +233,8 @@ 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
|
||||||
|
@ -19,9 +19,10 @@ 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 AND imgFilename IS NOT NULL
|
WHERE encodeStatus = 0
|
||||||
ORDER BY createdAt ASC;
|
AND imgFilename IS NOT NULL
|
||||||
`);
|
ORDER BY createdAt;
|
||||||
|
`);
|
||||||
const frames = stmt.all() as Frame[];
|
const frames = stmt.all() as Frame[];
|
||||||
|
|
||||||
const buffer: Frame[] = [];
|
const buffer: Frame[] = [];
|
||||||
@ -52,20 +53,24 @@ 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) VALUES (0);
|
INSERT INTO encoding_task (status)
|
||||||
|
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) VALUES (?, ?);
|
INSERT INTO encoding_task_data (encodingTaskID, frame)
|
||||||
|
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 SET encodeStatus = 1 WHERE id = ?;
|
UPDATE frame
|
||||||
`
|
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`);
|
||||||
@ -78,14 +83,21 @@ 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 * 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[];
|
const frames = stmt.all() as Frame[];
|
||||||
for (const frame of frames) {
|
for (const frame of frames) {
|
||||||
if (!frame.imgFilename) continue;
|
const imgPath = path.join(getScreenshotsDir(), frame.imgFilename!);
|
||||||
fs.unlinkSync(path.join(getScreenshotsDir(), frame.imgFilename));
|
if (fs.existsSync(imgPath)) {
|
||||||
|
fs.unlinkSync(imgPath);
|
||||||
|
}
|
||||||
const updateStmt = db.prepare(`
|
const updateStmt = db.prepare(`
|
||||||
UPDATE frame SET imgFilename = NULL WHERE id = ?;
|
UPDATE frame
|
||||||
|
SET imgFilename = NULL
|
||||||
|
WHERE id = ?;
|
||||||
`);
|
`);
|
||||||
updateStmt.run(frame.id);
|
updateStmt.run(frame.id);
|
||||||
}
|
}
|
||||||
@ -97,7 +109,9 @@ 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 FROM frame WHERE imgFilename IS NOT NULL;
|
SELECT imgFilename
|
||||||
|
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));
|
||||||
@ -118,7 +132,9 @@ 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 FROM frame WHERE id = ?;
|
DELETE
|
||||||
|
FROM frame
|
||||||
|
WHERE id = ?;
|
||||||
`);
|
`);
|
||||||
deleteStmt.run(id);
|
deleteStmt.run(id);
|
||||||
console.log(`Deleted frame ${id} from database`);
|
console.log(`Deleted frame ${id} from database`);
|
||||||
@ -146,11 +162,10 @@ 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
|
WHERE status = 0 LIMIT ?
|
||||||
LIMIT ?
|
`);
|
||||||
`);
|
|
||||||
|
|
||||||
const tasks = stmt.all(CONCURRENCY - tasksPerforming.length) as EncodingTask[];
|
const tasks = stmt.all(CONCURRENCY - tasksPerforming.length) as EncodingTask[];
|
||||||
|
|
||||||
@ -161,17 +176,19 @@ 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 SET status = 1 WHERE id = ?
|
UPDATE encoding_task
|
||||||
`);
|
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 ASC
|
ORDER BY frame.createdAt
|
||||||
`);
|
`);
|
||||||
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`);
|
||||||
@ -191,13 +208,19 @@ 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 SET status = 2 WHERE id = ?
|
UPDATE encoding_task
|
||||||
`);
|
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 SET videoPath = ?, videoFrameIndex = ?, encodeStatus = 2 WHERE id = ?
|
UPDATE frame
|
||||||
|
SET videoPath = ?,
|
||||||
|
videoFrameIndex = ?,
|
||||||
|
encodeStatus = 2
|
||||||
|
WHERE id = ?
|
||||||
`);
|
`);
|
||||||
updateFrameStmt.run(`${taskId}.mp4`, frameIndex, frame.id);
|
updateFrameStmt.run(`${taskId}.mp4`, frameIndex, frame.id);
|
||||||
}
|
}
|
||||||
|
@ -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, { verbose: console.log });
|
const db = new DB(dbPath);
|
||||||
const libSimpleExtensionPath = getLibSimpleExtensionPath();
|
const libSimpleExtensionPath = getLibSimpleExtensionPath();
|
||||||
|
|
||||||
db.loadExtension(libSimpleExtensionPath);
|
db.loadExtension(libSimpleExtensionPath);
|
||||||
|
@ -5,7 +5,6 @@ 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;
|
||||||
@ -21,8 +20,6 @@ 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();
|
||||||
}
|
}
|
||||||
@ -76,16 +73,6 @@ 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!);
|
||||||
@ -113,15 +100,12 @@ 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, maxInterval?: number): void {
|
addTask(id: TaskId, func: TaskFunction, interval?: 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
|
||||||
@ -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.
|
* @param id The unique string identifier for the task.
|
||||||
*/
|
*/
|
||||||
|
@ -110,10 +110,10 @@ app.on("ready", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
initDatabase().then((db) => {
|
initDatabase().then((db) => {
|
||||||
scheduler.addTask("screenshot", takeScreenshot, 2000, 2000);
|
scheduler.addTask("screenshot", takeScreenshot, 2000);
|
||||||
scheduler.addTask("check-encoding", checkFramesForEncoding, 5000, 10000);
|
scheduler.addTask("check-encoding", checkFramesForEncoding, 5000);
|
||||||
scheduler.addTask("process-encoding", processEncodingTasks, 10000, 30000);
|
scheduler.addTask("process-encoding", processEncodingTasks, 10000);
|
||||||
scheduler.addTask("delete-screenshots", deleteUnnecessaryScreenshots, 20000, 60000);
|
scheduler.addTask("delete-screenshots", deleteUnnecessaryScreenshots, 20000);
|
||||||
dbConnection = db;
|
dbConnection = db;
|
||||||
cache.put("server:dbConnection", dbConnection);
|
cache.put("server:dbConnection", dbConnection);
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user