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 os
|
||||||
import sys
|
import sys
|
||||||
|
import time
|
||||||
import requests
|
import requests
|
||||||
import json
|
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import filedialog
|
from tkinter import filedialog
|
||||||
from rich.console import Console
|
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.panel import Panel
|
||||||
from rich.table import Table
|
from rich.table import Table
|
||||||
from rich import print
|
from rich import print
|
||||||
@@ -15,7 +15,13 @@ from rich import print
|
|||||||
API_USER = "1020608869"
|
API_USER = "1020608869"
|
||||||
API_SECRET = "M7WF4mAXwSs2gs869LC9PecvYCBQyBSz"
|
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()
|
console = Console()
|
||||||
|
|
||||||
@@ -33,22 +39,140 @@ def select_video_file():
|
|||||||
)
|
)
|
||||||
return file_path
|
return file_path
|
||||||
|
|
||||||
def check_video(file_path):
|
def upload_video(file_path):
|
||||||
"""Checks the video using Sightengine API."""
|
"""Uploads a large video via the Sightengine Upload API. Returns the media_id."""
|
||||||
if not os.path.exists(file_path):
|
# Step 1: create a signed upload URL
|
||||||
console.print(f"[bold red]Error:[/bold red] File not found: {file_path}")
|
with Progress(
|
||||||
return
|
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 = {
|
params = {
|
||||||
'models': 'genai',
|
'models': 'genai',
|
||||||
'api_user': API_USER,
|
'api_user': API_USER,
|
||||||
'api_secret': API_SECRET
|
'api_secret': API_SECRET
|
||||||
}
|
}
|
||||||
|
|
||||||
files = {
|
# Submit the job
|
||||||
'media': open(file_path, 'rb')
|
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:
|
try:
|
||||||
with Progress(
|
with Progress(
|
||||||
SpinnerColumn(),
|
SpinnerColumn(),
|
||||||
@@ -56,25 +180,37 @@ def check_video(file_path):
|
|||||||
transient=True
|
transient=True
|
||||||
) as progress:
|
) as progress:
|
||||||
progress.add_task(description="Uploading and analyzing video...", total=None)
|
progress.add_task(description="Uploading and analyzing video...", total=None)
|
||||||
response = requests.post(API_ENDPOINT, files=files, data=params)
|
response = requests.post(SYNC_ENDPOINT, files={'media': media_file}, data=params)
|
||||||
|
|
||||||
response_data = json.loads(response.text)
|
response_data = response.json()
|
||||||
|
|
||||||
if response_data['status'] == 'success':
|
if response_data.get('status') == 'success':
|
||||||
display_results(file_path, response_data)
|
display_results(file_path, response_data)
|
||||||
else:
|
return
|
||||||
|
|
||||||
error_msg = response_data.get('error', {}).get('message', 'Unknown error')
|
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:
|
||||||
console.print(f"[bold red]API Error:[/bold red] {error_msg}")
|
console.print(f"[bold red]API Error:[/bold red] {error_msg}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
console.print(f"[bold red]System Error:[/bold red] {e}")
|
console.print(f"[bold red]System Error:[/bold red] {e}")
|
||||||
finally:
|
finally:
|
||||||
files['media'].close()
|
if not media_file.closed:
|
||||||
|
media_file.close()
|
||||||
|
|
||||||
def display_results(file_path, data):
|
def display_results(file_path, data):
|
||||||
"""Displays the analysis results using Rich."""
|
"""Displays the analysis results using Rich."""
|
||||||
|
|
||||||
# Calculate aggregate score (average of all frames for simplicity in this demo)
|
|
||||||
frames = data.get('data', {}).get('frames', [])
|
frames = data.get('data', {}).get('frames', [])
|
||||||
if not frames:
|
if not frames:
|
||||||
console.print("[yellow]No frames analyzed.[/yellow]")
|
console.print("[yellow]No frames analyzed.[/yellow]")
|
||||||
@@ -83,7 +219,6 @@ def display_results(file_path, data):
|
|||||||
total_ai_score = 0
|
total_ai_score = 0
|
||||||
count = 0
|
count = 0
|
||||||
|
|
||||||
# Create detailed table for frames (showing first few or high scores)
|
|
||||||
table = Table(title="Frame Analysis (Sample)")
|
table = Table(title="Frame Analysis (Sample)")
|
||||||
table.add_column("Time (s)", justify="right", style="cyan", no_wrap=True)
|
table.add_column("Time (s)", justify="right", style="cyan", no_wrap=True)
|
||||||
table.add_column("AI Probability", justify="right", style="magenta")
|
table.add_column("AI Probability", justify="right", style="magenta")
|
||||||
@@ -98,18 +233,13 @@ def display_results(file_path, data):
|
|||||||
if ai_score > max_ai_score:
|
if ai_score > max_ai_score:
|
||||||
max_ai_score = 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:
|
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]")
|
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)
|
position = frame.get('info', {}).get('position', 0)
|
||||||
# Provide string formatting for score
|
|
||||||
table.add_row(str(position), f"{ai_score:.2%}", verdict)
|
table.add_row(str(position), f"{ai_score:.2%}", verdict)
|
||||||
|
|
||||||
avg_ai_score = total_ai_score / count if count else 0
|
avg_ai_score = total_ai_score / count if count else 0
|
||||||
|
|
||||||
# Final Verdict Panel
|
|
||||||
final_verdict = "UNCERTAIN"
|
final_verdict = "UNCERTAIN"
|
||||||
color = "yellow"
|
color = "yellow"
|
||||||
if max_ai_score > 0.85:
|
if max_ai_score > 0.85:
|
||||||
|
|||||||
Reference in New Issue
Block a user