File size: 6,578 Bytes
7a5c845 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | #!/usr/bin/env python3
from __future__ import annotations
import json
import logging
import re
import sys
from pathlib import Path
from _gemini_runner import GeminiRunner, load_repo_module
BASE = load_repo_module("qwen_multi_event_base", "qwen/generate_multi_event_annotations.py")
def strip_detailed_caption_from_global_prompt(prompt: str) -> str:
cleaned = prompt
cleaned = cleaned.replace(
'- The detailed caption should be about 500-800 words.\n',
"",
)
cleaned = cleaned.replace(
'- The detailed caption may synthesize and reuse the structured fields plus stable recurring evidence from the event summaries, but it must not turn into a segment-by-segment narration.\n',
"",
)
cleaned = re.sub(
r',\n(\s*)"detailed_caption": "About 500-800 words\."\n',
"\n",
cleaned,
)
return cleaned
_ORIGINAL_BUILD_GLOBAL_CONTEXT_PROMPT = BASE.build_global_context_prompt
_ORIGINAL_BUILD_GLOBAL_REFINE_PROMPT = BASE.build_global_refine_prompt
_ORIGINAL_COERCE_GLOBAL_PAYLOAD = BASE.coerce_global_payload
def _gemini_build_global_context_prompt(*args, **kwargs):
prompt = _ORIGINAL_BUILD_GLOBAL_CONTEXT_PROMPT(*args, **kwargs)
return (
f"{prompt}\n\n"
"IMPORTANT FOR GEMINI:\n"
"- Return one single JSON object for the entire stitched video.\n"
"- Never return a JSON array.\n"
"- Never return one object per clip, per segment, or per event.\n"
"- The stitched video may contain multiple chronological event clips, but this context pass must summarize the whole stitched video into one object only.\n"
"- Do not return object-detection output, tracking output, grounding output, OCR output, or visual tool output.\n"
"- Never return keys such as box_2d, bbox, label, labels, mask, polygon, points, score, or tracking_id unless the requested schema explicitly asks for them.\n"
"- Never return timestamped clip-local records such as event_clip_id, start_time_seconds, or end_time_seconds in this context pass.\n"
"- If uncertain, still fill the requested schema directly with high-confidence whole-video annotations rather than switching to detection-style results.\n"
)
def _gemini_build_global_refine_prompt(*args, **kwargs):
return strip_detailed_caption_from_global_prompt(
_ORIGINAL_BUILD_GLOBAL_REFINE_PROMPT(*args, **kwargs)
)
def _gemini_coerce_global_payload(*args, **kwargs):
payload = _ORIGINAL_COERCE_GLOBAL_PAYLOAD(*args, **kwargs)
payload.pop("detailed_caption", None)
return payload
BASE.build_global_context_prompt = _gemini_build_global_context_prompt
BASE.build_global_refine_prompt = _gemini_build_global_refine_prompt
BASE.coerce_global_payload = _gemini_coerce_global_payload
def main() -> int:
args = BASE.parse_args()
BASE.setup_logging(args.log_level)
input_path = Path(args.input).expanduser().resolve()
output_path = Path(args.output).expanduser().resolve()
camera_motion_guide_path = Path(args.camera_motion_guide).expanduser().resolve()
suffixes = BASE.parse_video_suffixes(args.video_suffixes)
allow_uuids = BASE.load_uuid_allow_list(args.uuids, args.uuids_file)
camera_motion_guide = BASE.load_camera_motion_guide(camera_motion_guide_path)
groups = BASE.collect_segment_groups(
input_path=input_path,
recursive=args.recursive,
suffixes=suffixes,
allow_uuids=allow_uuids,
)
if not groups:
raise RuntimeError("No valid segmented videos were found.")
uuids = list(groups)
if args.limit is not None:
uuids = uuids[: args.limit]
pending_for_print = uuids
if args.resume and output_path.exists():
existing_videos_for_print = BASE.load_existing_output(
output_path=output_path,
overwrite=False,
resume=True,
)
pending_for_print = [uuid for uuid in uuids if uuid not in existing_videos_for_print]
if args.print_uuids_json:
print(json.dumps(pending_for_print, ensure_ascii=False))
return 0
uuids = BASE.apply_uuid_shard(
uuids=pending_for_print if args.resume else uuids,
num_shards=args.num_shards,
shard_index=args.shard_index,
)
logging.info("Discovered %d UUID groups.", len(uuids))
for uuid in uuids:
logging.info("UUID %s has %d segments.", uuid, len(groups[uuid]))
if args.dry_run:
logging.info("Dry run complete. Gemini model was not called.")
return 0
existing_videos = BASE.load_existing_output(
output_path=output_path,
overwrite=args.overwrite,
resume=args.resume,
)
pending_uuids = [uuid for uuid in uuids if uuid not in existing_videos]
if not pending_uuids:
logging.info("Nothing to do. All requested UUIDs already exist in %s.", output_path)
return 0
runner = GeminiRunner(
model_path=Path(args.model_path).expanduser().resolve(),
device_map=args.device_map,
dtype=args.dtype,
attn_implementation=args.attn_implementation,
trust_remote_code=args.trust_remote_code,
fps=args.fps,
max_pixels=args.max_pixels,
temperature=args.temperature,
top_p=args.top_p,
top_k=args.top_k,
repetition_penalty=args.repetition_penalty,
greedy=args.greedy,
)
try:
failures: list[str] = []
for index, uuid in enumerate(pending_uuids, start=1):
logging.info("Processing UUID %s (%d/%d)", uuid, index, len(pending_uuids))
try:
video_payload = BASE.annotate_uuid(
uuid=uuid,
segments=groups[uuid],
runner=runner,
camera_motion_guide=camera_motion_guide,
args=args,
)
existing_videos[uuid] = video_payload
BASE.write_output(output_path, existing_videos)
logging.info("Wrote %s", output_path)
except Exception as exc:
if not args.continue_on_error:
raise
logging.exception("Failed to process %s: %s", uuid, exc)
failures.append(uuid)
if failures:
logging.error("Completed with failures: %s", ", ".join(failures))
return 1
logging.info("Finished %d UUIDs.", len(pending_uuids))
return 0
finally:
runner.close()
if __name__ == "__main__":
sys.exit(main())
|