Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Sep 13, 2025

This PR implements a comprehensive Remotion-powered captioning service that provides state-of-the-art video captioning capabilities for all videos stored in Vinci Clips. The new service is accessible directly from the homepage and offers advanced animations and effects using Remotion technology.

Key Features

🎬 New Captioning Service

  • Remotion Integration: Built React-based video compositions with advanced caption rendering
  • Multiple Caption Styles: 5 predefined styles (Modern, Dynamic, Elegant, Playful, Minimalist) with unique animations
  • Homepage Access: New "AI Captions Studio" button prominently displayed on the homepage
  • Video Management: Complete interface to view and select from all available videos

🔧 Backend Infrastructure

  • New API Routes: /clips/remotion-captions/* endpoints for video listing, style management, and generation
  • TypeScript Support: Full TypeScript setup for Remotion compositions with React components
  • Word-level Processing: Converts segment-level transcripts to precise word-level timing
  • Scalable Architecture: Non-blocking service alongside existing FFmpeg captioning system

🎨 Frontend Interface

  • Dedicated Page: New /captions route with intuitive captioning workflow
  • Interactive Components: Video selector with thumbnails, style previews, and generation controls
  • Real-time Feedback: Loading states, error handling, and progress tracking
  • Responsive Design: Mobile-friendly interface with consistent design language

📱 Remotion Compositions

  • Social Media Optimized: 9:16 aspect ratio perfect for TikTok, Instagram Reels, and YouTube Shorts
  • Advanced Animations: Spring-based transitions, word-by-word reveals, and style-specific effects
  • Professional Quality: Multiple font styles, colors, shadows, and positioning options

Technical Implementation

The service leverages Remotion's powerful React-based video generation capabilities to create professional captions with:

// Example of the dynamic caption style with animations
const AnimatedText = ({ text, frame }) => {
  const words = text.split(' ');
  return words.map((word, index) => {
    const progress = spring({ frame: frame - (index * 3) });
    const scale = interpolate(progress, [0, 1], [0.8, 1]);
    return <span style={{ transform: `scale(${scale})` }}>{word}</span>;
  });
};

User Experience

  1. Discovery: Users can access the new captioning service directly from the homepage
  2. Selection: Browse all available videos with thumbnails and metadata
  3. Customization: Choose from 5 different caption styles with visual previews
  4. Generation: One-click generation with progress tracking
  5. Preview: Built-in video player to preview results before download

Screenshots

Updated Homepage

Homepage with AI Captions Studio button

Remotion Captions Interface

Captioning service interface showing video selection and style options

Configuration Updates

  • Removed automatic homepage redirect to showcase the new service
  • Added environment variable support for proper API connections
  • Updated homepage features to highlight Remotion capabilities
  • Integrated new service into existing navigation structure

The new Remotion Captions service provides a professional-grade captioning solution that complements the existing FFmpeg system, offering users advanced animation capabilities and superior visual quality for their video content.

Fixes #46.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • fonts.googleapis.com
    • Triggering command: next-server (v15.3.5) (dns block)
  • https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-cpu-linux-x86_64-2.9.1.tar.gz
    • Triggering command: node scripts/install.js (http block)

If you need me to access, download, or install something from one of these locations, you can either:


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Co-authored-by: sdntsng <19806109+sdntsng@users.noreply.github.com>
Comment on lines +54 to +170
router.post('/generate', async (req, res) => {
try {
const {
transcriptId,
style = 'modern',
startTime,
endTime,
captionSettings = {}
} = req.body;

if (!transcriptId) {
return res.status(400).json({
success: false,
error: 'Transcript ID is required'
});
}

// Get transcript data
const transcript = await Transcript.findById(transcriptId);
if (!transcript) {
return res.status(404).json({
success: false,
error: 'Transcript not found'
});
}

if (transcript.status !== 'completed') {
return res.status(400).json({
success: false,
error: 'Transcript not ready for caption generation'
});
}

// Prepare caption data from transcript
let words = [];
if (transcript.transcript && Array.isArray(transcript.transcript)) {
words = convertTranscriptToWords(transcript.transcript, startTime, endTime);
}

if (words.length === 0) {
return res.status(400).json({
success: false,
error: 'No caption data found in specified range'
});
}

// Create Remotion composition input data
const compositionInput = {
videoUrl: transcript.videoUrl,
words: words,
style: style,
settings: {
fontSize: captionSettings.fontSize || 48,
fontFamily: captionSettings.fontFamily || 'Arial',
color: captionSettings.color || '#FFFFFF',
backgroundColor: captionSettings.backgroundColor || 'rgba(0,0,0,0.7)',
position: captionSettings.position || 'bottom',
...captionSettings
},
duration: calculateDuration(words),
startTime: startTime || 0,
endTime: endTime || transcript.duration
};

// Bundle and render the Remotion composition
const bundleLocation = await bundle({
entryPoint: path.join(__dirname, '../remotion/composition.tsx'),
webpackOverride: (config) => config,
});

const compositions = await getCompositions(bundleLocation, {
inputProps: compositionInput,
});

const composition = compositions.find((c) => c.id === 'CaptionedVideo');
if (!composition) {
throw new Error('Captioned video composition not found');
}

// Create output directory
const outputDir = path.join(__dirname, '../../uploads/remotion-captions');
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}

const outputFileName = `${transcriptId}_remotion_${style}_${Date.now()}.mp4`;
const outputPath = path.join(outputDir, outputFileName);

// Render the video
await renderMedia({
composition,
serveUrl: bundleLocation,
codec: 'h264',
outputLocation: outputPath,
inputProps: compositionInput,
});

const captionedVideoUrl = `/uploads/remotion-captions/${outputFileName}`;

res.json({
success: true,
captionedVideoUrl,
style,
wordCount: words.length,
outputPath,
message: 'Remotion captioned video generated successfully'
});

} catch (error) {
console.error('Remotion caption generation error:', error);
res.status(500).json({
success: false,
error: 'Failed to generate Remotion captioned video',
details: error.message
});
}
});

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
a file system access
, but is not rate-limited.
This route handler performs
a file system access
, but is not rate-limited.

Copilot Autofix

AI 4 months ago

The best way to fix the problem is to apply a rate-limiting middleware to the /generate POST route (or, optionally, to all routes in this file/router). The most standard and robust approach is to use the express-rate-limit NPM package. The fix involves:

  1. Importing express-rate-limit at the top of the file.
  2. Creating a rate limiter configuration suitable for expensive operations (e.g., allowing, say, 5 requests per minute per IP to /generate).
  3. Applying the middleware specifically to the /generate route using Express' syntax: router.post('/generate', limiter, async (req, res) => { ... });
    Only the affected file, backend/src/routes/remotion-captions.js, will need to be modified:
  • Add import for express-rate-limit after existing imports.
  • Add the limiter configuration (for /generate; you may name it e.g. generateLimiter).
  • Edit the /generate route to include the rate limiting middleware before the handler.

Suggested changeset 2
backend/src/routes/remotion-captions.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/backend/src/routes/remotion-captions.js b/backend/src/routes/remotion-captions.js
--- a/backend/src/routes/remotion-captions.js
+++ b/backend/src/routes/remotion-captions.js
@@ -5,6 +5,7 @@
 const fs = require('fs');
 const { bundle } = require('@remotion/bundler');
 const { renderMedia, getCompositions } = require('@remotion/renderer');
+const rateLimit = require('express-rate-limit');
 
 /**
  * Get all available videos for captioning
@@ -51,7 +52,18 @@
  * Generate captioned video using Remotion
  * POST /clips/remotion-captions/generate
  */
-router.post('/generate', async (req, res) => {
+
+// Rate limiter for generate endpoint: max 5 requests per minute per IP
+const generateLimiter = rateLimit({
+    windowMs: 60 * 1000, // 1 minute
+    max: 5, // limit each IP to 5 requests per windowMs
+    message: {
+        success: false,
+        error: 'Too many requests, please try again after a minute.'
+    }
+});
+
+router.post('/generate', generateLimiter, async (req, res) => {
     try {
         const { 
             transcriptId, 
EOF
@@ -5,6 +5,7 @@
const fs = require('fs');
const { bundle } = require('@remotion/bundler');
const { renderMedia, getCompositions } = require('@remotion/renderer');
const rateLimit = require('express-rate-limit');

/**
* Get all available videos for captioning
@@ -51,7 +52,18 @@
* Generate captioned video using Remotion
* POST /clips/remotion-captions/generate
*/
router.post('/generate', async (req, res) => {

// Rate limiter for generate endpoint: max 5 requests per minute per IP
const generateLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 5, // limit each IP to 5 requests per windowMs
message: {
success: false,
error: 'Too many requests, please try again after a minute.'
}
});

router.post('/generate', generateLimiter, async (req, res) => {
try {
const {
transcriptId,
backend/package.json
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/backend/package.json b/backend/package.json
--- a/backend/package.json
+++ b/backend/package.json
@@ -31,7 +31,8 @@
     "typescript": "^5.9.2",
     "uuid": "^9.0.1",
     "winston": "^3.17.0",
-    "winston-daily-rotate-file": "^5.0.0"
+    "winston-daily-rotate-file": "^5.0.0",
+    "express-rate-limit": "^8.1.0"
   },
   "devDependencies": {
     "jest": "^29.6.2",
EOF
@@ -31,7 +31,8 @@
"typescript": "^5.9.2",
"uuid": "^9.0.1",
"winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0"
"winston-daily-rotate-file": "^5.0.0",
"express-rate-limit": "^8.1.0"
},
"devDependencies": {
"jest": "^29.6.2",
This fix introduces these dependencies
Package Version Security advisories
express-rate-limit (npm) 8.1.0 None
Copilot is powered by AI and may make mistakes. Always verify output.
Copilot AI changed the title [WIP] [FEATURE] Captioning Service: Build Remotion-powered Captioning for All Vinci Clips Videos Implement Remotion-powered Captioning Service for All Vinci Clips Videos Sep 14, 2025
Copilot AI requested a review from sdntsng September 14, 2025 00:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Captioning Service: Build Remotion-powered Captioning for All Vinci Clips Videos

2 participants