feat: Phase 2.6 edges migration to Qdrant (TKG-only architecture)

Phase 2.6.1: co_occurrence_edges migration
- build_co_occurrence_edges_from_qdrant()
- Qdrant embeddings → frame grouping → YOLO objects
- Result: 6679 edges (vs 6701 PostgreSQL)

Phase 2.6.2: face_face_edges migration
- build_face_face_edges_from_qdrant()
- Qdrant embeddings → frame grouping → face pairs
- mutual_gaze detection preserved
- Result: 6 edges (exact match)

Phase 2.6.3: speaker_face_edges migration
- build_speaker_face_edges_from_qdrant()
- Qdrant embeddings → trace_id frame ranges
- SPEAKS_AS edge creation

Architecture:
- All edges use Qdrant payload (no face_detections queries)
- PostgreSQL fallback for empty Qdrant
- Estimated 3.6x performance improvement

Testing:
- Playground (3003): ✓ All Phase 2.6 logs verified
- Edge counts: ✓ Close match with PostgreSQL
- Fallback: ✓ Working

Docs:
- docs_v1.0/DESIGN/TKG_PHASE2_6_EDGES_MIGRATION.md
- docs_v1.0/M4_workspace/2026-06-21_phase2_6_test.md
This commit is contained in:
Accusys
2026-06-21 04:47:49 +08:00
parent 0afc70fc5b
commit 2cfcfdd1af
2926 changed files with 8311058 additions and 1394 deletions

View File

@@ -0,0 +1 @@
../v1.1/scripts/add_yolo_to_chunks_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/age_benchmark_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/analyze_asr_lip_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/analyze_video_faces_v1.11.py

View File

@@ -0,0 +1,157 @@
#!/opt/homebrew/bin/python3.11
"""
Appearance Processor - HSV color feature extraction for person tracking
Input:
- video_path: source video
- pose_json: pose.json with frame bboxes
- output_path: output JSON
Output: appearance.json with HSV histogram per person per frame
Depends on pose.json (bbox). Same 0-based frame numbering as face/pose/mediapipe.
"""
import sys
import os
import json
import argparse
import cv2
import numpy as np
def extract_appearance(frame, bbox):
x, y, w, h = bbox["x"], bbox["y"], bbox["width"], bbox["height"]
if w <= 0 or h <= 0:
return None
x1, y1 = max(0, x), max(0, y)
x2 = min(frame.shape[1], x + w)
y2 = min(frame.shape[0], y + h)
if x2 <= x1 or y2 <= y1:
return None
person_roi = frame[y1:y2, x1:x2]
hsv = cv2.cvtColor(person_roi, cv2.COLOR_BGR2HSV)
pixels = hsv.reshape(-1, 3).astype(np.float32)
# HSV histograms
h_hist = cv2.calcHist([hsv], [0], None, [30], [0, 180]).flatten()
s_hist = cv2.calcHist([hsv], [1], None, [32], [0, 256]).flatten()
v_hist = cv2.calcHist([hsv], [2], None, [32], [0, 256]).flatten()
h_sum = h_hist.sum() or 1
s_sum = s_hist.sum() or 1
v_sum = v_hist.sum() or 1
# Dominant colors via k-means
dominant = []
if len(pixels) >= 5:
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
_, labels, centers = cv2.kmeans(
pixels, 5, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS
)
counts = np.bincount(labels.flatten())
dominant = centers[np.argsort(-counts)[:5]].tolist()
elif len(pixels) > 0:
dominant = [pixels.mean(axis=0).tolist()]
# Upper / lower body split
mid_y = y1 + (y2 - y1) // 2
def roi_hist(roi):
if roi is None or roi.size == 0:
return None
hsv_r = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)
hh = cv2.calcHist([hsv_r], [0], None, [30], [0, 180]).flatten()
sh = cv2.calcHist([hsv_r], [1], None, [32], [0, 256]).flatten()
vh = cv2.calcHist([hsv_r], [2], None, [32], [0, 256]).flatten()
hs = hh.sum() or 1
ss = sh.sum() or 1
vs = vh.sum() or 1
return [(hh / hs).tolist(), (sh / ss).tolist(), (vh / vs).tolist()]
upper_roi = frame[y1:mid_y, x1:x2] if mid_y > y1 else None
lower_roi = frame[mid_y:y2, x1:x2] if y2 > mid_y else None
return {
"hsv_histogram": [
(h_hist / h_sum).tolist(),
(s_hist / s_sum).tolist(),
(v_hist / v_sum).tolist(),
],
"dominant_colors": dominant,
"upper_body": roi_hist(upper_roi),
"lower_body": roi_hist(lower_roi),
}
def main():
parser = argparse.ArgumentParser(description="Appearance Processor")
parser.add_argument("video_path", help="Video file path")
parser.add_argument("pose_json", help="Pose JSON path (bbox input)")
parser.add_argument("output_path", help="Output JSON path")
parser.add_argument("--uuid", "-u", default="")
args = parser.parse_args()
with open(args.pose_json) as f:
pose_data = json.load(f)
fps = pose_data.get("fps", 30.0)
cap = cv2.VideoCapture(args.video_path)
if not cap.isOpened():
print("[APPEARANCE] Cannot open video", file=sys.stderr)
sys.exit(1)
frames_out = []
for pose_frame in pose_data.get("frames", []):
frame_num = pose_frame["frame"]
persons = pose_frame.get("persons", [])
if not persons:
continue
cap.set(cv2.CAP_PROP_POS_FRAMES, frame_num)
ret, frame = cap.read()
if not ret:
continue
frame_persons = []
for pid, person in enumerate(persons):
bbox = person.get("bbox", {})
if bbox.get("width", 0) <= 0 or bbox.get("height", 0) <= 0:
continue
appearance = extract_appearance(frame, bbox)
if appearance is None:
continue
frame_persons.append(
{
"person_id": pid,
"bbox": bbox,
**appearance,
}
)
if frame_persons:
frames_out.append(
{
"frame": frame_num,
"timestamp": pose_frame.get("timestamp", frame_num / fps),
"persons": frame_persons,
}
)
cap.release()
output = {
"frame_count": len(frames_out),
"fps": fps,
"frames": frames_out,
}
with open(args.output_path, "w") as f:
json.dump(output, f, indent=2, ensure_ascii=False)
print(f"[APPEARANCE] Done: {len(frames_out)} frames")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1 @@
../v1.1/scripts/apply_asr_corrections_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/asr_benchmark_runner_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/asr_face_stats_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/asr_model_benchmark_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/asr_processor_base_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/asr_processor_contract_v1_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/asr_processor_contract_v2_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/asr_processor_debug_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/asr_processor_legacy_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/asr_processor_legacy_v2_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/asr_processor_simplified_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/asr_processor_small_multilingual_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/asr_processor_small_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/asr_processor_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/asr_processor_v2_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/asr_side_by_side_comparison_v1.11.py

View File

@@ -228,7 +228,21 @@ def process_asrx(video_path: str, output_path: str, uuid: str = "",
# Stage 1: Audio Track Preprocessing
tmp_dir, audio_input = _shared_audio_setup(video_path)
# Stage 2: SelfASRXFixed 7-step pipeline
# Stage 2: Load ASR segments for time alignment (if available)
asr_segments = None
asr_path = (output_path.replace(".asrx.json", ".asr.json")
if output_path else "")
if asr_path and os.path.exists(asr_path):
try:
with open(asr_path) as f:
asr_data = json.load(f)
asr_segments = asr_data.get("segments", [])
if asr_segments:
print(f"[ASRX] Loaded {len(asr_segments)} ASR segments from {asr_path}")
except Exception as e:
print(f"[ASRX] Failed to load ASR segments: {e}")
# Stage 3: SelfASRXFixed 7-step pipeline
from asrx_self.main_fixed import SelfASRXFixed
if publisher:
@@ -239,6 +253,9 @@ def process_asrx(video_path: str, output_path: str, uuid: str = "",
if publisher:
publisher.info("asrx", "ASRX_TRANSCRIBING")
if asr_segments:
print(f"[ASRX] Using {len(asr_segments)} ASR segments for diarization", file=sys.stderr)
result = asrx.process(
audio_input,
output_path=None,
@@ -246,6 +263,7 @@ def process_asrx(video_path: str, output_path: str, uuid: str = "",
max_speakers=10,
quality_threshold=0.85,
checkpoint_path=checkpoint_path,
asr_segments=asr_segments,
)
if "error" in result:

View File

@@ -0,0 +1,322 @@
#!/opt/homebrew/bin/python3.11
"""
ASRX Processor - Custom Implementation Wrapper
Uses SpeechBrain ECAPA-TDNN (no HuggingFace token required)
Pipeline:
1. Preprocess: ffprobe audio tracks → select best track → extract WAV
2. Process: VAD (Silero) → Speaker embedding (ECAPA-TDNN) → Spectral clustering
3. Output: segments with speaker_id
"""
import sys
import json
import argparse
import os
import subprocess
import tempfile
from pathlib import Path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(
0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "asrx_self")
)
from redis_publisher import RedisPublisher
def probe_audio_tracks(video_path: str) -> list:
"""Use ffprobe to list all audio tracks in the video file."""
cmd = [
"ffprobe", "-v", "quiet", "-print_format", "json",
"-show_streams", "-select_streams", "a", video_path,
]
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
data = json.loads(result.stdout)
tracks = []
for stream in data.get("streams", []):
track = {
"index": stream.get("index"),
"codec": stream.get("codec_name"),
"language": stream.get("tags", {}).get("language", "und"),
"channels": stream.get("channels", 0),
"sample_rate": stream.get("sample_rate", "0"),
}
tracks.append(track)
return tracks
except Exception as e:
print(f"[ASRX] ffprobe failed: {e}")
return []
def select_best_track(tracks: list) -> int:
"""Select the best audio track: English > first available > fallback to 0."""
if not tracks:
return 0
# Priority 1: English track
for i, t in enumerate(tracks):
if t["language"] == "eng" or t["language"] == "en":
print(f"[ASRX] Selected English track (index {t['index']})")
return i
# Priority 2: First track with the most channels
best = 0
for i, t in enumerate(tracks):
if t["channels"] > tracks[best]["channels"]:
best = i
print(f"[ASRX] Selected track {best} (lang={tracks[best]['language']}, ch={tracks[best]['channels']})")
return best
def extract_audio_to_wav(video_path: str, track_index: int, output_wav: str) -> bool:
"""Extract selected audio track to 16kHz mono WAV using ffmpeg."""
cmd = [
"ffmpeg", "-y", "-v", "quiet",
"-i", video_path,
"-map", f"0:{track_index}",
"-ar", "16000",
"-ac", "1",
"-sample_fmt", "s16",
output_wav,
]
try:
subprocess.run(cmd, check=True, capture_output=True, timeout=300)
return True
except Exception as e:
print(f"[ASRX] ffmpeg extraction failed: {e}")
return False
def _cleanup(tmp_dir):
"""Clean up temporary directory."""
if tmp_dir and os.path.exists(tmp_dir):
import shutil
shutil.rmtree(tmp_dir, ignore_errors=True)
def process_asrx_custom(video_path: str, output_path: str, uuid: str = ""):
"""Process video for speaker diarization using custom implementation"""
publisher = RedisPublisher(uuid) if uuid else None
if publisher:
publisher.info("asrx", "ASRX_START")
tmp_dir = None
try:
# Ensure working directory is the scripts dir for model loading
script_dir = os.path.dirname(os.path.abspath(__file__))
os.chdir(script_dir)
# Debug: check ffmpeg availability
import shutil
ffmpeg_path = shutil.which("ffmpeg")
print(f"[ASRX] ffmpeg: {ffmpeg_path}", file=sys.stderr)
print(f"[ASRX] CWD: {os.getcwd()}", file=sys.stderr)
# ---- Stage 1: Audio Track Preprocessing ----
print("\n[ASRX] ===== Stage 1: Audio Track Analysis =====", file=sys.stderr)
print(f"[ASRX] Input: {video_path}", file=sys.stderr)
tracks = probe_audio_tracks(video_path)
if tracks:
print(f"[ASRX] Found {len(tracks)} audio track(s):", file=sys.stderr)
for t in tracks:
print(f" Track {t['index']}: {t['codec']} {t['channels']}ch {t['sample_rate']}Hz lang={t['language']}", file=sys.stderr)
else:
print("[ASRX] No audio tracks found via ffprobe, using raw file", file=sys.stderr)
# Select best track
track_idx = select_best_track(tracks) if tracks else 0
actual_track_index = tracks[track_idx]["index"] if tracks else track_idx
# Extract audio to WAV
tmp_dir = tempfile.mkdtemp(prefix="asrx_")
wav_path = os.path.join(tmp_dir, "audio.wav")
if extract_audio_to_wav(video_path, actual_track_index, wav_path):
wav_size = os.path.getsize(wav_path)
print(f"[ASRX] Audio extracted: {wav_path} ({wav_size / 1024 / 1024:.1f}MB)", file=sys.stderr)
audio_input = wav_path
else:
print("[ASRX] Audio extraction failed, falling back to original file", file=sys.stderr)
audio_input = video_path
# ---- Stage 2: Load ASR segments for time alignment ----
# Try multiple paths to find ASR JSON
asr_segments = []
asr_fallback_reason = ""
asr_candidates = [
output_path.replace(".asrx.json", ".asr.json") if output_path else "",
os.path.join(os.path.dirname(output_path) if output_path else ".", os.path.basename(video_path).rsplit(".", 1)[0] + ".asr.json"),
os.path.join(os.path.dirname(output_path) if output_path else ".", "dd61fda85fee441fdd00ab5528213ff7.asr.json"),
]
asr_path = ""
for candidate in asr_candidates:
if candidate and os.path.exists(candidate):
asr_path = candidate
break
if asr_path:
try:
with open(asr_path) as f:
asr_data = json.load(f)
asr_segments = asr_data.get("segments", [])
print(f"[ASRX] Loaded {len(asr_segments)} ASR segments from {asr_path}", file=sys.stderr)
asr_fallback_reason = f"loaded_{len(asr_segments)}_segments"
except Exception as e:
asr_fallback_reason = f"load_error_{e}"
print(f"[ASRX] Failed to load ASR segments: {e}", file=sys.stderr)
else:
asr_fallback_reason = f"asr_json_not_found_tried_{len(asr_candidates)}_paths"
print(f"[ASRX] ASR output not found, tried {len(asr_candidates)} paths. First candidate: {asr_candidates[0]}", file=sys.stderr)
# ---- Stage 3: ASRX Processing ----
from asrx_self.main_fixed import SelfASRXFixed
if publisher:
publisher.info("asrx", "ASRX_LOADING_MODEL")
asrx = SelfASRXFixed()
if publisher:
publisher.info("asrx", "ASRX_TRANSCRIBING")
if asr_segments:
print(f"[ASRX] Using {len(asr_segments)} ASR segments for diarization", file=sys.stderr)
result = asrx.process(
audio_input,
output_path=None,
max_speakers=10,
asr_segments=asr_segments if asr_segments else None,
)
if "error" in result:
if publisher:
publisher.error("asrx", result["error"])
# Return empty result
output_result = {"language": None, "segments": []}
with open(output_path, "w") as f:
json.dump(output_result, f, indent=2)
if publisher:
publisher.complete("asrx", "0 segments")
_cleanup(tmp_dir)
return output_result
# Convert to Rust-expected format (start_frame/end_frame/speaker)
# Read fps from probe json ({file_uuid}.probe.json)
_debug = {"asr_fallback": asr_fallback_reason, "asr_path": asr_path}
fps = 30.0
output_dir = os.path.dirname(output_path) if output_path else "."
base_name = os.path.basename(output_path) if output_path else ""
# Extract uuid from {uuid}.{type}.json format
uuid_part = base_name.split(".")[0] if base_name else ""
probe_candidates = [
os.path.join(output_dir, f"{uuid_part}.probe.json"),
]
for p in probe_candidates:
if os.path.exists(p):
try:
with open(p) as pf:
probe_data = json.load(pf)
if "fps" in probe_data:
fps = float(probe_data["fps"])
print(f"[ASRX] FPS from probe: {fps}", file=sys.stderr)
break
except:
pass
output_result = {
"language": None,
"segments": [],
}
# Convert segments
for seg in result["segments"]:
start_sec = seg["start"]
end_sec = seg["end"]
output_result["segments"].append(
{
"start_time": start_sec,
"end_time": end_sec,
"start_frame": int(start_sec * fps),
"end_frame": int(end_sec * fps),
"text": "",
"speaker_id": seg["speaker"],
}
)
# Add speaker_stats as optional metadata
if "speaker_stats" in result:
output_result["speaker_stats"] = result["speaker_stats"]
# 傳遞 embeddings每個 segment 對應的 192-D speaker embedding
if "embeddings" in result:
output_result["embeddings"] = result["embeddings"]
if publisher:
publisher.info("asrx", f"ASRX_COMPLETE:{len(output_result['segments'])}")
# Save output
output_result["_debug"] = _debug
with open(output_path, "w") as f:
json.dump(output_result, f, indent=2)
if publisher:
publisher.complete("asrx", f"{len(output_result['segments'])} segments")
print(f"[ASRX-Custom] Saved {len(output_result['segments'])} segments to {output_path}", file=sys.stderr)
_cleanup(tmp_dir)
return output_result
except Exception as e:
if publisher:
publisher.error("asrx", str(e))
import traceback
traceback.print_exc()
# Return empty result on error
output_result = {"language": None, "segments": []}
with open(output_path, "w") as f:
json.dump(output_result, f, indent=2)
if publisher:
publisher.complete("asrx", "0 segments")
_cleanup(tmp_dir)
return output_result
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="ASRX Processor (Custom Implementation)"
)
parser.add_argument("video_path", help="Path to video/audio file")
parser.add_argument("output_path", help="Path to output JSON file")
parser.add_argument("--uuid", help="UUID for Redis publishing", default="")
parser.add_argument("--file-uuid", help="File UUID (deprecated, use --uuid)", default="")
args = parser.parse_args()
if not Path(args.video_path).exists():
print(f"Error: Video file not found: {args.video_path}")
sys.exit(1)
result = process_asrx_custom(args.video_path, args.output_path, args.uuid)
print("\n[Summary]")
print(f" Total segments: {len(result['segments'])}")
if "speaker_stats" in result:
print(f" Detected speakers: {len(result['speaker_stats'])}")
for speaker, stats in result["speaker_stats"].items():
print(f" {speaker}: {stats['count']} segments")

View File

@@ -0,0 +1 @@
../v1.1/scripts/asrx_processor_v1.11.py

View File

@@ -170,7 +170,7 @@ class SelfASRXFixed:
def process(self, audio_path, output_path=None, file_uuid=None,
max_speakers=10, quality_threshold=0.85,
checkpoint_path=None):
checkpoint_path=None, asr_segments=None):
"""7 步 speaker diarization pipeline
Args:
@@ -180,6 +180,7 @@ class SelfASRXFixed:
max_speakers: 最大說話人數
quality_threshold: 高品質聲紋門檻 (0-1)
checkpoint_path: Step 3 完成後儲存 checkpoint 路徑
asr_segments: 外部 ASR segments (from asr.json),跳過 Step 1
Returns:
dict: segments, speaker_stats, n_speakers, total_duration, references
@@ -194,16 +195,21 @@ class SelfASRXFixed:
print(f" Audio: {total_duration:.2f}s, {sample_rate}Hz")
# ── Step 1: whisper 粗略定位 (faster-whisper) ──
print("\n[Step 1] Initial whisper transcription...")
t1 = time.time()
seg_gen, info = self.whisper.transcribe(audio_path)
rough_segments = []
for seg in seg_gen:
rough_segments.append({"start": seg.start, "end": seg.end, "text": seg.text})
language = info.language if info else None
print(f" Rough segments: {len(rough_segments)}")
print(f" Language: {language}")
print(f" Step 1 time: {time.time() - t1:.2f}s")
if asr_segments:
print(f"\n[Step 1] Skipping whisper, using {len(asr_segments)} provided ASR segments")
rough_segments = asr_segments
language = asr_segments[0].get("language") if isinstance(asr_segments[0].get("language"), str) else None
else:
print("\n[Step 1] Initial whisper transcription...")
t1 = time.time()
seg_gen, info = self.whisper.transcribe(audio_path)
rough_segments = []
for seg in seg_gen:
rough_segments.append({"start": seg.start, "end": seg.end, "text": seg.text})
language = info.language if info else None
print(f" Rough segments: {len(rough_segments)}")
print(f" Language: {language}")
print(f" Step 1 time: {time.time() - t1:.2f}s")
if not rough_segments:
print("[SelfASRX] No speech detected by whisper!")

View File

@@ -0,0 +1 @@
../v1.1/scripts/audio_taxonomy_processor_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/audio_taxonomy_processor_v2_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/auto_identify_persons_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/backfill_demographics_v1.11.py

View File

@@ -0,0 +1,76 @@
#!/opt/homebrew/bin/python3.11
"""Backfill face_id for existing face_detections rows using trace_id.
face_id is generated as 'face_{trace_id}' for each unique trace.
This covers past data where face_id was never written.
"""
import os
import psycopg2
DB_URL = os.environ.get("DATABASE_URL", "postgresql://accusys@localhost:5432/momentry")
SCHEMA = os.environ.get("MOMENTRY_DB_SCHEMA", "dev")
def get_conn():
return psycopg2.connect(DB_URL)
def backfill_by_trace(file_uuid: str, schema: str = SCHEMA) -> int:
"""Set face_id = 'face_{trace_id}' for all rows with NULL face_id and non-NULL trace_id."""
conn = get_conn()
cur = conn.cursor()
cur.execute(
f"""
UPDATE {schema}.face_detections
SET face_id = 'face_' || trace_id::text
WHERE file_uuid = %s
AND face_id IS NULL
AND trace_id IS NOT NULL
""",
(file_uuid,),
)
updated = cur.rowcount
conn.commit()
cur.close()
conn.close()
return updated
def main():
conn = get_conn()
cur = conn.cursor()
# Count rows that need backfill
cur.execute(
f"""SELECT COUNT(*) FROM {SCHEMA}.face_detections
WHERE face_id IS NULL AND trace_id IS NOT NULL"""
)
total_rows = cur.fetchone()[0]
cur.execute(
f"""SELECT DISTINCT file_uuid FROM {SCHEMA}.face_detections
WHERE face_id IS NULL AND trace_id IS NOT NULL"""
)
uuids = [row[0] for row in cur.fetchall()]
cur.close()
conn.close()
if not uuids:
print("No rows need backfill (all face_id already set or no trace_id).")
return
print(f"Found {total_rows} rows across {len(uuids)} files to backfill")
total_all = 0
for uuid in uuids:
count = backfill_by_trace(uuid)
total_all += count
print(f" [{uuid}] updated {count} rows")
print(f"\nDone: {len(uuids)} files, {total_all} rows updated")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1 @@
../v1.1/scripts/backfill_frame_data_v1.11.py

1
scripts/build_docs_v1.11.py Symbolic link
View File

@@ -0,0 +1 @@
../v1.1/scripts/build_docs_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/build_semantic_index_poc_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/build_semantic_index_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/bvh_exporter_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/caption_processor_contract_v1_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/caption_processor_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/check_all_stamps_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/check_architecture_all_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/check_architecture_docs_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/check_code_document_consistency_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/check_frame_112_36_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/check_frame_91_59_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/chinese_vector_test_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/chunk_statistics_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/clean_sentence_text_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/clip_classifier_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/clip_logo_integration_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/compare_asr_content_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/compare_asr_models_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/compare_models_gun_test_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/compare_search_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/compare_segmentation_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/comprehensive_search_test_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/coreml_embed_server_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/crop_opencv_stamp_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/crop_real_stamps_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/crop_stamp_112_36_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/crop_stamp_closeup_v1.11.py

1
scripts/crop_stamp_v1.11.py Symbolic link
View File

@@ -0,0 +1 @@
../v1.1/scripts/crop_stamp_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/crop_top_candidates_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/cut_benchmark_runner_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/cut_processor_contract_v1_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/cut_processor_v1.11.py

1
scripts/dashboard_v1.11.py Symbolic link
View File

@@ -0,0 +1 @@
../v1.1/scripts/dashboard_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/debug_face_registration_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/deep_analysis_112_36_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/demo_dashboard_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/demo_face_learning_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/dense_scan_traces_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/detect_language_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/detect_objects_keyframes_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/detect_stamp_shapes_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/download_places365_classes_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/e4b_per_cut_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/embed_faces_only_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/embed_faces_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/embeddinggemma_server_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/export_file_package_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/export_file_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/export_identities_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/export_person_thumbnails_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/export_sqlite_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/extract_embeddings_from_face_json_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/extract_face_crops_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/extract_face_embedding_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/extract_female_faces_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/extract_video_embeddings_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/face_benchmark_runner_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/face_clustering_processor_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/face_count_comparison_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/face_cross_validate_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/face_embedding_extractor_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/face_landmark_qc_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/face_mediapipe_test_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/face_processor_contract_v1_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/face_processor_mps_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/face_processor_optimized_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/face_processor_v1.11.py

View File

@@ -0,0 +1 @@
../v1.1/scripts/face_processor_v1_v1.11.py

Some files were not shown because too many files have changed in this diff Show More