Compare commits
No commits in common. "96b4cecaec8128187cbfa0400a13abc9a002383f" and "3d76a5ece54e7a2e28b95c0a124c9df571781674" have entirely different histories.
96b4cecaec
...
3d76a5ece5
@ -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
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user