Adding Continuous mode
This commit is contained in:
7
.claude/settings.local.json
Normal file
7
.claude/settings.local.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"WebFetch(domain:sightengine.com)"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user