MogensR commited on
Commit
a542ea3
·
1 Parent(s): eb309e2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +5 -1726
app.py CHANGED
@@ -26,6 +26,9 @@
26
  from typing import Optional, Tuple, Dict, Any
27
  import logging
28
 
 
 
 
29
  # Fix OpenMP threads issue - remove problematic environment variable
30
  try:
31
  if 'OMP_NUM_THREADS' in os.environ:
@@ -737,6 +740,7 @@ def refine_mask_hq(image, mask):
737
  logger.error(f"Mask refinement error: {e}")
738
  # Return original mask if refinement fails
739
  return mask if len(mask.shape) == 2 else cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
 
740
  def create_green_screen_background(frame):
741
  """Create green screen background (Stage 1 of two-stage process)"""
742
  h, w = frame.shape[:2]
@@ -1185,261 +1189,6 @@ def get_model_status():
1185
  else:
1186
  return "⏳ Models not loaded. Click 'Load Models' for ENHANCED cinema-quality processing."
1187
 
1188
- def create_procedural_background(prompt, style, width, height):
1189
- """Create procedural background based on text prompt and style"""
1190
- try:
1191
- # Analyze prompt for colors and patterns
1192
- prompt_lower = prompt.lower()
1193
-
1194
- # Color mapping based on prompt keywords
1195
- color_map = {
1196
- 'blue': ['#1e3c72', '#2a5298', '#3498db'],
1197
- 'ocean': ['#74b9ff', '#0984e3', '#00cec9'],
1198
- 'sky': ['#87CEEB', '#4682B4', '#1E90FF'],
1199
- 'green': ['#27ae60', '#2ecc71', '#58d68d'],
1200
- 'nature': ['#2d5016', '#3c6e1f', '#4caf50'],
1201
- 'forest': ['#1B4332', '#2D5A36', '#40916C'],
1202
- 'red': ['#e74c3c', '#c0392b', '#ff7675'],
1203
- 'sunset': ['#ff7675', '#fd79a8', '#fdcb6e'],
1204
- 'orange': ['#e67e22', '#f39c12', '#ff9f43'],
1205
- 'purple': ['#6c5ce7', '#a29bfe', '#fd79a8'],
1206
- 'pink': ['#fd79a8', '#fdcb6e', '#ff7675'],
1207
- 'yellow': ['#f1c40f', '#f39c12', '#fdcb6e'],
1208
- 'tech': ['#2c3e50', '#34495e', '#74b9ff'],
1209
- 'space': ['#0c0c0c', '#2d3748', '#4a5568'],
1210
- 'dark': ['#1a1a1a', '#2d2d2d', '#404040'],
1211
- 'office': ['#f8f9fa', '#e9ecef', '#74b9ff'],
1212
- 'corporate': ['#2c3e50', '#34495e', '#74b9ff'],
1213
- 'warm': ['#ff7675', '#fd79a8', '#fdcb6e'],
1214
- 'cool': ['#74b9ff', '#0984e3', '#00cec9'],
1215
- 'minimal': ['#ffffff', '#f1f2f6', '#ddd'],
1216
- 'abstract': ['#6c5ce7', '#a29bfe', '#fd79a8']
1217
- }
1218
-
1219
- # Find matching colors
1220
- selected_colors = ['#3498db', '#2ecc71', '#e74c3c'] # Default
1221
- for keyword, colors in color_map.items():
1222
- if keyword in prompt_lower:
1223
- selected_colors = colors
1224
- break
1225
-
1226
- # Create background based on style
1227
- if style == "abstract":
1228
- return create_abstract_background(selected_colors, width, height)
1229
- elif style == "minimalist":
1230
- return create_minimalist_background(selected_colors, width, height)
1231
- elif style == "corporate":
1232
- return create_corporate_background(selected_colors, width, height)
1233
- elif style == "nature":
1234
- return create_nature_background(selected_colors, width, height)
1235
- elif style == "artistic":
1236
- return create_artistic_background(selected_colors, width, height)
1237
- else:
1238
- # Default: photorealistic gradient
1239
- bg_config = {
1240
- "type": "gradient",
1241
- "colors": selected_colors[:2],
1242
- "direction": "diagonal"
1243
- }
1244
- return create_gradient_background(bg_config, width, height)
1245
-
1246
- except Exception as e:
1247
- logger.error(f"Procedural background creation failed: {e}")
1248
- return None
1249
-
1250
- def create_abstract_background(colors, width, height):
1251
- """Create abstract geometric background"""
1252
- try:
1253
- background = np.zeros((height, width, 3), dtype=np.uint8)
1254
-
1255
- # Convert hex colors to BGR
1256
- bgr_colors = []
1257
- for color in colors:
1258
- hex_color = color.lstrip('#')
1259
- rgb = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
1260
- bgr = rgb[::-1]
1261
- bgr_colors.append(bgr)
1262
-
1263
- # Base gradient
1264
- for y in range(height):
1265
- progress = y / height
1266
- color = [
1267
- int(bgr_colors[0][i] + (bgr_colors[1][i] - bgr_colors[0][i]) * progress)
1268
- for i in range(3)
1269
- ]
1270
- background[y, :] = color
1271
-
1272
- # Add geometric shapes
1273
- import random
1274
- random.seed(42) # Reproducible
1275
-
1276
- for _ in range(8):
1277
- center_x = random.randint(width//4, 3*width//4)
1278
- center_y = random.randint(height//4, 3*height//4)
1279
- radius = random.randint(width//20, width//8)
1280
- color = bgr_colors[random.randint(0, len(bgr_colors)-1)]
1281
-
1282
- overlay = background.copy()
1283
- cv2.circle(overlay, (center_x, center_y), radius, color, -1)
1284
- cv2.addWeighted(background, 0.7, overlay, 0.3, 0, background)
1285
-
1286
- return background
1287
-
1288
- except Exception as e:
1289
- logger.error(f"Abstract background creation failed: {e}")
1290
- return None
1291
-
1292
- def create_minimalist_background(colors, width, height):
1293
- """Create minimalist background"""
1294
- try:
1295
- bg_config = {
1296
- "type": "gradient",
1297
- "colors": colors[:2],
1298
- "direction": "soft_radial"
1299
- }
1300
- background = create_gradient_background(bg_config, width, height)
1301
-
1302
- # Add subtle element
1303
- overlay = background.copy()
1304
- center_x, center_y = width//2, height//2
1305
-
1306
- hex_color = colors[0].lstrip('#')
1307
- rgb = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
1308
- bgr = rgb[::-1]
1309
-
1310
- cv2.circle(overlay, (center_x, center_y), min(width, height)//3, bgr, -1)
1311
- cv2.addWeighted(background, 0.95, overlay, 0.05, 0, background)
1312
-
1313
- return background
1314
-
1315
- except Exception as e:
1316
- logger.error(f"Minimalist background creation failed: {e}")
1317
- return None
1318
-
1319
- def create_corporate_background(colors, width, height):
1320
- """Create corporate background"""
1321
- try:
1322
- bg_config = {
1323
- "type": "gradient",
1324
- "colors": ['#2c3e50', '#34495e', '#74b9ff'],
1325
- "direction": "diagonal"
1326
- }
1327
- background = create_gradient_background(bg_config, width, height)
1328
-
1329
- # Add subtle grid
1330
- grid_color = (80, 80, 80)
1331
- grid_spacing = width // 20
1332
-
1333
- def create_corporate_background(colors, width, height):
1334
- """Create corporate background"""
1335
- try:
1336
- bg_config = {
1337
- "type": "gradient",
1338
- "colors": ['#2c3e50', '#34495e', '#74b9ff'],
1339
- "direction": "diagonal"
1340
- }
1341
- background = create_gradient_background(bg_config, width, height)
1342
-
1343
- # Add subtle grid
1344
- grid_color = (80, 80, 80)
1345
- grid_spacing = width // 20
1346
-
1347
- for x in range(0, width, grid_spacing):
1348
- cv2.line(background, (x, 0), (x, height), grid_color, 1)
1349
-
1350
- for y in range(0, height, grid_spacing):
1351
- cv2.line(background, (0, y), (width, y), grid_color, 1)
1352
-
1353
- background = cv2.GaussianBlur(background, (3, 3), 1.0)
1354
- return background
1355
-
1356
- except Exception as e:
1357
- logger.error(f"Corporate background creation failed: {e}")
1358
- return None
1359
-
1360
- def create_nature_background(colors, width, height):
1361
- """Create nature background"""
1362
- try:
1363
- bg_config = {
1364
- "type": "gradient",
1365
- "colors": ['#2d5016', '#3c6e1f', '#4caf50'],
1366
- "direction": "vertical"
1367
- }
1368
- background = create_gradient_background(bg_config, width, height)
1369
-
1370
- # Add organic shapes
1371
- import random
1372
- random.seed(42)
1373
-
1374
- overlay = background.copy()
1375
-
1376
- for _ in range(5):
1377
- center_x = random.randint(width//6, 5*width//6)
1378
- center_y = random.randint(height//6, 5*height//6)
1379
-
1380
- axes_x = random.randint(width//20, width//6)
1381
- axes_y = random.randint(height//20, height//6)
1382
- angle = random.randint(0, 180)
1383
-
1384
- color = (random.randint(40, 80), random.randint(120, 160), random.randint(30, 70))
1385
- cv2.ellipse(overlay, (center_x, center_y), (axes_x, axes_y), angle, 0, 360, color, -1)
1386
-
1387
- cv2.addWeighted(background, 0.8, overlay, 0.2, 0, background)
1388
- background = cv2.GaussianBlur(background, (5, 5), 2.0)
1389
-
1390
- return background
1391
-
1392
- except Exception as e:
1393
- logger.error(f"Nature background creation failed: {e}")
1394
- return None
1395
-
1396
- def create_artistic_background(colors, width, height):
1397
- """Create artistic background with creative elements"""
1398
- try:
1399
- # Start with base gradient
1400
- bg_config = {
1401
- "type": "gradient",
1402
- "colors": colors,
1403
- "direction": "diagonal"
1404
- }
1405
- background = create_gradient_background(bg_config, width, height)
1406
-
1407
- # Add artistic elements
1408
- import random
1409
- random.seed(42)
1410
-
1411
- # Convert colors to BGR
1412
- bgr_colors = []
1413
- for color in colors:
1414
- hex_color = color.lstrip('#')
1415
- rgb = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
1416
- bgr_colors.append(rgb[::-1])
1417
-
1418
- overlay = background.copy()
1419
-
1420
- # Add flowing curves
1421
- for i in range(3):
1422
- pts = []
1423
- for x in range(0, width, width//10):
1424
- y = int(height//2 + (height//4) * np.sin(2 * np.pi * x / width + i))
1425
- pts.append([x, y])
1426
-
1427
- pts = np.array(pts, np.int32)
1428
- color = bgr_colors[i % len(bgr_colors)]
1429
- cv2.polylines(overlay, [pts], False, color, thickness=width//50)
1430
-
1431
- # Blend with base
1432
- cv2.addWeighted(background, 0.7, overlay, 0.3, 0, background)
1433
-
1434
- # Add texture
1435
- background = cv2.GaussianBlur(background, (3, 3), 1.0)
1436
-
1437
- return background
1438
-
1439
- except Exception as e:
1440
- logger.error(f"Artistic background creation failed: {e}")
1441
- return None
1442
-
1443
  def create_interface():
1444
  """Create enhanced Gradio interface with comprehensive features and 4-method background system"""
1445
 
@@ -1617,1474 +1366,4 @@ def switch_background_method(method):
1617
  padding: 12px 8px;
1618
  border: 1px solid #ddd;
1619
  border-radius: 6px;
1620
- text-align: center;
1621
- background: {gradient};
1622
- min-height: 60px;
1623
- display: flex;
1624
- align-items: center;
1625
- justify-content: center;
1626
- '>
1627
- <div>
1628
- <strong style='color: white; text-shadow: 1px 1px 2px rgba(0,0,0,0.8); font-size: 12px; display: block;'>{config["name"]}</strong>
1629
- <small style='color: rgba(255,255,255,0.9); text-shadow: 1px 1px 1px rgba(0,0,0,0.6); font-size: 10px;'>{config.get("description", "")[:30]}...</small>
1630
- </div>
1631
- </div>
1632
- """
1633
-
1634
- bg_preview_html += "</div>"
1635
- gr.HTML(bg_preview_html)
1636
-
1637
- # AI Background Generation Function
1638
- def generate_ai_background(prompt, style):
1639
- """Generate AI background using procedural methods"""
1640
- if not prompt or not prompt.strip():
1641
- return None, "❌ Please enter a prompt"
1642
-
1643
- try:
1644
- # Create procedural background based on prompt
1645
- bg_image = create_procedural_background(prompt, style, 1920, 1080)
1646
-
1647
- if bg_image is not None:
1648
- # Save generated image
1649
- import tempfile
1650
- with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as tmp:
1651
- cv2.imwrite(tmp.name, bg_image)
1652
- return tmp.name, f"✅ Background generated: {prompt[:50]}..."
1653
- else:
1654
- return None, "❌ Generation failed, try different prompt"
1655
- except Exception as e:
1656
- logger.error(f"AI generation error: {e}")
1657
- return None, f"❌ Generation error: {str(e)}"
1658
-
1659
- # Enhanced video processing function that handles all 4 methods
1660
- def process_video_enhanced(video_path, bg_method, custom_img, prof_choice, grad_type,
1661
- color1, color2, color3, use_third, ai_prompt, ai_style, ai_img,
1662
- progress=gr.Progress()):
1663
- """Process video with any of the 4 background methods using TWO-STAGE approach"""
1664
-
1665
- if not models_loaded:
1666
- return None, "❌ Models not loaded. Click 'Load Models' first."
1667
-
1668
- if not video_path:
1669
- return None, "❌ No video file provided."
1670
-
1671
- try:
1672
- progress(0, desc="🎬 Preparing background...")
1673
-
1674
- # Determine which background to use based on method
1675
- if bg_method == "upload":
1676
- if custom_img and os.path.exists(custom_img):
1677
- return process_video_hq(video_path, "custom", custom_img, progress)
1678
- else:
1679
- return None, "❌ No image uploaded. Please upload a background image."
1680
-
1681
- elif bg_method == "professional":
1682
- if prof_choice and prof_choice in PROFESSIONAL_BACKGROUNDS:
1683
- return process_video_hq(video_path, prof_choice, None, progress)
1684
- else:
1685
- return None, f"❌ Invalid professional background: {prof_choice}"
1686
-
1687
- elif bg_method == "colors":
1688
- # Create custom gradient as temporary image
1689
- try:
1690
- colors = [color1 or "#3498db", color2 or "#2ecc71"]
1691
- if use_third and color3:
1692
- colors.append(color3)
1693
-
1694
- bg_config = {
1695
- "type": "gradient" if grad_type != "solid" else "color",
1696
- "colors": colors,
1697
- "direction": grad_type if grad_type != "solid" else "vertical"
1698
- }
1699
-
1700
- if grad_type == "solid":
1701
- bg_config["colors"] = [colors[0]]
1702
-
1703
- # Create temporary image for gradient
1704
- gradient_bg = create_professional_background(bg_config, 1920, 1080)
1705
- temp_path = f"/tmp/gradient_{int(time.time())}.png"
1706
- cv2.imwrite(temp_path, gradient_bg)
1707
-
1708
- return process_video_hq(video_path, "custom", temp_path, progress)
1709
- except Exception as e:
1710
- return None, f"❌ Error creating gradient: {str(e)}"
1711
-
1712
- elif bg_method == "ai":
1713
- if ai_img and os.path.exists(ai_img):
1714
- return process_video_hq(video_path, "custom", ai_img, progress)
1715
- else:
1716
- return None, "❌ No AI background generated. Click 'Generate Background' first."
1717
-
1718
- else:
1719
- return None, f"❌ Unknown background method: {bg_method}"
1720
-
1721
- except Exception as e:
1722
- logger.error(f"Enhanced processing error: {e}")
1723
- return None, f"❌ Processing error: {str(e)}"
1724
-
1725
- # Connect all the functions
1726
- load_models_btn.click(
1727
- fn=download_and_setup_models,
1728
- outputs=status_text
1729
- )
1730
-
1731
- generate_ai_btn.click(
1732
- fn=generate_ai_background,
1733
- inputs=[ai_prompt, ai_style],
1734
- outputs=[ai_generated_image, status_text]
1735
- )
1736
-
1737
- process_btn.click(
1738
- fn=process_video_enhanced,
1739
- inputs=[
1740
- video_input, # video_path
1741
- background_method, # bg_method
1742
- custom_background, # custom_img
1743
- professional_choice, # prof_choice
1744
- gradient_type, # grad_type
1745
- color1, color2, color3, use_third_color, # colors
1746
- ai_prompt, ai_style, ai_generated_image # AI
1747
- ],
1748
- outputs=[video_output, result_text]
1749
- )
1750
-
1751
- # Comprehensive info section
1752
- with gr.Accordion("ℹ️ ENHANCED Quality & Features", open=False):
1753
- gr.Markdown("""
1754
- ### 🏆 TWO-STAGE Cinema-Quality Features:
1755
-
1756
- **🎬 Two-Stage Processing:**
1757
- - **Stage 1**: Original Video → Green Screen Video (SAM2 + MatAnyone segmentation)
1758
- - **Stage 2**: Green Screen Video → Final Background (Professional chroma key replacement)
1759
- - **Why Two-Stage?**: Better edge quality, cleaner separation, professional results
1760
-
1761
- **🤖 Advanced AI Models:**
1762
- - **SAM2**: State-of-the-art segmentation (Large/Tiny auto-selection)
1763
- - **MatAnyone**: CVPR 2025 professional matting technology
1764
- - **Multi-Fallback Loading**: 4+ methods each for maximum reliability
1765
- - **OpenCV Fallbacks**: Enhanced backup systems for compatibility
1766
-
1767
- **🎨 4 Background Methods:**
1768
- - **A) Upload Image**: Use any custom image as background
1769
- - **B) Professional Presets**: 15+ high-quality professional backgrounds
1770
- - **C) Colors/Gradients**: Custom color combinations with 6 gradient types
1771
- - **D) AI Generated**: Procedural backgrounds from text prompts
1772
-
1773
- **🎬 Professional Quality:**
1774
- - **✨ Edge Feathering**: Smooth, natural transitions
1775
- - **🎬 Gamma Correction**: Professional color compositing
1776
- - **🔍 Multi-Point Segmentation**: 7-point strategic person detection
1777
- - **🧹 Morphological Processing**: Advanced mask cleanup
1778
- - **🟢 Green Screen Intermediate**: Professional chroma key workflow
1779
-
1780
- **🎵 Audio & Video:**
1781
- - **High-Quality Audio**: 192kbps AAC preservation
1782
- - **📺 H.264 Codec**: CRF 18 for broadcast quality
1783
- - **🎞️ Frame Processing**: Advanced error handling
1784
- - **💾 Smart Caching**: Optimized memory management
1785
-
1786
- ### 💡 Usage Tips:
1787
- - Upload videos in common formats (MP4, MOV, AVI)
1788
- - For best results, ensure good lighting in original video
1789
- - Custom backgrounds work best with high resolution images
1790
- - AI prompts: Try "modern office", "sunset mountain", "abstract tech"
1791
- - GPU processing is faster but CPU fallback always available
1792
- - Two-stage processing gives cinema-quality results
1793
- """)
1794
-
1795
- # Footer
1796
- gr.Markdown("---")
1797
- gr.Markdown(
1798
- "*🎬 Cinema-Quality Video Background Replacement - "
1799
- "Enhanced with TWO-STAGE processing and 4-method background system*"
1800
- )
1801
-
1802
- return demo
1803
-
1804
- def create_advanced_procedural_background(prompt, style, width, height):
1805
- """Create advanced procedural background with more sophisticated algorithms"""
1806
- try:
1807
- # This is an enhanced version of procedural generation
1808
- base_bg = create_procedural_background(prompt, style, width, height)
1809
-
1810
- if base_bg is None:
1811
- return None
1812
-
1813
- # Add noise texture for realism
1814
- noise = np.random.normal(0, 10, (height, width, 3)).astype(np.int16)
1815
- enhanced_bg = base_bg.astype(np.int16) + noise
1816
- enhanced_bg = np.clip(enhanced_bg, 0, 255).astype(np.uint8)
1817
-
1818
- # Slight blur for smoothness
1819
- enhanced_bg = cv2.GaussianBlur(enhanced_bg, (3, 3), 1.0)
1820
-
1821
- return enhanced_bg
1822
-
1823
- except Exception as e:
1824
- logger.error(f"Advanced procedural background creation failed: {e}")
1825
- return create_procedural_background(prompt, style, width, height)
1826
-
1827
- def generate_textured_background(primary_color, texture_type="noise", width=1920, height=1080):
1828
- """Generate textured background with different patterns"""
1829
- try:
1830
- # Convert hex color to BGR
1831
- if primary_color.startswith('#'):
1832
- color_hex = primary_color.lstrip('#')
1833
- color_rgb = tuple(int(color_hex[i:i+2], 16) for i in (0, 2, 4))
1834
- color_bgr = color_rgb[::-1]
1835
- else:
1836
- # Default color if invalid
1837
- color_bgr = (64, 64, 128)
1838
-
1839
- # Create base background
1840
- background = np.full((height, width, 3), color_bgr, dtype=np.uint8)
1841
-
1842
- # Apply texture based on type
1843
- if texture_type == "noise":
1844
- # Apply random noise
1845
- noise = np.random.randint(0, 40, (height, width, 3), dtype=np.int16)
1846
- noise -= 20 # Center around zero to both add and subtract
1847
- textured = background.astype(np.int16) + noise
1848
- textured = np.clip(textured, 0, 255).astype(np.uint8)
1849
- background = textured
1850
-
1851
- elif texture_type == "dots":
1852
- # Create dot pattern
1853
- overlay = background.copy()
1854
- dot_color = tuple(min(255, c + 30) for c in color_bgr)
1855
-
1856
- for y in range(0, height, 20):
1857
- for x in range(0, width, 20):
1858
- cv2.circle(overlay, (x, y), 3, dot_color, -1)
1859
-
1860
- cv2.addWeighted(background, 0.8, overlay, 0.2, 0, background)
1861
-
1862
- elif texture_type == "lines":
1863
- # Create line pattern
1864
- overlay = background.copy()
1865
- line_color = tuple(max(0, c - 30) for c in color_bgr)
1866
-
1867
- for y in range(0, height, 15):
1868
- cv2.line(overlay, (0, y), (width, y), line_color, 1)
1869
-
1870
- cv2.addWeighted(background, 0.9, overlay, 0.1, 0, background)
1871
-
1872
- elif texture_type == "fabric":
1873
- # Create fabric-like texture
1874
- texture = np.zeros((height, width, 3), dtype=np.uint8)
1875
-
1876
- # Create weave pattern
1877
- for y in range(height):
1878
- for x in range(width):
1879
- # Weave pattern
1880
- pattern_val = (np.sin(x/5) * 10 + np.sin(y/5) * 10)
1881
- pattern_val = int(pattern_val) % 20
1882
-
1883
- # Adjust color based on pattern
1884
- adjusted_color = tuple(
1885
- max(0, min(255, c + pattern_val - 10)) for c in color_bgr
1886
- )
1887
- texture[y, x] = adjusted_color
1888
-
1889
- # Blend with base
1890
- cv2.addWeighted(background, 0.7, texture, 0.3, 0, background)
1891
-
1892
- # Apply final blur for smoothness
1893
- background = cv2.GaussianBlur(background, (3, 3), 0.5)
1894
-
1895
- return background
1896
-
1897
- except Exception as e:
1898
- logger.error(f"Textured background generation failed: {e}")
1899
- # Return simple colored background as fallback
1900
- return np.full((height, width, 3), (64, 64, 128), dtype=np.uint8)
1901
-
1902
- def create_pattern_background(pattern_type, colors, width=1920, height=1080):
1903
- """Create background with specific patterns"""
1904
- try:
1905
- if not colors or len(colors) == 0:
1906
- colors = ["#3498db", "#2ecc71"]
1907
-
1908
- # Convert hex colors to BGR
1909
- bgr_colors = []
1910
- for color in colors:
1911
- if color.startswith('#'):
1912
- hex_color = color.lstrip('#')
1913
- rgb = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
1914
- bgr_colors.append(rgb[::-1])
1915
- else:
1916
- bgr_colors.append((64, 64, 128)) # Default fallback
1917
-
1918
- # Initialize background
1919
- background = np.zeros((height, width, 3), dtype=np.uint8)
1920
-
1921
- if pattern_type == "stripes":
1922
- # Create striped background
1923
- stripe_width = height // len(bgr_colors)
1924
- if stripe_width == 0:
1925
- stripe_width = 1
1926
-
1927
- for i, color in enumerate(bgr_colors):
1928
- start_y = i * stripe_width
1929
- end_y = min(start_y + stripe_width, height)
1930
- background[start_y:end_y, :] = color
1931
-
1932
- elif pattern_type == "checkered":
1933
- # Create checkered background
1934
- cell_size = min(width, height) // 10
1935
- for y in range(0, height, cell_size):
1936
- for x in range(0, width, cell_size):
1937
- color_idx = ((y // cell_size) + (x // cell_size)) % len(bgr_colors)
1938
- y_end = min(y + cell_size, height)
1939
- x_end = min(x + cell_size, width)
1940
- background[y:y_end, x:x_end] = bgr_colors[color_idx]
1941
-
1942
- elif pattern_type == "radial":
1943
- # Create radial pattern
1944
- center_x, center_y = width // 2, height // 2
1945
- max_dist = np.sqrt(center_x**2 + center_y**2)
1946
-
1947
- for y in range(height):
1948
- for x in range(width):
1949
- dist = np.sqrt((x - center_x)**2 + (y - center_y)**2)
1950
- color_idx = int((dist / max_dist) * len(bgr_colors)) % len(bgr_colors)
1951
- background[y, x] = bgr_colors[color_idx]
1952
-
1953
- elif pattern_type == "waves":
1954
- # Create wave pattern
1955
- for y in range(height):
1956
- for x in range(width):
1957
- wave_value = np.sin(x/30) + np.sin(y/30)
1958
- color_idx = int((wave_value + 2) / 4 * len(bgr_colors)) % len(bgr_colors)
1959
- background[y, x] = bgr_colors[color_idx]
1960
-
1961
- else: # Default to gradient
1962
- # Create simple gradient
1963
- for y in range(height):
1964
- progress = y / height
1965
- for x in range(width):
1966
- if len(bgr_colors) >= 2:
1967
- r = int(bgr_colors[0][0] + progress * (bgr_colors[1][0] - bgr_colors[0][0]))
1968
- g = int(bgr_colors[0][1] + progress * (bgr_colors[1][1] - bgr_colors[0][1]))
1969
- b = int(bgr_colors[0][2] + progress * (bgr_colors[1][2] - bgr_colors[0][2]))
1970
- background[y, x] = (r, g, b)
1971
- else:
1972
- background[y, x] = bgr_colors[0]
1973
-
1974
- # Apply slight blur for smoothness
1975
- background = cv2.GaussianBlur(background, (3, 3), 0.5)
1976
-
1977
- return background
1978
-
1979
- except Exception as e:
1980
- logger.error(f"Pattern background creation failed: {e}")
1981
- # Return simple fallback
1982
- return np.zeros((height, width, 3), dtype=np.uint8)
1983
-
1984
- def create_dynamic_background(base_color, intensity=0.5, width=1920, height=1080):
1985
- """Create dynamic-looking background with motion-like elements"""
1986
- try:
1987
- # Convert hex color to BGR
1988
- if base_color.startswith('#'):
1989
- color_hex = base_color.lstrip('#')
1990
- color_rgb = tuple(int(color_hex[i:i+2], 16) for i in (0, 2, 4))
1991
- base_bgr = color_rgb[::-1]
1992
- else:
1993
- # Default color if invalid
1994
- base_bgr = (64, 64, 128)
1995
-
1996
- # Create base background
1997
- background = np.full((height, width, 3), base_bgr, dtype=np.uint8)
1998
-
1999
- # Create dynamic elements
2000
- overlay = np.zeros_like(background)
2001
-
2002
- # Scale intensity
2003
- intensity = max(0.1, min(1.0, intensity))
2004
-
2005
- # Create motion streaks
2006
- import random
2007
- random.seed(42) # For reproducibility
2008
-
2009
- for _ in range(int(50 * intensity)):
2010
- start_x = random.randint(0, width)
2011
- start_y = random.randint(0, height)
2012
- end_x = start_x + random.randint(-width//3, width//3)
2013
- end_y = start_y + random.randint(-height//3, height//3)
2014
-
2015
- # Randomize color slightly
2016
- color_variation = random.randint(-20, 20)
2017
- streak_color = tuple(
2018
- max(0, min(255, c + color_variation)) for c in base_bgr
2019
- )
2020
-
2021
- # Draw streak
2022
- cv2.line(overlay, (start_x, start_y), (end_x, end_y),
2023
- streak_color, thickness=random.randint(1, 5))
2024
-
2025
- # Add some glow effect
2026
- overlay_blurred = cv2.GaussianBlur(overlay, (21, 21), 11.0)
2027
-
2028
- # Blend with base
2029
- alpha = 0.7 * intensity
2030
- cv2.addWeighted(background, 1.0-alpha, overlay_blurred, alpha, 0, background)
2031
-
2032
- # Add subtle texture
2033
- texture = np.random.randint(0, 15, (height, width, 3), dtype=np.int16)
2034
- texture -= 7 # Center around zero
2035
- textured = background.astype(np.int16) + texture
2036
- background = np.clip(textured, 0, 255).astype(np.uint8)
2037
-
2038
- return background
2039
-
2040
- except Exception as e:
2041
- logger.error(f"Dynamic background creation failed: {e}")
2042
- # Return simple colored background as fallback
2043
- return np.full((height, width, 3), (64, 64, 128), dtype=np.uint8)
2044
-
2045
- def create_themed_background(theme, width=1920, height=1080):
2046
- """Create themed background based on specific categories"""
2047
- try:
2048
- theme_lower = theme.lower()
2049
-
2050
- # Business/Professional themes
2051
- if theme_lower in ["business", "professional", "corporate", "office"]:
2052
- colors = ["#2c3e50", "#34495e", "#3498db"]
2053
- bg_config = {
2054
- "type": "gradient",
2055
- "colors": colors,
2056
- "direction": "diagonal"
2057
- }
2058
- background = create_gradient_background(bg_config, width, height)
2059
-
2060
- # Add subtle grid for professional look
2061
- overlay = background.copy()
2062
- grid_color = tuple(max(0, c - 30) for c in (41, 128, 185)) # Darker blue
2063
-
2064
- grid_spacing = width // 40
2065
- for x in range(0, width, grid_spacing):
2066
- cv2.line(overlay, (x, 0), (x, height), grid_color, 1)
2067
-
2068
- for y in range(0, height, grid_spacing):
2069
- cv2.line(overlay, (0, y), (width, y), grid_color, 1)
2070
-
2071
- cv2.addWeighted(background, 0.9, overlay, 0.1, 0, background)
2072
-
2073
- # Creative/Artistic themes
2074
- elif theme_lower in ["creative", "artistic", "design", "art"]:
2075
- colors = ["#8e44ad", "#9b59b6", "#e74c3c", "#f1c40f"]
2076
-
2077
- # Create colorful base
2078
- background = np.zeros((height, width, 3), dtype=np.uint8)
2079
-
2080
- # Convert colors to BGR
2081
- bgr_colors = []
2082
- for color in colors:
2083
- hex_color = color.lstrip('#')
2084
- rgb = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
2085
- bgr_colors.append(rgb[::-1])
2086
-
2087
- # Add colorful elements
2088
- import random
2089
- random.seed(42)
2090
-
2091
- for _ in range(15):
2092
- center_x = random.randint(width//10, width-width//10)
2093
- center_y = random.randint(height//10, height-height//10)
2094
- radius = random.randint(width//20, width//5)
2095
- color = bgr_colors[random.randint(0, len(bgr_colors)-1)]
2096
-
2097
- cv2.circle(background, (center_x, center_y), radius, color, -1)
2098
-
2099
- # Blur for artistic look
2100
- background = cv2.GaussianBlur(background, (51, 51), 25.0)
2101
-
2102
- # Nature/Outdoor themes
2103
- elif theme_lower in ["nature", "outdoor", "forest", "garden"]:
2104
- colors = ["#27ae60", "#2ecc71", "#1abc9c"]
2105
- bg_config = {
2106
- "type": "gradient",
2107
- "colors": colors,
2108
- "direction": "vertical"
2109
- }
2110
- background = create_gradient_background(bg_config, width, height)
2111
-
2112
- # Add natural texture
2113
- overlay = background.copy()
2114
-
2115
- # Create organic shapes
2116
- import random
2117
- random.seed(42)
2118
-
2119
- for _ in range(10):
2120
- points = []
2121
- for i in range(6):
2122
- angle = i * (2 * np.pi / 6)
2123
- radius = random.randint(width//15, width//8)
2124
- x = int(width/2 + radius * np.cos(angle) + random.randint(-width//10, width//10))
2125
- y = int(height/2 + radius * np.sin(angle) + random.randint(-height//10, height//10))
2126
- points.append([x, y])
2127
-
2128
- points = np.array(points, dtype=np.int32)
2129
- color = (random.randint(30, 60), random.randint(100, 150), random.randint(30, 60))
2130
- cv2.fillPoly(overlay, [points], color)
2131
-
2132
- cv2.addWeighted(background, 0.7, overlay, 0.3, 0, background)
2133
-
2134
- # Technology/Digital themes
2135
- elif theme_lower in ["tech", "digital", "technology", "futuristic"]:
2136
- colors = ["#0a0a0a", "#1a1a1a", "#2980b9"]
2137
- bg_config = {
2138
- "type": "gradient",
2139
- "colors": colors,
2140
- "direction": "radial"
2141
- }
2142
- background = create_gradient_background(bg_config, width, height)
2143
-
2144
- # Add tech elements
2145
- overlay = background.copy()
2146
-
2147
- # Grid lines
2148
- line_color = (0, 120, 215) # Blue
2149
- grid_spacing = width // 30
2150
-
2151
- for x in range(0, width, grid_spacing):
2152
- cv2.line(overlay, (x, 0), (x, height), line_color, 1)
2153
-
2154
- for y in range(0, height, grid_spacing):
2155
- cv2.line(overlay, (0, y), (width, y), line_color, 1)
2156
-
2157
- # Digital circuits
2158
- import random
2159
- random.seed(42)
2160
-
2161
- for _ in range(20):
2162
- start_x = random.randint(0, width)
2163
- start_y = random.randint(0, height)
2164
-
2165
- # Create circuit path
2166
- points = [(start_x, start_y)]
2167
- current_x, current_y = start_x, start_y
2168
-
2169
- for _ in range(5):
2170
- # Horizontal or vertical movement only
2171
- if random.choice([True, False]):
2172
- current_x += random.randint(-width//10, width//10)
2173
- else:
2174
- current_y += random.randint(-height//10, height//10)
2175
-
2176
- current_x = max(0, min(width-1, current_x))
2177
- current_y = max(0, min(height-1, current_y))
2178
- points.append((current_x, current_y))
2179
-
2180
- # Draw circuit
2181
- for i in range(len(points)-1):
2182
- cv2.line(overlay, points[i], points[i+1], (0, 200, 255), 2)
2183
-
2184
- cv2.addWeighted(background, 0.8, overlay, 0.2, 0, background)
2185
-
2186
- # Default/Generic theme
2187
- else:
2188
- # Create simple gradient background
2189
- colors = ["#3498db", "#2ecc71", "#e74c3c"]
2190
- bg_config = {
2191
- "type": "gradient",
2192
- "colors": colors[:2],
2193
- "direction": "diagonal"
2194
- }
2195
- background = create_gradient_background(bg_config, width, height)
2196
-
2197
- return background
2198
-
2199
- except Exception as e:
2200
- logger.error(f"Themed background creation failed: {e}")
2201
- # Return simple colored background as fallback
2202
- return np.full((height, width, 3), (64, 64, 128), dtype=np.uint8)
2203
-
2204
- def enhance_background_quality(background):
2205
- """Apply post-processing enhancements to improve background quality"""
2206
- try:
2207
- if background is None:
2208
- return None
2209
-
2210
- # Convert to float for better precision
2211
- bg_float = background.astype(np.float32)
2212
-
2213
- # Adjust contrast slightly
2214
- mean_val = np.mean(bg_float)
2215
- contrast_enhanced = bg_float + (bg_float - mean_val) * 0.2
2216
-
2217
- # Ensure valid range
2218
- contrast_enhanced = np.clip(contrast_enhanced, 0, 255)
2219
-
2220
- # Convert to PIL for higher quality enhancements
2221
- img_pil = Image.fromarray(contrast_enhanced.astype(np.uint8))
2222
-
2223
- # Enhance saturation slightly
2224
- enhancer = ImageEnhance.Color(img_pil)
2225
- img_pil = enhancer.enhance(1.1)
2226
-
2227
- # Enhance sharpness
2228
- enhancer = ImageEnhance.Sharpness(img_pil)
2229
- img_pil = enhancer.enhance(1.2)
2230
-
2231
- # Convert back to OpenCV format
2232
- enhanced_bg = np.array(img_pil)
2233
-
2234
- # Apply slight noise reduction
2235
- enhanced_bg = cv2.fastNlMeansDenoisingColored(enhanced_bg, None, 3, 3, 7, 21)
2236
-
2237
- return enhanced_bg
2238
-
2239
- except Exception as e:
2240
- logger.error(f"Background enhancement failed: {e}")
2241
- return background # Return original if enhancement fails
2242
-
2243
- def add_vignette_effect(image, intensity=0.3):
2244
- """Add subtle vignette effect to background for professional look"""
2245
- try:
2246
- if image is None:
2247
- return None
2248
-
2249
- # Make a copy to avoid modifying original
2250
- result = image.copy()
2251
- h, w = result.shape[:2]
2252
-
2253
- # Create radial gradient mask
2254
- center_x, center_y = w // 2, h // 2
2255
- radius = min(center_x, center_y)
2256
-
2257
- mask = np.zeros((h, w), dtype=np.float32)
2258
- y, x = np.ogrid[:h, :w]
2259
- mask_squared = (x - center_x)**2 + (y - center_y)**2
2260
-
2261
- # Normalize distances
2262
- mask = mask_squared / (4 * radius**2)
2263
- mask = np.clip(mask, 0, 1)
2264
-
2265
- # Adjust vignette intensity
2266
- mask = intensity * mask
2267
-
2268
- # Apply vignette
2269
- mask = cv2.merge([mask, mask, mask])
2270
- result = result * (1 - mask)
2271
- result = result.astype(np.uint8)
2272
-
2273
- return result
2274
-
2275
- except Exception as e:
2276
- logger.error(f"Vignette effect failed: {e}")
2277
- return image # Return original if vignette fails
2278
-
2279
- def analyze_video_for_optimal_background(video_path, frame_samples=10):
2280
- """Analyze video to determine optimal background color and style"""
2281
- try:
2282
- if not os.path.exists(video_path):
2283
- return None, "Video file not found"
2284
-
2285
- cap = cv2.VideoCapture(video_path)
2286
- if not cap.isOpened():
2287
- return None, "Cannot open video file"
2288
-
2289
- total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
2290
- frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
2291
- frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
2292
-
2293
- if total_frames == 0:
2294
- return None, "Video appears to be empty"
2295
-
2296
- # Sample frames evenly throughout video
2297
- frame_indices = [int(i * total_frames / frame_samples) for i in range(frame_samples)]
2298
-
2299
- dominant_colors = []
2300
- brightness_values = []
2301
- saturation_values = []
2302
-
2303
- for frame_idx in frame_indices:
2304
- cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx)
2305
- ret, frame = cap.read()
2306
-
2307
- if not ret:
2308
- continue
2309
-
2310
- # Convert to HSV for better color analysis
2311
- hsv_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
2312
-
2313
- # Calculate average HSV values
2314
- avg_h = np.mean(hsv_frame[:, :, 0])
2315
- avg_s = np.mean(hsv_frame[:, :, 1])
2316
- avg_v = np.mean(hsv_frame[:, :, 2])
2317
-
2318
- brightness_values.append(avg_v)
2319
- saturation_values.append(avg_s)
2320
-
2321
- # Find dominant color (simple approach)
2322
- small_frame = cv2.resize(frame, (32, 32))
2323
- pixels = small_frame.reshape(-1, 3)
2324
- from sklearn.cluster import KMeans
2325
- kmeans = KMeans(n_clusters=1)
2326
- kmeans.fit(pixels)
2327
- dominant_color = kmeans.cluster_centers_[0].astype(int)
2328
- dominant_colors.append(dominant_color)
2329
-
2330
- cap.release()
2331
-
2332
- # Average dominant colors across frames
2333
- if dominant_colors:
2334
- avg_dominant = np.mean(dominant_colors, axis=0).astype(int)
2335
-
2336
- # Convert BGR to hex
2337
- hex_color = '#{:02x}{:02x}{:02x}'.format(
2338
- avg_dominant[2], avg_dominant[1], avg_dominant[0]
2339
- )
2340
-
2341
- # Determine complementary color for background
2342
- r, g, b = avg_dominant[2], avg_dominant[1], avg_dominant[0]
2343
- complementary = '#{:02x}{:02x}{:02x}'.format(255-r, 255-g, 255-b)
2344
-
2345
- avg_brightness = np.mean(brightness_values)
2346
- avg_saturation = np.mean(saturation_values)
2347
-
2348
- # Recommend background based on analysis
2349
- if avg_brightness > 200: # Very bright video
2350
- background_style = "dark"
2351
- background_colors = ["#1a1a1a", "#2c3e50", "#34495e"]
2352
- elif avg_brightness < 50: # Very dark video
2353
- background_style = "light"
2354
- background_colors = ["#ecf0f1", "#f5f5f5", "#e0e0e0"]
2355
- else:
2356
- # Use complementary color scheme
2357
- background_style = "complementary"
2358
- background_colors = [complementary, hex_color]
2359
-
2360
- # Recommend gradient direction based on video aspect ratio
2361
- if frame_width > frame_height:
2362
- direction = "horizontal"
2363
- else:
2364
- direction = "vertical"
2365
-
2366
- result = {
2367
- "dominant_color": hex_color,
2368
- "complementary_color": complementary,
2369
- "recommended_style": background_style,
2370
- "recommended_colors": background_colors,
2371
- "recommended_direction": direction,
2372
- "video_brightness": float(avg_brightness),
2373
- "video_saturation": float(avg_saturation),
2374
- "video_dimensions": (frame_width, frame_height)
2375
- }
2376
-
2377
- return result, "Analysis successful"
2378
-
2379
- return None, "Failed to analyze video colors"
2380
-
2381
- except Exception as e:
2382
- logger.error(f"Video analysis error: {str(e)}")
2383
- return None, f"Analysis error: {str(e)}"
2384
- def apply_cinematic_color_grading(frame):
2385
- """Apply professional color grading to video frames"""
2386
- try:
2387
- # Convert to float for better precision
2388
- frame_float = frame.astype(np.float32) / 255.0
2389
-
2390
- # Split channels
2391
- b, g, r = cv2.split(frame_float)
2392
-
2393
- # Adjust shadows, midtones and highlights for each channel
2394
- # Red channel - Enhance reds slightly
2395
- r = np.power(r, 0.95) # Raise shadows
2396
- r = r * 1.05 # Boost overall
2397
-
2398
- # Green channel - Natural look
2399
- g = np.power(g, 1.0) # Keep natural
2400
-
2401
- # Blue channel - Slight coolness in shadows
2402
- b = np.power(b, 0.98) # Enhance shadows slightly
2403
-
2404
- # Merge channels
2405
- graded = cv2.merge([b, g, r])
2406
-
2407
- # Increase overall contrast slightly
2408
- contrast = 1.1
2409
- brightness = 0.0
2410
- graded = graded * contrast + brightness
2411
-
2412
- # Apply slight S-curve for cinematic look
2413
- graded = 0.5 + (graded - 0.5) * 1.15
2414
-
2415
- # Clip values to valid range
2416
- graded = np.clip(graded, 0.0, 1.0)
2417
-
2418
- # Convert back to 8-bit
2419
- graded_8bit = (graded * 255.0).astype(np.uint8)
2420
-
2421
- return graded_8bit
2422
-
2423
- except Exception as e:
2424
- logger.error(f"Color grading error: {e}")
2425
- return frame
2426
-
2427
- def enhance_video_quality(input_path, output_path, enhance_level=1):
2428
- """Enhance video quality with professional processing"""
2429
- try:
2430
- if not os.path.exists(input_path):
2431
- return False, "Input video not found"
2432
-
2433
- cap = cv2.VideoCapture(input_path)
2434
- if not cap.isOpened():
2435
- return False, "Cannot open input video"
2436
-
2437
- # Get video properties
2438
- fps = cap.get(cv2.CAP_PROP_FPS)
2439
- width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
2440
- height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
2441
- total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
2442
-
2443
- # Create output video
2444
- fourcc = cv2.VideoWriter_fourcc(*'mp4v')
2445
- out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
2446
-
2447
- if not out.isOpened():
2448
- return False, "Cannot create output video"
2449
-
2450
- frame_count = 0
2451
-
2452
- # Process frames
2453
- while True:
2454
- ret, frame = cap.read()
2455
- if not ret:
2456
- break
2457
-
2458
- # Progress tracking
2459
- if frame_count % 100 == 0:
2460
- logger.info(f"Enhancing: {frame_count}/{total_frames} frames")
2461
-
2462
- # Apply enhancements based on level
2463
- if enhance_level >= 1:
2464
- # Basic enhancements
2465
- frame = cv2.bilateralFilter(frame, 5, 50, 50) # Preserve edges while reducing noise
2466
-
2467
- if enhance_level >= 2:
2468
- # Intermediate enhancements
2469
- frame = apply_cinematic_color_grading(frame)
2470
-
2471
- if enhance_level >= 3:
2472
- # Advanced enhancements (more processing intensive)
2473
- hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
2474
- hsv[:, :, 1] = hsv[:, :, 1] * 1.2 # Increase saturation
2475
- hsv[:, :, 1] = np.clip(hsv[:, :, 1], 0, 255)
2476
- frame = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
2477
-
2478
- # Sharpen the image
2479
- kernel = np.array([[-1, -1, -1], [-1, 9, -1], [-1, -1, -1]])
2480
- frame = cv2.filter2D(frame, -1, kernel)
2481
-
2482
- # Write enhanced frame
2483
- out.write(frame)
2484
- frame_count += 1
2485
-
2486
- # Release resources
2487
- cap.release()
2488
- out.release()
2489
-
2490
- return True, f"Enhanced {frame_count} frames successfully"
2491
-
2492
- except Exception as e:
2493
- logger.error(f"Video enhancement error: {str(e)}")
2494
- return False, f"Enhancement error: {str(e)}"
2495
-
2496
- def extract_best_frame_for_thumbnail(video_path, output_path=None):
2497
- """Extract the best frame from video for thumbnail or preview"""
2498
- try:
2499
- if not os.path.exists(video_path):
2500
- return None, "Video file not found"
2501
-
2502
- cap = cv2.VideoCapture(video_path)
2503
- if not cap.isOpened():
2504
- return None, "Cannot open video file"
2505
-
2506
- total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
2507
- fps = cap.get(cv2.CAP_PROP_FPS)
2508
-
2509
- if total_frames == 0:
2510
- return None, "Video appears to be empty"
2511
-
2512
- # Sample frames every few seconds
2513
- sample_interval = int(fps * 2) # Every 2 seconds
2514
- frame_indices = list(range(0, total_frames, sample_interval))
2515
-
2516
- # Limit to a reasonable number of samples
2517
- if len(frame_indices) > 20:
2518
- frame_indices = frame_indices[:20]
2519
-
2520
- best_frame = None
2521
- best_score = -1
2522
-
2523
- for idx in frame_indices:
2524
- cap.set(cv2.CAP_PROP_POS_FRAMES, idx)
2525
- ret, frame = cap.read()
2526
-
2527
- if not ret:
2528
- continue
2529
-
2530
- # Calculate frame quality score
2531
- score = calculate_frame_quality(frame)
2532
-
2533
- if score > best_score:
2534
- best_score = score
2535
- best_frame = frame.copy()
2536
-
2537
- cap.release()
2538
-
2539
- if best_frame is None:
2540
- return None, "Could not extract good frame"
2541
-
2542
- # Save if output path provided
2543
- if output_path:
2544
- cv2.imwrite(output_path, best_frame)
2545
- return output_path, f"Best frame saved with score {best_score:.2f}"
2546
-
2547
- return best_frame, f"Best frame extracted with score {best_score:.2f}"
2548
-
2549
- except Exception as e:
2550
- logger.error(f"Frame extraction error: {str(e)}")
2551
- return None, f"Extraction error: {str(e)}"
2552
-
2553
- def calculate_frame_quality(frame):
2554
- """Calculate quality score for a video frame"""
2555
- try:
2556
- # Convert to grayscale for some calculations
2557
- gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
2558
-
2559
- # Calculate sharpness using Laplacian
2560
- laplacian = cv2.Laplacian(gray, cv2.CV_64F)
2561
- sharpness = np.var(laplacian)
2562
-
2563
- # Calculate brightness
2564
- brightness = np.mean(gray)
2565
-
2566
- # Calculate contrast
2567
- contrast = np.std(gray)
2568
-
2569
- # Calculate saturation from original frame
2570
- hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
2571
- saturation = np.mean(hsv[:, :, 1])
2572
-
2573
- # Penalize extreme brightness (too dark or too bright)
2574
- brightness_score = 1.0 - abs(brightness - 127.5) / 127.5
2575
-
2576
- # Combine factors with weights
2577
- score = (
2578
- sharpness * 0.4 + # Sharpness is important
2579
- brightness_score * 0.2 + # Moderate brightness
2580
- contrast * 0.2 + # Good contrast
2581
- saturation * 0.2 # Vibrant colors
2582
- )
2583
-
2584
- return score
2585
-
2586
- except Exception as e:
2587
- logger.error(f"Frame quality calculation error: {e}")
2588
- return 0.0
2589
- def optimize_video_for_web(input_path, output_path, target_size_mb=10, preserve_quality=True):
2590
- """Optimize video file size for web sharing while maintaining quality"""
2591
- try:
2592
- if not os.path.exists(input_path):
2593
- return False, "Input video not found"
2594
-
2595
- # Get original file size in MB
2596
- original_size = os.path.getsize(input_path) / (1024 * 1024)
2597
- logger.info(f"Original video size: {original_size:.2f} MB")
2598
-
2599
- # Calculate target bitrate
2600
- if original_size <= target_size_mb:
2601
- logger.info("Video already smaller than target size")
2602
- shutil.copy2(input_path, output_path)
2603
- return True, f"Video already optimized at {original_size:.2f} MB"
2604
-
2605
- # Get video duration
2606
- cap = cv2.VideoCapture(input_path)
2607
- fps = cap.get(cv2.CAP_PROP_FPS)
2608
- frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
2609
- duration_sec = frame_count / fps if fps > 0 else 0
2610
- cap.release()
2611
-
2612
- if duration_sec <= 0:
2613
- return False, "Could not determine video duration"
2614
-
2615
- # Calculate target bitrate in kbps
2616
- target_bitrate = int((target_size_mb * 8 * 1024) / duration_sec)
2617
-
2618
- # Set CRF value based on preserve_quality flag
2619
- crf = 23 if preserve_quality else 28
2620
-
2621
- # Use ffmpeg for high-quality compression
2622
- cmd = [
2623
- "ffmpeg", "-y",
2624
- "-i", input_path,
2625
- "-c:v", "libx264",
2626
- "-preset", "medium",
2627
- "-crf", str(crf),
2628
- "-maxrate", f"{target_bitrate}k",
2629
- "-bufsize", f"{target_bitrate*2}k",
2630
- "-c:a", "aac",
2631
- "-b:a", "128k",
2632
- output_path
2633
- ]
2634
-
2635
- logger.info(f"Running optimization command: {' '.join(cmd)}")
2636
-
2637
- import subprocess
2638
- result = subprocess.run(cmd, capture_output=True, text=True)
2639
-
2640
- if result.returncode != 0:
2641
- logger.error(f"FFMPEG error: {result.stderr}")
2642
- return False, f"Optimization failed: {result.stderr[:100]}..."
2643
-
2644
- # Verify output file exists and has reasonable size
2645
- if not os.path.exists(output_path):
2646
- return False, "Output file was not created"
2647
-
2648
- new_size = os.path.getsize(output_path) / (1024 * 1024)
2649
- logger.info(f"Optimized video size: {new_size:.2f} MB")
2650
-
2651
- size_reduction = ((original_size - new_size) / original_size) * 100
2652
-
2653
- return True, f"Optimized from {original_size:.2f} MB to {new_size:.2f} MB ({size_reduction:.1f}% reduction)"
2654
-
2655
- except Exception as e:
2656
- logger.error(f"Video optimization error: {str(e)}")
2657
- return False, f"Optimization error: {str(e)}"
2658
-
2659
- def generate_report(video_path, background_choice, processing_time, output_path):
2660
- """Generate comprehensive processing report"""
2661
- try:
2662
- if not os.path.exists(video_path) or not os.path.exists(output_path):
2663
- return "Could not generate report - missing files"
2664
-
2665
- # Get video properties
2666
- input_cap = cv2.VideoCapture(video_path)
2667
- input_fps = input_cap.get(cv2.CAP_PROP_FPS)
2668
- input_frames = int(input_cap.get(cv2.CAP_PROP_FRAME_COUNT))
2669
- input_width = int(input_cap.get(cv2.CAP_PROP_FRAME_WIDTH))
2670
- input_height = int(input_cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
2671
- input_size = os.path.getsize(video_path) / (1024 * 1024) # MB
2672
- input_cap.release()
2673
-
2674
- # Get output properties
2675
- output_cap = cv2.VideoCapture(output_path)
2676
- output_fps = output_cap.get(cv2.CAP_PROP_FPS)
2677
- output_frames = int(output_cap.get(cv2.CAP_PROP_FRAME_COUNT))
2678
- output_width = int(output_cap.get(cv2.CAP_PROP_FRAME_WIDTH))
2679
- output_height = int(output_cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
2680
- output_size = os.path.getsize(output_path) / (1024 * 1024) # MB
2681
- output_cap.release()
2682
-
2683
- # Background info
2684
- if background_choice in PROFESSIONAL_BACKGROUNDS:
2685
- bg_name = PROFESSIONAL_BACKGROUNDS[background_choice]["name"]
2686
- bg_type = PROFESSIONAL_BACKGROUNDS[background_choice]["type"]
2687
- else:
2688
- bg_name = background_choice
2689
- bg_type = "custom"
2690
-
2691
- # Format report
2692
- report = f"""
2693
- 📊 Video Processing Report
2694
- ======================================
2695
-
2696
- 📹 Input Video:
2697
- - Resolution: {input_width}x{input_height}
2698
- - Frames: {input_frames}
2699
- - FPS: {input_fps:.2f}
2700
- - Size: {input_size:.2f} MB
2701
-
2702
- 🎬 Output Video:
2703
- - Resolution: {output_width}x{output_height}
2704
- - Frames: {output_frames}
2705
- - FPS: {output_fps:.2f}
2706
- - Size: {output_size:.2f} MB
2707
-
2708
- 🎨 Background:
2709
- - Choice: {bg_name}
2710
- - Type: {bg_type}
2711
-
2712
- ⏱️ Processing:
2713
- - Total time: {processing_time:.2f} seconds
2714
- - Frames per second: {output_frames/processing_time if processing_time > 0 else 0:.2f}
2715
-
2716
- 💾 Efficiency:
2717
- - Size reduction: {(input_size - output_size)/input_size*100:.2f}%
2718
- - Storage saved: {input_size - output_size:.2f} MB
2719
-
2720
- 🔍 Quality Analysis:
2721
- - Preservation: High (TWO-STAGE processing)
2722
- - Method: SAM2 + MatAnyone segmentation
2723
- - Edge quality: Enhanced (bilateral filtering)
2724
-
2725
- Report generated: {time.strftime("%Y-%m-%d %H:%M:%S")}
2726
- """
2727
-
2728
- return report
2729
-
2730
- except Exception as e:
2731
- logger.error(f"Report generation error: {str(e)}")
2732
- return f"Error generating report: {str(e)}"
2733
-
2734
- def get_memory_usage():
2735
- """Get current memory usage for monitoring"""
2736
- try:
2737
- import psutil
2738
- process = psutil.Process(os.getpid())
2739
- memory_info = process.memory_info()
2740
-
2741
- # Convert to MB
2742
- rss_mb = memory_info.rss / (1024 * 1024)
2743
- vms_mb = memory_info.vms / (1024 * 1024)
2744
-
2745
- # Get GPU memory if available
2746
- gpu_memory = None
2747
- if torch.cuda.is_available():
2748
- try:
2749
- gpu_memory = torch.cuda.memory_allocated() / (1024 * 1024)
2750
- except:
2751
- pass
2752
-
2753
- memory_data = {
2754
- "rss_mb": rss_mb,
2755
- "vms_mb": vms_mb,
2756
- "gpu_mb": gpu_memory,
2757
- "percent": process.memory_percent(),
2758
- }
2759
-
2760
- return memory_data
2761
-
2762
- except Exception as e:
2763
- logger.warning(f"Could not get memory usage: {e}")
2764
- return {"error": str(e)}
2765
-
2766
- def optimize_memory_usage():
2767
- """Optimize memory usage by cleaning up unused resources"""
2768
- try:
2769
- # Clear PyTorch cache
2770
- if torch.cuda.is_available():
2771
- torch.cuda.empty_cache()
2772
-
2773
- # Run garbage collector
2774
- gc.collect()
2775
-
2776
- # Clear OpenCV cache
2777
- cv2.ocl.setUseOpenCL(False)
2778
-
2779
- return True
2780
- except Exception as e:
2781
- logger.warning(f"Memory optimization failed: {e}")
2782
- return False
2783
-
2784
- def validate_video_file(video_path):
2785
- """Validate video file format and basic properties"""
2786
- if not video_path or not os.path.exists(video_path):
2787
- return False, "Video file not found"
2788
-
2789
- try:
2790
- cap = cv2.VideoCapture(video_path)
2791
- if not cap.isOpened():
2792
- return False, "Cannot open video file"
2793
-
2794
- frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
2795
- if frame_count == 0:
2796
- return False, "Video appears to be empty"
2797
-
2798
- cap.release()
2799
- return True, "Video file valid"
2800
- except Exception as e:
2801
- return False, f"Error validating video: {str(e)
2802
-
2803
- def validate_video_file(video_path):
2804
- """Validate video file format and basic properties"""
2805
- if not video_path or not os.path.exists(video_path):
2806
- return False, "Video file not found"
2807
-
2808
- try:
2809
- cap = cv2.VideoCapture(video_path)
2810
- if not cap.isOpened():
2811
- return False, "Cannot open video file"
2812
-
2813
- frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
2814
- if frame_count == 0:
2815
- return False, "Video appears to be empty"
2816
-
2817
- cap.release()
2818
- return True, "Video file valid"
2819
- except Exception as e:
2820
- return False, f"Error validating video: {str(e)}"
2821
-
2822
- def cleanup_temp_files():
2823
- """Clean up temporary files to free disk space"""
2824
- try:
2825
- temp_patterns = [
2826
- "/tmp/processed_video_*.mp4",
2827
- "/tmp/final_output_*.mp4",
2828
- "/tmp/greenscreen_*.mp4",
2829
- "/tmp/gradient_*.png",
2830
- "/tmp/*.pt", # Model checkpoints
2831
- ]
2832
-
2833
- import glob
2834
- for pattern in temp_patterns:
2835
- for file_path in glob.glob(pattern):
2836
- try:
2837
- if os.path.exists(file_path):
2838
- # Only delete files older than 1 hour
2839
- if time.time() - os.path.getmtime(file_path) > 3600:
2840
- os.remove(file_path)
2841
- logger.info(f"Cleaned up: {file_path}")
2842
- except Exception as e:
2843
- logger.warning(f"Could not clean up {file_path}: {e}")
2844
- except Exception as e:
2845
- logger.warning(f"Cleanup error: {e}")
2846
-
2847
- def get_system_info():
2848
- """Get system information for debugging"""
2849
- try:
2850
- import platform
2851
- try:
2852
- import psutil
2853
- memory_gb = psutil.virtual_memory().total / (1024**3)
2854
- except ImportError:
2855
- memory_gb = "Unknown (psutil not available)"
2856
-
2857
- info = {
2858
- "platform": platform.platform(),
2859
- "python": platform.python_version(),
2860
- "cpu_count": os.cpu_count(),
2861
- "memory_gb": memory_gb,
2862
- "cuda_available": torch.cuda.is_available(),
2863
- }
2864
-
2865
- if torch.cuda.is_available():
2866
- try:
2867
- info["gpu_name"] = torch.cuda.get_device_name(0)
2868
- info["gpu_memory_gb"] = torch.cuda.get_device_properties(0).total_memory / (1024**3)
2869
- except:
2870
- info["gpu_name"] = "GPU Available"
2871
- info["gpu_memory_gb"] = "Unknown"
2872
-
2873
- return info
2874
- except Exception as e:
2875
- logger.warning(f"Could not get system info: {e}")
2876
- return {"error": str(e)}
2877
-
2878
- def check_dependencies():
2879
- """Check if all required dependencies are available"""
2880
- try:
2881
- required_packages = [
2882
- ('cv2', 'opencv-python'),
2883
- ('PIL', 'Pillow'),
2884
- ('numpy', 'numpy'),
2885
- ('torch', 'torch'),
2886
- ('gradio', 'gradio'),
2887
- ('requests', 'requests')
2888
- ]
2889
-
2890
- missing_packages = []
2891
- for import_name, package_name in required_packages:
2892
- try:
2893
- __import__(import_name)
2894
- logger.info(f"✅ {package_name} available")
2895
- except ImportError:
2896
- missing_packages.append(package_name)
2897
- logger.warning(f"❌ {package_name} missing")
2898
-
2899
- if missing_packages:
2900
- logger.error(f"Missing required packages: {', '.join(missing_packages)}")
2901
- return False, missing_packages
2902
-
2903
- return True, []
2904
- except Exception as e:
2905
- logger.error(f"Error checking dependencies: {e}")
2906
- return False, [str(e)]
2907
-
2908
- def create_directories():
2909
- """Create necessary directories for the application"""
2910
- try:
2911
- directories = [
2912
- "/tmp/MyAvatar",
2913
- "/tmp/MyAvatar/My_Videos",
2914
- os.path.expanduser("~/.cache/sam2"),
2915
- ]
2916
-
2917
- for directory in directories:
2918
- os.makedirs(directory, exist_ok=True)
2919
- logger.info(f"📁 Created/verified directory: {directory}")
2920
-
2921
- return True
2922
- except Exception as e:
2923
- logger.error(f"Error creating directories: {e}")
2924
- return False
2925
- def initialize_application():
2926
- """Initialize the application with all necessary setup"""
2927
- try:
2928
- logger.info("🔧 Initializing application...")
2929
-
2930
- # Check dependencies
2931
- deps_ok, missing = check_dependencies()
2932
- if not deps_ok:
2933
- logger.error(f"❌ Missing dependencies: {missing}")
2934
- return False
2935
-
2936
- # Create directories
2937
- if not create_directories():
2938
- logger.error("❌ Failed to create necessary directories")
2939
- return False
2940
-
2941
- # Log system information
2942
- try:
2943
- sys_info = get_system_info()
2944
- logger.info(f"🖥️ System: {sys_info}")
2945
- except Exception as e:
2946
- logger.warning(f"Could not log system information: {e}")
2947
-
2948
- # Clean up old temporary files
2949
- cleanup_temp_files()
2950
-
2951
- logger.info("✅ Application initialized successfully")
2952
- return True
2953
-
2954
- except Exception as e:
2955
- logger.error(f"❌ Application initialization failed: {e}")
2956
- return False
2957
-
2958
- # Additional utility functions for advanced users
2959
- def batch_process_videos(video_dir, output_dir, background_choice="office_modern"):
2960
- """
2961
- Batch process multiple videos (for advanced users)
2962
- This function is not connected to the UI but can be used programmatically
2963
- """
2964
- try:
2965
- if not models_loaded:
2966
- logger.error("Models not loaded. Call download_and_setup_models() first.")
2967
- return False
2968
-
2969
- video_files = []
2970
- for ext in ['*.mp4', '*.mov', '*.avi']:
2971
- video_files.extend(Path(video_dir).glob(ext))
2972
-
2973
- if not video_files:
2974
- logger.warning(f"No video files found in {video_dir}")
2975
- return False
2976
-
2977
- os.makedirs(output_dir, exist_ok=True)
2978
-
2979
- for i, video_path in enumerate(video_files):
2980
- try:
2981
- logger.info(f"Processing {i+1}/{len(video_files)}: {video_path.name}")
2982
-
2983
- # Process video (simplified version without Gradio progress)
2984
- result_path, message = process_video_hq(str(video_path), background_choice, None)
2985
-
2986
- if result_path:
2987
- # Copy to output directory
2988
- output_path = Path(output_dir) / f"processed_{video_path.name}"
2989
- shutil.copy2(result_path, output_path)
2990
- logger.info(f"✅ Saved: {output_path}")
2991
- else:
2992
- logger.error(f"❌ Failed to process {video_path.name}: {message}")
2993
-
2994
- except Exception as e:
2995
- logger.error(f"Error processing {video_path.name}: {e}")
2996
-
2997
- return True
2998
-
2999
- except Exception as e:
3000
- logger.error(f"Batch processing error: {e}")
3001
- return False
3002
-
3003
- def get_available_backgrounds():
3004
- """Get list of available professional backgrounds"""
3005
- return {key: config["name"] for key, config in PROFESSIONAL_BACKGROUNDS.items()}
3006
-
3007
- def create_custom_gradient(colors, direction="vertical", width=1920, height=1080):
3008
- """
3009
- Create a custom gradient background
3010
-
3011
- Args:
3012
- colors: List of hex colors (e.g., ["#ff0000", "#00ff00"])
3013
- direction: "vertical", "horizontal", "diagonal", "radial"
3014
- width, height: Dimensions
3015
-
3016
- Returns:
3017
- numpy array of the generated background
3018
- """
3019
- try:
3020
- bg_config = {
3021
- "type": "gradient",
3022
- "colors": colors,
3023
- "direction": direction
3024
- }
3025
- return create_gradient_background(bg_config, width, height)
3026
- except Exception as e:
3027
- logger.error(f"Error creating custom gradient: {e}")
3028
- return None
3029
-
3030
- def main():
3031
- """Main application entry point"""
3032
- try:
3033
- print("🎬 Cinema-Quality Video Background Replacement")
3034
- print("=" * 50)
3035
-
3036
- # Initialize application
3037
- os.makedirs("/tmp/MyAvatar/My_Videos/", exist_ok=True)
3038
- os.makedirs(os.path.expanduser("~/.cache/sam2"), exist_ok=True)
3039
-
3040
- print("🚀 Features:")
3041
- print(" • SAM2 + MatAnyone AI models")
3042
- print(" • TWO-STAGE processing (Original → Green Screen → Final)")
3043
- print(" • 4 background methods (Upload/Professional/Colors/AI)")
3044
- print(" • Multi-fallback loading system")
3045
- print(" • Cinema-quality processing")
3046
- print(" • Enhanced stability & error handling")
3047
- print("=" * 50)
3048
-
3049
- # Create and launch interface
3050
- logger.info("🌐 Creating Gradio interface...")
3051
- demo = create_interface()
3052
-
3053
- logger.info("🚀 Launching application...")
3054
-
3055
- demo.launch(
3056
- server_name="0.0.0.0",
3057
- server_port=7860,
3058
- share=True,
3059
- show_error=True
3060
- )
3061
-
3062
- except KeyboardInterrupt:
3063
- logger.info("🛑 Application stopped by user")
3064
- print("\n🛑 Application stopped by user")
3065
- except Exception as e:
3066
- logger.error(f"❌ Application failed to start: {e}")
3067
- logger.error(f"Full traceback: {traceback.format_exc()}")
3068
- print(f"❌ Application failed to start: {e}")
3069
- print("Check logs for detailed error information.")
3070
-
3071
- # Export main functions for external use
3072
- __all__ = [
3073
- 'download_and_setup_models',
3074
- 'process_video_hq',
3075
- 'create_professional_background',
3076
- 'get_available_backgrounds',
3077
- 'create_custom_gradient',
3078
- 'batch_process_videos',
3079
- 'validate_video_file',
3080
- 'create_procedural_background',
3081
- 'create_abstract_background',
3082
- 'create_minimalist_background',
3083
- 'create_corporate_background',
3084
- 'create_nature_background',
3085
- 'create_artistic_background',
3086
- 'create_green_screen_background'
3087
- ]
3088
-
3089
- if __name__ == "__main__":
3090
- main()
 
26
  from typing import Optional, Tuple, Dict, Any
27
  import logging
28
 
29
+ # Import utility functions
30
+ from utils import *
31
+
32
  # Fix OpenMP threads issue - remove problematic environment variable
33
  try:
34
  if 'OMP_NUM_THREADS' in os.environ:
 
740
  logger.error(f"Mask refinement error: {e}")
741
  # Return original mask if refinement fails
742
  return mask if len(mask.shape) == 2 else cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
743
+
744
  def create_green_screen_background(frame):
745
  """Create green screen background (Stage 1 of two-stage process)"""
746
  h, w = frame.shape[:2]
 
1189
  else:
1190
  return "⏳ Models not loaded. Click 'Load Models' for ENHANCED cinema-quality processing."
1191
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1192
  def create_interface():
1193
  """Create enhanced Gradio interface with comprehensive features and 4-method background system"""
1194
 
 
1366
  padding: 12px 8px;
1367
  border: 1px solid #ddd;
1368
  border-radius: 6px;
1369
+ text-align: center;