updated docker for ffmpeg
This commit is contained in:
parent
540b7b6aa7
commit
b98b5106d9
@ -9,6 +9,8 @@ RUN yarn build
|
|||||||
|
|
||||||
|
|
||||||
FROM node:20.15.1-alpine
|
FROM node:20.15.1-alpine
|
||||||
|
# Install FFmpeg for video processing (reversed video generation)
|
||||||
|
RUN apk add --no-cache ffmpeg
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY backend/package.json backend/yarn.lock ./
|
COPY backend/package.json backend/yarn.lock ./
|
||||||
RUN yarn install --pure-lockfile
|
RUN yarn install --pure-lockfile
|
||||||
|
|||||||
@ -21,6 +21,8 @@ RUN yarn install --pure-lockfile
|
|||||||
FROM node:20.15.1-alpine AS build
|
FROM node:20.15.1-alpine AS build
|
||||||
RUN apk add --no-cache git nginx curl
|
RUN apk add --no-cache git nginx curl
|
||||||
RUN apk add --no-cache lsof procps
|
RUN apk add --no-cache lsof procps
|
||||||
|
# Install FFmpeg for video processing (reversed video generation)
|
||||||
|
RUN apk add --no-cache ffmpeg
|
||||||
RUN yarn global add concurrently
|
RUN yarn global add concurrently
|
||||||
|
|
||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
FROM node:20.15.1-alpine
|
FROM node:20.15.1-alpine
|
||||||
|
|
||||||
RUN apk update && apk add bash
|
# Install bash and FFmpeg for video processing (reversed video generation)
|
||||||
|
RUN apk update && apk add --no-cache bash ffmpeg
|
||||||
|
|
||||||
# Create app directory
|
# Create app directory
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
const AssetsDBApi = require('../db/api/assets');
|
const AssetsDBApi = require('../db/api/assets');
|
||||||
|
const Asset_variantsDBApi = require('../db/api/asset_variants');
|
||||||
const { createEntityService } = require('../factories/service.factory');
|
const { createEntityService } = require('../factories/service.factory');
|
||||||
const ValidationError = require('./notifications/errors/validation');
|
const ValidationError = require('./notifications/errors/validation');
|
||||||
|
const { downloadToBuffer, uploadBuffer } = require('./file');
|
||||||
|
const videoProcessing = require('./videoProcessing');
|
||||||
|
const { logger } = require('../utils/logger');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Valid MIME type patterns for each asset type
|
* Valid MIME type patterns for each asset type
|
||||||
@ -68,11 +72,11 @@ const BaseService = createEntityService(AssetsDBApi, {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assets Service with validation
|
* Assets Service with validation and video pre-processing
|
||||||
*/
|
*/
|
||||||
class AssetsService extends BaseService {
|
class AssetsService extends BaseService {
|
||||||
/**
|
/**
|
||||||
* Create asset with MIME type validation
|
* Create asset with MIME type validation and video pre-processing
|
||||||
*/
|
*/
|
||||||
static async create(data, currentUser) {
|
static async create(data, currentUser) {
|
||||||
// Validate asset_type and mime_type match
|
// Validate asset_type and mime_type match
|
||||||
@ -85,7 +89,99 @@ class AssetsService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Call parent create
|
// Call parent create
|
||||||
return super.create(data, currentUser);
|
const asset = await super.create(data, currentUser);
|
||||||
|
|
||||||
|
// Pre-generate reversed video for video assets (async, doesn't block response)
|
||||||
|
if (assetType === 'video' && data.storage_key) {
|
||||||
|
AssetsService.preGenerateReversedVideo(asset, currentUser).catch((err) => {
|
||||||
|
logger.error(
|
||||||
|
{ err, assetId: asset.id },
|
||||||
|
'Failed to pre-generate reversed video (non-blocking)',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return asset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pre-generate reversed video variant for a video asset.
|
||||||
|
* Runs asynchronously after asset creation - doesn't block the upload response.
|
||||||
|
* This ensures reversed videos are ready for instant use in transitions.
|
||||||
|
*
|
||||||
|
* @param {Object} asset - Created asset record
|
||||||
|
* @param {Object} currentUser - Current user context
|
||||||
|
*/
|
||||||
|
static async preGenerateReversedVideo(asset, currentUser) {
|
||||||
|
const log = logger.child({
|
||||||
|
assetId: asset.id,
|
||||||
|
operation: 'preGenerateReversed',
|
||||||
|
});
|
||||||
|
|
||||||
|
log.info('Starting pre-generation of reversed video');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check if FFmpeg is available
|
||||||
|
const ffmpegAvailable = await videoProcessing.isFFmpegAvailable();
|
||||||
|
if (!ffmpegAvailable) {
|
||||||
|
log.warn('FFmpeg not available, skipping pre-generation');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if reversed variant already exists (shouldn't happen on create, but safety check)
|
||||||
|
const existingAsset = await AssetsDBApi.findBy({ id: asset.id });
|
||||||
|
const variants = existingAsset?.asset_variants_asset || [];
|
||||||
|
const existingReversed = variants.find((v) => v.variant_type === 'reversed');
|
||||||
|
|
||||||
|
if (existingReversed) {
|
||||||
|
log.debug('Reversed variant already exists');
|
||||||
|
return existingReversed.storage_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download original video to buffer
|
||||||
|
const storageKey = asset.storage_key;
|
||||||
|
log.info({ storageKey }, 'Downloading original video');
|
||||||
|
|
||||||
|
const originalBuffer = await downloadToBuffer(storageKey);
|
||||||
|
|
||||||
|
// Generate reversed video
|
||||||
|
log.info('Generating reversed video with FFmpeg');
|
||||||
|
const reversedBuffer = await videoProcessing.reverseVideo(
|
||||||
|
originalBuffer,
|
||||||
|
asset.original_file_name || 'video.mp4',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Upload reversed video to storage
|
||||||
|
const reversedKey = `assets/${asset.id}/reversed.mp4`;
|
||||||
|
log.info({ reversedKey }, 'Uploading reversed video');
|
||||||
|
|
||||||
|
const result = await uploadBuffer(reversedKey, reversedBuffer, {
|
||||||
|
contentType: 'video/mp4',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create variant record
|
||||||
|
await Asset_variantsDBApi.create(
|
||||||
|
{
|
||||||
|
assetId: asset.id,
|
||||||
|
variant_type: 'reversed',
|
||||||
|
cdn_url: result.url,
|
||||||
|
storage_key: reversedKey,
|
||||||
|
size_mb: reversedBuffer.length / (1024 * 1024),
|
||||||
|
},
|
||||||
|
{ currentUser },
|
||||||
|
);
|
||||||
|
|
||||||
|
log.info(
|
||||||
|
{ reversedKey, sizeMb: (reversedBuffer.length / (1024 * 1024)).toFixed(2) },
|
||||||
|
'Pre-generated reversed video successfully',
|
||||||
|
);
|
||||||
|
|
||||||
|
return reversedKey;
|
||||||
|
} catch (err) {
|
||||||
|
log.error({ err }, 'Failed to pre-generate reversed video');
|
||||||
|
// Don't throw - this is a background operation
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user