Files
saqel/backend/app/Services/VideoCoverageValidator.php
T

39 lines
1.6 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services;
/** Structural coverage gate. Content accuracy still requires academic review. */
final class VideoCoverageValidator
{
public static function valid(array $coverage, int $durationSeconds, string $videoSha256): bool
{
if ($durationSeconds <= 0 || $durationSeconds > 4 * 60 * 60 ||
!preg_match('/^[0-9a-f]{64}$/i', $videoSha256) ||
!hash_equals(strtolower($videoSha256), strtolower((string)($coverage['video_sha256'] ?? ''))) ||
($coverage['truncated'] ?? true) !== false ||
!empty($coverage['gaps']) ||
!is_array($coverage['segments'] ?? null) || $coverage['segments'] === []) {
return false;
}
$lastEnd = 0.0;
foreach ($coverage['segments'] as $segment) {
if (!is_array($segment) ||
!is_numeric($segment['start_seconds'] ?? null) ||
!is_numeric($segment['end_seconds'] ?? null) ||
!is_string($segment['transcript_ref'] ?? null) || trim($segment['transcript_ref']) === '' ||
!is_string($segment['visual_ref'] ?? null) || trim($segment['visual_ref']) === '') {
return false;
}
$start = (float)$segment['start_seconds'];
$end = (float)$segment['end_seconds'];
if (!is_finite($start) || !is_finite($end) ||
abs($start - $lastEnd) > 0.5 || $end <= $start || $end > $durationSeconds + 0.5) {
return false;
}
$lastEnd = $end;
}
return abs($lastEnd - $durationSeconds) <= 0.5;
}
}