jebin2 commited on
Commit
490d656
·
1 Parent(s): 8ad2957

resize on download

Browse files
Files changed (2) hide show
  1. src/automation.py +9 -6
  2. src/utils.py +81 -1
src/automation.py CHANGED
@@ -304,24 +304,24 @@ class ContentAutomation:
304
  if assets.get("hook_video") and assets["hook_video"].get("video_url"):
305
  hook_url = assets["hook_video"]["video_url"]
306
  download_tasks.append(
307
- self._download_with_fallback(hook_url, "hook_video.mp4", assets["hook_video"], "local_path")
308
  )
309
  # VEO library videos
310
  if assets["hook_video"].get("veo_video_data") and assets["hook_video"].get("veo_video_data").get("video_url"):
311
  veo_hook_url = assets["hook_video"]["veo_video_data"]["video_url"]
312
  download_tasks.append(
313
- self._download_with_fallback(veo_hook_url, "veo_hook_url.mp4", assets["hook_video"]["veo_video_data"], "local_path")
314
  )
315
 
316
  # Download library videos
317
  for i, video in enumerate(assets.get("selected_videos", [])):
318
  if video.get("url"):
319
  download_tasks.append(
320
- self._download_with_fallback(video["url"], f"library_video_{i}.mp4", video, "local_path")
321
  )
322
  if video.get("alternate_url"):
323
  download_tasks.append(
324
- self._download_with_fallback(video["alternate_url"], f"library_all_video_alternate_url_{i}.mp4", video, "alternate_url_local_path")
325
  )
326
 
327
  # Download library videos
@@ -343,12 +343,15 @@ class ContentAutomation:
343
  # Verify all required assets have local_path
344
  self._verify_assets_downloaded(assets)
345
 
346
- async def _download_with_fallback(self, url: str, filename: str, target_dict: Dict, key: str = "local_path"):
347
  """Download file with fallback to ensure local_path is always set"""
348
  try:
349
  local_path = await self.api_clients.download_file(url, filename)
 
 
 
 
350
  target_dict[key] = local_path
351
- await self.api_clients.upload_to_temp_gcs(local_path)
352
  logger.info(f"✓ Downloaded {filename}")
353
  return local_path
354
  except Exception as e:
 
304
  if assets.get("hook_video") and assets["hook_video"].get("video_url"):
305
  hook_url = assets["hook_video"]["video_url"]
306
  download_tasks.append(
307
+ self._download_with_fallback(hook_url, "hook_video.mp4", assets["hook_video"], "local_path", resize=True)
308
  )
309
  # VEO library videos
310
  if assets["hook_video"].get("veo_video_data") and assets["hook_video"].get("veo_video_data").get("video_url"):
311
  veo_hook_url = assets["hook_video"]["veo_video_data"]["video_url"]
312
  download_tasks.append(
313
+ self._download_with_fallback(veo_hook_url, "veo_hook_url.mp4", assets["hook_video"]["veo_video_data"], "local_path", resize=True, remove_black_padding=True)
314
  )
315
 
316
  # Download library videos
317
  for i, video in enumerate(assets.get("selected_videos", [])):
318
  if video.get("url"):
319
  download_tasks.append(
320
+ self._download_with_fallback(video["url"], f"library_video_{i}.mp4", video, "local_path", resize=True)
321
  )
322
  if video.get("alternate_url"):
323
  download_tasks.append(
324
+ self._download_with_fallback(video["alternate_url"], f"library_all_video_alternate_url_{i}.mp4", video, "alternate_url_local_path", resize=True)
325
  )
326
 
327
  # Download library videos
 
343
  # Verify all required assets have local_path
344
  self._verify_assets_downloaded(assets)
345
 
346
+ async def _download_with_fallback(self, url: str, filename: str, target_dict: Dict, key: str = "local_path", resize: bool = False, remove_black_padding: bool = False):
347
  """Download file with fallback to ensure local_path is always set"""
348
  try:
349
  local_path = await self.api_clients.download_file(url, filename)
350
+ if remove_black_padding:
351
+ utils.remove_black_padding(local_path, overwrite=True)
352
+ if resize:
353
+ utils.resize_video(local_path, overwrite=True)
354
  target_dict[key] = local_path
 
355
  logger.info(f"✓ Downloaded {filename}")
356
  return local_path
357
  except Exception as e:
src/utils.py CHANGED
@@ -12,6 +12,8 @@ import imagehash
12
  import subprocess
13
  import os
14
  import uuid
 
 
15
 
16
  class ColoredFormatter(logging.Formatter):
17
  """Custom formatter with colors for terminal output"""
@@ -640,4 +642,82 @@ def interpolate_video(input_path: str, target_duration: float = 4.0, fps: int =
640
  ]
641
 
642
  subprocess.run(cmd, check=True)
643
- return output_path
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  import subprocess
13
  import os
14
  import uuid
15
+ import re
16
+ import shutil
17
 
18
  class ColoredFormatter(logging.Formatter):
19
  """Custom formatter with colors for terminal output"""
 
642
  ]
643
 
644
  subprocess.run(cmd, check=True)
645
+ return output_path
646
+
647
+ def resize_video(input_path: str, target_width: int = 1080, target_height: int = 1920, overwrite: bool = False) -> str:
648
+ """
649
+ Resize a video to the given resolution (default 1080x1920) using FFmpeg.
650
+ If overwrite=True, replaces the original file safely after successful conversion.
651
+ """
652
+ if not os.path.exists(input_path):
653
+ raise FileNotFoundError(f"Input video not found: {input_path}")
654
+
655
+ temp_output = os.path.join("/tmp", f"{uuid.uuid4().hex}.mp4")
656
+
657
+ # FFmpeg resize command (output goes to /tmp first)
658
+ cmd = [
659
+ "ffmpeg", "-y", "-i", input_path,
660
+ "-vf", f"scale={target_width}:{target_height}",
661
+ "-c:v", "libx264", "-crf", "18", "-preset", "slow",
662
+ "-c:a", "copy",
663
+ temp_output
664
+ ]
665
+
666
+ # Run FFmpeg process
667
+ result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
668
+ if result.returncode != 0:
669
+ raise RuntimeError(f"FFmpeg failed:\n{result.stderr.decode('utf-8', errors='ignore')}")
670
+
671
+ # Overwrite original safely if requested
672
+ if overwrite:
673
+ shutil.move(temp_output, input_path)
674
+ return input_path
675
+
676
+ return temp_output
677
+
678
+ def remove_black_padding(input_path: str, overwrite: bool = False) -> str:
679
+ """
680
+ Automatically detect and remove black padding (crop only) using FFmpeg.
681
+ Saves to /tmp with a unique UUID filename unless overwrite=True.
682
+
683
+ Args:
684
+ input_path (str): Path to the input video.
685
+ overwrite (bool): If True, safely replace the original file.
686
+
687
+ Returns:
688
+ str: Path to the cropped video.
689
+ """
690
+ if not os.path.exists(input_path):
691
+ raise FileNotFoundError(f"Input video not found: {input_path}")
692
+
693
+ # Step 1: Detect crop parameters using cropdetect
694
+ detect_cmd = [
695
+ "ffmpeg", "-i", input_path, "-vf", "cropdetect=24:16:0",
696
+ "-frames:v", "500", "-f", "null", "-"
697
+ ]
698
+ result = subprocess.run(detect_cmd, stderr=subprocess.PIPE, text=True)
699
+ matches = re.findall(r"crop=\S+", result.stderr)
700
+
701
+ if not matches:
702
+ raise RuntimeError("Could not detect any crop region — video may not have black bars.")
703
+
704
+ # Get most frequent crop value
705
+ crop_value = max(set(matches), key=matches.count)
706
+ logger.info(f"Detected crop: {crop_value}")
707
+
708
+ # Step 2: Create temp output file
709
+ tmp_output = os.path.join("/tmp", f"{uuid.uuid4().hex}_cropped.mp4")
710
+
711
+ # Step 3: Run FFmpeg crop command
712
+ crop_cmd = ["ffmpeg", "-y", "-i", input_path, "-vf", crop_value, "-c:a", "copy", tmp_output]
713
+ crop_proc = subprocess.run(crop_cmd, stderr=subprocess.PIPE, text=True)
714
+
715
+ if crop_proc.returncode != 0:
716
+ raise RuntimeError(f"FFmpeg crop failed:\n{crop_proc.stderr}")
717
+
718
+ # Step 4: Handle overwrite safely
719
+ if overwrite:
720
+ shutil.move(tmp_output, input_path)
721
+ return input_path
722
+
723
+ return tmp_output