UNPKG

next-video

Version:

A React component for adding video to your Next.js application. It extends both the video element and your Next app with features for automatic video optimization.

141 lines (140 loc) 5.14 kB
import chalk from "chalk"; import chokidar from "chokidar"; import { cwd } from "node:process"; import { readdir } from "node:fs/promises"; import path from "node:path"; import { callHandler } from "../process.js"; import { createAsset, getAsset } from "../assets.js"; import { getVideoConfig } from "../config.js"; import { getPackageVersion } from "../utils/utils.js"; import log from "../utils/logger.js"; const command = "sync"; const desc = "Checks for new video files in the videos directory, uploads them, and checks any existing assets for updates."; function builder(yargs) { return yargs.options({ dir: { alias: "d", describe: "The directory you initialized next-video with.", type: "string", default: "videos" }, watch: { alias: "w", describe: "Watch the videos directory for changes.", type: "boolean", default: false } }); } function watcher(dir) { const watcher2 = chokidar.watch(dir, { ignored: /(^|[\/\\])\..*|\.json$/, persistent: true }); watcher2.on("add", async (filePath) => { try { await getAsset(filePath); } catch { const newAsset = await createAsset(filePath); if (newAsset) { log.add(`New file found: ${filePath}`); const videoConfig = await getVideoConfig(); return callHandler("local.video.added", newAsset, videoConfig); } } }); } async function handler(argv) { const directoryPath = path.join(cwd(), argv.dir); const version = getPackageVersion("next-video"); log.space(log.label(`\u25B6\uFE0E next-video ${version}`)); log.space(); try { const files = (await getFiles(directoryPath)).map((file) => path.relative(directoryPath, file)); const jsonFiles = files.filter((file) => file.endsWith(".json")); const otherFiles = files.filter( (file) => !file.match(/(^|[\/\\])\..*|\.json$/) ); const newFileProcessor = async (file) => { log.info(log.label("Processing file:"), file); const filePath = path.join(directoryPath, file); const newAsset = await createAsset(filePath); if (newAsset) { const videoConfig = await getVideoConfig(); return callHandler("local.video.added", newAsset, videoConfig); } }; const existingFileProcessor = async (file) => { const filePath = path.join(directoryPath, file); const parsedPath = path.parse(filePath); const assetPath = path.join(parsedPath.dir, parsedPath.name); const existingAsset = await getAsset(assetPath); const assetStatus = existingAsset?.status; if (assetStatus && ["sourced", "pending", "uploading", "processing"].includes(assetStatus)) { const videoConfig = await getVideoConfig(); return callHandler("local.video.added", existingAsset, videoConfig); } }; const unprocessedFilter = (file) => { const jsonFile = `${file}.json`; return !jsonFiles.includes(jsonFile); }; const unprocessedVideos = otherFiles.filter(unprocessedFilter); if (unprocessedVideos.length > 0) { const s = unprocessedVideos.length === 1 ? "" : "s"; log.add(`Found ${unprocessedVideos.length} unprocessed video${s}`); } const processing = await Promise.all([ ...unprocessedVideos.map(newFileProcessor), ...jsonFiles.map(existingFileProcessor) ]); const processed = processing.flat().filter((asset) => asset); if (processed.length > 0) { const s = processed.length === 1 ? "" : "s"; log.success( `Processed (or resumed processing) ${processed.length} video${s}` ); } else { log.info("No new or unprocessed videos found"); } if (argv.watch) { const relativePath = path.relative(cwd(), directoryPath); log.info(`Watching for file changes in ./${relativePath}`); log.space(); watcher(directoryPath); } } catch (err) { if (err.code === "ENOENT" && err.path === directoryPath) { log.warning(`Directory does not exist: ${directoryPath}`); log.info( `Did you forget to run ${chalk.bold.magenta("next-video init")}? You can also use the ${chalk.bold( "--dir" )} flag to specify a different directory.` ); return; } if (err.code === "ENOENT") { log.warning(`Source video file does not exist: ${err.path}`); return; } if (err.message.includes("MUX_TOKEN_ID environment variable is missing or empty") || err.message.includes("MUX_TOKEN_SECRET environment variable is missing or empty")) { log.error(`Mux MUX_TOKEN_ID or MUX_TOKEN_SECRET can't be found. Visit \x1B[4;34mhttps://next-video.dev/docs#remote-storage-and-optimization\x1B[0m for more information.`); return; } log.error("An unknown error occurred", err); } } async function getFiles(dir) { const dirents = await readdir(dir, { withFileTypes: true }); const files = await Promise.all(dirents.map((dirent) => { const res = path.resolve(dir, dirent.name); return dirent.isDirectory() ? getFiles(res) : res; })); return files.flat(); } export { builder, command, desc, handler };