├── .gitignore ├── .yarnrc.yml ├── README.md ├── app.config.ts ├── app.vue ├── components ├── Clicker.vue ├── Demo.vue ├── Hero.vue └── Uploader.vue ├── nuxt.config.ts ├── package.json ├── pages └── index.vue ├── prettier.config.cjs ├── public ├── cover.jpg ├── cross-icon.png ├── demo │ ├── bees.mp4 │ ├── dog.mp4 │ ├── fountain.mp4 │ └── jellyfish.mp4 ├── favicon.png ├── logo.png └── star-icon.png ├── server ├── api │ ├── file.post.js │ ├── prediction.get.js │ └── prediction.post.js └── tsconfig.json ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Nuxt dev/build outputs 2 | .output 3 | .data 4 | .nuxt 5 | .nitro 6 | .cache 7 | dist 8 | 9 | # Node dependencies 10 | node_modules 11 | 12 | # Logs 13 | logs 14 | *.log 15 | 16 | # Misc 17 | .DS_Store 18 | .fleet 19 | .idea 20 | .yarn 21 | 22 | # Local env files 23 | .env 24 | .env.* 25 | !.env.example 26 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Green Screen Creator 2 | 3 | Track objects in videos and add a green screen to the background. 4 | 5 | Powered by Meta's [Segment Anything Model (SAM)](https://replicate.com/meta/sam-2-video) on Replicate. 6 | 7 | ## Setup 8 | 9 | Make sure to install the dependencies: 10 | 11 | ```bash 12 | # npm 13 | npm install 14 | 15 | # pnpm 16 | pnpm install 17 | 18 | # yarn 19 | yarn install 20 | 21 | # bun 22 | bun install 23 | ``` 24 | 25 | ## Development Server 26 | 27 | Start the development server on `http://localhost:3000`: 28 | 29 | ```bash 30 | # npm 31 | npm run dev 32 | 33 | # pnpm 34 | pnpm run dev 35 | 36 | # yarn 37 | yarn dev 38 | 39 | # bun 40 | bun run dev 41 | ``` 42 | -------------------------------------------------------------------------------- /app.config.ts: -------------------------------------------------------------------------------- 1 | export default defineAppConfig({ 2 | ui: { 3 | primary: 'green', 4 | container: { 5 | constrained: 'max-w-5xl' 6 | } 7 | } 8 | }) 9 | -------------------------------------------------------------------------------- /app.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /components/Clicker.vue: -------------------------------------------------------------------------------- 1 | 61 | 62 | 293 | 294 | 302 | -------------------------------------------------------------------------------- /components/Demo.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /components/Hero.vue: -------------------------------------------------------------------------------- 1 | 95 | 96 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /components/Uploader.vue: -------------------------------------------------------------------------------- 1 | 116 | 117 | 559 | 560 | 584 | -------------------------------------------------------------------------------- /nuxt.config.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtConfig({ 2 | compatibilityDate: '2024-04-03', 3 | runtimeConfig: { 4 | public: {} 5 | }, 6 | devtools: { enabled: false }, 7 | ssr: false, 8 | nitro: { 9 | preset: 'vercel', 10 | routeRules: { 11 | '/**': { 12 | headers: { 13 | 'Cross-Origin-Embedder-Policy': 'require-corp', 14 | 'Cross-Origin-Opener-Policy': 'same-origin' 15 | } 16 | } 17 | } 18 | }, 19 | sourcemap: { 20 | server: false, 21 | client: false 22 | }, 23 | app: { 24 | head: { 25 | htmlAttrs: { 26 | class: 'h-full' 27 | }, 28 | title: 'Green Screen Creator', 29 | link: [ 30 | { rel: 'canonical', href: 'https://green-screen-creator.vercel.app' }, 31 | { rel: 'icon', type: 'image/x-icon', href: '/favicon.png' } 32 | ], 33 | meta: [ 34 | { hid: 'charset', charset: 'utf-8' }, 35 | { 'http-equiv': 'X-UA-Compatible', content: 'IE=edge' }, 36 | { 37 | hid: 'viewport', 38 | name: 'viewport', 39 | content: 40 | 'width=device-width,height=device-height,initial-scale=1.0,user-scalable=0,minimum-scale=1.0,maximum-scale=1.0,viewport-fit=cover' 41 | }, 42 | { 43 | hid: 'format-detection', 44 | name: 'format-detection', 45 | content: 'telephone=no' 46 | }, 47 | { 48 | hid: 'description', 49 | name: 'description', 50 | content: 51 | 'Track an object in a video and add a green screen to the background.' 52 | }, 53 | { 54 | hid: 'og:type', 55 | name: 'og:type', 56 | property: 'og:type', 57 | content: 'website' 58 | }, 59 | { 60 | hid: 'og:url', 61 | name: 'og:url', 62 | property: 'og:url', 63 | content: 'https://green-screen-creator.vercel.app' 64 | }, 65 | { 66 | hid: 'og:site_name', 67 | name: 'og:site_name', 68 | property: 'og:site_name', 69 | content: 'green-screen-creator.vercel.app' 70 | }, 71 | { 72 | hid: 'og:title', 73 | name: 'og:title', 74 | property: 'og:title', 75 | content: 'Green Screen Creator' 76 | }, 77 | { 78 | hid: 'og:description', 79 | name: 'og:description', 80 | property: 'og:description', 81 | content: 82 | 'Track an object in a video and add a green screen to the background.' 83 | }, 84 | { 85 | hid: 'og:image', 86 | name: 'og:image', 87 | property: 'og:image', 88 | content: 'https://green-screen-creator.vercel.app/cover.jpg' 89 | }, 90 | { 91 | name: 'twitter:card', 92 | content: 'summary_large_image' 93 | }, 94 | { 95 | name: 'twitter:title', 96 | content: 'Green Screen Creator' 97 | }, 98 | { 99 | name: 'twitter:description', 100 | content: 101 | 'Track an object in a video and add a green screen to the background.' 102 | }, 103 | { 104 | name: 'twitter:image', 105 | content: 'https://green-screen-creator.vercel.app/cover.jpg' 106 | }, 107 | { 108 | hid: 'msapplication-TileColor', 109 | name: 'msapplication-TileColor', 110 | content: '#ffffff' 111 | }, 112 | { hid: 'theme-color', name: 'theme-color', content: '#ffffff' }, 113 | { 114 | hid: 'mobile-web-app-capable', 115 | name: 'mobile-web-app-capable', 116 | content: 'yes' 117 | }, 118 | { 119 | hid: 'apple-mobile-web-app-title', 120 | name: 'apple-mobile-web-app-title', 121 | content: 'green-screen-creator.vercel.app' 122 | } 123 | ], 124 | script: [ 125 | { 126 | async: true, 127 | src: `https://www.googletagmanager.com/gtag/js?id=${process.env.GTAG_ID}` 128 | }, 129 | { 130 | children: ` 131 | window.dataLayer = window.dataLayer || []; 132 | function gtag(){dataLayer.push(arguments);} 133 | gtag('js', new Date()); 134 | gtag('config', '${process.env.GTAG_ID}');` 135 | } 136 | ], 137 | bodyAttrs: { 138 | class: 'antialiased h-full min-h-screen relative' 139 | } 140 | } 141 | }, 142 | modules: ['@nuxt/ui', '@vueuse/nuxt'], 143 | colorMode: { 144 | preference: 'light' 145 | }, 146 | ui: { 147 | global: true 148 | } 149 | }) 150 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "green-screen-creator", 3 | "private": true, 4 | "type": "module", 5 | "scripts": { 6 | "build": "nuxt build", 7 | "dev": "nuxt dev", 8 | "generate": "nuxt generate", 9 | "preview": "nuxt preview", 10 | "postinstall": "nuxt prepare" 11 | }, 12 | "dependencies": { 13 | "@ffmpeg/core": "0.10.0", 14 | "@ffmpeg/ffmpeg": "0.9.8", 15 | "@nuxt/ui": "^2.18.3", 16 | "@vueuse/core": "^10.11.0", 17 | "@vueuse/nuxt": "^10.11.0", 18 | "nuxt": "^3.12.4", 19 | "three": "^0.167.1", 20 | "vue": "latest" 21 | }, 22 | "devDependencies": { 23 | "pug": "^3.0.3", 24 | "pug-plain-loader": "^1.1.0", 25 | "stylus": "^0.63.0", 26 | "stylus-loader": "^8.1.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pages/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /prettier.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | trailingComma: 'none', 3 | tabWidth: 2, 4 | semi: false, 5 | singleQuote: true, 6 | bracketSpacing: true 7 | } 8 | -------------------------------------------------------------------------------- /public/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/replicate/green-screen-creator/ebbb84cb8bafcd1dee4b2ad1432dde70fe1a4724/public/cover.jpg -------------------------------------------------------------------------------- /public/cross-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/replicate/green-screen-creator/ebbb84cb8bafcd1dee4b2ad1432dde70fe1a4724/public/cross-icon.png -------------------------------------------------------------------------------- /public/demo/bees.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/replicate/green-screen-creator/ebbb84cb8bafcd1dee4b2ad1432dde70fe1a4724/public/demo/bees.mp4 -------------------------------------------------------------------------------- /public/demo/dog.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/replicate/green-screen-creator/ebbb84cb8bafcd1dee4b2ad1432dde70fe1a4724/public/demo/dog.mp4 -------------------------------------------------------------------------------- /public/demo/fountain.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/replicate/green-screen-creator/ebbb84cb8bafcd1dee4b2ad1432dde70fe1a4724/public/demo/fountain.mp4 -------------------------------------------------------------------------------- /public/demo/jellyfish.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/replicate/green-screen-creator/ebbb84cb8bafcd1dee4b2ad1432dde70fe1a4724/public/demo/jellyfish.mp4 -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/replicate/green-screen-creator/ebbb84cb8bafcd1dee4b2ad1432dde70fe1a4724/public/favicon.png -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/replicate/green-screen-creator/ebbb84cb8bafcd1dee4b2ad1432dde70fe1a4724/public/logo.png -------------------------------------------------------------------------------- /public/star-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/replicate/green-screen-creator/ebbb84cb8bafcd1dee4b2ad1432dde70fe1a4724/public/star-icon.png -------------------------------------------------------------------------------- /server/api/file.post.js: -------------------------------------------------------------------------------- 1 | export default defineEventHandler(async (event) => { 2 | try { 3 | const { api_token, file_name, data } = await readBody(event) 4 | 5 | // Extract the base64 data from the data URI 6 | const base64Data = data.split(',')[1] 7 | 8 | // Decode base64 to a Uint8Array 9 | const uint8Array = Uint8Array.from(atob(base64Data), (c) => c.charCodeAt(0)) 10 | 11 | // Create a Blob from the Uint8Array 12 | const blob = new Blob([uint8Array], { type: 'application/octet-stream' }) 13 | 14 | // Create form data 15 | const form = new FormData() 16 | form.append('content', blob, file_name) 17 | 18 | // Upload the file data to Replicate's file storage 19 | const result = await fetch('https://api.replicate.com/v1/files', { 20 | method: 'POST', 21 | headers: { 22 | Authorization: `Bearer ${api_token}` 23 | }, 24 | body: form 25 | }) 26 | 27 | const json = await result.json() 28 | 29 | return { data: json.urls.get } 30 | } catch (e) { 31 | console.log('--- error (api/prediction): ', e) 32 | 33 | return { 34 | error: e.message 35 | } 36 | } 37 | }) 38 | -------------------------------------------------------------------------------- /server/api/prediction.get.js: -------------------------------------------------------------------------------- 1 | export default defineEventHandler(async (event) => { 2 | try { 3 | const { api_token, id } = getQuery(event) 4 | 5 | const result = await fetch( 6 | `https://api.replicate.com/v1/predictions/${id}`, 7 | { 8 | method: 'GET', 9 | headers: { 10 | Authorization: `Bearer ${api_token}` 11 | } 12 | } 13 | ) 14 | 15 | const json = await result.json() 16 | 17 | // Remove potentially long data 18 | // delete json.logs 19 | 20 | return { data: json } 21 | } catch (e) { 22 | console.log('--- error (api/prediction): ', e) 23 | 24 | return { 25 | error: e.message 26 | } 27 | } 28 | }) 29 | -------------------------------------------------------------------------------- /server/api/prediction.post.js: -------------------------------------------------------------------------------- 1 | export default defineEventHandler(async (event) => { 2 | try { 3 | const { api_token, version, input } = await readBody(event) 4 | 5 | const result = await fetch('https://api.replicate.com/v1/predictions', { 6 | method: 'POST', 7 | headers: { 8 | Authorization: `Bearer ${api_token}` 9 | }, 10 | body: JSON.stringify({ 11 | version, 12 | input 13 | }) 14 | }) 15 | 16 | const json = await result.json() 17 | 18 | return { data: json } 19 | } catch (e) { 20 | console.log('--- error (api/prediction): ', e) 21 | 22 | return { 23 | error: e.message 24 | } 25 | } 26 | }) 27 | -------------------------------------------------------------------------------- /server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.nuxt/tsconfig.server.json" 3 | } 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // https://nuxt.com/docs/guide/concepts/typescript 3 | "extends": "./.nuxt/tsconfig.json" 4 | } 5 | --------------------------------------------------------------------------------