├── README.md ├── backend ├── .DS_Store ├── .env.example ├── .gitignore ├── LICENSE ├── LOGS.md ├── README.md ├── api │ └── server.js ├── config │ └── supabase.config.js ├── curl-test-multiple.sh ├── curl-test.sh ├── index.js ├── libs │ ├── supabase │ │ └── storage.js │ └── utils.js ├── out │ └── .DS_Store ├── package-lock.json ├── package.json ├── public │ ├── audio.mp3 │ ├── cover.jpg │ ├── video.mp4 │ └── video2.mp4 └── src │ ├── AudioTrack.jsx │ ├── SequentialVideo.jsx │ ├── SplitScreenVideo.jsx │ ├── fileServer.js │ ├── generateDynamicVideo.js │ └── videoGeneration.js └── frontend ├── .env.example ├── .gitignore ├── README.md ├── bun.lockb ├── components.json ├── eslint.config.js ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── favicon.ico ├── og-image.png └── placeholder.svg ├── scripts ├── README.md ├── SELF_HOSTING.md └── supabase-export.js ├── src ├── App.css ├── App.tsx ├── components │ ├── AudioSelector.tsx │ ├── AudioUploadDialog.tsx │ ├── AuthDialog.tsx │ ├── AvatarGrid.tsx │ ├── ContentGenerator.tsx │ ├── ContentPreview.tsx │ ├── DemoGrid.tsx │ ├── DemoUploader.tsx │ ├── HookInput.tsx │ ├── MobileWarningModal.tsx │ ├── Navbar.tsx │ ├── NavbarWrapper.tsx │ ├── ProtectedRoute.tsx │ ├── SelfHostBanner.tsx │ ├── UserMenu.tsx │ ├── VideoCard.tsx │ ├── VideoGrid.tsx │ ├── VideoUploadDialog.tsx │ ├── carousel │ │ ├── CarouselEditor.tsx │ │ ├── CarouselMaker.tsx │ │ ├── CarouselPreview.tsx │ │ ├── SlideManager.tsx │ │ └── types.ts │ ├── layout │ │ ├── Footer.tsx │ │ └── MainNav.tsx │ └── ui │ │ ├── accordion.tsx │ │ ├── alert-dialog.tsx │ │ ├── alert.tsx │ │ ├── aspect-ratio.tsx │ │ ├── avatar.tsx │ │ ├── badge.tsx │ │ ├── breadcrumb.tsx │ │ ├── button.tsx │ │ ├── calendar.tsx │ │ ├── card.tsx │ │ ├── carousel.tsx │ │ ├── chart.tsx │ │ ├── checkbox.tsx │ │ ├── collapsible.tsx │ │ ├── command.tsx │ │ ├── context-menu.tsx │ │ ├── dialog.tsx │ │ ├── drawer.tsx │ │ ├── dropdown-menu.tsx │ │ ├── form.tsx │ │ ├── hover-card.tsx │ │ ├── input-otp.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── menubar.tsx │ │ ├── navigation-menu.tsx │ │ ├── pagination.tsx │ │ ├── popover.tsx │ │ ├── progress.tsx │ │ ├── radio-group.tsx │ │ ├── resizable.tsx │ │ ├── scroll-area.tsx │ │ ├── select.tsx │ │ ├── separator.tsx │ │ ├── sheet.tsx │ │ ├── sidebar.tsx │ │ ├── skeleton.tsx │ │ ├── slider.tsx │ │ ├── sonner.tsx │ │ ├── switch.tsx │ │ ├── table.tsx │ │ ├── tabs.tsx │ │ ├── textarea.tsx │ │ ├── toast.tsx │ │ ├── toaster.tsx │ │ ├── toggle-group.tsx │ │ ├── toggle.tsx │ │ ├── tooltip.tsx │ │ └── use-toast.ts ├── config │ └── index.ts ├── contexts │ └── AuthContext.tsx ├── hooks │ ├── use-mobile.tsx │ ├── use-toast.ts │ └── useVideos.ts ├── index.css ├── integrations │ └── supabase │ │ ├── client.ts │ │ └── types.ts ├── lib │ └── utils.ts ├── main.tsx ├── pages │ ├── Carousels.tsx │ ├── Dashboard.tsx │ ├── Index.tsx │ ├── Landing.tsx │ ├── NotFound.tsx │ ├── SelfHost.tsx │ └── Videos.tsx ├── services │ └── templateService.ts └── vite-env.d.ts ├── supabase └── config.toml ├── tailwind.config.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json ├── vercel.json └── vite.config.ts /backend/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rish9600/viralaiugc/15e136dcc5f97888f3236cc0df259c856bb60461/backend/.DS_Store -------------------------------------------------------------------------------- /backend/.env.example: -------------------------------------------------------------------------------- 1 | # App 2 | PORT=3000 3 | RENDER_CONCURRENCY=5 4 | 5 | # Supabase 6 | SUPABASE_URL= 7 | SUPABASE_KEY= 8 | SUPABASE_STORAGE_BUCKET=generated-videos 9 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ 3 | .env -------------------------------------------------------------------------------- /backend/LOGS.md: -------------------------------------------------------------------------------- 1 | # Monitoring Logs 2 | 3 | ## 1. Direct Pod Logs (Simplest Method) 4 | 5 | For basic log viewing: 6 | 7 | ```bash 8 | # Follow logs from all pods in the deployment 9 | kubectl logs -f deployment/remotion-video-generator -n remotion 10 | 11 | # View logs from a specific pod 12 | kubectl get pods -n remotion 13 | kubectl logs -f pod-name-xyz -n remotion 14 | ``` 15 | 16 | This is good for quick debugging but doesn't persist logs if pods restart. 17 | 18 | ## 2. Deploy a Log Dashboard with Digital Ocean Monitoring 19 | 20 | Digital Ocean's built-in monitoring can be enabled for your Kubernetes cluster: 21 | 22 | 1. Go to your Kubernetes cluster in the Digital Ocean dashboard 23 | 2. Click on "Insights" tab 24 | 3. Enable "Monitoring" if not already enabled 25 | 4. Access the Kubernetes dashboard to view logs and metrics 26 | 27 | This provides a simple dashboard with basic log viewing capabilities. 28 | 29 | ## 3. Set Up Centralized Logging with EFK Stack 30 | 31 | For a more robust logging solution, you can deploy the EFK (Elasticsearch, Fluentd, Kibana) stack: 32 | 33 | ```bash 34 | # Add the Elastic Helm repository 35 | helm repo add elastic https://helm.elastic.co 36 | helm repo update 37 | 38 | # Create a namespace for logging 39 | kubectl create namespace logging 40 | 41 | # Install Elasticsearch 42 | helm install elasticsearch elastic/elasticsearch --namespace logging 43 | 44 | # Install Fluentd to collect logs 45 | kubectl apply -f https://raw.githubusercontent.com/fluent/fluentd-kubernetes-daemonset/master/fluentd-daemonset-elasticsearch-rbac.yaml 46 | 47 | # Install Kibana for log visualization 48 | helm install kibana elastic/kibana --namespace logging 49 | ``` 50 | 51 | Then access Kibana by port-forwarding: 52 | 53 | ```bash 54 | kubectl port-forward svc/kibana-kibana 5601:5601 -n logging 55 | ``` 56 | 57 | Visit http://localhost:5601 in your browser to access the Kibana dashboard. 58 | 59 | ## 4. Use Managed Logging Service (Recommended for Production) 60 | 61 | For a professional, zero-maintenance solution: 62 | 63 | 1. **Papertrail**: 64 | 65 | ```bash 66 | # Create a Kubernetes Secret with your Papertrail host and port 67 | kubectl create secret generic papertrail-destination --from-literal=host=logsX.papertrailapp.com --from-literal=port=XXXXX -n remotion 68 | 69 | # Deploy the Papertrail log forwarder 70 | kubectl apply -f https://github.com/papertrail/papertrail-kubernetes/raw/master/papertrail.yml -n remotion 71 | ``` 72 | 73 | 2. **Datadog**: 74 | 75 | ```bash 76 | # Add Datadog Helm repository 77 | helm repo add datadog https://helm.datadoghq.com 78 | helm repo update 79 | 80 | # Install Datadog with API key 81 | helm install datadog --set datadog.apiKey=YOUR_API_KEY datadog/datadog 82 | ``` 83 | 84 | 3. **New Relic**: 85 | 86 | ```bash 87 | # Add New Relic Helm repository 88 | helm repo add newrelic https://helm-charts.newrelic.com 89 | helm repo update 90 | 91 | # Install New Relic with license key 92 | helm install newrelic-bundle newrelic/nri-bundle \ 93 | --set global.licenseKey=YOUR_LICENSE_KEY \ 94 | --set global.cluster=remotion-cluster 95 | ``` 96 | 97 | ## 5. Add Log Forwarding to Your Deployment 98 | 99 | You can modify your application to forward logs to an external service: 100 | 101 | ```yaml 102 | # Add to your deployment.yaml under containers 103 | - name: fluent-bit 104 | image: fluent/fluent-bit:1.9 105 | volumeMounts: 106 | - name: log-volume 107 | mountPath: /var/log 108 | - name: fluent-bit-config 109 | mountPath: /fluent-bit/etc/ 110 | volumes: 111 | - name: log-volume 112 | emptyDir: {} 113 | - name: fluent-bit-config 114 | configMap: 115 | name: fluent-bit-config 116 | ``` 117 | 118 | Then create a ConfigMap with Fluent Bit configuration pointing to your log service. 119 | 120 | ## 6. Use Digital Ocean Managed Database for Audit Logs 121 | 122 | If you want to store logs for compliance or auditing: 123 | 124 | 1. Create a Digital Ocean Managed Postgres Database 125 | 2. Add a logging middleware to your application that writes important events to this database 126 | 3. Create a simple dashboard to query these logs 127 | 128 | ## Setting Up Log Alerts 129 | 130 | For any of these solutions, you can set up alerts for critical events: 131 | 132 | ```bash 133 | # Example: Set up alert for pod restarts or failures using kubectl 134 | kubectl create -f - < 5 146 | for: 10m 147 | labels: 148 | severity: warning 149 | annotations: 150 | summary: "Pod is restarting frequently" 151 | description: "Pod {{$labels.pod}} in namespace {{$labels.namespace}} is restarting frequently" 152 | EOF 153 | ``` 154 | 155 | For your Remotion video generator, I recommend starting with the basic kubectl logs for development, and then implementing either the EFK stack or a managed logging service like Papertrail or Datadog for production use. This will give you both real-time log visibility and historical log analysis capabilities. 156 | -------------------------------------------------------------------------------- /backend/README.md: -------------------------------------------------------------------------------- 1 | # Local Development Setup Guide 2 | 3 | This guide will help you set up and run the Remotion Video API locally on your machine. 4 | 5 | ## Prerequisites 6 | 7 | 1. Node.js (v18 or higher) 8 | 2. FFmpeg (for video processing) 9 | 3. Git (for cloning the repository) 10 | 11 | ## Step 1: Install FFmpeg 12 | 13 | ### macOS 14 | ```bash 15 | brew install ffmpeg 16 | ``` 17 | 18 | ### Ubuntu/Debian 19 | ```bash 20 | sudo apt update 21 | sudo apt install ffmpeg 22 | ``` 23 | 24 | ### Windows 25 | Download FFmpeg from the official website: https://ffmpeg.org/download.html 26 | 27 | ## Step 2: Clone the Repository 28 | 29 | ```bash 30 | git clone 31 | cd remotion-video-api 32 | ``` 33 | 34 | ## Step 3: Install Dependencies 35 | 36 | ```bash 37 | npm install 38 | ``` 39 | 40 | ## Step 4: Environment Setup 41 | 42 | 1. Create a `.env` file in the root directory: 43 | ```bash 44 | touch .env 45 | ``` 46 | 47 | 2. Add the following environment variables to your `.env` file: 48 | ``` 49 | # App 50 | PORT=3000 51 | RENDER_CONCURRENCY=5 52 | 53 | # Supabase 54 | SUPABASE_URL= 55 | SUPABASE_KEY= 56 | SUPABASE_STORAGE_BUCKET= 57 | ``` 58 | 59 | Replace the placeholder values with your actual Supabase credentials. 60 | 61 | ## Step 5: Start the Server 62 | 63 | ```bash 64 | npm start 65 | ``` 66 | 67 | The server will start on: 68 | - Main API server: http://localhost:3000 69 | - File server: http://localhost:8787 70 | 71 | ## Step 6: Verify Installation 72 | 73 | 1. Check if the server is running by visiting http://localhost:3000/health in your browser 74 | 2. You should see a "Service is healthy" message 75 | 76 | ## Common Issues and Solutions 77 | 78 | 1. **Port Already in Use** 79 | If you see the error `EADDRINUSE: address already in use :::3000`, you can kill the existing process: 80 | ```bash 81 | lsof -i :3000 | grep LISTEN | awk '{print $2}' | xargs kill -9 82 | ``` 83 | 84 | 2. **FFmpeg Not Found** 85 | If you see errors related to FFmpeg not being found, make sure it's properly installed and available in your system's PATH. 86 | 87 | 3. **Missing Dependencies** 88 | If you encounter any missing dependency errors, run: 89 | ```bash 90 | npm install 91 | ``` 92 | 93 | ## Development Tips 94 | 95 | 1. The server automatically restarts when you make changes to the code 96 | 2. Check the console output for any errors or warnings 97 | 3. The file server at port 8787 is used for serving temporary files during video processing 98 | 99 | ## API Endpoints 100 | 101 | The server provides the following main endpoints: 102 | 103 | - `POST /api/generate-video`: Generate a new video 104 | - `GET /api/status/:videoId`: Check the status of a video generation 105 | - `GET /health`: Health check endpoint 106 | 107 | For detailed API documentation, refer to the API documentation in the project. -------------------------------------------------------------------------------- /backend/config/supabase.config.js: -------------------------------------------------------------------------------- 1 | // Load environment variables from .env file 2 | require("dotenv").config(); 3 | 4 | const { createClient } = require("@supabase/supabase-js"); 5 | 6 | const supabaseUrl = process.env.SUPABASE_URL; 7 | const supabaseKey = process.env.SUPABASE_KEY; 8 | 9 | // Add validation 10 | if (!supabaseUrl || !supabaseKey) { 11 | console.error("Missing Supabase credentials:"); 12 | console.error("SUPABASE_URL:", supabaseUrl ? "Set" : "Missing"); 13 | console.error("SUPABASE_KEY:", supabaseKey ? "Set" : "Missing"); 14 | console.error("Please check your .env file or environment variables."); 15 | process.exit(1); // Exit with error 16 | } 17 | 18 | const supabase = createClient(supabaseUrl, supabaseKey); 19 | 20 | module.exports = supabase; 21 | -------------------------------------------------------------------------------- /backend/curl-test-multiple.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Test rendering a video with direct URLs 4 | echo "Rendering video with direct URL sources..." 5 | curl -X POST http://localhost:3000/render-video \ 6 | -H "Content-Type: application/json" \ 7 | -d '{ 8 | "durationInSeconds": 10, 9 | "videoSourceUrl": "https://rbpvjxsqghhirdixhaay.supabase.co/storage/v1/object/public/ai_ugc//0fvz63mttsrme0cn8svvrh4m2g.mp4", 10 | "demoVideoSourceUrl": "https://rbpvjxsqghhirdixhaay.supabase.co/storage/v1/object/public/ai_ugc//tmpfwk9oqq4.output.mp4", 11 | "titleText": "VIDEO FROM DIRECT URL WITH SPLIT SCREEN", 12 | "textPosition": "center", 13 | "enableAudio": false, 14 | "splitScreen": true, 15 | "splitPosition": "bottom-top" 16 | }' 17 | 18 | sleep 2 19 | 20 | # Test rendering a video with direct URLs 21 | echo "Rendering video with direct URL sources..." 22 | curl -X POST http://localhost:3000/render-video \ 23 | -H "Content-Type: application/json" \ 24 | -d '{ 25 | "durationInSeconds": 10, 26 | "videoSourceUrl": "https://rbpvjxsqghhirdixhaay.supabase.co/storage/v1/object/public/ai_ugc//0fvz63mttsrme0cn8svvrh4m2g.mp4", 27 | "titleText": "VIDEO FROM DIRECT URL", 28 | "textPosition": "top", 29 | "enableAudio": false, 30 | "splitScreen": false 31 | }' 32 | 33 | echo -e "\n\nNow try viewing the videos at the URLs shown above." -------------------------------------------------------------------------------- /backend/curl-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Test rendering a video with direct URLs 4 | echo "Rendering video with direct URL sources..." 5 | curl -X POST http://localhost:3000/render-video \ 6 | -H "Content-Type: application/json" \ 7 | -d '{ 8 | "videoSourceUrl": "https://rbpvjxsqghhirdixhaay.supabase.co/storage/v1/object/public/ai_ugc/dev/test.mp4", 9 | "demoVideoSourceUrl": "https://rbpvjxsqghhirdixhaay.supabase.co/storage/v1/object/public/ai_ugc/0fvz63mttsrme0cn8svvrh4m2g.mp4", 10 | "audioSourceUrl": "https://rbpvjxsqghhirdixhaay.supabase.co/storage/v1/object/public/ai_ugc/dev/audio.mp3", 11 | "titleText": "VIDEO FROM DIRECT URL WITH SPLIT SCREEN", 12 | "textPosition": "bottom", 13 | "audioOffsetInSeconds": 0, 14 | "enableAudio": true, 15 | "splitScreen": true, 16 | "splitPosition": "left-right" 17 | }' 18 | 19 | echo -e "\n\nNow try viewing the video at the URL shown above." -------------------------------------------------------------------------------- /backend/libs/supabase/storage.js: -------------------------------------------------------------------------------- 1 | // CommonJS Version 2 | const fs = require("fs"); 3 | const supabase = require("../../config/supabase.config"); 4 | 5 | const STORAGE_BUCKET = 6 | process.env.SUPABASE_STORAGE_BUCKET || "generated-videos"; 7 | 8 | // Function to upload video to Supabase storage 9 | async function uploadToSupabase(filePath, fileName) { 10 | try { 11 | // Read the file as a buffer 12 | const fileBuffer = fs.readFileSync(filePath); 13 | 14 | // Upload to Supabase storage 15 | const { error } = await supabase.storage 16 | .from(STORAGE_BUCKET) 17 | .upload(fileName, fileBuffer, { 18 | contentType: "video/mp4", 19 | upsert: true, // Overwrite if the file already exists 20 | }); 21 | 22 | if (error) { 23 | throw error; 24 | } 25 | 26 | // Get the public URL of the uploaded file 27 | const { data: urlData } = supabase.storage 28 | .from(STORAGE_BUCKET) 29 | .getPublicUrl(fileName); 30 | 31 | return urlData.publicUrl; 32 | } catch (error) { 33 | console.error("Error uploading to Supabase:", error); 34 | throw error; 35 | } 36 | } 37 | 38 | module.exports = { 39 | uploadToSupabase, 40 | }; 41 | -------------------------------------------------------------------------------- /backend/libs/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Gets the duration of a video file using ffprobe 3 | * @param {string} videoUrl URL of the video 4 | * @returns {Promise} Duration in seconds or null if it cannot be determined 5 | */ 6 | async function getVideoDuration(videoUrl, execPromise) { 7 | if (!videoUrl) return null; 8 | 9 | try { 10 | const ffprobeCommand = `ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "${videoUrl}"`; 11 | const { stdout } = await execPromise(ffprobeCommand); 12 | 13 | const duration = parseFloat(stdout.trim()); 14 | if (isNaN(duration)) { 15 | console.warn(`Could not parse video duration from: ${stdout}`); 16 | return null; 17 | } 18 | 19 | const roundedDuration = Math.floor(duration); 20 | 21 | console.log( 22 | `Detected video duration: ${duration} > ${roundedDuration} secs for ${videoUrl}` 23 | ); 24 | return roundedDuration; 25 | } catch (err) { 26 | console.warn(`Could not detect video duration: ${err.message}`); 27 | return null; 28 | } 29 | } 30 | 31 | module.exports = getVideoDuration; 32 | -------------------------------------------------------------------------------- /backend/out/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rish9600/viralaiugc/15e136dcc5f97888f3236cc0df259c856bb60461/backend/out/.DS_Store -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "remotion-video-api", 3 | "version": "1.0.0", 4 | "description": "Remotion video generation API", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js", 8 | "build": "remotion build src/index.jsx", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "keywords": [ 12 | "remotion", 13 | "video", 14 | "api" 15 | ], 16 | "author": "", 17 | "license": "ISC", 18 | "dependencies": { 19 | "@ffmpeg-installer/ffmpeg": "^1.1.0", 20 | "@remotion/bundler": "^4.0.0", 21 | "@remotion/cli": "^4.0.0", 22 | "@remotion/renderer": "^4.0.0", 23 | "@supabase/supabase-js": "^2.49.1", 24 | "body-parser": "^1.20.2", 25 | "cors": "^2.8.5", 26 | "dotenv": "^16.4.7", 27 | "express": "^4.18.2", 28 | "fluent-ffmpeg": "^2.1.3", 29 | "remotion": "^4.0.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /backend/public/audio.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rish9600/viralaiugc/15e136dcc5f97888f3236cc0df259c856bb60461/backend/public/audio.mp3 -------------------------------------------------------------------------------- /backend/public/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rish9600/viralaiugc/15e136dcc5f97888f3236cc0df259c856bb60461/backend/public/cover.jpg -------------------------------------------------------------------------------- /backend/public/video.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rish9600/viralaiugc/15e136dcc5f97888f3236cc0df259c856bb60461/backend/public/video.mp4 -------------------------------------------------------------------------------- /backend/public/video2.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rish9600/viralaiugc/15e136dcc5f97888f3236cc0df259c856bb60461/backend/public/video2.mp4 -------------------------------------------------------------------------------- /backend/src/AudioTrack.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Audio, useVideoConfig } from "remotion"; 3 | 4 | // TODO: Use enableAudio or pass a param externalAudioOnly to make the volume 100% 5 | export const AudioTrack = ({ enableAudio, audioSource, offsetInSeconds }) => { 6 | const { fps } = useVideoConfig(); 7 | 8 | // Convert offset to frames 9 | const offsetInFrames = Math.round(offsetInSeconds * fps); 10 | 11 | return ( 12 |