diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000000000000000000000000000000000000..56611752ac822de651a144907c93a58542e3e29c --- /dev/null +++ b/TODO.md @@ -0,0 +1,6 @@ + + +to allow multiple videos to be processed a the same time: + +[ ] yield from the loop at each step +[ ] random processing of videos diff --git a/package-lock.json b/package-lock.json index 371a39370edea3f54ad06230fedc079c11fab7a2..6fb19e7250b36ac6c008a036dfa76987162e0902 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,15 +14,19 @@ "@types/express": "^4.17.17", "@types/ffmpeg-concat": "^1.1.2", "@types/uuid": "^9.0.2", + "eventsource-parser": "^1.0.0", "express": "^4.18.2", "ffmpeg-concat": "^1.3.0", "fluent-ffmpeg": "^2.1.2", "fs-extra": "^11.1.1", + "gpt-tokens": "^1.1.1", "node-fetch": "^3.3.1", + "openai": "^3.3.0", "puppeteer": "^20.8.0", "temp-dir": "^3.0.0", "ts-node": "^10.9.1", - "uuid": "^9.0.0" + "uuid": "^9.0.0", + "yaml": "^2.3.1" } }, "node_modules/@babel/code-frame": { @@ -686,6 +690,14 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" }, + "node_modules/axios": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", + "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "dependencies": { + "follow-redirects": "^1.14.8" + } + }, "node_modules/b4a": { "version": "1.6.4", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", @@ -1222,6 +1234,11 @@ "ms": "2.0.0" } }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" + }, "node_modules/decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -1479,6 +1496,14 @@ "node": ">= 0.6" } }, + "node_modules/eventsource-parser": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-1.0.0.tgz", + "integrity": "sha512-9jgfSCa3dmEme2ES3mPByGXfgZ87VbP97tng1G2nWwWx6bV2nYxm2AWCrbQjXToSe+yYlqaZNtxffR9IeQr95g==", + "engines": { + "node": ">=14.18" + } + }, "node_modules/execa": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", @@ -1772,6 +1797,25 @@ "node": ">=0.8.0" } }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/foreground-child": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", @@ -2218,6 +2262,16 @@ "through2": "^0.6.3" } }, + "node_modules/gpt-tokens": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/gpt-tokens/-/gpt-tokens-1.1.1.tgz", + "integrity": "sha512-fB1u0ZH7PywF9FByfWCqn6Hpp3so/pFUmk3AiV4QlOskr57LK8Ds3YJOjdemWKRGJQ+2pT9ikt++Eb+/et9gTQ==", + "dependencies": { + "decimal.js": "^10.4.3", + "js-tiktoken": "^1.0.7", + "openai": "^3.3.0" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -2609,6 +2663,14 @@ "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==" }, + "node_modules/js-tiktoken": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.7.tgz", + "integrity": "sha512-biba8u/clw7iesNEWLOLwrNGoBP2lA+hTaBLs/D45pJdUPFXyxD6nhcDVtADChghv4GgyAiMKYMiRx7x6h7Biw==", + "dependencies": { + "base64-js": "^1.5.1" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3349,6 +3411,28 @@ "wrappy": "1" } }, + "node_modules/openai": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-3.3.0.tgz", + "integrity": "sha512-uqxI/Au+aPRnsaQRe8CojU0eCR7I0mBiKjD3sNMzY6DaC1ZVrc85u98mtJW6voDug8fgGN+DIZmTDxTthxb7dQ==", + "dependencies": { + "axios": "^0.26.0", + "form-data": "^4.0.0" + } + }, + "node_modules/openai/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/optionator": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", @@ -5059,6 +5143,14 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/yaml": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", + "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", + "engines": { + "node": ">= 14" + } + }, "node_modules/yargs": { "version": "17.7.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", diff --git a/package.json b/package.json index 1b1b3540288999a7558c687b6f65575e499d9c89..fbc9036c1e77383a478aa50d9da65de44058fcb0 100644 --- a/package.json +++ b/package.json @@ -21,14 +21,18 @@ "@types/express": "^4.17.17", "@types/ffmpeg-concat": "^1.1.2", "@types/uuid": "^9.0.2", + "eventsource-parser": "^1.0.0", "express": "^4.18.2", "ffmpeg-concat": "^1.3.0", "fluent-ffmpeg": "^2.1.2", "fs-extra": "^11.1.1", + "gpt-tokens": "^1.1.1", "node-fetch": "^3.3.1", + "openai": "^3.3.0", "puppeteer": "^20.8.0", "temp-dir": "^3.0.0", "ts-node": "^10.9.1", - "uuid": "^9.0.0" + "uuid": "^9.0.0", + "yaml": "^2.3.1" } } diff --git a/src/config.mts b/src/config.mts index 7d5dd34f0bcb119b200b631ed1dd156465a57982..f21d34b4c1b644004a0c4459f683f7cd3115be8d 100644 --- a/src/config.mts +++ b/src/config.mts @@ -2,9 +2,9 @@ import path from "node:path" export const storagePath = `${process.env.VC_STORAGE_PATH || './sandbox'}` -export const tasksDirPath = path.join(storagePath, "tasks") -export const pendingTasksDirFilePath = path.join(tasksDirPath, "pending") -export const completedTasksDirFilePath = path.join(tasksDirPath, "completed") +export const metadataDirPath = path.join(storagePath, "metadata") +export const pendingMetadataDirFilePath = path.join(metadataDirPath, "pending") +export const completedMetadataDirFilePath = path.join(metadataDirPath, "completed") export const filesDirPath = path.join(storagePath, "files") export const pendingFilesDirFilePath = path.join(filesDirPath, "pending") diff --git a/src/data/all_words.json b/src/data/all_words.json index 8f40dbabf69752152a716a6f081e00efff8b7a95..b69d06ee89b2b62c9c501efaa5229cf5d6e70509 100644 --- a/src/data/all_words.json +++ b/src/data/all_words.json @@ -4269,7 +4269,7 @@ "tank", "tap", "target", - "task", + "video", "taste", "tax", "tea", diff --git a/src/data/good_words.json b/src/data/good_words.json index 0ab3beb34f8305deff7b4e93b9656a22f327c216..7c36f2b3dc848f1c7f7216e7f4db26403b13fea9 100644 --- a/src/data/good_words.json +++ b/src/data/good_words.json @@ -4259,7 +4259,7 @@ "tank", "tap", "target", - "task", + "video", "taste", "tax", "tea", diff --git a/src/index.mts b/src/index.mts index 8e475cb4ef0aa10888bb4d1da6a633876d7993bc..551a47a765b69c74e32ccd963d0a2a8e310b5d10 100644 --- a/src/index.mts +++ b/src/index.mts @@ -4,17 +4,21 @@ import path from "node:path" import { validate as uuidValidate } from "uuid" import express from "express" -import { VideoTask, VideoTaskRequest } from "./types.mts" +import { Video, VideoStatus, VideoAPIRequest } from "./types.mts" import { parseVideoRequest } from "./utils/parseVideoRequest.mts" -import { savePendingTask } from "./scheduler/savePendingTask.mts" -import { getTask } from "./scheduler/getTask.mts" +import { savePendingVideo } from "./scheduler/savePendingVideo.mts" +import { getVideo } from "./scheduler/getVideo.mts" import { main } from "./main.mts" import { completedFilesDirFilePath } from "./config.mts" -import { deleteTask } from "./scheduler/deleteTask.mts" -import { getPendingTasks } from "./scheduler/getPendingTasks.mts" +import { markVideoAsToDelete } from "./scheduler/markVideoAsToDelete.mts" +import { markVideoAsToAbort } from "./scheduler/markVideoAsToAbort.mts" +import { markVideoAsToPause } from "./scheduler/markVideoAsToPause.mts" +import { markVideoAsPending } from "./scheduler/markVideoAsPending.mts" +import { getPendingVideos } from "./scheduler/getPendingVideos.mts" import { hasValidAuthorization } from "./utils/hasValidAuthorization.mts" -import { getAllTasksForOwner } from "./scheduler/getAllTasksForOwner.mts" +import { getAllVideosForOwner } from "./scheduler/getAllVideosForOwner.mts" import { initFolders } from "./initFolders.mts" +import { sortVideosByYoungestFirst } from "./utils/sortVideosByYoungestFirst.mts" initFolders() // to disable all processing (eg. to debug) @@ -26,8 +30,8 @@ const port = 7860 app.use(express.json()) -app.post("/", async (req, res) => { - const request = req.body as VideoTaskRequest +app.post("/:ownerId", async (req, res) => { + const request = req.body as VideoAPIRequest if (!hasValidAuthorization(req.headers)) { console.log("Invalid authorization") @@ -37,68 +41,45 @@ app.post("/", async (req, res) => { return } - let task: VideoTask = null + const ownerId = req.params.ownerId - console.log(`creating task from request..`) - console.log(`request: `, JSON.stringify(request)) - try { - task = await parseVideoRequest(request) - } catch (err) { - console.error(`failed to create task: ${task} (${err})`) + if (!uuidValidate(ownerId)) { + console.error("invalid owner id") res.status(400) - res.write(JSON.stringify({ error: "query seems to be malformed" })) + res.write(JSON.stringify({ error: `invalid owner id` })) res.end() return } - console.log(`saving task ${task.id}`) + let video: Video = null + + console.log(`creating video from request..`) + console.log(`request: `, JSON.stringify(request)) try { - await savePendingTask(task) - res.status(200) - res.write(JSON.stringify(task)) - res.end() + video = await parseVideoRequest(ownerId, request) } catch (err) { - console.error(err) - res.status(500) - res.write(JSON.stringify({ error: "couldn't save the task" })) - res.end() - } -}) - -// only get the tasks for a specific owner -app.get("/owner/:ownerId", async (req, res) => { - if (!hasValidAuthorization(req.headers)) { - console.log("Invalid authorization") - res.status(401) - res.write(JSON.stringify({ error: "invalid token" })) - res.end() - return - } - - const ownerId = req.params.ownerId - - if (!uuidValidate(ownerId)) { - console.error("invalid owner id") + console.error(`failed to create video: ${video} (${err})`) res.status(400) - res.write(JSON.stringify({ error: `invalid owner id` })) + res.write(JSON.stringify({ error: "query seems to be malformed" })) res.end() return } + console.log(`saving video ${video.id}`) try { - const tasks = await getAllTasksForOwner(ownerId) + await savePendingVideo(video) res.status(200) - res.write(JSON.stringify(tasks, null, 2)) + res.write(JSON.stringify(video)) res.end() } catch (err) { console.error(err) res.status(500) - res.write(JSON.stringify({ error: `couldn't get the tasks for owner ${ownerId}` })) + res.write(JSON.stringify({ error: "couldn't save the video" })) res.end() } }) -app.get("/download/:id\.mp4", async (req, res) => { +app.get("/:ownerId/:videoId\.mp4", async (req, res) => { /* for simplicity, let's skip auth when fetching videos @@ -113,7 +94,7 @@ app.get("/download/:id\.mp4", async (req, res) => { } */ - const [ownerId, videoId] = `${req.params.id}`.split("_") + const ownerId = req.params.ownerId if (!uuidValidate(ownerId)) { console.error("invalid owner id") @@ -123,6 +104,7 @@ app.get("/download/:id\.mp4", async (req, res) => { return } + const videoId = req.params.videoId if (!uuidValidate(videoId)) { console.error("invalid video id") @@ -132,9 +114,9 @@ app.get("/download/:id\.mp4", async (req, res) => { return } - let task: VideoTask = null + let video: Video = null try { - task = await getTask(ownerId, videoId) + video = await getVideo(ownerId, videoId) console.log(`returning video ${videoId} to owner ${ownerId}`) } catch (err) { res.status(404) @@ -143,7 +125,7 @@ app.get("/download/:id\.mp4", async (req, res) => { return } - const completedFilePath = path.join(completedFilesDirFilePath, task.fileName) + const completedFilePath = path.join(completedFilesDirFilePath, video.fileName) // note: we DON'T want to use the pending file path, as there may be operations on it // (ie. a process might be busy writing stuff to it) @@ -177,8 +159,9 @@ app.get("/download/:id\.mp4", async (req, res) => { } }) -// get all pending tasks -app.get("/", async (req, res) => { +// get metadata (json) +app.get("/:ownerId/:videoId", async (req, res) => { + if (!hasValidAuthorization(req.headers)) { console.log("Invalid authorization") res.status(401) @@ -187,20 +170,106 @@ app.get("/", async (req, res) => { return } + const ownerId = req.params.ownerId + + if (!uuidValidate(ownerId)) { + console.error("invalid owner id") + res.status(400) + res.write(JSON.stringify({ error: `invalid owner id` })) + res.end() + return + } + + const videoId = req.params.videoId + + if (!uuidValidate(videoId)) { + console.error("invalid video id") + res.status(400) + res.write(JSON.stringify({ error: `invalid video id` })) + res.end() + return + } + try { - const tasks = await getPendingTasks() + const video = await getVideo(ownerId, videoId) res.status(200) - res.write(JSON.stringify(tasks, null, 2)) + res.write(JSON.stringify(video)) + res.end() + } catch (err) { + console.error(err) + res.status(404) + res.write(JSON.stringify({ error: "couldn't find this video" })) + res.end() + } +}) + +// only get the videos for a specific owner +app.get("/:ownerId", async (req, res) => { + if (!hasValidAuthorization(req.headers)) { + console.log("Invalid authorization") + res.status(401) + res.write(JSON.stringify({ error: "invalid token" })) + res.end() + return + } + + const ownerId = req.params.ownerId + + if (!uuidValidate(ownerId)) { + console.error(`invalid owner d ${ownerId}`) + res.status(400) + res.write(JSON.stringify({ error: `invalid owner id ${ownerId}` })) + res.end() + return + } + + try { + const videos = await getAllVideosForOwner(ownerId) + sortVideosByYoungestFirst(videos) + res.status(200) + res.write(JSON.stringify(videos, null, 2)) + res.end() + } catch (err) { + console.error(err) + res.status(500) + res.write(JSON.stringify({ error: `couldn't get the videos for owner ${ownerId}` })) + res.end() + } +}) + +// get all pending videos - this is for admin usage only +app.get("/", async (req, res) => { + if (!hasValidAuthorization(req.headers)) { + // this is what users will see in the space - but no need to show something scary + console.log("Invalid authorization") + res.status(200) + res.write(` +This space is the REST API used by VideoChain UI:
+https://jbilcke-hf-videochain-ui.hf.space + `) + res.end() + // res.status(401) + // res.write(JSON.stringify({ error: "invalid token" })) + // res.end() + return + } + + try { + const videos = await getPendingVideos() + res.status(200) + res.write(JSON.stringify(videos, null, 2)) res.end() } catch (err) { console.error(err) res.status(500) - res.write(JSON.stringify({ error: "couldn't get the tasks" })) + res.write(JSON.stringify({ error: "couldn't get the videos" })) res.end() } }) -app.get("/:id", async (req, res) => { + +// edit a video +app.patch("/:ownerId/:videoId", async (req, res) => { if (!hasValidAuthorization(req.headers)) { console.log("Invalid authorization") @@ -210,38 +279,113 @@ app.get("/:id", async (req, res) => { return } - const [ownerId, videoId] = `${req.params.id}`.split("_") + const ownerId = req.params.ownerId if (!uuidValidate(ownerId)) { - console.error("invalid owner id") + console.error(`invalid owner id ${ownerId}`) res.status(400) - res.write(JSON.stringify({ error: `invalid owner id` })) + res.write(JSON.stringify({ error: `invalid owner id ${ownerId}` })) res.end() return } + const videoId = req.params.videoId if (!uuidValidate(videoId)) { - console.error("invalid video id") + console.error(`invalid video id ${videoId}`) res.status(400) - res.write(JSON.stringify({ error: `invalid video id` })) + res.write(JSON.stringify({ error: `invalid video id ${videoId}` })) res.end() return } + let status: VideoStatus = "unknown" try { - const task = await getTask(ownerId, videoId) - res.status(200) - res.write(JSON.stringify(task)) - res.end() + const request = req.body as { status: VideoStatus } + if (['pending', 'abort', 'delete', 'pause'].includes(request.status)) { + status = request.status + } else { + throw new Error(`invalid video status "${request.status}"`) + } } catch (err) { - console.error(err) - res.status(404) - res.write(JSON.stringify({ error: "couldn't find this task" })) + console.error(`invalid parameter (${err})`) + res.status(401) + res.write(JSON.stringify({ error: `invalid parameter (${err})` })) res.end() + return + } + + switch (status) { + case 'delete': + try { + await markVideoAsToDelete(ownerId, videoId) + console.log(`deleting video ${videoId}`) + res.status(200) + res.write(JSON.stringify({ success: true })) + res.end() + } catch (err) { + console.error(`failed to delete video ${videoId} (${err})`) + res.status(500) + res.write(JSON.stringify({ error: `failed to delete video ${videoId}` })) + res.end() + } + break + + case 'abort': + try { + await markVideoAsToAbort(ownerId, videoId) + console.log(`aborted video ${videoId}`) + res.status(200) + res.write(JSON.stringify({ success: true })) + res.end() + } catch (err) { + console.error(`failed to abort video ${videoId} (${err})`) + res.status(500) + res.write(JSON.stringify({ error: `failed to abort video ${videoId}` })) + res.end() + } + break + + case 'pause': + try { + await markVideoAsToPause(ownerId, videoId) + console.log(`paused video ${videoId}`) + res.status(200) + res.write(JSON.stringify({ success: true })) + res.end() + } catch (err) { + console.error(`failed to pause video ${videoId} (${err})`) + res.status(500) + res.write(JSON.stringify({ error: `failed to pause video ${videoId}` })) + res.end() + } + break + + case 'pending': + try { + await markVideoAsPending(ownerId, videoId) + console.log(`unpausing video ${videoId}`) + res.status(200) + res.write(JSON.stringify({ success: true })) + res.end() + } catch (err) { + console.error(`failed to unpause video ${videoId} (${err})`) + res.status(500) + res.write(JSON.stringify({ error: `failed to unpause video ${videoId}` })) + res.end() + } + break + + default: + console.log(`unsupported status ${status}`) + res.status(401) + res.write(JSON.stringify({ error: `unsupported status ${status}` })) + res.end() } }) +// delete a video - this is legacy, we should use other functions instead +/* app.delete("/:id", async (req, res) => { if (!hasValidAuthorization(req.headers)) { @@ -270,27 +414,32 @@ app.delete("/:id", async (req, res) => { return } - let task: VideoTask = null + // ecurity note: we always check the existence if the video first + // that's because we are going to delete all the associated files with a glob, + // so we must be sure the id is not a system path or something ^^ + let video: Video = null try { - task = await getTask(ownerId, videoId) + video = await getVideo(ownerId, videoId) } catch (err) { console.error(err) res.status(404) - res.write(JSON.stringify({ error: "couldn't find this task" })) + res.write(JSON.stringify({ error: "couldn't find this video" })) res.end() + return } try { - await deleteTask(task) + await markVideoAsToDelete(ownerId, videoId) res.status(200) res.write(JSON.stringify({ success: true })) res.end() } catch (err) { console.error(err) res.status(500) - res.write(JSON.stringify({ success: false, error: "failed to delete the task" })) + res.write(JSON.stringify({ success: false, error: "failed to delete the video" })) res.end() } }) +*/ app.listen(port, () => { console.log(`Open http://localhost:${port}`) }) \ No newline at end of file diff --git a/src/initFolders.mts b/src/initFolders.mts index 6642a088ff4fa7b9448b363cc4389c4442e22374..525220a9f413a43b19db42341a5c1d795e0766fc 100644 --- a/src/initFolders.mts +++ b/src/initFolders.mts @@ -1,11 +1,18 @@ -import { tasksDirPath, pendingTasksDirFilePath, completedTasksDirFilePath, filesDirPath, pendingFilesDirFilePath, completedFilesDirFilePath } from "./config.mts" +import { + metadataDirPath, + pendingMetadataDirFilePath, + completedMetadataDirFilePath, + filesDirPath, + pendingFilesDirFilePath, + completedFilesDirFilePath +} from "./config.mts" import { createDirIfNeeded } from "./utils/createDirIfNeeded.mts" export const initFolders = () => { console.log(`initializing folders..`) - createDirIfNeeded(tasksDirPath) - createDirIfNeeded(pendingTasksDirFilePath) - createDirIfNeeded(completedTasksDirFilePath) + createDirIfNeeded(metadataDirPath) + createDirIfNeeded(pendingMetadataDirFilePath) + createDirIfNeeded(completedMetadataDirFilePath) createDirIfNeeded(filesDirPath) createDirIfNeeded(pendingFilesDirFilePath) createDirIfNeeded(completedFilesDirFilePath) diff --git a/src/llm/enrichVideoSpecsUsingLLM.mts b/src/llm/enrichVideoSpecsUsingLLM.mts new file mode 100644 index 0000000000000000000000000000000000000000..c4df0ac751c006c49abaa6a8482fbd6f579d9876 --- /dev/null +++ b/src/llm/enrichVideoSpecsUsingLLM.mts @@ -0,0 +1,79 @@ +import { ChatCompletionRequestMessage } from "openai" + +import { Video, VideoAPIRequest } from "../types.mts" +import { generateYAML } from "./openai/generateYAML.mts" +import { HallucinatedVideoRequest, OpenAIErrorResponse } from "./types.mts" +import { getQueryChatMessages } from "../preproduction/prompts.mts" +import { getValidNumber } from "../utils/getValidNumber.mts" + + +export const enrichVideoSpecsUsingLLM = async (video: Video): Promise