akashyadav758
Dockerize stack, strip watermarking, add navigator monitor
c6e6dac
Raw
History Blame Contribute Delete
8.03 kB
#!/usr/bin/env python3
"""CLI β€” Full video editor (V2V with segmentation + merge).
Usage:
python -m cli.edit "Make it anime style" -m MEDIA_ID -v video.mp4
python -m cli.edit "Cyberpunk neon" -m MEDIA_ID --total-seconds 45 -o output/
"""
import argparse
import asyncio
import logging
import os
import subprocess
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from omniflash import ExtensionBridge, poll_status, download_video, ASPECTS, DEFAULT_PROJECT
from omniflash.generators.v2v import edit_video
from omniflash.generators.common import build_client_context, build_generation_context
from omniflash.config import ENDPOINTS, CLIENT_CTX, FPS, SEGMENT_DURATION
import random
log = logging.getLogger("cli.edit")
def get_video_duration(video_path):
"""Get video duration in seconds using ffprobe."""
try:
r = subprocess.run(
["ffprobe", "-v", "quiet", "-show_entries", "format=duration",
"-of", "csv=p=0", video_path],
capture_output=True, text=True
)
return float(r.stdout.strip())
except Exception:
try:
size = os.path.getsize(video_path)
return max(10, size / (1024 * 1024) * 3)
except Exception:
return None
def get_video_fps(video_path):
"""Get video FPS using ffprobe."""
try:
r = subprocess.run(
["ffprobe", "-v", "quiet", "-select_streams", "v:0",
"-show_entries", "stream=r_frame_rate",
"-of", "csv=p=0", video_path],
capture_output=True, text=True
)
fps_str = r.stdout.strip()
if "/" in fps_str:
num, den = fps_str.split("/")
return float(num) / float(den)
return float(fps_str)
except Exception:
return FPS
async def edit_segment(bridge, prompt, aspect, project_id, media_id,
start_frame, end_frame, segment_num, output_dir):
"""Edit a single segment and download."""
body = {
"mediaGenerationContext": build_generation_context("BLOCK_SILENCED_VIDEOS"),
"clientContext": build_client_context(project_id),
"requests": [{
"aspectRatio": aspect,
"textInput": {"structuredPrompt": {"parts": [{"text": prompt}]}},
"videoModelKey": "abra_edit",
"seed": random.randint(1, 9999),
"metadata": {},
"videoInput": {
"mediaId": media_id,
"startFrameIndex": start_frame,
"endFrameIndex": end_frame,
},
}],
}
start_sec = start_frame / FPS
end_sec = end_frame / FPS
log.info("βœ‚οΈ Segment %d: %.0fs-%.0fs (frames %d-%d)",
segment_num, start_sec, end_sec, start_frame, end_frame)
result = await bridge.api_request(ENDPOINTS["generate_edit"], body)
status = result.get("status", 0)
if status != 200:
err = result.get("data", {})
if isinstance(err, dict):
err = err.get("error", {}).get("message", result.get("error", "Unknown"))
log.error("❌ Segment %d failed (%s): %s", segment_num, status, err)
return None
data = result.get("data", {})
media_list = data.get("media", [])
if not media_list:
log.error("❌ No media for segment %d", segment_num)
return None
result_media_id = media_list[0].get("name")
credits = data.get("remainingCredits", "?")
log.info("βœ… Segment %d submitted! media_id=%s, credits=%s",
segment_num, result_media_id[:12], credits)
if not await poll_status(bridge, result_media_id, project_id):
return None
out_path = os.path.join(output_dir, f"segment_{segment_num:03d}.mp4")
temp_dir = os.path.join(output_dir, ".temp")
os.makedirs(temp_dir, exist_ok=True)
temp_path = os.path.join(temp_dir, f"segment_{segment_num:03d}.mp4")
if await download_video(bridge, result_media_id, temp_path):
# Watermark removal disabled β€” save the raw downloaded segment as-is.
os.replace(temp_path, out_path)
# Cleanup empty .temp dir
try:
os.rmdir(temp_dir)
except OSError:
pass
return out_path
return None
async def run(args):
aspect = ASPECTS.get(args.aspect, "VIDEO_ASPECT_RATIO_PORTRAIT")
total_seconds = args.total_seconds
fps = FPS
if args.video_file and os.path.exists(args.video_file):
if not total_seconds:
total_seconds = get_video_duration(args.video_file)
log.info("πŸ“Ή Video: %s (%.1fs)", args.video_file, total_seconds or 0)
fps = get_video_fps(args.video_file)
log.info("πŸ“Ή FPS: %.1f", fps)
if not total_seconds:
log.error("❌ Can't determine video duration. Use --total-seconds")
return
os.makedirs(args.output, exist_ok=True)
# Calculate segments
segments = []
current = 0
seg_num = 1
while current < total_seconds:
start_frame = int(current * fps)
end_frame = int(min(current + SEGMENT_DURATION, total_seconds) * fps)
if end_frame <= start_frame:
break
segments.append((seg_num, start_frame, end_frame))
current += SEGMENT_DURATION
seg_num += 1
log.info("πŸ“‹ Total: %.1fs β†’ %d segments of %ds each",
total_seconds, len(segments), SEGMENT_DURATION)
log.info("─" * 50)
bridge = ExtensionBridge()
await bridge.start()
if not await bridge.wait_for_extension(timeout=30):
return
# Process segments (max 5 concurrent)
semaphore = asyncio.Semaphore(5)
results = [None] * len(segments)
async def process_segment(idx, seg_num, start_frame, end_frame):
async with semaphore:
out = await edit_segment(
bridge, args.prompt, aspect, args.project_id,
args.media_id, start_frame, end_frame, seg_num, args.output
)
results[idx] = out
tasks = [
asyncio.create_task(process_segment(idx, sn, sf, ef))
for idx, (sn, sf, ef) in enumerate(segments)
]
await asyncio.gather(*tasks)
await bridge.close()
saved = [r for r in results if r]
log.info("─" * 50)
log.info("πŸŽ‰ Done! %d/%d segments saved to %s/", len(saved), len(segments), args.output)
for f in saved:
log.info(" βœ… %s", os.path.basename(f))
# Merge with ffmpeg
if len(saved) > 1 and args.merge:
merge_path = os.path.join(args.output, "merged_output.mp4")
log.info("πŸ”— Merging %d segments...", len(saved))
try:
concat_file = os.path.join(args.output, "concat.txt")
with open(concat_file, "w") as f:
for s in saved:
f.write(f"file '{os.path.abspath(s)}'\n")
subprocess.run([
"ffmpeg", "-y", "-f", "concat", "-safe", "0",
"-i", concat_file, "-c", "copy", merge_path
], capture_output=True)
os.remove(concat_file)
log.info("βœ… Merged: %s", merge_path)
except Exception as e:
log.warning("⚠️ Merge failed (ffmpeg needed): %s", e)
def main():
parser = argparse.ArgumentParser(description="Omni Flash β€” Full Video Editor")
parser.add_argument("prompt", help="Edit prompt")
parser.add_argument("--media-id", "-m", required=True, help="Flow media ID")
parser.add_argument("--video-file", "-v", help="Local video (for duration/fps)")
parser.add_argument("--total-seconds", "-t", type=float)
parser.add_argument("--output", "-o", default="output", help="Output directory")
parser.add_argument("--aspect", "-a", choices=["portrait", "landscape"], default="portrait")
parser.add_argument("--merge", action="store_true")
parser.add_argument("--project-id", "-p", default=DEFAULT_PROJECT)
args = parser.parse_args()
asyncio.run(run(args))
if __name__ == "__main__":
main()