ຂ້າພະເຈົ້າກໍ່ສ້າງອຸປະກອນການທົດສອບຫນ້າຈໍຂອງຂ້າພະເຈົ້າສໍາລັບຫນຶ່ງສິ່ງທີ່ຂ້າພະເຈົ້າຕ້ອງການ: ຂໍຂອບໃຈວ່າມັນສາມາດເບິ່ງການດາວໂຫລດຫນ້າຈໍ, ຂໍຂອບໃຈວ່າສິ່ງທີ່ເກີດຂຶ້ນເປັນການເຮັດວຽກ, ແລະຫຼັງຈາກນັ້ນປ່ຽນຄວາມຮູ້ຂອງຕົນເອງໃນອຸປະກອນການອັດຕະໂນມັດ (n8n flows, step lists, structured summaries, the entire pipeline). ການຈັດການ JSON ແລະການດາວໂຫລດບັນຊີລາຍລະອຽດທີ່ດີທີ່ສຸດແມ່ນການຈັດການ JSON ແລະການດາວໂຫລດບັນຊີລາຍລະອຽດທີ່ດີທີ່ສຸດແມ່ນການຈັດການ JSON ແລະການດາວໂຫລດບັນຊີລາຍລະອຽດທີ່ດີທີ່ສຸດແມ່ນການຈັດການ JSON ແລະການດາວໂຫລດບັນຊີລາຍລະອຽດທີ່ດີທີ່ສຸດ. ດັ່ງນັ້ນ, ຂ້າພະເຈົ້າໄດ້ປິ່ນປົວຮູບເງົາເຊັ່ນດຽວກັນກັບ "points data" ແລະເລີ່ມຕົ້ນການປິ່ນປົວພວກເຂົາເຊັ່ນດຽວກັນກັບຈຸດຄົ້ນຄ້ວາ. ມັນເປັນສິ່ງທີ່ບໍ່ໄດ້ເລີ່ມຕົ້ນ (ການດໍາເນີນການທີ່ບໍ່ໄດ້ເລີ່ມຕົ້ນ) ການຕັດເລີ່ມຕົ້ນຂອງຂ້າພະເຈົ້າໄດ້ຖືກສະແດງໃຫ້ເຫັນວ່າ: sample every Nth frame. ສະບັບທີ່ບໍ່ໄດ້ເຮັດວຽກໃນສອງວິທີ, ແລະທັງສອງໄດ້ຖືກເບິ່ງຢູ່ໃນຜະລິດຕະພັນ. ການອັບໂຫລດປະເພດໃນທ້ອງຖ່າຍຮູບຂອງຂ້າພະເຈົ້າແມ່ນການອັບໂຫລດ 6-12 ນາທີ. ໃນຫຼາຍກ່ວາການອັບໂຫລດນີ້, ຜູ້ຊ່ຽວຊານໄດ້ປິ່ນປົວເພື່ອຊອກຫາ, ດາວນ໌ໂຫລດເວັບໄຊ, ຫຼື cursor ແມ່ນພຽງແຕ່นั่งอยู่. ການຢັ້ງຢືນມາດຕະຖານທີ່ເຫມາະສົມໄດ້ຢ່າງງ່າຍດາຍໄດ້ປິ່ນປົວຮູບເງົາໃນຂະນະທີ່ຈຸດປະສົງທີ່ບໍ່ມີ. ການທົດສອບຄ່າໃຊ້ຈ່າຍໄດ້ກາຍເປັນ linear ກັບຄວາມຍາວຂອງວິດີໂອ, ເຊັ່ນດຽວກັນກັບເວລາທີ່ອຸປະກອນຂໍ້ມູນບໍ່ມີ. Failure #1: it wasted frames on dead air. ການປະທັບໃຈທີ່ດີທີ່ສຸດ (ຫນຶ່ງທີ່ເຮັດໃຫ້ຂ້າພະເຈົ້າ rewrite ການ sampler) ແມ່ນບັນຊີລາຍລະອຽດຂອງການເຮັດວຽກຂອງ admin ທີ່ງ່າຍດາຍ, ໃນເວລາທີ່ການດາວໂຫລດໃບອະນຸຍາດທີ່ສໍາຄັນເປີດແລະດາວໂຫລດຢ່າງງ່າຍດາຍ. sampler ອັດຕະໂນມັດຊອກຫາວໂຫລດແຜ່ນພຽງແຕ່ກ່ອນແລະພຽງແຕ່ຫຼັງຈາກການດາວໂຫລດ - ດັ່ງນັ້ນມາດຕະຖານບໍ່ໄດ້ເບິ່ງການເລືອກໃບອະນຸຍາດ. ການຊອກຫາວໂຫລດເບິ່ງຄວາມປອດໄພແລະບໍ່ເສຍຄ່າ: ມັນກໍານົດການປ່ຽນແປງທີ່ແຕກຕ່າງກັນ, ໃນຂະນະທີ່ມັນໄດ້ຊອກຫາວໂຫລດສະພາບແວດລ້ອມທີ່ຜ່ານມາ, ແຕ່ບໍ່ແມ່ນ UI ອັດຕະໂນມັດທີ່ເຮັດໃຫ້ມັນ. Failure #2: it missed the “blink-and-you-miss-it” UI moments. ການເຊື່ອມຕໍ່ທີ່ - ການຊໍາລະເງິນສໍາລັບເວລາສະພາບແວດລ້ອມໃນຂະນະທີ່ຍັງບໍ່ມີການເຮັດວຽກທີ່ແທ້ຈິງ - ແມ່ນບໍ່ຈໍາເປັນ. I needed a sampler that spends frames where the video is changing and stops paying rent on static UI. ຄວາມຄິດເຫັນທີ່ Key Idea: Spend frames where the information is ການປິ່ນປົວ naive ແມ່ນ “ຕົວຢ່າງທຸກ Nth frame.” ມັນຮູ້ສຶກ fair. ມັນຍັງບໍ່ແມ່ນ. ດາວນ໌ໂຫລດ screenshot ແມ່ນບໍ່ເປັນຮູບເງົາ. ມັນເປັນທີ່ຜ່ານມາກັບບັນຊີລາຍຊື່: ສະພາບອາກາດຍາວທີ່ stabilized ໂດຍການປ່ຽນແປງຢ່າງງ່າຍດາຍ. ການຢັ້ງຢືນປະມວນຜົນປະມວນຜົນປະມວນຜົນປະມວນຜົນປະມວນຜົນ: ທ່ານມີຄ່າໃຊ້ຈ່າຍເພີ່ມເຕີມສໍາລັບ UI stable. ທ່ານ underfund breaks ສັດທີ່ workflow ແມ່ນເຮັດວຽກ. One analogy (I will use it once and move on): sampling uniformly is like paying every team the same bonus regardless of impact.It is simple accounting, not good allocation. ການປິ່ນປົວແມ່ນກວດສອບການປ່ຽນແປງຢ່າງງ່າຍດາຍ, ການກວດສອບເວລາໃນຊຸດ, ແລະການນໍາໃຊ້ຄຸນນະສົມບັດຄຸນນະສົມບັດຄຸນນະສົມບັດໃນໄລຍະຊຸດນີ້. ດາວໂຫລດ Runtime Architecture: Where the sampler sits ການເຮັດວຽກ, ລະບົບໄດ້ແຕກຕ່າງກັນໃນສອງສ່ວນການຮ່ວມມື: ການບໍລິການການວິເຄາະ Python (ດໍາເນີນການເປັນບໍລິການ Cloud Run) ທີ່ປິ່ນປົວວິດີໂອທີ່ອັບໂຫລດ, ເລືອກ keyframes, ດໍາເນີນການການວິເຄາະ multimodal, ແລະຜະລິດຜົນປະໂຫຍດການປະມວນຜົນ. Next.js app ທີ່ໄດ້ຮັບຜົນກະທົບໂດຍຜ່ານ webhook ແລະ persists ມັນ (ແລະດໍາເນີນການ UI dashboard). ການທົດສອບຕົວຢ່າງແມ່ນຢູ່ພາຍໃນການທົດສອບ, ທີ່ຜ່ານມາການໂທຮູບແບບທີ່ດີເລີດ. ຄໍາຖາມທີ່ບໍ່ແມ່ນອະນຸຍາດ: sampler ແມ່ນບໍ່ເປັນ "ການປັບປຸງທີ່ດີ". ມັນເປັນການຄວບຄຸມພື້ນທີ່. ມັນປ່ຽນແປງ "ຫຼັງຈາກວ່າວິດີໂອນີ້ເປັນປະມານທີ່ຍິ່ງໃຫຍ່?" ໃນ "ຄອມພິວເຕີທີ່ທ່ານຕ້ອງການທີ່ຈະຊື້? " ການທົດສອບສະບັບເລີ່ມຕົ້ນໂດຍຜ່ານການເຂົ້າລະຫັດ Webhook - HMAC-SHA256 ໂດຍຜ່ານ bytes JSON ຄຸນນະສົມບັດ, verificated ກັບ ໃນຕອນແລງທີ່ໄດ້ຮັບການເຂົ້າເຖິງ. ການຝຶກອົບຮົມທີ່ສໍາຄັນທີ່ຢູ່: ດາວນ໌ໂຫລດ bytes ທີ່ທ່ານກໍາລັງຊອກຫາ ( ), ບໍ່ເປັນ dict Python ທີ່ໄດ້ຮັບ re-serialized ໂດຍ libraries HTTP. That mismatch produces intermittent verification failures that feel like ghosts. But the webhook seam is a different post—what matters here is what happens payload ດໍາ ເນີນ ການ analyzer. timingSafeEqual data=body_bytes ທີ່ຜ່ານມາ ການປະມວນຜົນ Keyframe Adaptive: score → segment → allocate → pick indices ການ sampler ແມ່ນທໍ່ໄຮໂດຼລິກ 4 ປະເພດ: ການປ່ຽນແປງ Score ລາຄາຖືກ per frame (ຫຼື per step of frames). ຊຸດ timeline ໃນການເຮັດວຽກ "ສູງການປ່ຽນແປງ" ແລະ "ຂະຫນາດໃຫຍ່ stability". ສະຫນັບສະຫນູນຄຸນນະສົມບັດຄຸນນະສົມບັດຄຸນນະສົມບັດຄຸນນະສົມບັດຄຸນສົມບັດຄຸນສົມບັດຄຸນສົມບັດຄຸນສົມບັດຄຸນສົມບັດຄຸນສົມບັດຄຸນສົມບັດຄຸນສົມບັດຄຸນສົມບັດຄຸນສົມບັດຄຸນສົມບັດຄຸນສົມບັດຄຸນສົມບັດຄຸນສົມບັດຄຸນສົມບັດຄຸນສົມບັດຄຸນສົມບັດ ຄູ່ມືທີ່ດີທີ່ສຸດສໍາລັບການຄົ້ນຄວ້າທີ່ດີທີ່ສຸດ ການກໍ່ສ້າງນີ້ແມ່ນສິ່ງທີ່ເຮັດໃຫ້ມັນງ່າຍດາຍ. Scoring ແມ່ນປະສິດທິພາບ. segmentation ແມ່ນ linear. allocation ແມ່ນຄາດຄະເນດິນ. ການຊອກຫາເລີ່ມຕົ້ນແມ່ນເຄື່ອງຈັກ. ສະຖານທີ່ 1 — Scoring: ການປ່ຽນແປງຮູບພາບງ່າຍ ຂໍຂອບໃຈວ່າທ່ານກໍາລັງຊອກຫາຂໍ້ມູນເພີ່ມເຕີມ. ຖ້າຫາກວ່າທ່ານກໍາລັງຊອກຫາຂໍ້ມູນເພີ່ມເຕີມ, ຂໍຂອບໃຈວ່າທ່ານກໍາລັງຊອກຫາຂໍ້ມູນເພີ່ມເຕີມ. ຮູບພາບ ສໍາ ລັບ Screen Recording Baseline Signal : frame difference energy ດາວໂຫລດ Grayscale ຊື່ຫຍໍ້ຂອງ : Calculate absolute difference between consecutive frames ເລືອກ Medium ຂອງຮູບພາບ diff (ຄຸນນະສົມບັດ normalize). ປະເພດຂອງ catches: ການເດີນທາງ Cursor ການພິມ (blinking caret ແລະການປັບປຸງເອກະສານ) Dropdowns ແລະ modals ເວັບໄຊທ໌ Transitions ການປ່ຽນແປງ State Hover ມັນບໍ່ແມ່ນທີ່ດີ, ແຕ່ມັນແມ່ນໄວແລະມີຄຸນນະສົມບັດທີ່ດີກັບ "ເປັນສິ່ງທີ່ເກີດຂຶ້ນ". ສະຖານທີ່ 2 — ການກໍ່ສ້າງ segment: ການປ່ຽນແປງ flux scores noisy ໃນ runs ຈໍານວນ per-frame ແມ່ນ spiky. I don’t want the allocator to chase noise. ດັ່ງນັ້ນຂ້າພະເຈົ້າ segment using a simple state machine with hysteresis: ດາວນ໌ໂຫລດ Rolling Average Score ການປ່ຽນແປງໃນຊຸດ "ຮ້ອນ" ໃນເວລາທີ່ຊຸດ rolling ອາຍແກັສໄດ້ສູງສຸດຫຼາຍກ່ວາແຜ່ນ. ການປ່ຽນແປງໄປຫາ "ລັກສະນະ" ໃນເວລາທີ່ມັນກ່ອນຫນ້ານີ້ຫນ້ອຍກ່ອນຫນ້ານີ້. ສະຫນັບສະຫນູນຄວາມຍາວຂອງ segment ຂະຫນາດຂະຫນາດນ້ອຍເພື່ອຂ້າພະເຈົ້າບໍ່ສ້າງອຸດົມສົມບູນຂອງ micro-segments. ມັນເປັນວິສະວະກໍາ: ການຄຸ້ມຄອງ stability, tuning ນ້ໍາຫນັກ overhead, ແລະ output predictable. ປະເພດ 3 - ການຄຸ້ມຄອງຄຸ້ມຄອງຄຸ້ມຄອງຄຸ້ມຄອງຄຸ້ມຄອງ ການບໍລິໂພກ proportional purely ແມ່ນບໍ່ພຽງພໍ. ມັນບໍ່ສາມາດເຂົ້າໄປໃນການແບ່ງຂັນແລະມັນສາມາດເຂົ້າໃຈ segment short. ດັ່ງນັ້ນ, my allocator ມີສາມປົກກະຕິ: ທັງຫມົດ segment ໄດ້ໄດ້ຮັບການດິນ (min_frames_per_segment). ບໍ່ມີ segment ສາມາດກ່ວາ cap (max_frames_per_segment) ການຄາດຄະເນດິນຟ້າອິນເຕີເນັດທີ່ແຕກຕ່າງກັນໄດ້ຖືກຈັດຂຶ້ນ proportionally ກັບ segment utility. ມັນເຮັດໃຫ້ການປະໂຫຍດສະດວກແລະປ້ອງກັນການປິ່ນປົວ. ສະຖານທີ່ 4 - ການເລືອກ Index ໃນ segment ຫຼັງຈາກ segment ໄດ້ໄດ້ຮັບການຮັບປະກັນ K frames, ຂໍຂອບໃຈວ່າຂ້າພະເຈົ້າຈະກວດສອບ K indices ສະຫນັບສະຫນູນ segment: ສະ ຫນັບ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ນໍາ ເວັບ ໄຊ ທ ໌ ອອນ ໄລ ນ ໌ ວັນ ທີ ການ ສ້າງ ຕັ້ງ ສະ ເພາະ ສໍາ ລັບ lovers ສັດ ລ້ຽງ. ດາວນ໌ໂຫລດອຸປະກອນການຝຶກອົບຮົມ ຖ້າຫາກວ່າຂ້າພະເຈົ້າຕ້ອງການຄວາມຫມັ້ນຄົງເພີ່ມເຕີມຫຼັງຈາກນັ້ນ, ຂ້າພະເຈົ້າສາມາດກວດສອບກັບສູງສຸດທ້ອງຖິ່ນຂອງລາຍລະອຽດ, ແຕ່ການເລືອກທີ່ແຕກຕ່າງກັນຢ່າງງ່າຍດາຍແມ່ນການຄົ້ນຄວ້າທີ່ເຂັ້ມແຂງແລະຮັກສາລະຫັດຢ່າງງ່າຍດາຍ. ການນໍາໃຊ້ທີ່ໃຊ້ຢ່າງກວ້າງຂວາງ (Scoring + Segmentation + Allocation + Extraction) ເພື່ອເຮັດໃຫ້ບົດຄວາມນີ້ສາມາດຕອບສະຫນອງ, ນີ້ແມ່ນ script Python ຫນຶ່ງທີ່ທ່ານສາມາດດໍາເນີນກັບ MP4 ທັງ ຫມົດ. ມັນເລືອກ keyframes ໂດຍອັບໂຫລດແລະຂຽນພວກເຂົາໄປຢ້ຽມຢາມ directory output. ລະຫັດ QR ດາວນ໌ໂຫລດ Python ລະຫັດ QR ປະເພດ Run: python adaptive_keyframes.py --video input.mp4 --budget 60 --out ./keyframes ຫນ້າທໍາອິດ : adaptive_keyframes.py import argparse import os from dataclasses import dataclass from typing import List, Tuple import cv2 import numpy as np @dataclass class Segment: start: int # inclusive frame index end: int # exclusive frame index score: float @property def length(self) -> int: return max(0, self.end - self.start) def frame_diff_score(prev_bgr: np.ndarray, curr_bgr: np.ndarray) -> float: """Cheap per-frame change score in [0, 1] (roughly). Uses grayscale mean absolute difference normalized by 255. """ prev_gray = cv2.cvtColor(prev_bgr, cv2.COLOR_BGR2GRAY) curr_gray = cv2.cvtColor(curr_bgr, cv2.COLOR_BGR2GRAY) diff = cv2.absdiff(prev_gray, curr_gray) return float(diff.mean() / 255.0) def compute_scores( cap: cv2.VideoCapture, stride: int = 1, max_frames: int | None = None, ) -> Tuple[List[float], int]: """Return (scores, total_frames_read). scores[i] is the change score between frame i and i+stride (based on sampled reads). """ scores: List[float] = [] ok, prev = cap.read() if not ok: return scores, 0 frame_idx = 1 frames_read = 1 while True: # Skip stride-1 frames between comparisons. for _ in range(stride - 1): ok = cap.grab() if not ok: return scores, frames_read frame_idx += 1 frames_read += 1 if max_frames is not None and frames_read >= max_frames: return scores, frames_read ok, curr = cap.read() if not ok: return scores, frames_read frames_read += 1 s = frame_diff_score(prev, curr) scores.append(s) prev = curr frame_idx += 1 if max_frames is not None and frames_read >= max_frames: return scores, frames_read def segment_scores( scores: List[float], window: int = 8, hot_thresh: float = 0.030, cold_thresh: float = 0.020, min_len: int = 12, ) -> List[Segment]: """Convert per-step scores into segments with a utility score. Uses a rolling mean with hysteresis to avoid segment flicker. """ if not scores: return [] # Rolling mean via cumulative sum. x = np.array(scores, dtype=np.float32) c = np.cumsum(np.insert(x, 0, 0.0)) def roll_mean(i: int) -> float: j0 = max(0, i - window + 1) n = i - j0 + 1 return float((c[i + 1] - c[j0]) / n) segments: List[Segment] = [] state_hot = False seg_start = 0 seg_scores: List[float] = [] for i in range(len(scores)): rm = roll_mean(i) if state_hot: seg_scores.append(scores[i]) if rm < cold_thresh: # Close hot segment at i+1 seg_end = i + 1 if seg_end - seg_start < min_len: # Too short: merge into previous if possible, else keep. pass segments.append(Segment(seg_start, seg_end, float(np.mean(seg_scores) if seg_scores else 0.0))) # Start cold state_hot = False seg_start = seg_end seg_scores = [] else: if rm > hot_thresh: # Close cold segment seg_end = i + 1 cold_score = float(np.mean(scores[seg_start:seg_end]) if seg_end > seg_start else 0.0) segments.append(Segment(seg_start, seg_end, cold_score)) # Start hot state_hot = True seg_start = seg_end seg_scores = [] # Close tail tail_end = len(scores) if tail_end > seg_start: tail_score = float(np.mean(scores[seg_start:tail_end])) segments.append(Segment(seg_start, tail_end, tail_score)) # Merge very short segments to keep output stable. merged: List[Segment] = [] for seg in segments: if not merged: merged.append(seg) continue if seg.length < min_len: prev = merged[-1] combined = Segment(prev.start, seg.end, (prev.score * prev.length + seg.score * seg.length) / max(1, (prev.length + seg.length))) merged[-1] = combined else: merged.append(seg) # One more pass: ensure non-empty and strictly increasing. cleaned: List[Segment] = [] for seg in merged: if seg.length <= 0: continue if cleaned and seg.start < cleaned[-1].end: seg = Segment(cleaned[-1].end, seg.end, seg.score) if seg.length > 0: cleaned.append(seg) return cleaned def allocate_frames( segments: List[Segment], budget: int, min_frames_per_segment: int = 1, max_frames_per_segment: int = 30, ) -> List[int]: """Allocate keyframes to segments using floor + proportional + cap.""" if budget <= 0 or not segments: return [] n = len(segments) min_total = min_frames_per_segment * n # If budget is smaller than the floor, distribute 1-by-1. if min_total >= budget: alloc = [0] * n for i in range(budget): alloc[i % n] += 1 return alloc utilities = np.array([max(0.0, s.score) for s in segments], dtype=np.float64) total_u = float(utilities.sum()) alloc = [min_frames_per_segment] * n remaining = budget - min_total if total_u == 0.0: raw = np.full(n, remaining / n, dtype=np.float64) else: raw = utilities * (remaining / total_u) # Add integer parts. for i in range(n): add = int(raw[i]) alloc[i] = min(max_frames_per_segment, alloc[i] + add) allocated = sum(alloc) # Distribute leftover by fractional parts, respecting caps. if allocated < budget: frac = raw - np.floor(raw) order = np.argsort(-frac) # descending fractional idx = 0 safety = 0 while allocated < budget and safety < 10_000: i = int(order[idx % n]) if alloc[i] < max_frames_per_segment: alloc[i] += 1 allocated += 1 idx += 1 safety += 1 # If we somehow exceeded budget due to caps/floor interplay, trim from lowest utility. if allocated > budget: order = np.argsort(utilities) # ascending utility idx = 0 safety = 0 while allocated > budget and safety < 10_000: i = int(order[idx % n]) if alloc[i] > 0 and alloc[i] > min_frames_per_segment: alloc[i] -= 1 allocated -= 1 idx += 1 safety += 1 return alloc def pick_indices_for_segment(seg: Segment, k: int) -> List[int]: """Pick k indices in [seg.start, seg.end] over the score-step domain. Note: scores are defined between frames; we later map these to actual frames. """ if k <= 0 or seg.length <= 0: return [] if k == 1: return [seg.start] # Evenly spaced across [start, end-1] xs = np.linspace(seg.start, seg.end - 1, num=k) idxs = sorted({int(round(x)) for x in xs}) # Ensure exactly k by filling gaps if rounding collapsed points. while len(idxs) < k: # Insert midpoints between existing points. candidates = [] for a, b in zip(idxs, idxs[1:]): if b - a >= 2: candidates.append((a + b) // 2) if not candidates: # Fall back: walk forward. x = idxs[-1] if x + 1 < seg.end: idxs.append(x + 1) else: break else: for c in candidates: if c not in idxs and seg.start <= c < seg.end: idxs.append(c) if len(idxs) >= k: break idxs = sorted(idxs) # Trim if we overshot. return idxs[:k] def select_keyframe_indices(segments: List[Segment], alloc: List[int], stride: int = 1) -> List[int]: """Return concrete frame indices (0-based) to extract from the video.""" chosen: List[int] = [] for seg, k in zip(segments, alloc): step_idxs = pick_indices_for_segment(seg, k) # Map score-step domain to frame indices. # score i corresponds to diff between frame i and i+stride; # picking frame i is a reasonable representative. for si in step_idxs: chosen.append(si * stride) chosen = sorted(set(chosen)) return chosen def extract_frames(video_path: str, frame_indices: List[int], out_dir: str) -> None: os.makedirs(out_dir, exist_ok=True) cap = cv2.VideoCapture(video_path) if not cap.isOpened(): raise RuntimeError(f"Failed to open video: {video_path}") frame_set = set(frame_indices) max_idx = max(frame_set) if frame_set else -1 idx = 0 saved = 0 while idx <= max_idx: ok, frame = cap.read() if not ok: break if idx in frame_set: path = os.path.join(out_dir, f"frame_{idx:06d}.jpg") ok2 = cv2.imwrite(path, frame) if not ok2: raise RuntimeError(f"Failed to write: {path}") saved += 1 idx += 1 cap.release() if saved == 0 and frame_indices: raise RuntimeError("No frames were saved; check indices and video decoding") def main() -> None: ap = argparse.ArgumentParser() ap.add_argument("--video", required=True, help="Path to input video") ap.add_argument("--out", required=True, help="Output directory for keyframes") ap.add_argument("--budget", type=int, default=60, help="Total keyframes to extract") ap.add_argument("--stride", type=int, default=2, help="Compare every Nth frame for scoring") ap.add_argument("--window", type=int, default=8, help="Rolling window for segmentation") ap.add_argument("--hot", type=float, default=0.030, help="Enter hot segment threshold") ap.add_argument("--cold", type=float, default=0.020, help="Exit hot segment threshold") args = ap.parse_args() cap = cv2.VideoCapture(args.video) if not cap.isOpened(): raise RuntimeError(f"Failed to open video: {args.video}") scores, frames_read = compute_scores(cap, stride=args.stride) cap.release() segments = segment_scores(scores, window=args.window, hot_thresh=args.hot, cold_thresh=args.cold) alloc = allocate_frames(segments, budget=args.budget, min_frames_per_segment=1, max_frames_per_segment=max(2, args.budget)) keyframes = select_keyframe_indices(segments, alloc, stride=args.stride) # Keep within a hard limit (rounding/uniqueness can change count). if len(keyframes) > args.budget: keyframes = keyframes[: args.budget] extract_frames(args.video, keyframes, args.out) print(f"frames_read={frames_read}") print(f"scores={len(scores)} segments={len(segments)}") print(f"budget={args.budget} selected={len(keyframes)}") if segments: hot_share = sum(1 for s in segments if s.score > args.hot) / len(segments) print(f"segment_hot_share={hot_share:.2f}") if __name__ == "__main__": main() ຊື່ນີ້ແມ່ນ intentionally direct: ຜະລິດຕະພັນຂອງການຄົ້ນຄວ້າ Segment ມັນຮັບປະກັນວ່າທ່ານບໍ່ໄດ້ກ່ວາຈຸດຄົ້ນຄວ້າ frame ຂອງທ່ານ. ມັນຂຽນເອກະສານ frame deterministic ສໍາລັບການທົດສອບ downstream. ໃນການບໍລິການການຜະລິດຂອງຂ້າພະເຈົ້າ, frames extracted ໃຫ້ການໂທ multimodal (Gemini via ໃນການປິ່ນປົວຂອງຂ້າພະເຈົ້າ) ແລະການປິ່ນປົວຂອງການປິ່ນປົວຂອງການປິ່ນປົວຂອງການປິ່ນປົວແມ່ນຖືກສົ່ງໄປໃນ app Next.js via signed webhook. google-generativeai ຊື່ຫຍໍ້ຂອງ : tuning notes (the stuff that matters after the first demo) ຫຼັງຈາກນັ້ນທ່ານໄດ້ຮັບການເຮັດວຽກຂອງຮູບແບບ, ຄວາມປອດໄພທີ່ຜ່ານມາຈາກການດໍາເນີນການ tuning ໃນເວລາທີ່ສະດວກສະບາຍໃນໂລກທີ່ແທ້ຈິງ. 1) ເລືອກລະດັບທີ່ເຫມາະສົມກັບອຸປະກອນຂອງທ່ານ ຖ້າຫາກວ່າທ່ານມີຜົນປະໂຫຍດທັງຫມົດໃນ 30 FPS ດາວໂຫລດ, ທ່ານຈະຊອກຫາການປ່ຽນແປງຂອງ cursor ຂະຫນາດນ້ອຍໃນທົ່ວໂລກ. ມັນບໍ່ແມ່ນປົກກະຕິ, ແຕ່ມັນສາມາດປັບປຸງສຸຂະພາບຂອງການປ່ຽນແປງຂອງທ່ານ. ຂັ້ນຕອນຂອງ 2-5 ແມ່ນສະຖານທີ່ເລີ່ມຕົ້ນທີ່ດີສໍາລັບການດາວໂຫລດ screenshot. You are still sensitive to UI changes, but you suppress sub-frame noise. 2) Hysteresis ອະນຸຍາດໃຫ້ segment flicker ການນໍາໃຊ້ ແລະ (ສອງເສັ້ນຜ່າສູນກາງ) ແມ່ນສິ່ງທີ່ສໍາຄັນ. ມີເສັ້ນຜ່າສູນກາງຫນຶ່ງ, ທ່ານຈະຕັດສິນໃຈລະຫວ່າງຄວາມຮ້ອນ / ຄວາມຮ້ອນໂດຍທົ່ວໄປໃນໄລຍະການຕັດ. hot_thresh cold_thresh Hysteresis ເຮັດໃຫ້ທ່ານ segment stability ແລະເຮັດໃຫ້ budgets ເຮັດໃຫ້ທ່ານຄາດຄະເນດິນ. 3) ຕາຕະລາງແລະ Caps ແມ່ນບໍ່ຈໍາເປັນ ຖ້າຫາກວ່າທ່ານບໍ່ມີແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ ຖ້າຫາກວ່າທ່ານບໍ່ຈໍາເປັນຕ້ອງກວດສອບການເຮັດວຽກຂອງພວກເຮົາ, ໃນເວລາທີ່ທ່ານກໍາລັງຊອກຫາຂໍ້ມູນເພີ່ມເຕີມກ່ຽວກັບການເຮັດວຽກຂອງພວກເຮົາ, ທ່ານຈະໄດ້ຮັບການປັບປຸງການເຮັດວຽກຂອງພວກເຮົາ. 4) ດາວນ໌ໂຫລດ Bytes ຖ້າ ຫາກ ວ່າ ທ່ານ Sign ດາວນ໌ໂຫລດ ກາຕູນ HTTP ສາມາດ serialized ກັບການຊໍາລະດັບ / spacing key ທີ່ແຕກຕ່າງກັນຈາກສິ່ງທີ່ທ່ານໄດ້ລົງທະບຽນ. ມັນຜະລິດຄວາມຜິດພາດການຢັ້ງຢືນ intermittent ທີ່ຮູ້ຈັກເປັນ spots. json.dumps(payload) json=payload ຊື່ຫຍໍ້ຂອງ : Byte ( ) eliminates that whole class of bugs. This matters here because the analysis payload — the one carrying your carefully selected keyframes — is the most expensive artifact in the pipeline. If it gets dropped by a signing mismatch, you have wasted the entire frame budget. data=body_bytes ວິທີການອອກແບບ Scales ໃນການຜະລິດ ການທົດສອບຕົວຈິງເຮັດວຽກໂດຍສະເພາະວ່າມັນມີສອງຄວາມຈໍາກັດທີ່ເຂັ້ມແຂງ: ລາຄາສິໂນທີ່ມີຮູບເງົາທີ່ກໍານົດໄວ້, ບໍ່ແມ່ນຄວາມຍາວຂອງວິດີໂອ. ຫຼັງຈາກການຄາດຄະເນດຽວກັນ, ຂັ້ນຕອນ multimodal ລາຄາສິໂນມີແຜ່ນຂະຫນາດໃຫຍ່. ຊື່ຫຍໍ້ຂອງ : Scoring is a linear pass; segmentation is a linear pass; allocation is linear with tiny constant factors. ທີ່ສໍາຄັນທີ່ສຸດ: sampler ເຮັດໃຫ້ລະບົບຂອງຂ້າພະເຈົ້າເຮັດວຽກເປັນສິ່ງທີ່ຂ້າພະເຈົ້າສາມາດເຮັດວຽກ. ໃນຖານະເປັນຜູ້ຊ່ຽວຊານໃນການຄົ້ນຄວ້າ, ຜູ້ຊ່ຽວຊານໃນການຄົ້ນຄວ້າຄົ້ນຄວ້າຄົ້ນຄວ້າຄົ້ນຄວ້າຄົ້ນຄວ້າຄົ້ນຄວ້າຄົ້ນຄວ້າຄົ້ນຄວ້າຄົ້ນຄວ້າຄົ້ນຄວ້າຄົ້ນຄວ້າຄົ້ນຄວ້າຄົ້ນຄວ້າຄົ້ນຄວ້າຄົ້ນຄວ້າຄົ້ນຄວ້າຄົ້ນຄວ້າ ນີ້ແມ່ນສິ່ງທຸກຢ່າງ: ການຄາດຄະເນດິນດີຕ້ອນຮັບປະໂຫຍດສະຫມັກ fair sampling.