Adding Continuous mode

This commit is contained in:
2026-03-16 17:02:41 +00:00
parent b73cbeb027
commit 47136f2223
2 changed files with 172 additions and 35 deletions

View File

@@ -0,0 +1,7 @@
{
"permissions": {
"allow": [
"WebFetch(domain:sightengine.com)"
]
}
}

View File

@@ -1,11 +1,11 @@
import os
import sys
import time
import requests
import json
import tkinter as tk
from tkinter import filedialog
from rich.console import Console
from rich.progress import Progress, SpinnerColumn, TextColumn
from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
from rich.panel import Panel
from rich.table import Table
from rich import print
@@ -15,7 +15,13 @@ from rich import print
API_USER = "1020608869"
API_SECRET = "M7WF4mAXwSs2gs869LC9PecvYCBQyBSz"
API_ENDPOINT = "https://api.sightengine.com/1.0/video/check-sync.json"
SYNC_ENDPOINT = "https://api.sightengine.com/1.0/video/check-sync.json"
ASYNC_ENDPOINT = "https://api.sightengine.com/1.0/video/check.json"
UPLOAD_ENDPOINT = "https://api.sightengine.com/1.0/upload/create-video.json"
SIZE_THRESHOLD = 50 * 1024 * 1024 # 50 MB
POLL_INTERVAL = 5 # seconds between polls
POLL_TIMEOUT = 600 # max seconds to wait for async result
console = Console()
@@ -23,32 +29,150 @@ def select_video_file():
"""Opens a file dialog to select a video file using Tkinter."""
root = tk.Tk()
root.withdraw() # Hide the main window
file_path = filedialog.askopenfilename(
title="Select a video file to analyze",
filetypes=[
("Video Files", "*.mp4 *.mov *.avi *.mkv *.webm *.flv *.wmv"),
("Video Files", "*.mp4 *.mov *.avi *.mkv *.webm *.flv *.wmv"),
("All Files", "*.*")
]
)
return file_path
def check_video(file_path):
"""Checks the video using Sightengine API."""
if not os.path.exists(file_path):
console.print(f"[bold red]Error:[/bold red] File not found: {file_path}")
return
def upload_video(file_path):
"""Uploads a large video via the Sightengine Upload API. Returns the media_id."""
# Step 1: create a signed upload URL
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
transient=True
) as progress:
progress.add_task(description="Requesting upload URL...", total=None)
resp = requests.get(UPLOAD_ENDPOINT, params={
'api_user': API_USER,
'api_secret': API_SECRET
})
data = resp.json()
if data.get('status') != 'success':
raise RuntimeError(f"Upload URL error: {data.get('error', {}).get('message', 'Unknown')}")
media_id = data['media']['id']
upload_url = data['media']['upload_url']
# Step 2: PUT the file to the signed URL
file_size = os.path.getsize(file_path)
console.print(f"[blue]Uploading[/blue] {os.path.basename(file_path)} ({file_size / 1024 / 1024:.1f} MB)...")
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
TimeElapsedColumn(),
transient=True
) as progress:
progress.add_task(description="Uploading video to Sightengine...", total=None)
with open(file_path, 'rb') as f:
put_resp = requests.put(upload_url, data=f, headers={'Content-Type': 'video/mp4'})
if put_resp.status_code not in (200, 204):
raise RuntimeError(f"Upload failed: HTTP {put_resp.status_code}")
console.print(f"[green]Upload complete.[/green] Media ID: {media_id}")
return media_id
def check_video_async(file_path):
"""Submits a video for async analysis and polls until results are ready."""
file_size = os.path.getsize(file_path)
params = {
'models': 'genai',
'api_user': API_USER,
'api_secret': API_SECRET
}
files = {
'media': open(file_path, 'rb')
# Submit the job
if file_size >= SIZE_THRESHOLD:
media_id = upload_video(file_path)
console.print("[blue]Submitting async analysis job...[/blue]")
with Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}"), transient=True) as progress:
progress.add_task(description="Submitting job...", total=None)
params['media_id'] = media_id
resp = requests.post(ASYNC_ENDPOINT, data=params)
else:
console.print("[blue]Submitting async analysis job (file upload)...[/blue]")
with Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}"), transient=True) as progress:
progress.add_task(description="Uploading and submitting job...", total=None)
with open(file_path, 'rb') as f:
resp = requests.post(ASYNC_ENDPOINT, files={'media': f}, data=params)
resp_data = resp.json()
if resp_data.get('status') not in ('success', 'submitted'):
error_msg = resp_data.get('error', {}).get('message', 'Unknown error')
raise RuntimeError(f"Async submission error: {error_msg}")
request_id = resp_data.get('request', {}).get('id') or resp_data.get('media', {}).get('id')
if not request_id:
raise RuntimeError(f"No request ID in response: {resp_data}")
console.print(f"[blue]Job submitted.[/blue] Request ID: {request_id}")
# Poll for results
console.print(f"[blue]Waiting for results[/blue] (polling every {POLL_INTERVAL}s, timeout {POLL_TIMEOUT}s)...")
start = time.time()
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
TimeElapsedColumn(),
transient=True
) as progress:
progress.add_task(description="Analyzing video...", total=None)
while time.time() - start < POLL_TIMEOUT:
time.sleep(POLL_INTERVAL)
poll_resp = requests.get(ASYNC_ENDPOINT, params={
'request_id': request_id,
'models': 'genai',
'api_user': API_USER,
'api_secret': API_SECRET
})
poll_data = poll_resp.json()
status = poll_data.get('status')
if status == 'finished':
return poll_data
elif status in ('error', 'failure'):
error_msg = poll_data.get('error', {}).get('message', 'Unknown error')
raise RuntimeError(f"Async analysis error: {error_msg}")
# status == 'processing' or similar — keep polling
raise RuntimeError(f"Timed out waiting for async results after {POLL_TIMEOUT}s")
def check_video(file_path):
"""Checks the video using Sightengine API (sync, with async fallback for long videos)."""
if not os.path.exists(file_path):
console.print(f"[bold red]Error:[/bold red] File not found: {file_path}")
return
file_size = os.path.getsize(file_path)
# Large files go directly to async mode
if file_size >= SIZE_THRESHOLD:
console.print(f"[yellow]Large file ({file_size / 1024 / 1024:.1f} MB) — using async upload mode.[/yellow]")
try:
response_data = check_video_async(file_path)
display_results(file_path, response_data)
except Exception as e:
console.print(f"[bold red]System Error:[/bold red] {e}")
return
# Small files: try sync first
params = {
'models': 'genai',
'api_user': API_USER,
'api_secret': API_SECRET
}
media_file = open(file_path, 'rb')
try:
with Progress(
SpinnerColumn(),
@@ -56,25 +180,37 @@ def check_video(file_path):
transient=True
) as progress:
progress.add_task(description="Uploading and analyzing video...", total=None)
response = requests.post(API_ENDPOINT, files=files, data=params)
response_data = json.loads(response.text)
if response_data['status'] == 'success':
response = requests.post(SYNC_ENDPOINT, files={'media': media_file}, data=params)
response_data = response.json()
if response_data.get('status') == 'success':
display_results(file_path, response_data)
return
error_msg = response_data.get('error', {}).get('message', 'Unknown error')
# Fallback: video too long for sync mode
if 'too long' in error_msg.lower() or 'continuous moderation' in error_msg.lower():
console.print(f"[yellow]Video too long for sync mode — switching to async mode...[/yellow]")
media_file.close()
try:
response_data = check_video_async(file_path)
display_results(file_path, response_data)
except Exception as e:
console.print(f"[bold red]System Error:[/bold red] {e}")
else:
error_msg = response_data.get('error', {}).get('message', 'Unknown error')
console.print(f"[bold red]API Error:[/bold red] {error_msg}")
except Exception as e:
console.print(f"[bold red]System Error:[/bold red] {e}")
finally:
files['media'].close()
if not media_file.closed:
media_file.close()
def display_results(file_path, data):
"""Displays the analysis results using Rich."""
# Calculate aggregate score (average of all frames for simplicity in this demo)
frames = data.get('data', {}).get('frames', [])
if not frames:
console.print("[yellow]No frames analyzed.[/yellow]")
@@ -82,8 +218,7 @@ def display_results(file_path, data):
total_ai_score = 0
count = 0
# Create detailed table for frames (showing first few or high scores)
table = Table(title="Frame Analysis (Sample)")
table.add_column("Time (s)", justify="right", style="cyan", no_wrap=True)
table.add_column("AI Probability", justify="right", style="magenta")
@@ -97,19 +232,14 @@ def display_results(file_path, data):
count += 1
if ai_score > max_ai_score:
max_ai_score = ai_score
# Add to table if it's a significant score or just to show some data
# Showing only frames with > 0.1 score or every 10th frame to keep it clean if many frames
if ai_score > 0.5 or count % 10 == 1:
verdict = "[red]FAKE[/red]" if ai_score > 0.8 else ("[yellow]SUSPICIOUS[/yellow]" if ai_score > 0.4 else "[green]REAL[/green]")
# Assuming 1 frame per second roughly? API documentation says 'position' is likely offset
position = frame.get('info', {}).get('position', 0)
# Provide string formatting for score
table.add_row(str(position), f"{ai_score:.2%}", verdict)
avg_ai_score = total_ai_score / count if count else 0
# Final Verdict Panel
final_verdict = "UNCERTAIN"
color = "yellow"
if max_ai_score > 0.85:
@@ -118,22 +248,22 @@ def display_results(file_path, data):
elif max_ai_score < 0.2:
final_verdict = "AUTHENTIC"
color = "green"
request_info = data.get('request', {})
req_id = request_info.get('id', 'N/A')
ops = request_info.get('operations', 0)
panel_content = f"""
[bold]File:[/bold] {os.path.basename(file_path)}
[bold]Request ID:[/bold] {req_id}
[bold]Operations Cost:[/bold] {ops}
[bold]Max AI Score:[/bold] {max_ai_score:.2%}
[bold]Average AI Score:[/bold] {avg_ai_score:.2%}
[bold size=20 style={color}]VERDICT: {final_verdict}[/bold size=20 style={color}]
"""
console.print(Panel(panel_content, title="Analysis Result", expand=False))
console.print(table)