diff --git a/apps/api/app/api/routes/chunked_upload.py b/apps/api/app/api/routes/chunked_upload.py index 45664d786..d2c1e49cf 100644 --- a/apps/api/app/api/routes/chunked_upload.py +++ b/apps/api/app/api/routes/chunked_upload.py @@ -173,7 +173,7 @@ def _cleanup_expired_uploads() -> int: expires_at = expires_at.replace(tzinfo=timezone.utc) # Only cleanup uploads that are not actively being uploaded - if expires_at < now and meta.get("status") != "uploading": + if expires_at < now and meta.get("status") != "uploading": upload_id = meta["upload_id"] chunk_dir = _get_chunk_dir(upload_id) if chunk_dir.exists(): diff --git a/apps/worker/video_processing/dedup.py b/apps/worker/video_processing/dedup.py index 492ef0995..dbc69308a 100644 --- a/apps/worker/video_processing/dedup.py +++ b/apps/worker/video_processing/dedup.py @@ -27,12 +27,20 @@ if SessionLocal is None: def compute_phash(image: np.ndarray, hash_size: int = 8) -> str: - """Compute perceptual hash of an image.""" - image = cv2.resize(image, (hash_size * 4, hash_size * 4)) - gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) - avg = gray.mean() - diff = (gray > avg).astype(int) - hash_str = """.join(str(b) for row in diff for b in row) + """Compute perceptual hash of an image using DCT.""" + # Resize to 32x32 for DCT + resized = cv2.resize(image, (hash_size * 4, hash_size * 4)) + gray = cv2.cvtColor(resized, cv2.COLOR_BGR2GRAY).astype(np.float32) + # Apply 2D DCT + dct = cv2.dct(gray) + # Take top-left 8x8 low-frequency components + dct_low = dct[:hash_size, :hash_size] + # Compute median (excluding DC component at [0,0]) + dct_low[0, 0] = 0 + median = np.median(dct_low) + # Generate hash based on comparison with median + diff = (dct_low > median).astype(int) + hash_str = "".join(str(b) for row in diff for b in row) return hex(int(hash_str, 2))[2:]