├── .devcontainer └── devcontainer.json ├── README.md ├── app.py ├── apt.txt ├── fonts ├── Merriweather-Black.ttf ├── Merriweather-BlackItalic.ttf ├── Merriweather-Bold.ttf ├── Merriweather-BoldItalic.ttf ├── Merriweather-Italic.ttf ├── Merriweather-Light.ttf ├── Merriweather-LightItalic.ttf ├── Merriweather-Regular.ttf ├── Merriweather.zip └── OFL.txt ├── horse.jpg ├── packages.txt └── requirements.txt /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Python 3", 3 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 4 | "image": "mcr.microsoft.com/devcontainers/python:1-3.11-bullseye", 5 | "customizations": { 6 | "codespaces": { 7 | "openFiles": [ 8 | "README.md", 9 | "app.py" 10 | ] 11 | }, 12 | "vscode": { 13 | "settings": {}, 14 | "extensions": [ 15 | "ms-python.python", 16 | "ms-python.vscode-pylance" 17 | ] 18 | } 19 | }, 20 | "updateContentCommand": "[ -f packages.txt ] && sudo apt update && sudo apt upgrade -y && sudo xargs apt install -y = 1.24.0 94 | - YOLOv8 (via Ultralytics) 95 | - OpenCV >= 4.7.0 96 | - Pillow >= 9.5.0 97 | - NumPy >= 1.24.0 98 | 99 | ### Object Detection 100 | - Uses YOLOv8 segmentation model 101 | - Supports 80 object classes 102 | - Real-time object detection and masking 103 | - Configurable confidence thresholds 104 | 105 | ### Text Processing 106 | - PIL-based text rendering 107 | - Advanced styling options 108 | - Multiple layer support 109 | - Real-time preview capabilities 110 | 111 | ## 🤝 Contributing 112 | 113 | 1. Fork the repository 114 | 2. Create your feature branch (`git checkout -b feature/AmazingFeature`) 115 | 3. Commit your changes (`git commit -m 'Add some AmazingFeature'`) 116 | 4. Push to the branch (`git push origin feature/AmazingFeature`) 117 | 5. Open a Pull Request 118 | 119 | ## 📫 Support 120 | 121 | For questions, feature requests, or support: 122 | - Create an issue in the repository 123 | - Contact [support@shotcut.in](mailto:support@shotcut.in) 124 | - Visit [shotcut.in](https://shotcut.in) for more information 125 | 126 | ## 🛣️ Roadmap 127 | 128 | - [ ] Additional font family support 129 | - [ ] Custom object detection model support 130 | - [ ] Advanced animation effects 131 | - [ ] Batch processing capabilities 132 | - [ ] Export in multiple formats 133 | 134 | Made with ❤️ by Tanish Mittal 135 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | import cv2 3 | import numpy as np 4 | from ultralytics import YOLO 5 | import tempfile 6 | import os 7 | from PIL import Image, ImageFont, ImageDraw 8 | import time 9 | from pathlib import Path 10 | import uuid 11 | from datetime import datetime 12 | from collections import defaultdict 13 | 14 | class JobStatus: 15 | PENDING = "pending" 16 | PROCESSING = "processing" 17 | COMPLETED = "completed" 18 | FAILED = "failed" 19 | 20 | class JobStore: 21 | def __init__(self): 22 | self.jobs = defaultdict(dict) 23 | 24 | def set_job_status(self, job_id, status, **kwargs): 25 | self.jobs[job_id].update({"status": status, **kwargs}) 26 | 27 | def get_job_status(self, job_id): 28 | return self.jobs.get(job_id, {}) 29 | 30 | FONT_FAMILIES = { 31 | "Merriweather Regular": "Merriweather-Regular.ttf", 32 | "Merriweather Bold": "Merriweather-Bold.ttf", 33 | "Merriweather Light": "Merriweather-Light.ttf", 34 | "Merriweather Black": "Merriweather-Black.ttf", 35 | "Merriweather Italic": "Merriweather-Italic.ttf", 36 | "Merriweather Bold Italic": "Merriweather-BoldItalic.ttf" 37 | } 38 | 39 | TARGET_CLASSES = { 40 | 0: 'person', 41 | 1: 'bicycle', 42 | 2: 'car', 43 | 3: 'motorcycle', 44 | 4: 'airplane', 45 | 5: 'bus', 46 | 6: 'train', 47 | 7: 'truck', 48 | 8: 'boat', 49 | 9: 'traffic light', 50 | 10: 'fire hydrant', 51 | 11: 'stop sign', 52 | 12: 'parking meter', 53 | 13: 'bench', 54 | 14: 'bird', 55 | 15: 'cat', 56 | 16: 'dog', 57 | 17: 'horse', 58 | 18: 'sheep', 59 | 19: 'cow', 60 | 20: 'elephant', 61 | 21: 'bear', 62 | 22: 'zebra', 63 | 23: 'giraffe', 64 | 24: 'backpack', 65 | 25: 'umbrella', 66 | 26: 'handbag', 67 | 27: 'tie', 68 | 28: 'suitcase', 69 | 29: 'frisbee', 70 | 30: 'skis', 71 | 31: 'snowboard', 72 | 32: 'sports ball', 73 | 33: 'kite', 74 | 34: 'baseball bat', 75 | 35: 'baseball glove', 76 | 36: 'skateboard', 77 | 37: 'surfboard', 78 | 38: 'tennis racket', 79 | 39: 'bottle', 80 | 40: 'wine glass', 81 | 41: 'cup', 82 | 42: 'fork', 83 | 43: 'knife', 84 | 44: 'spoon', 85 | 45: 'bowl', 86 | 46: 'banana', 87 | 47: 'apple', 88 | 48: 'sandwich', 89 | 49: 'orange', 90 | 50: 'broccoli', 91 | 51: 'carrot', 92 | 52: 'hot dog', 93 | 53: 'pizza', 94 | 54: 'donut', 95 | 55: 'cake', 96 | 56: 'chair', 97 | 57: 'couch', 98 | 58: 'potted plant', 99 | 59: 'bed', 100 | 60: 'dining table', 101 | 61: 'toilet', 102 | 62: 'TV', 103 | 63: 'laptop', 104 | 64: 'mouse', 105 | 65: 'remote', 106 | 66: 'keyboard', 107 | 67: 'cell phone', 108 | 68: 'microwave', 109 | 69: 'oven', 110 | 70: 'toaster', 111 | 71: 'sink', 112 | 72: 'refrigerator', 113 | 73: 'book', 114 | 74: 'clock', 115 | 75: 'vase', 116 | 76: 'scissors', 117 | 77: 'teddy bear', 118 | 78: 'hair dryer', 119 | 79: 'toothbrush'} 120 | 121 | def get_pil_font(font_family, font_size): 122 | try: 123 | font_path = Path(f"fonts/{FONT_FAMILIES[font_family]}") 124 | return ImageFont.truetype(str(font_path), font_size) 125 | except Exception: 126 | return ImageFont.load_default() 127 | 128 | @st.cache_resource 129 | def load_model(): 130 | model = YOLO('yolo11x-seg.pt') 131 | model.conf = 0.5 132 | model.iou = 0.7 133 | return model 134 | 135 | def add_text_with_pil(image, text, font_family, font_size, font_color, position, 136 | opacity=1.0, rotation=0, font_weight="normal", x_pos=0, y_pos=0): 137 | image = image.astype(np.uint8) 138 | image_pil = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) 139 | image_rgba = image_pil.convert('RGBA') 140 | 141 | overlay = Image.new('RGBA', image_pil.size, (0, 0, 0, 0)) 142 | draw = ImageDraw.Draw(overlay) 143 | 144 | font = get_pil_font(font_family, font_size) 145 | text_bbox = draw.textbbox((0, 0), text, font=font) 146 | text_width = text_bbox[2] - text_bbox[0] 147 | text_height = text_bbox[3] - text_bbox[1] 148 | 149 | x = x_pos if x_pos else (image_pil.width - text_width) // 2 150 | y = y_pos if y_pos else (image_pil.height - text_height) // 2 151 | 152 | if isinstance(font_color, str): 153 | font_color = tuple(int(font_color.lstrip('#')[i:i+2], 16) for i in (0, 2, 4)) 154 | font_color_with_opacity = (*font_color, int(255 * opacity)) 155 | 156 | if rotation != 0: 157 | temp_size = int(np.sqrt(text_width**2 + text_height**2)) + 20 158 | temp_img = Image.new('RGBA', (temp_size, temp_size), (0, 0, 0, 0)) 159 | temp_draw = ImageDraw.Draw(temp_img) 160 | 161 | temp_x = (temp_size - text_width) // 2 162 | temp_y = (temp_size - text_height) // 2 163 | temp_draw.text((temp_x, temp_y), text, font=font, fill=font_color_with_opacity) 164 | 165 | rotated_text = temp_img.rotate(rotation, expand=True, resample=Image.BICUBIC) 166 | paste_x = x - (rotated_text.width - text_width) // 2 167 | paste_y = y - (rotated_text.height - text_height) // 2 168 | overlay.paste(rotated_text, (paste_x, paste_y), rotated_text) 169 | else: 170 | draw.text((x, y), text, font=font, fill=font_color_with_opacity) 171 | 172 | result = Image.alpha_composite(image_rgba, overlay) 173 | return cv2.cvtColor(np.array(result), cv2.COLOR_RGBA2BGR) 174 | 175 | def update_preview(frame, text_sets, detected_objects=None): 176 | if frame is None: 177 | return None 178 | 179 | # Create the text overlay on a separate frame 180 | frame_with_text = frame.copy() 181 | for text_set in text_sets: 182 | if text_set.get("text"): 183 | frame_with_text = add_text_with_pil( 184 | frame_with_text, 185 | text_set["text"], 186 | text_set["font_family"], 187 | text_set["font_size"], 188 | text_set["font_color"], 189 | "center", 190 | text_set["opacity"], 191 | text_set["rotation"], 192 | text_set["font_weight"], 193 | text_set["x_pos"], 194 | text_set["y_pos"] 195 | ) 196 | 197 | if detected_objects: 198 | # Create combined mask from all detected objects 199 | mask_combined = np.zeros((frame.shape[0], frame.shape[1])) 200 | for mask in detected_objects: 201 | mask_combined = np.maximum(mask_combined, mask) 202 | 203 | # Create a 3-channel mask for proper masking 204 | mask_3channel = np.stack([mask_combined] * 3, axis=-1) 205 | 206 | # Convert to uint8 and ensure proper data type 207 | mask_3channel = (mask_3channel * 255).astype(np.uint8) 208 | frame = frame.astype(np.uint8) 209 | frame_with_text = frame_with_text.astype(np.uint8) 210 | 211 | # Apply inverse masks to both frames 212 | frame = cv2.multiply(frame, mask_3channel, scale=1/255) 213 | frame_with_text = cv2.multiply(frame_with_text, 255 - mask_3channel, scale=1/255) 214 | 215 | # Combine the frames 216 | preview_frame = cv2.add(frame, frame_with_text) 217 | else: 218 | preview_frame = frame_with_text 219 | 220 | # Ensure output is uint8 221 | preview_frame = preview_frame.astype(np.uint8) 222 | return preview_frame 223 | 224 | def get_first_frame(video_file): 225 | if not video_file: 226 | return None 227 | 228 | with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as tmp_file: 229 | tmp_file.write(video_file.getvalue()) 230 | video_path = tmp_file.name 231 | 232 | cap = cv2.VideoCapture(video_path) 233 | ret, frame = cap.read() 234 | cap.release() 235 | os.remove(video_path) 236 | 237 | return frame.astype(np.uint8) if ret else None 238 | 239 | def process_video(video_file, text_sets, selected_classes, model, job_store, job_id, 240 | confidence_threshold=0.5, iou_threshold=0.7): 241 | try: 242 | with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as tmp_file: 243 | tmp_file.write(video_file.read()) 244 | video_path = tmp_file.name 245 | 246 | cap = cv2.VideoCapture(video_path) 247 | width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) 248 | height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) 249 | fps = cap.get(cv2.CAP_PROP_FPS) 250 | total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) 251 | 252 | output_path = f'output_video_{job_id}.mp4' 253 | out = cv2.VideoWriter(output_path, cv2.VideoWriter_fourcc(*'MP4V'), fps, (width, height)) 254 | 255 | model.conf = confidence_threshold 256 | model.iou = iou_threshold 257 | 258 | selected_class_ids = [k for k, v in TARGET_CLASSES.items() if v in selected_classes] 259 | progress_bar = st.progress(0) 260 | frame_count = 0 261 | 262 | while True: 263 | ret, frame = cap.read() 264 | if not ret: 265 | break 266 | 267 | results = model(frame) 268 | mask_combined = np.zeros((height, width)) 269 | 270 | for result in results: 271 | for box_idx, cls in enumerate(result.boxes.cls): 272 | if int(cls) in selected_class_ids: 273 | try: 274 | mask = result.masks.data[box_idx].numpy() 275 | mask = cv2.resize(mask, (width, height)) 276 | mask_combined = np.maximum(mask_combined, np.where(mask >= 0.3, 1.0, 0)) 277 | except (IndexError, AttributeError): 278 | continue 279 | 280 | frame_with_text = frame.copy() 281 | for text_set in text_sets: 282 | frame_with_text = add_text_with_pil( 283 | frame_with_text, 284 | text_set["text"], 285 | text_set["font_family"], 286 | text_set["font_size"], 287 | text_set["font_color"], 288 | "center", 289 | text_set["opacity"], 290 | text_set["rotation"], 291 | text_set["font_weight"], 292 | text_set["x_pos"], 293 | text_set["y_pos"] 294 | ) 295 | 296 | # Apply masks 297 | frame[mask_combined == 0] = 0 298 | frame_with_text[mask_combined == 1] = 0 299 | final_frame = frame + frame_with_text 300 | 301 | out.write(final_frame) 302 | frame_count += 1 303 | progress_bar.progress(frame_count / total_frames) 304 | 305 | cap.release() 306 | out.release() 307 | os.remove(video_path) 308 | 309 | job_store.set_job_status(job_id, JobStatus.COMPLETED, result_path=output_path) 310 | return True 311 | 312 | except Exception as e: 313 | job_store.set_job_status(job_id, JobStatus.FAILED, error=str(e)) 314 | return False 315 | 316 | def main(): 317 | st.set_page_config(page_title="Video Text Overlay App", page_icon="🎥", layout="wide") 318 | 319 | if 'job_store' not in st.session_state: 320 | st.session_state.job_store = JobStore() 321 | 322 | model = load_model() 323 | 324 | # Clean, modern header 325 | st.markdown(""" 326 |

Video Text Overlay

327 |

Add professional text overlays to your videos with smart object detection

328 | """, unsafe_allow_html=True) 329 | 330 | # Main content with better spacing 331 | upload_col, preview_col = st.columns([1, 2]) 332 | 333 | with upload_col: 334 | st.markdown("### Upload Video") 335 | video_file = st.file_uploader("", type=["mp4", "mov", "avi"]) 336 | 337 | if video_file: 338 | st.video(video_file) 339 | 340 | # Detection Settings in a clean card 341 | st.markdown("### Detection Settings") 342 | selected_classes = st.multiselect( 343 | "Objects to Detect", 344 | options=list(TARGET_CLASSES.values()), 345 | default=['person'] 346 | ) 347 | 348 | col1, col2 = st.columns(2) 349 | with col1: 350 | confidence_threshold = st.slider("Confidence", 0.0, 1.0, 0.5, 0.05) 351 | with col2: 352 | iou_threshold = st.slider("IOU Threshold", 0.0, 1.0, 0.7, 0.05) 353 | 354 | # Preview column 355 | with preview_col: 356 | if video_file and 'first_frame' not in st.session_state: 357 | first_frame = get_first_frame(video_file) 358 | if first_frame is not None: 359 | st.session_state['first_frame'] = first_frame 360 | st.session_state['frame_height'] = first_frame.shape[0] 361 | st.session_state['frame_width'] = first_frame.shape[1] 362 | 363 | # Modern tab design for preview with detection first 364 | if 'first_frame' in st.session_state: 365 | preview_tabs = st.tabs(["With Detection", "Preview"]) 366 | 367 | # Initialize text sets in session state 368 | if 'text_sets' not in st.session_state: 369 | st.session_state.text_sets = [{}] 370 | 371 | # Handle layer removal 372 | layers_to_remove = [] 373 | 374 | # Text settings in a cleaner layout 375 | text_sets = [] 376 | for i, _ in enumerate(st.session_state.text_sets): 377 | with st.container(): 378 | # Layer header with remove button 379 | col1, col2 = st.columns([3, 1]) 380 | with col1: 381 | st.markdown(f"#### Text Layer {i+1}") 382 | with col2: 383 | if i > 0: # Only show remove button for layers after the first 384 | if st.button("🗑️ Remove", key=f"remove_{i}"): 385 | layers_to_remove.append(i) 386 | 387 | # Text input and basic settings 388 | text_input = st.text_area("Text Content", key=f"text_{i}", 389 | help="Enter the text you want to display") 390 | 391 | # Three columns for better organization 392 | c1, c2, c3 = st.columns(3) 393 | with c1: 394 | font_family = st.selectbox("Font", 395 | options=list(FONT_FAMILIES.keys()), 396 | key=f"font_{i}") 397 | font_size = st.slider("Size", 10, 500, 150, key=f"size_{i}") 398 | 399 | with c2: 400 | max_x = st.session_state.get('frame_width', 1920) 401 | max_y = st.session_state.get('frame_height', 1080) 402 | x_pos = st.slider("X Position", 0, max_x, max_x//2, key=f"x_{i}") 403 | y_pos = st.slider("Y Position", 0, max_y, max_y//2, key=f"y_{i}") 404 | 405 | with c3: 406 | opacity = st.slider("Opacity", 0.0, 1.0, 1.0, 0.1, key=f"opacity_{i}") 407 | rotation = st.slider("Rotation", -180, 180, 0, key=f"rotation_{i}") 408 | 409 | # Color and weight in a row 410 | col1, col2 = st.columns([1, 3]) 411 | with col1: 412 | font_color = st.color_picker("Color", "#FFFFFF", key=f"color_{i}") 413 | with col2: 414 | font_weight = st.select_slider("Weight", 415 | options=["light", "normal", "bold", "black"], 416 | value="normal", 417 | key=f"weight_{i}") 418 | 419 | if text_input: 420 | text_set = { 421 | "text": text_input, 422 | "font_family": font_family, 423 | "font_size": font_size, 424 | "font_color": font_color, 425 | "x_pos": x_pos, 426 | "y_pos": y_pos, 427 | "opacity": opacity, 428 | "rotation": rotation, 429 | "font_weight": font_weight 430 | } 431 | text_sets.append(text_set) 432 | 433 | st.markdown("
", unsafe_allow_html=True) 434 | 435 | # Remove layers marked for removal 436 | for i in reversed(layers_to_remove): 437 | st.session_state.text_sets.pop(i) 438 | st.rerun() 439 | 440 | # Add text layer button 441 | col1, col2, col3 = st.columns([1, 1, 1]) 442 | with col2: 443 | if st.button("Add Text Layer", use_container_width=True): 444 | st.session_state.text_sets.append({}) 445 | 446 | # Show detection preview first 447 | with preview_tabs[0]: 448 | selected_class_ids = [k for k, v in TARGET_CLASSES.items() if v in selected_classes] 449 | results = model(st.session_state['first_frame']) 450 | detected_masks = [] 451 | 452 | for result in results: 453 | for box_idx, cls in enumerate(result.boxes.cls): 454 | if int(cls) in selected_class_ids: 455 | try: 456 | mask = result.masks.data[box_idx].numpy() 457 | mask = cv2.resize(mask, 458 | (st.session_state['frame_width'], 459 | st.session_state['frame_height'])) 460 | detected_masks.append(np.where(mask >= 0.3, 1.0, 0)) 461 | except (IndexError, AttributeError): 462 | continue 463 | 464 | preview = update_preview(st.session_state['first_frame'], text_sets, detected_masks) 465 | if preview is not None: 466 | st.image(cv2.cvtColor(preview, cv2.COLOR_BGR2RGB), 467 | use_container_width=True) 468 | 469 | # Basic preview second 470 | with preview_tabs[1]: 471 | basic_preview = update_preview(st.session_state['first_frame'], text_sets) 472 | if basic_preview is not None: 473 | st.image(cv2.cvtColor(basic_preview, cv2.COLOR_BGR2RGB), 474 | use_container_width=True) 475 | 476 | # Process section with better styling 477 | if video_file and text_sets and selected_classes: 478 | st.markdown("
", unsafe_allow_html=True) 479 | col1, col2, col3 = st.columns([1, 2, 1]) 480 | with col2: 481 | if st.button("Process Video", type="primary", use_container_width=True): 482 | job_id = str(uuid.uuid4()) 483 | with st.spinner("Processing your video..."): 484 | success = process_video( 485 | video_file, 486 | text_sets, 487 | selected_classes, 488 | model, 489 | st.session_state.job_store, 490 | job_id, 491 | confidence_threshold, 492 | iou_threshold 493 | ) 494 | 495 | if success: 496 | st.success("Processing completed successfully!") 497 | status = st.session_state.job_store.get_job_status(job_id) 498 | if status.get('result_path'): 499 | with open(status['result_path'], "rb") as file: 500 | st.download_button( 501 | "Download Processed Video", 502 | file, 503 | file_name=f"processed_video.mp4", 504 | mime="video/mp4", 505 | use_container_width=True 506 | ) 507 | else: 508 | st.error("Processing failed. Please try again.") 509 | 510 | if __name__ == "__main__": 511 | main() 512 | -------------------------------------------------------------------------------- /apt.txt: -------------------------------------------------------------------------------- 1 | libgl1-mesa-glx 2 | libglib2.0-0 3 | libsm6 4 | libxext6 5 | libxrender1 6 | -------------------------------------------------------------------------------- /fonts/Merriweather-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tansihmittal/textbehindvideo/e63b08d4cd7bef287cf73aeae69a4b0a86fc02cd/fonts/Merriweather-Black.ttf -------------------------------------------------------------------------------- /fonts/Merriweather-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tansihmittal/textbehindvideo/e63b08d4cd7bef287cf73aeae69a4b0a86fc02cd/fonts/Merriweather-BlackItalic.ttf -------------------------------------------------------------------------------- /fonts/Merriweather-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tansihmittal/textbehindvideo/e63b08d4cd7bef287cf73aeae69a4b0a86fc02cd/fonts/Merriweather-Bold.ttf -------------------------------------------------------------------------------- /fonts/Merriweather-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tansihmittal/textbehindvideo/e63b08d4cd7bef287cf73aeae69a4b0a86fc02cd/fonts/Merriweather-BoldItalic.ttf -------------------------------------------------------------------------------- /fonts/Merriweather-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tansihmittal/textbehindvideo/e63b08d4cd7bef287cf73aeae69a4b0a86fc02cd/fonts/Merriweather-Italic.ttf -------------------------------------------------------------------------------- /fonts/Merriweather-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tansihmittal/textbehindvideo/e63b08d4cd7bef287cf73aeae69a4b0a86fc02cd/fonts/Merriweather-Light.ttf -------------------------------------------------------------------------------- /fonts/Merriweather-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tansihmittal/textbehindvideo/e63b08d4cd7bef287cf73aeae69a4b0a86fc02cd/fonts/Merriweather-LightItalic.ttf -------------------------------------------------------------------------------- /fonts/Merriweather-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tansihmittal/textbehindvideo/e63b08d4cd7bef287cf73aeae69a4b0a86fc02cd/fonts/Merriweather-Regular.ttf -------------------------------------------------------------------------------- /fonts/Merriweather.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tansihmittal/textbehindvideo/e63b08d4cd7bef287cf73aeae69a4b0a86fc02cd/fonts/Merriweather.zip -------------------------------------------------------------------------------- /fonts/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright 2016 The Merriweather Project Authors (https://github.com/EbenSorkin/Merriweather), with Reserved Font Name "Merriweather". 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | https://openfontlicense.org 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /horse.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tansihmittal/textbehindvideo/e63b08d4cd7bef287cf73aeae69a4b0a86fc02cd/horse.jpg -------------------------------------------------------------------------------- /packages.txt: -------------------------------------------------------------------------------- 1 | libgl1-mesa-glx 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | streamlit>=1.24.0 2 | opencv-python>=4.7.0 3 | numpy>=1.24.0 4 | ultralytics>=8.0.0 5 | Pillow>=9.5.0 6 | python-dateutil>=2.8.2 7 | uuid>=1.30 8 | pathlib>=1.0.1 9 | typing_extensions>=4.5.0 10 | python-magic>=0.4.27 11 | --------------------------------------------------------------------------------