diff --git a/dev/plugin.spec.ts b/dev/plugin.spec.ts index f6c7439..9f26e5f 100644 --- a/dev/plugin.spec.ts +++ b/dev/plugin.spec.ts @@ -6,232 +6,220 @@ import fs from 'fs' import { promises as fsPromises } from 'fs' describe('Plugin tests', () => { - let server: Server - let id: number | string - - beforeAll(async () => { - if (!server) { - console.log('Before all: Starting server') - server = await start() - await new Promise(resolve => setTimeout(resolve, 70000)) - console.log('Before all: Server started') - } - }, 90000) - - afterAll(async () => { - console.log('After all: Cleaning up') - await mongoose.connection.dropDatabase() - await mongoose.connection.close() - server.close() - await fsPromises.rmdir('./src/media') - await fsPromises.rm('./src/videos', { recursive: true }) - await fsPromises.rmdir('./src/override-segments') - console.log('After all: Cleanup complete') + let server: Server + let id: number | string + + beforeAll(async () => { + if (!server) { + console.log('Before all: Starting server') + server = await start() + await new Promise(resolve => setTimeout(resolve, 70000)) + console.log('Before all: Server started') + } + }, 90000) + + afterAll(async () => { + console.log('After all: Cleaning up') + await mongoose.connection.dropDatabase() + await mongoose.connection.close() + server.close() + await fsPromises.rmdir('./src/media') + await fsPromises.rm('./src/videos', { recursive: true }) + await fsPromises.rmdir('./src/override-segments') + console.log('After all: Cleanup complete') + }) + + it('input media collection uploads videos', async () => { + const testVideoBuffer = await fsPromises.readFile('./src/mocks/testVideo.mp4') + + const createdMedia = await payload.create({ + collection: 'media', + file: { + data: testVideoBuffer, + name: 'testVideo.mp4', + mimetype: 'video/mp4', + size: testVideoBuffer.byteLength, + }, + data: { alt: 'Ligma Test' }, }) - - it('input media collection uploads videos', async () => { - const testVideoBuffer = await fsPromises.readFile('./src/mocks/testVideo.mp4') - - const createdMedia = await payload.create({ - collection: 'media', - file: { - data: testVideoBuffer, - name: 'testVideo.mp4', - mimetype: 'video/mp4', - size: testVideoBuffer.byteLength, - }, - data: { alt: 'Ligma Test' }, - }) - await new Promise(resolve => setTimeout(resolve, 50000)) - expect(createdMedia).toBeTruthy() - expect(createdMedia.id).toBeDefined() - expect(createdMedia.filename).toBe('testVideo.mp4') - expect(createdMedia.mimeType).toBe('video/mp4') - expect(createdMedia.filesize).toBe(testVideoBuffer.byteLength) - expect(createdMedia.alt).toBe('Ligma Test') - }, 70000) - - it('standard video outputs are present', () => { - const resolutions = [144, 240, 360, 480, 720] - const exceptedNumSegments = 13 - for (let i = 0; i < resolutions.length; i++) { - for (let j = 0; j < exceptedNumSegments; j++) { - expect( - fs.existsSync( - `src/override-segments/testVideo-${resolutions[i]}p-segment${j}.ts`, - ), - ).toBe(true) - } - } + await new Promise(resolve => setTimeout(resolve, 50000)) + expect(createdMedia).toBeTruthy() + expect(createdMedia.id).toBeDefined() + expect(createdMedia.filename).toBe('testVideo.mp4') + expect(createdMedia.mimeType).toBe('video/mp4') + expect(createdMedia.filesize).toBe(testVideoBuffer.byteLength) + expect(createdMedia.alt).toBe('Ligma Test') + }, 70000) + + it('standard video outputs are present', () => { + const resolutions = [144, 240, 360, 480, 720] + const exceptedNumSegments = 13 + for (let i = 0; i < resolutions.length; i++) { + for (let j = 0; j < exceptedNumSegments; j++) { + expect( + fs.existsSync(`src/override-segments/testVideo-${resolutions[i]}p-segment${j}.ts`), + ).toBe(true) + } + } + }) + + it('default resolution manifest playlists are present', () => { + const resolutions = [144, 240, 360, 480, 720] + for (let i = 0; i < resolutions.length; i++) { + expect( + fs.existsSync(`./src/override-segments/testVideo-${resolutions[i]}p-playlist.m3u8`), + ).toBe(true) + } + }) + + it('saves master manifest into collection source video was upload too', async () => { + const res = await payload.find({ + collection: 'media', + where: { + filename: { + equals: 'testVideo.m3u8', + }, + }, }) - - it('default resolution manifest playlists are present', () => { - const resolutions = [144, 240, 360, 480, 720] - for (let i = 0; i < resolutions.length; i++) { - expect( - fs.existsSync(`./src/override-segments/testVideo-${resolutions[i]}p-playlist.m3u8`), - ).toBe(true) - } + expect(res.docs.length).toBe(1) + const retrievedMedia = res.docs[0] + expect(retrievedMedia).toBeTruthy() + expect(retrievedMedia.filename).toBe('testVideo.m3u8') + id = retrievedMedia.id + }) + + it('deletes orginal file on config', async () => { + const res = await payload.find({ + collection: 'media', + where: { + filename: { + equals: 'testVideo.mp4', + }, + }, }) - - it('saves master manifest into collection source video was upload too', async () => { - const res = await payload.find({ - collection: 'media', - where: { - filename: { - equals: 'testVideo.m3u8', - }, - }, - }) - expect(res.docs.length).toBe(1) - const retrievedMedia = res.docs[0] - expect(retrievedMedia).toBeTruthy() - expect(retrievedMedia.filename).toBe('testVideo.m3u8') - id = retrievedMedia.id + expect(res.docs.length).toBe(0) + }) + + it('video input collection uploads videos', async () => { + const testVideoBuffer = await fsPromises.readFile('./src/mocks/testVideo2.mp4') + + const createdMedia = await payload.create({ + collection: 'videos', + file: { + data: testVideoBuffer, + name: 'testVideo2.mp4', + mimetype: 'video/mp4', + size: testVideoBuffer.byteLength, + }, + data: { alt: 'Ligma Test' }, }) - - it('deletes orginal file on config', async () => { - const res = await payload.find({ - collection: 'media', - where: { - filename: { - equals: 'testVideo.mp4', - }, - }, - }) - expect(res.docs.length).toBe(0) + await new Promise(resolve => setTimeout(resolve, 40000)) + expect(createdMedia).toBeTruthy() + expect(createdMedia.id).toBeDefined() + expect(createdMedia.filename).toBe('testVideo2.mp4') + expect(createdMedia.mimeType).toBe('video/mp4') + expect(createdMedia.filesize).toBe(testVideoBuffer.byteLength) + expect(createdMedia.alt).toBe('Ligma Test') + }, 60000) + + it('custom video outputs are present', () => { + const resolutions = [144, 240, 300] + const exceptedNumSegments = 12 + for (let i = 0; i < resolutions.length; i++) { + for (let j = 0; j < exceptedNumSegments; j++) { + expect( + fs.existsSync(`./src/override-segments/testVideo2-${resolutions[i]}p-segment${j}.ts`), + ).toBe(true) + } + } + }) + + it('custom resolution manifest playlists are present', () => { + const resolutions = [144, 240, 300] + for (let i = 0; i < resolutions.length; i++) { + expect( + fs.existsSync(`./src/override-segments/testVideo2-${resolutions[i]}p-playlist.m3u8`), + ).toBe(true) + } + }) + + it('saves master manifest into custom collection source video was upload too', async () => { + const res = await payload.find({ + collection: 'videos', + where: { + filename: { + equals: 'testVideo2.m3u8', + }, + }, }) - - it('video input collection uploads videos', async () => { - const testVideoBuffer = await fsPromises.readFile('./src/mocks/testVideo2.mp4') - - const createdMedia = await payload.create({ - collection: 'videos', - file: { - data: testVideoBuffer, - name: 'testVideo2.mp4', - mimetype: 'video/mp4', - size: testVideoBuffer.byteLength, - }, - data: { alt: 'Ligma Test' }, - }) - await new Promise(resolve => setTimeout(resolve, 40000)) - expect(createdMedia).toBeTruthy() - expect(createdMedia.id).toBeDefined() - expect(createdMedia.filename).toBe('testVideo2.mp4') - expect(createdMedia.mimeType).toBe('video/mp4') - expect(createdMedia.filesize).toBe(testVideoBuffer.byteLength) - expect(createdMedia.alt).toBe('Ligma Test') - }, 60000) - - it('custom video outputs are present', () => { - const resolutions = [144, 240, 300] - const exceptedNumSegments = 12 - for (let i = 0; i < resolutions.length; i++) { - for (let j = 0; j < exceptedNumSegments; j++) { - expect( - fs.existsSync( - `./src/override-segments/testVideo2-${resolutions[i]}p-segment${j}.ts`, - ), - ).toBe(true) - } - } + expect(res.docs.length).toBe(1) + const retrievedMedia = res.docs[0] + expect(retrievedMedia).toBeTruthy() + expect(retrievedMedia.filename).toBe('testVideo2.m3u8') + }) + + it('keep orginal source file on config', async () => { + const res = await payload.find({ + collection: 'videos', + where: { + filename: { + equals: 'testVideo2.mp4', + }, + }, }) - - it('custom resolution manifest playlists are present', () => { - const resolutions = [144, 240, 300] - for (let i = 0; i < resolutions.length; i++) { - expect( - fs.existsSync( - `./src/override-segments/testVideo2-${resolutions[i]}p-playlist.m3u8`, - ), - ).toBe(true) - } + expect(res.docs.length).toBe(1) + const retrievedMedia = res.docs[0] + expect(retrievedMedia).toBeTruthy() + expect(retrievedMedia.filename).toBe('testVideo2.mp4') + }) + + it('deletes output segments when master manifest is deleted by ID', async () => { + await payload.delete({ + collection: 'media', + id, }) - - it('saves master manifest into custom collection source video was upload too', async () => { - const res = await payload.find({ - collection: 'videos', - where: { - filename: { - equals: 'testVideo2.m3u8', - }, - }, - }) - expect(res.docs.length).toBe(1) - const retrievedMedia = res.docs[0] - expect(retrievedMedia).toBeTruthy() - expect(retrievedMedia.filename).toBe('testVideo2.m3u8') - }) - - it('keep orginal source file on config', async () => { - const res = await payload.find({ - collection: 'videos', - where: { - filename: { - equals: 'testVideo2.mp4', - }, - }, - }) - expect(res.docs.length).toBe(1) - const retrievedMedia = res.docs[0] - expect(retrievedMedia).toBeTruthy() - expect(retrievedMedia.filename).toBe('testVideo2.mp4') - }) - - it('deletes output segments when master manifest is deleted by ID', async () => { - await payload.delete({ - collection: 'media', - id, - }) - await new Promise(resolve => setTimeout(resolve, 3000)) - - const resolutions = [144, 240, 360, 480, 720] - const exceptedNumSegments = 13 - for (let i = 0; i < resolutions.length; i++) { - for (let j = 0; j < exceptedNumSegments; j++) { - expect( - fs.existsSync( - `src/override-segments/testVideo-${resolutions[i]}p-segment${j}.ts`, - ), - ).toBe(false) - } - } - for (let i = 0; i < resolutions.length; i++) { - expect( - fs.existsSync(`./src/override-segments/testVideo-${resolutions[i]}p-playlist.m3u8`), - ).toBe(false) - } - }) - - it('deletes output segments when master manifest is deleted by bulk delete', async () => { - await payload.delete({ - collection: 'videos', - where: { - filename: { - equals: 'testVideo2.m3u8', - }, - }, - }) - await new Promise(resolve => setTimeout(resolve, 3000)) - - const resolutions = [144, 240, 300] - const exceptedNumSegments = 12 - for (let i = 0; i < resolutions.length; i++) { - for (let j = 0; j < exceptedNumSegments; j++) { - expect( - fs.existsSync( - `src/override-segments/testVideo2-${resolutions[i]}p-segment${j}.ts`, - ), - ).toBe(false) - } - } - for (let i = 0; i < resolutions.length; i++) { - expect( - fs.existsSync( - `./src/override-segments/testVideo2-${resolutions[i]}p-playlist.m3u8`, - ), - ).toBe(false) - } + await new Promise(resolve => setTimeout(resolve, 3000)) + + const resolutions = [144, 240, 360, 480, 720] + const exceptedNumSegments = 13 + for (let i = 0; i < resolutions.length; i++) { + for (let j = 0; j < exceptedNumSegments; j++) { + expect( + fs.existsSync(`src/override-segments/testVideo-${resolutions[i]}p-segment${j}.ts`), + ).toBe(false) + } + } + for (let i = 0; i < resolutions.length; i++) { + expect( + fs.existsSync(`./src/override-segments/testVideo-${resolutions[i]}p-playlist.m3u8`), + ).toBe(false) + } + }) + + it('deletes output segments when master manifest is deleted by bulk delete', async () => { + await payload.delete({ + collection: 'videos', + where: { + filename: { + equals: 'testVideo2.m3u8', + }, + }, }) + await new Promise(resolve => setTimeout(resolve, 3000)) + + const resolutions = [144, 240, 300] + const exceptedNumSegments = 12 + for (let i = 0; i < resolutions.length; i++) { + for (let j = 0; j < exceptedNumSegments; j++) { + expect( + fs.existsSync(`src/override-segments/testVideo2-${resolutions[i]}p-segment${j}.ts`), + ).toBe(false) + } + } + for (let i = 0; i < resolutions.length; i++) { + expect( + fs.existsSync(`./src/override-segments/testVideo2-${resolutions[i]}p-playlist.m3u8`), + ).toBe(false) + } + }) }) diff --git a/dev/src/payload.config.ts b/dev/src/payload.config.ts index ace2836..5167517 100644 --- a/dev/src/payload.config.ts +++ b/dev/src/payload.config.ts @@ -39,75 +39,75 @@ import { OverrideSegments } from './collections/OverrideSegments' // }) export default buildConfig({ - admin: { - user: Users.slug, - bundler: webpackBundler(), - webpack: config => { - const newConfig = { - ...config, - resolve: { - ...config.resolve, - alias: { - ...(config?.resolve?.alias || {}), - react: path.join(__dirname, '../node_modules/react'), - 'react-dom': path.join(__dirname, '../node_modules/react-dom'), - payload: path.join(__dirname, '../node_modules/payload'), - }, - }, - } - return newConfig + admin: { + user: Users.slug, + bundler: webpackBundler(), + webpack: config => { + const newConfig = { + ...config, + resolve: { + ...config.resolve, + alias: { + ...(config?.resolve?.alias || {}), + react: path.join(__dirname, '../node_modules/react'), + 'react-dom': path.join(__dirname, '../node_modules/react-dom'), + payload: path.join(__dirname, '../node_modules/payload'), + }, }, + } + return newConfig }, - cors: '*', - editor: slateEditor({}), - collections: [Users, Media, Videos], - serverURL: process.env.PAYLOAD_PUBLIC_SERVER_URL, - typescript: { - outputFile: path.resolve(__dirname, 'payload-types.ts'), - }, - graphQL: { - schemaOutputFile: path.resolve(__dirname, 'generated-schema.graphql'), - }, - plugins: [ - abrVideos({ - enabled: true, - collections: { - media: { - keepOriginal: false, - }, - videos: { - keepOriginal: true, - resolutions: [ - { size: 144, bitrate: 150 }, - { size: 240, bitrate: 250 }, - { size: 300, bitrate: 500 }, - ], - segmentDuration: 1, - }, - }, - segmentsOverrides: OverrideSegments, - }), - // cloudStorage({ - // collections: { - // media: { - // adapter: adapter, - // prefix: 'test-media', - // disableLocalStorage: true, - // }, - // 'override-segments': { - // adapter: adapter, - // prefix: 'segments', - // disableLocalStorage: true, - // }, - // }, - // }), - ], - db: mongooseAdapter({ - url: process.env.DATABASE_URI!, - }), - upload: { - limits: { - fileSize: 50000000, // 5MB, written in bytes + }, + cors: '*', + serverURL: process.env.PAYLOAD_PUBLIC_SERVER_URL, + editor: slateEditor({}), + collections: [Users, Media, Videos], + typescript: { + outputFile: path.resolve(__dirname, 'payload-types.ts'), + }, + graphQL: { + schemaOutputFile: path.resolve(__dirname, 'generated-schema.graphql'), + }, + plugins: [ + abrVideos({ + enabled: true, + collections: { + media: { + keepOriginal: false, }, + videos: { + keepOriginal: true, + resolutions: [ + { size: 144, bitrate: 150 }, + { size: 240, bitrate: 250 }, + { size: 300, bitrate: 500 }, + ], + segmentDuration: 1, + }, + }, + segmentsOverrides: OverrideSegments, + }), + // cloudStorage({ + // collections: { + // media: { + // adapter: adapter, + // prefix: 'test-media', + // disableLocalStorage: true, + // }, + // 'override-segments': { + // adapter: adapter, + // prefix: 'segments', + // disableLocalStorage: true, + // }, + // }, + // }), + ], + db: mongooseAdapter({ + url: process.env.DATABASE_URI!, + }), + upload: { + limits: { + fileSize: 50000000, // 500MB, written in bytes }, + }, }) diff --git a/dev/src/server.ts b/dev/src/server.ts index 263f99e..e132ae0 100644 --- a/dev/src/server.ts +++ b/dev/src/server.ts @@ -7,23 +7,23 @@ require('dotenv').config() const app = express() app.get('/', (_, res) => { - res.redirect('/admin') + res.redirect('/admin') }) const PORT = 3000 export const start = async (args?: Partial) => { - await payload.init({ - secret: process.env.PAYLOAD_SECRET, - express: app, - onInit: async () => { - payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`) - }, - ...(args || {}), - }) + await payload.init({ + secret: process.env.PAYLOAD_SECRET, + express: app, + onInit: async () => { + payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`) + }, + ...(args || {}), + }) - return app.listen(PORT) + return app.listen(PORT) } if (require.main === module) { - start() + start() } diff --git a/package.json b/package.json index f4b726d..3057318 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "plugin-adaptive-bitrate-videos", - "version": "1.0.5", + "version": "1.1.0", "homepage:": "https://github.com/cgilly2fast/plugin-adaptive-bitrate-videos", "repository": "git@github.com:cgilly2fast/plugin-adaptive-bitrate-videos.git", "description": "Payload plugin for automatic adaptive bitrate process of videos.", @@ -25,11 +25,11 @@ "clean": "rimraf dist && rimraf dev/yarn.lock", "prepublishOnly": "yarn clean && yarn build && cd dev && yarn test" }, - "author": "Colby Gilbert", - "license": "MIT", - "bugs": { - "url": "https://github.com/cgilly2fast/plugin-adaptive-bitrate-videos/issues" - }, + "author": "Colby Gilbert", + "license": "MIT", + "bugs": { + "url": "https://github.com/cgilly2fast/plugin-adaptive-bitrate-videos/issues" + }, "peerDependencies": { "payload": "^2.0.0" }, @@ -55,6 +55,8 @@ "webpack": "^5.90.3" }, "dependencies": { + "@ffmpeg-installer/ffmpeg": "^1.1.0", + "@ffprobe-installer/ffprobe": "^2.1.2", "fluent-ffmpeg": "^2.1.3" } } diff --git a/src/collections/Segments.ts b/src/collections/Segments.ts index da9da34..a3a4953 100644 --- a/src/collections/Segments.ts +++ b/src/collections/Segments.ts @@ -3,23 +3,23 @@ import type { CollectionConfig } from 'payload/types' import type { PluginOptions } from '../types' export const generateSegmentsCollection = (pluginOptions: PluginOptions): CollectionConfig => { - const newConfig: CollectionConfig = { - ...(pluginOptions?.segmentsOverrides || {}), - slug: pluginOptions?.segmentsOverrides?.slug || 'segments', - access: { - read: () => true, - update: () => false, - ...(pluginOptions?.segmentsOverrides?.access || {}), - }, - admin: { - ...(pluginOptions?.segmentsOverrides?.admin || {}), - }, - fields: [...(pluginOptions?.segmentsOverrides?.fields || [])], - hooks: { - ...(pluginOptions?.segmentsOverrides?.hooks || {}), - }, - upload: true, - } + const newConfig: CollectionConfig = { + ...(pluginOptions?.segmentsOverrides || {}), + slug: pluginOptions?.segmentsOverrides?.slug || 'segments', + access: { + read: () => true, + update: () => false, + ...(pluginOptions?.segmentsOverrides?.access || {}), + }, + admin: { + ...(pluginOptions?.segmentsOverrides?.admin || {}), + }, + fields: [...(pluginOptions?.segmentsOverrides?.fields || [])], + hooks: { + ...(pluginOptions?.segmentsOverrides?.hooks || {}), + }, + upload: true, + } - return newConfig + return newConfig } diff --git a/src/components/AfterDashboard/index.scss b/src/components/AfterDashboard/index.scss deleted file mode 100644 index d6362db..0000000 --- a/src/components/AfterDashboard/index.scss +++ /dev/null @@ -1,10 +0,0 @@ -.after-dashboard { - background-color: var(--theme-success-200); - border: 1px solid var(--theme-success-300); - color: var(--theme-success-500); - padding: var(--base); - - p { - margin-bottom: 0; - } -} diff --git a/src/components/AfterDashboard/index.tsx b/src/components/AfterDashboard/index.tsx deleted file mode 100644 index 96b8d19..0000000 --- a/src/components/AfterDashboard/index.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react' - -//import './index.scss'; - -const baseClass = 'after-dashboard' - -const AfterDashboard: React.FC = () => { - return ( -
-

This component was added by the plugin

-
- Find it here: src/components/afterDashboard -
-
- ) -} - -export default AfterDashboard diff --git a/src/endpoints/ProcessVideo/index.ts b/src/endpoints/ProcessVideo/index.ts index 55416d3..60153e5 100644 --- a/src/endpoints/ProcessVideo/index.ts +++ b/src/endpoints/ProcessVideo/index.ts @@ -5,83 +5,83 @@ import { createMasterManifest, createPlaylistManifests } from './utils/manifestU import { sliceVideo } from './service/sliceVideo' import { PayloadRequest } from 'payload/types' import { PossibleBitrates, PossibleResolutions, ProcessVideoParams } from '../../types' -import { getUploadBuffer, getUploadPath } from './utils/fileUploadUtils' +import { getUploadBuffer } from './utils/fileUploadUtils' -const processVideo: PayloadHandler = async (req: PayloadRequest, res, next) => { - try { - const { - inputPath, - baseURL, - keepOriginal, - resolutions, - originalID, - originalData, - segmentDuration, - inputCollectionSlug, - outputCollectionSlug, - } = req.body as ProcessVideoParams +const processVideo: PayloadHandler = async (req: PayloadRequest, res) => { + try { + const { + inputPath, + baseURL, + keepOriginal, + resolutions, + originalID, + originalData, + segmentDuration, + inputCollectionSlug, + outputCollectionSlug, + } = req.body as unknown as ProcessVideoParams - const decodedInputPath = decodeURIComponent(baseURL + inputPath) + const decodedInputPath = decodeURIComponent(baseURL + inputPath) - const videoName = path.basename(decodedInputPath, path.extname(decodedInputPath)) - const tempDir = path.join(__dirname, 'temp') - const tempOutputDir = path.join(tempDir, videoName) + const videoName = path.basename(decodedInputPath, path.extname(decodedInputPath)) + const tempDir = path.join(__dirname, 'temp') + const tempOutputDir = path.join(tempDir, videoName) - if (!fs.existsSync(tempOutputDir)) { - fs.mkdirSync(tempOutputDir, { recursive: true }) - } - let possibleBitrates: PossibleBitrates = {} - let possibleResolutions: PossibleResolutions = [] - for (let i = 0; i < resolutions.length; i++) { - const { size, bitrate } = resolutions[i] - possibleBitrates[size] = bitrate - possibleResolutions.push(size) - } + if (!fs.existsSync(tempOutputDir)) { + fs.mkdirSync(tempOutputDir, { recursive: true }) + } + let possibleBitrates: PossibleBitrates = {} + let possibleResolutions: PossibleResolutions = [] + for (let i = 0; i < resolutions.length; i++) { + const { size, bitrate } = resolutions[i] + possibleBitrates[size] = bitrate + possibleResolutions.push(size) + } - const uploadBufferToOutputCollection = getUploadBuffer(req.payload, outputCollectionSlug) - const uploadBufferToInputCollection = getUploadBuffer(req.payload, inputCollectionSlug) + const uploadBufferToOutputCollection = getUploadBuffer(req.payload, outputCollectionSlug) + const uploadBufferToInputCollection = getUploadBuffer(req.payload, inputCollectionSlug) - const videoInfo = await sliceVideo( - videoName, - inputPath, - tempOutputDir, - possibleResolutions, - possibleBitrates, - baseURL, - segmentDuration, - uploadBufferToOutputCollection, - outputCollectionSlug, - ) - await createPlaylistManifests( - videoName, - videoInfo.playlists, - segmentDuration, - uploadBufferToOutputCollection, - ) - await createMasterManifest( - videoName, - videoInfo, - possibleBitrates, - baseURL, - uploadBufferToInputCollection, - outputCollectionSlug, - originalData, - ) + const videoInfo = await sliceVideo( + videoName, + inputPath, + tempOutputDir, + possibleResolutions, + possibleBitrates, + baseURL, + segmentDuration, + uploadBufferToOutputCollection, + outputCollectionSlug, + ) + await createPlaylistManifests( + videoName, + videoInfo.playlists, + segmentDuration, + uploadBufferToOutputCollection, + ) + await createMasterManifest( + videoName, + videoInfo, + possibleBitrates, + baseURL, + uploadBufferToInputCollection, + outputCollectionSlug, + originalData, + ) - fs.rmSync(tempDir, { recursive: true, force: true }) + fs.rmSync(tempDir, { recursive: true, force: true }) - if (!keepOriginal) { - req.payload.delete({ - collection: inputCollectionSlug, - id: originalID, - }) - } - console.log('Video processing done') - return res.json({ success: true }) - } catch (error: any) { - console.error('Error in processing video:', error) - return res.json({ success: false, error: error.message }) + if (!keepOriginal) { + req.payload.delete({ + collection: inputCollectionSlug, + id: originalID, + }) } + console.log('Video processing done') + return res.json({ success: true }) + } catch (error: any) { + console.error('Error in processing video:', error) + return res.json({ success: false, error: error.message }) + } } export default processVideo diff --git a/src/endpoints/ProcessVideo/service/sliceVideo.ts b/src/endpoints/ProcessVideo/service/sliceVideo.ts index 0dcd213..0e73de2 100644 --- a/src/endpoints/ProcessVideo/service/sliceVideo.ts +++ b/src/endpoints/ProcessVideo/service/sliceVideo.ts @@ -1,181 +1,181 @@ import ffmpeg, { FfprobeStream } from 'fluent-ffmpeg' import http from 'http' import https from 'https' -import { URL } from 'url' import fs from 'fs' import { promises as fsPromises } from 'fs' +import ffmpegInstaller from '@ffmpeg-installer/ffmpeg' +import ffprobeInstaller from '@ffprobe-installer/ffprobe' import path from 'path' import { - PlaylistInfo, - PossibleBitrates, - PossibleResolutions, - Segment, - UploadBufferFunc, - VideoInfo, + PlaylistInfo, + PossibleBitrates, + PossibleResolutions, + Segment, + UploadBufferFunc, + VideoInfo, } from '../../../types' import { calcDimensions, getFrameRate } from '../utils/ffmpegUtils' export async function sliceVideo( - videoName: string, - inputPath: string, - tempOutputDir: string, - possibleResolutions: PossibleResolutions, - possibleBitrates: PossibleBitrates, - baseURL: string, - segmentDuration: number, - uploadBuffer: UploadBufferFunc, - outputCollectionSlug: string, + videoName: string, + inputPath: string, + tempOutputDir: string, + possibleResolutions: PossibleResolutions, + possibleBitrates: PossibleBitrates, + baseURL: string, + segmentDuration: number, + uploadBuffer: UploadBufferFunc, + outputCollectionSlug: string, ): Promise { - return new Promise((resolve, reject) => { - ffmpeg.ffprobe(inputPath, async (err, metadata) => { - if (err) return reject(err) - - const duration = metadata.format.duration - if (!duration) { - reject(`Video does not have a duration ${inputPath}`) - return - } - - const videoMetadata = metadata.streams.find( - (stream: FfprobeStream) => stream.codec_type === 'video', - ) - if (!videoMetadata) { - reject(`Could not find a video stream on input file ${inputPath}`) - return - } - - const { width, height } = videoMetadata - if (!width || !height) { - reject(`Video metadata does not have a width or height ${inputPath}`) - return - } - - const aspectRatio = width / height - let orientation = '' - let maxResolution = 0 - - if (width < height) { - orientation = 'x' - maxResolution = width - } else { - orientation = 'y' - maxResolution = height - } - - let playlists: PlaylistInfo[] = [] - const resolutions = possibleResolutions.filter( - resolution => resolution <= maxResolution, - ) - - const copiedVideoPath = path.join(tempOutputDir, `${path.basename(inputPath)}`) - await new Promise((resolveCopy, rejectCopy) => { - const file = fs.createWriteStream(copiedVideoPath) - console.log(copiedVideoPath) - const protocol = inputPath.toLowerCase().startsWith('https:') ? https : http - protocol.get(inputPath, resp => { - resp.pipe(file) - - file.on('finish', () => { - file.close() - resolveCopy() - }) - file.on('error', err => { - rejectCopy(err) - }) - }) + return new Promise((resolve, reject) => { + ffmpeg.setFfmpegPath(ffmpegInstaller.path) + ffmpeg.setFfprobePath(ffprobeInstaller.path) + + ffmpeg.ffprobe(inputPath, async (err, metadata) => { + if (err) return reject(err) + + const duration = metadata.format.duration + if (!duration) { + reject(`Video does not have a duration ${inputPath}`) + return + } + + const videoMetadata = metadata.streams.find( + (stream: FfprobeStream) => stream.codec_type === 'video', + ) + if (!videoMetadata) { + reject(`Could not find a video stream on input file ${inputPath}`) + return + } + + const { width, height } = videoMetadata + if (!width || !height) { + reject(`Video metadata does not have a width or height ${inputPath}`) + return + } + + const aspectRatio = width / height + let orientation = '' + let maxResolution = 0 + + if (width < height) { + orientation = 'x' + maxResolution = width + } else { + orientation = 'y' + maxResolution = height + } + + let playlists: PlaylistInfo[] = [] + const resolutions = possibleResolutions.filter(resolution => resolution <= maxResolution) + + const copiedVideoPath = path.join(tempOutputDir, `${path.basename(inputPath)}`) + await new Promise((resolveCopy, rejectCopy) => { + const file = fs.createWriteStream(copiedVideoPath) + console.log(copiedVideoPath) + const protocol = inputPath.toLowerCase().startsWith('https:') ? https : http + protocol.get(inputPath, resp => { + resp.pipe(file) + + file.on('finish', () => { + file.close() + resolveCopy() + }) + file.on('error', err => { + rejectCopy(err) + }) + }) + }) + for (const resolution of resolutions) { + const outResolutionDir = path.join(tempOutputDir, `${resolution}`) + if (!fs.existsSync(outResolutionDir)) { + fs.mkdirSync(outResolutionDir, { recursive: true }) + } + + const segmentFilePattern = path.join( + outResolutionDir, + `${videoName}-${resolution}p-segment%d.ts`, + ) + + let sizeParam = '' + if (orientation === 'x') { + sizeParam = `${resolution}x?` + } else { + sizeParam = `?x${resolution}` + } + + console.log(inputPath, resolution, orientation, sizeParam) + await new Promise((resolveSegment, rejectSegment) => { + ffmpeg(copiedVideoPath) + .size(sizeParam) + .outputOptions([ + '-map 0', + '-profile:v baseline', + '-level 3.0', + '-c:v libx264', + `-b:v ${possibleBitrates[resolution]}k`, + `-force_key_frames expr:gte(t,n_forced*${segmentDuration})`, + '-c:a aac', + '-b:a 128k', + '-hls_list_size 0', + '-start_number 0', + '-hls_init_time 0', + `-hls_time ${segmentDuration}`, + `-hls_segment_filename ${segmentFilePattern}`, + '-f hls', + ]) + .output(path.join(outResolutionDir, `playlist.m3u8`)) + .on('end', () => { + resolveSegment() }) - for (const resolution of resolutions) { - const outResolutionDir = path.join(tempOutputDir, `${resolution}`) - if (!fs.existsSync(outResolutionDir)) { - fs.mkdirSync(outResolutionDir, { recursive: true }) - } - - const segmentFilePattern = path.join( - outResolutionDir, - `${videoName}-${resolution}p-segment%d.ts`, - ) - - let sizeParam = '' - if (orientation === 'x') { - sizeParam = `${resolution}x?` - } else { - sizeParam = `?x${resolution}` - } - - console.log(inputPath, resolution, orientation, sizeParam) - await new Promise((resolveSegment, rejectSegment) => { - ffmpeg(copiedVideoPath) - .size(sizeParam) - .outputOptions([ - '-map 0', - '-profile:v baseline', - '-level 3.0', - '-c:v libx264', - `-b:v ${possibleBitrates[resolution]}k`, - `-force_key_frames expr:gte(t,n_forced*${segmentDuration})`, - '-c:a aac', - '-b:a 128k', - '-hls_list_size 0', - '-start_number 0', - '-hls_init_time 0', - `-hls_time ${segmentDuration}`, - `-hls_segment_filename ${segmentFilePattern}`, - '-f hls', - ]) - .output(path.join(outResolutionDir, `playlist.m3u8`)) - .on('end', () => { - resolveSegment() - }) - .on('error', function (err: any) { - console.log(err) - rejectSegment(err) - }) - .run() - }) - - const numSegments = Math.ceil(duration / segmentDuration) - let segments: Segment[] = [] - let promises = [] - for (let i = 0; i < numSegments; i++) { - const segmentName = `${videoName}-${resolution}p-segment${i}.ts` - const segmentPath = path.join(outResolutionDir, segmentName) - - let computedDuration = segmentDuration - if (i === numSegments - 1) { - computedDuration = duration % segmentDuration - } - const buffer = await fsPromises.readFile(segmentPath) - - promises.push( - uploadBuffer(buffer, 'video/mp2t', segmentName, buffer.byteLength), - ) - - segments.push({ - index: i, - path: `${baseURL}/${outputCollectionSlug}/${segmentName}`, - duration: computedDuration, - }) - } - - const res = await Promise.all(promises) - const bitrate = possibleBitrates[resolution] / 1000 - - playlists.push({ - resolution, - bitrate, - segments, - ...calcDimensions(aspectRatio, orientation, resolution), - }) - } - - resolve({ - playlists, - orientation, - maxResolution, - duration, - aspectRatio, - frameRate: getFrameRate(videoMetadata), + .on('error', function (err: any) { + console.log(err) + rejectSegment(err) }) + .run() + }) + + const numSegments = Math.ceil(duration / segmentDuration) + let segments: Segment[] = [] + let promises = [] + for (let i = 0; i < numSegments; i++) { + const segmentName = `${videoName}-${resolution}p-segment${i}.ts` + const segmentPath = path.join(outResolutionDir, segmentName) + + let computedDuration = segmentDuration + if (i === numSegments - 1) { + computedDuration = duration % segmentDuration + } + const buffer = await fsPromises.readFile(segmentPath) + + promises.push(uploadBuffer(buffer, 'video/mp2t', segmentName, buffer.byteLength)) + + segments.push({ + index: i, + path: `${baseURL}/${outputCollectionSlug}/${segmentName}`, + duration: computedDuration, + }) + } + + const res = await Promise.all(promises) + const bitrate = possibleBitrates[resolution] / 1000 + + playlists.push({ + resolution, + bitrate, + segments, + ...calcDimensions(aspectRatio, orientation, resolution), }) + } + + resolve({ + playlists, + orientation, + maxResolution, + duration, + aspectRatio, + frameRate: getFrameRate(videoMetadata), + }) }) + }) } diff --git a/src/endpoints/ProcessVideo/utils/fileUploadUtils.ts b/src/endpoints/ProcessVideo/utils/fileUploadUtils.ts index 44b8cba..3fa3ea0 100644 --- a/src/endpoints/ProcessVideo/utils/fileUploadUtils.ts +++ b/src/endpoints/ProcessVideo/utils/fileUploadUtils.ts @@ -2,24 +2,24 @@ import { Payload } from 'payload/dist/payload' import { UploadBufferFunc, UploadPathFunc } from '../../../types' export const getUploadPath = - (payload: Payload, outputCollectionSlug: string): UploadPathFunc => - (path: string) => { - return payload.create({ collection: outputCollectionSlug, filePath: path, data: {} }) - } + (payload: Payload, outputCollectionSlug: string): UploadPathFunc => + (path: string) => { + return payload.create({ collection: outputCollectionSlug, filePath: path, data: {} }) + } export const getUploadBuffer = - (payload: Payload, outputCollectionSlug: string): UploadBufferFunc => - async ( - data: Buffer, - mimetype: string, - name: string, - size: number, - originalData?: Record, - ) => { - const file = { data, mimetype, name, size } - return payload.create({ - collection: outputCollectionSlug, - file, - data: originalData ?? {}, - }) - } + (payload: Payload, outputCollectionSlug: string): UploadBufferFunc => + async ( + data: Buffer, + mimetype: string, + name: string, + size: number, + originalData?: Record, + ) => { + const file = { data, mimetype, name, size } + return payload.create({ + collection: outputCollectionSlug, + file, + data: originalData ?? {}, + }) + } diff --git a/src/hooks/afterOperation.ts b/src/hooks/afterOperation.ts index db7b0b1..7de59dd 100644 --- a/src/hooks/afterOperation.ts +++ b/src/hooks/afterOperation.ts @@ -2,80 +2,80 @@ import { CollectionAfterOperationHook } from 'payload/types' import { GetAfterOperationHookParams } from '../types' export const getAfterOperationHook = - ({ - keepOriginal, - resolutions, - segmentDuration, - outputCollectionSlug, - }: GetAfterOperationHookParams): CollectionAfterOperationHook => - async ({ operation, result, req, collection }) => { - if (operation === 'create') { - const { id, filename, mimeType, url, createdAt, updatedAt, ...data } = result as any - if (!mimeType.startsWith('video/')) { - return result - } + ({ + keepOriginal, + resolutions, + segmentDuration, + outputCollectionSlug, + }: GetAfterOperationHookParams): CollectionAfterOperationHook => + async ({ operation, result, req, collection }) => { + if (operation === 'create') { + const { id, filename, mimeType, url, createdAt, updatedAt, ...data } = result as any + if (!mimeType.startsWith('video/')) { + return result + } - const baseURL = req.payload.config.serverURL.replace(/\/$/, '') - setTimeout(async () => { - fetch(`${baseURL}/api/process-video`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - baseURL, - inputPath: url, - keepOriginal, - originalID: id, - originalData: data, - resolutions, - segmentDuration, - inputCollectionSlug: collection.slug, - outputCollectionSlug, - }), - }) - }, 1000) - return result - } - if (operation === 'deleteByID') { - const { filename, mimeType } = result as any - if (!mimeType.startsWith('application/x-mpegURL')) { - return result - } + const baseURL = req.payload.config.serverURL.replace(/\/$/, '') + setTimeout(async () => { + fetch(`${baseURL}/api/process-video`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + baseURL, + inputPath: url, + keepOriginal, + originalID: id, + originalData: data, + resolutions, + segmentDuration, + inputCollectionSlug: collection.slug, + outputCollectionSlug, + }), + }) + }, 1000) + return result + } + if (operation === 'deleteByID') { + const { filename, mimeType } = result as any + if (!mimeType.startsWith('application/x-mpegURL')) { + return result + } - const videoName = filename.split('.')[0] + const videoName = filename.split('.')[0] - req.payload.delete({ - collection: outputCollectionSlug, - where: { - filename: { - contains: videoName, - }, - }, - }) - return result - } + req.payload.delete({ + collection: outputCollectionSlug, + where: { + filename: { + contains: videoName, + }, + }, + }) + return result + } - if (operation === 'delete') { - const { docs } = result as any - for (let i = 0; i < docs.length; i++) { - const { filename, mimeType } = docs[i] - if (!mimeType.startsWith('application/x-mpegURL')) { - continue - } + if (operation === 'delete') { + const { docs } = result as any + for (let i = 0; i < docs.length; i++) { + const { filename, mimeType } = docs[i] + if (!mimeType.startsWith('application/x-mpegURL')) { + continue + } - const videoName = filename.split('.')[0] + const videoName = filename.split('.')[0] - req.payload.delete({ - collection: outputCollectionSlug, - where: { - filename: { - contains: videoName, - }, - }, - }) - } - return result - } - return result + req.payload.delete({ + collection: outputCollectionSlug, + where: { + filename: { + contains: videoName, + }, + }, + }) + } + return result } + return result + } diff --git a/src/hooks/operations/create.ts b/src/hooks/operations/create.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/plugin.ts b/src/plugin.ts index 2b25f5f..962a661 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1,4 +1,4 @@ -import type { Config, Plugin } from 'payload/config' +import type { Plugin } from 'payload/config' import type { PluginOptions } from './types' import { generateSegmentsCollection } from './collections/Segments' @@ -7,68 +7,67 @@ import processVideo from './endpoints/ProcessVideo' import { extendWebpackConfig } from './webpack' const DefaultResolution = [ - { size: 144, bitrate: 150 }, - { size: 240, bitrate: 250 }, - { size: 360, bitrate: 500 }, - { size: 480, bitrate: 1000 }, - { size: 720, bitrate: 1500 }, - { size: 1080, bitrate: 4000 }, - { size: 1440, bitrate: 6000 }, - { size: 2160, bitrate: 10000 }, + { size: 144, bitrate: 150 }, + { size: 240, bitrate: 250 }, + { size: 360, bitrate: 500 }, + { size: 480, bitrate: 1000 }, + { size: 720, bitrate: 1500 }, + { size: 1080, bitrate: 4000 }, + { size: 1440, bitrate: 6000 }, + { size: 2160, bitrate: 10000 }, ] export const abrVideos = - (pluginOptions: PluginOptions): Plugin => - incomingConfig => { - let config = { ...incomingConfig } - const { collections: allCollectionOptions, enabled } = pluginOptions + (pluginOptions: PluginOptions): Plugin => + incomingConfig => { + let config = { ...incomingConfig } + const { collections: allCollectionOptions, enabled } = pluginOptions - // If the plugin is disabled, return the config without modifying it - // The order of this check is important, we still want any webpack extensions to be applied even if the plugin is disabled - if (enabled === false) { - return config - } - const webpack = extendWebpackConfig(incomingConfig) - config.admin = { - ...(config.admin || {}), - webpack, - } - config.collections = [ - ...(config.collections || []).map(existingCollection => { - const options = allCollectionOptions[existingCollection.slug] + // If the plugin is disabled, return the config without modifying it + // The order of this check is important, we still want any webpack extensions to be applied even if the plugin is disabled + if (enabled === false) { + return config + } + const webpack = extendWebpackConfig(incomingConfig) + config.admin = { + ...(config.admin || {}), + webpack, + } + config.collections = [ + ...(config.collections || []).map(existingCollection => { + const options = allCollectionOptions[existingCollection.slug] - if (!options) return existingCollection + if (!options) return existingCollection - const { keepOriginal, resolutions, segmentDuration } = options + const { keepOriginal, resolutions, segmentDuration } = options - return { - ...existingCollection, - hooks: { - ...(existingCollection.hooks || {}), - afterOperation: [ - ...(existingCollection.hooks?.afterOperation || []), - getAfterOperationHook({ - keepOriginal: keepOriginal ?? false, - resolutions: resolutions ?? DefaultResolution, - segmentDuration: segmentDuration ?? 2, - outputCollectionSlug: - pluginOptions?.segmentsOverrides?.slug || 'segments', - }), - ], - }, - } - }), - generateSegmentsCollection(pluginOptions), - ] - config.endpoints = [ - ...(config.endpoints || []), - { - path: '/process-video', - method: 'post', - handler: processVideo, - }, - // Add additional endpoints here - ] + return { + ...existingCollection, + hooks: { + ...(existingCollection.hooks || {}), + afterOperation: [ + ...(existingCollection.hooks?.afterOperation || []), + getAfterOperationHook({ + keepOriginal: keepOriginal ?? false, + resolutions: resolutions ?? DefaultResolution, + segmentDuration: segmentDuration ?? 2, + outputCollectionSlug: pluginOptions?.segmentsOverrides?.slug || 'segments', + }), + ], + }, + } + }), + generateSegmentsCollection(pluginOptions), + ] + config.endpoints = [ + ...(config.endpoints || []), + { + path: '/process-video', + method: 'post', + handler: processVideo, + }, + // Add additional endpoints here + ] - return config - } + return config + } diff --git a/src/types.ts b/src/types.ts index 0d8ecd8..5262ee1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -4,88 +4,88 @@ import { CollectionConfig, TypeWithID } from 'payload/types' * Configuration options for the plugin. */ export interface PluginOptions { - /** - * Enable or disable the plugin. - * @default false - */ - enabled?: boolean - - /** - * Object with keys set to the slug of collections you want to enable the plugin for, - * and values set to collection-specific options. - */ - collections: Record - - /** - * Object that overrides the default collection used to store reference to the output segments. - * @default SegmentOverrideDefault - */ - segmentsOverrides?: Partial + /** + * Enable or disable the plugin. + * @default false + */ + enabled?: boolean + + /** + * Object with keys set to the slug of collections you want to enable the plugin for, + * and values set to collection-specific options. + */ + collections: Record + + /** + * Object that overrides the default collection used to store reference to the output segments. + * @default SegmentOverrideDefault + */ + segmentsOverrides?: Partial } /** * Options specific to each collection. */ export interface CollectionOptions { - /** - * Whether to keep the original source file after processing. - */ - keepOriginal: boolean - - /** - * Custom resolutions for the plugin to output segment videos to. - * @default ResolutionsDefault - */ - resolutions?: Resolution[] - - /** - * The output segment length in seconds for each resolution output. - * @default 2 - */ - segmentDuration?: number + /** + * Whether to keep the original source file after processing. + */ + keepOriginal: boolean + + /** + * Custom resolutions for the plugin to output segment videos to. + * @default ResolutionsDefault + */ + resolutions?: Resolution[] + + /** + * The output segment length in seconds for each resolution output. + * @default 2 + */ + segmentDuration?: number } export interface GetAfterOperationHookParams extends Required { - outputCollectionSlug: string + outputCollectionSlug: string } export interface ProcessVideoParams extends GetAfterOperationHookParams { - inputCollectionSlug: string - inputPath: string - baseURL: string - originalID: string - originalData: Record + inputCollectionSlug: string + inputPath: string + baseURL: string + originalID: string + originalData: Record } export interface Resolution { - size: number - bitrate: number + size: number + bitrate: number } export interface NewCollectionTypes { - title: string + title: string } export interface Segment { - index: number - path: string - duration: number + index: number + path: string + duration: number } export interface VideoInfo { - playlists: PlaylistInfo[] - orientation: string - maxResolution: number - duration: number - aspectRatio: number - frameRate: number + playlists: PlaylistInfo[] + orientation: string + maxResolution: number + duration: number + aspectRatio: number + frameRate: number } export interface PlaylistInfo { - segments: Segment[] - resolution: number - bitrate: number - width: number - height: number + segments: Segment[] + resolution: number + bitrate: number + width: number + height: number } export type PossibleResolutions = number[] @@ -93,11 +93,11 @@ export type PossibleResolutions = number[] export type PossibleBitrates = Record export type UploadBufferFunc = ( - data: Buffer, - mimetype: string, - name: string, - size: number, - orginalData?: Record, + data: Buffer, + mimetype: string, + name: string, + size: number, + orginalData?: Record, ) => Promise> export type UploadPathFunc = (path: string) => Promise> diff --git a/src/webpack.ts b/src/webpack.ts index 7740156..ee2f1e3 100644 --- a/src/webpack.ts +++ b/src/webpack.ts @@ -3,27 +3,25 @@ import type { Config } from 'payload/config' import type { Configuration as WebpackConfig } from 'webpack' export const extendWebpackConfig = - (config: Config): ((webpackConfig: WebpackConfig) => WebpackConfig) => - webpackConfig => { - const existingWebpackConfig = - typeof config.admin?.webpack === 'function' - ? config.admin.webpack(webpackConfig) - : webpackConfig + (config: Config): ((webpackConfig: WebpackConfig) => WebpackConfig) => + webpackConfig => { + const existingWebpackConfig = + typeof config.admin?.webpack === 'function' + ? config.admin.webpack(webpackConfig) + : webpackConfig - const mockModulePath = path.resolve(__dirname, './mocks/mockFile.js') + const mockModulePath = path.resolve(__dirname, './mocks/mockFile.js') - const newWebpack = { - ...existingWebpackConfig, - resolve: { - ...(existingWebpackConfig.resolve || {}), - alias: { - ...(existingWebpackConfig.resolve?.alias - ? existingWebpackConfig.resolve.alias - : {}), - // Add additional aliases here like so: - [path.resolve(__dirname, './endpoints/ProcessVideo')]: mockModulePath, - }, - }, - } - return newWebpack + const newWebpack = { + ...existingWebpackConfig, + resolve: { + ...(existingWebpackConfig.resolve || {}), + alias: { + ...(existingWebpackConfig.resolve?.alias ? existingWebpackConfig.resolve.alias : {}), + // Add additional aliases here like so: + [path.resolve(__dirname, './endpoints/ProcessVideo')]: mockModulePath, + }, + }, } + return newWebpack + } diff --git a/yarn.lock b/yarn.lock index afea3af..899179a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -501,6 +501,114 @@ resolved "https://registry.yarnpkg.com/@faceless-ui/window-info/-/window-info-2.1.1.tgz#ed1474a60ab794295bca4c29e295b1e11a584d22" integrity sha512-gMAgda7beR4CNpBIXjgRVn97ek0LG3PAj9lxmoYdg574IEzLFZAh3eAYtTaS2XLKgb4+IHhsuBzlGmHbeOo2Aw== +"@ffmpeg-installer/darwin-arm64@4.1.5": + version "4.1.5" + resolved "https://registry.yarnpkg.com/@ffmpeg-installer/darwin-arm64/-/darwin-arm64-4.1.5.tgz#b7b5c262dd96d1aea4807514e1cdcf6e11f82743" + integrity sha512-hYqTiP63mXz7wSQfuqfFwfLOfwwFChUedeCVKkBtl/cliaTM7/ePI9bVzfZ2c+dWu3TqCwLDRWNSJ5pqZl8otA== + +"@ffmpeg-installer/darwin-x64@4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@ffmpeg-installer/darwin-x64/-/darwin-x64-4.1.0.tgz#48e1706c690e628148482bfb64acb67472089aaa" + integrity sha512-Z4EyG3cIFjdhlY8wI9aLUXuH8nVt7E9SlMVZtWvSPnm2sm37/yC2CwjUzyCQbJbySnef1tQwGG2Sx+uWhd9IAw== + +"@ffmpeg-installer/ffmpeg@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@ffmpeg-installer/ffmpeg/-/ffmpeg-1.1.0.tgz#87fdb9e7d180e8d78f7903f9441e36f978938a90" + integrity sha512-Uq4rmwkdGxIa9A6Bd/VqqYbT7zqh1GrT5/rFwCwKM70b42W5gIjWeVETq6SdcL0zXqDtY081Ws/iJWhr1+xvQg== + optionalDependencies: + "@ffmpeg-installer/darwin-arm64" "4.1.5" + "@ffmpeg-installer/darwin-x64" "4.1.0" + "@ffmpeg-installer/linux-arm" "4.1.3" + "@ffmpeg-installer/linux-arm64" "4.1.4" + "@ffmpeg-installer/linux-ia32" "4.1.0" + "@ffmpeg-installer/linux-x64" "4.1.0" + "@ffmpeg-installer/win32-ia32" "4.1.0" + "@ffmpeg-installer/win32-x64" "4.1.0" + +"@ffmpeg-installer/linux-arm64@4.1.4": + version "4.1.4" + resolved "https://registry.yarnpkg.com/@ffmpeg-installer/linux-arm64/-/linux-arm64-4.1.4.tgz#7219f3f901bb67f7926cb060b56b6974a6cad29f" + integrity sha512-dljEqAOD0oIM6O6DxBW9US/FkvqvQwgJ2lGHOwHDDwu/pX8+V0YsDL1xqHbj1DMX/+nP9rxw7G7gcUvGspSoKg== + +"@ffmpeg-installer/linux-arm@4.1.3": + version "4.1.3" + resolved "https://registry.yarnpkg.com/@ffmpeg-installer/linux-arm/-/linux-arm-4.1.3.tgz#c554f105ed5f10475ec25d7bec94926ce18db4c1" + integrity sha512-NDf5V6l8AfzZ8WzUGZ5mV8O/xMzRag2ETR6+TlGIsMHp81agx51cqpPItXPib/nAZYmo55Bl2L6/WOMI3A5YRg== + +"@ffmpeg-installer/linux-ia32@4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@ffmpeg-installer/linux-ia32/-/linux-ia32-4.1.0.tgz#adad70b0d0d9d8d813983d6e683c5a338a75e442" + integrity sha512-0LWyFQnPf+Ij9GQGD034hS6A90URNu9HCtQ5cTqo5MxOEc7Rd8gLXrJvn++UmxhU0J5RyRE9KRYstdCVUjkNOQ== + +"@ffmpeg-installer/linux-x64@4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@ffmpeg-installer/linux-x64/-/linux-x64-4.1.0.tgz#b4a5d89c4e12e6d9306dbcdc573df716ec1c4323" + integrity sha512-Y5BWhGLU/WpQjOArNIgXD3z5mxxdV8c41C+U15nsE5yF8tVcdCGet5zPs5Zy3Ta6bU7haGpIzryutqCGQA/W8A== + +"@ffmpeg-installer/win32-ia32@4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@ffmpeg-installer/win32-ia32/-/win32-ia32-4.1.0.tgz#6eac4fb691b64c02e7a116c1e2d167f3e9b40638" + integrity sha512-FV2D7RlaZv/lrtdhaQ4oETwoFUsUjlUiasiZLDxhEUPdNDWcH1OU9K1xTvqz+OXLdsmYelUDuBS/zkMOTtlUAw== + +"@ffmpeg-installer/win32-x64@4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@ffmpeg-installer/win32-x64/-/win32-x64-4.1.0.tgz#17e8699b5798d4c60e36e2d6326a8ebe5e95a2c5" + integrity sha512-Drt5u2vzDnIONf4ZEkKtFlbvwj6rI3kxw1Ck9fpudmtgaZIHD4ucsWB2lCZBXRxJgXR+2IMSti+4rtM4C4rXgg== + +"@ffprobe-installer/darwin-arm64@5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@ffprobe-installer/darwin-arm64/-/darwin-arm64-5.0.1.tgz#a020a623955d55aa8daf45cb668c3044876b553b" + integrity sha512-vwNCNjokH8hfkbl6m95zICHwkSzhEvDC3GVBcUp5HX8+4wsX10SP3B+bGur7XUzTIZ4cQpgJmEIAx6TUwRepMg== + +"@ffprobe-installer/darwin-x64@5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@ffprobe-installer/darwin-x64/-/darwin-x64-5.1.0.tgz#f52316ac0bbe6f4ac70fdaea8db259ba4a055b00" + integrity sha512-J+YGscZMpQclFg31O4cfVRGmDpkVsQ2fZujoUdMAAYcP0NtqpC49Hs3SWJpBdsGB4VeqOt5TTm1vSZQzs1NkhA== + +"@ffprobe-installer/ffprobe@^2.1.2": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@ffprobe-installer/ffprobe/-/ffprobe-2.1.2.tgz#ef9826b714cefe5e2debbe357afbb1ba738dfb32" + integrity sha512-ZNvwk4f2magF42Zji2Ese16SMj9BS7Fui4kRjg6gTYTxY3gWZNpg85n4MIfQyI9nimHg4x/gT6FVkp/bBDuBwg== + optionalDependencies: + "@ffprobe-installer/darwin-arm64" "5.0.1" + "@ffprobe-installer/darwin-x64" "5.1.0" + "@ffprobe-installer/linux-arm" "5.2.0" + "@ffprobe-installer/linux-arm64" "5.2.0" + "@ffprobe-installer/linux-ia32" "5.2.0" + "@ffprobe-installer/linux-x64" "5.2.0" + "@ffprobe-installer/win32-ia32" "5.1.0" + "@ffprobe-installer/win32-x64" "5.1.0" + +"@ffprobe-installer/linux-arm64@5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@ffprobe-installer/linux-arm64/-/linux-arm64-5.2.0.tgz#b6cb3735792d9d012d1caba4de2a6f90af2a8966" + integrity sha512-X1VvWtlLs6ScP73biVLuHD5ohKJKsMTa0vafCESOen4mOoNeLAYbxOVxDWAdFz9cpZgRiloFj5QD6nDj8E28yQ== + +"@ffprobe-installer/linux-arm@5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@ffprobe-installer/linux-arm/-/linux-arm-5.2.0.tgz#0120863c181303a1610b1e6956c6a5492d6c45a6" + integrity sha512-PF5HqEhCY7WTWHtLDYbA/+rLS+rhslWvyBlAG1Fk8VzVlnRdl93o6hy7DE2kJgxWQbFaR3ZktPQGEzfkrmQHvQ== + +"@ffprobe-installer/linux-ia32@5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@ffprobe-installer/linux-ia32/-/linux-ia32-5.2.0.tgz#d42a892003811b5e1f2c958d330b841ef6ff3233" + integrity sha512-TFVK5sasXyXhbIG7LtPRDmtkrkOsInwKcL43iEvEw+D9vCS2rc//mn9/0Q+BR0UoJEiMK4+ApYr/3LLVUBPOCQ== + +"@ffprobe-installer/linux-x64@5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@ffprobe-installer/linux-x64/-/linux-x64-5.2.0.tgz#5dd8dbd51d130b5997bf49cb874e1f92e97f02e7" + integrity sha512-D3UeqTLYPNs7pBWPLUYGehPdRVqU8eACox4OZy3pZUZatxye2YKlvBwEfaLdL1v2Z4FOAlLUhms0kY8m8kqSRA== + +"@ffprobe-installer/win32-ia32@5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@ffprobe-installer/win32-ia32/-/win32-ia32-5.1.0.tgz#43b1462b9d89570fe3723c20b66bab684516751a" + integrity sha512-5O3vOoNRxmut0/Nu9vSazTdSHasrr+zPT2B3Hm7kjmO3QVFcIfVImS6ReQnZeSy8JPJOqXts5kX5x/3KOX54XQ== + +"@ffprobe-installer/win32-x64@5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@ffprobe-installer/win32-x64/-/win32-x64-5.1.0.tgz#87841123e8b903cc327f1e5b9aa69e5d2fbe6d7b" + integrity sha512-jMGYeAgkrdn4e2vvYt/qakgHRE3CPju4bn5TmdPfoAm1BlX1mY9cyMd8gf5vSzI8gH8Zq5WQAyAkmekX/8TSTg== + "@floating-ui/core@^1.6.0": version "1.6.7" resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.7.tgz#7602367795a390ff0662efd1c7ae8ca74e75fb12"