Spaces:
Runtime error
Runtime error
Heewon Oh commited on
Commit ·
912c002
1
Parent(s): 742e266
refactor: remove LGBM dependency and rollback to 3-tier verdict
Browse files- Remove LGBM model loading, feature extraction, codec-aware classification
- Simplify classify() to pure 3-tier distribution-based verdict
- Delete core/codec_aware.py (no longer needed)
- Remove lightgbm from requirements.txt
- app.py +2 -2
- core/__pycache__/proprietary.cpython-312.pyc +0 -0
- core/codec_aware.py +0 -32
- core/proprietary.py +3 -133
- requirements.txt +1 -1
app.py
CHANGED
|
@@ -373,12 +373,12 @@ def analyze_audio(audio_path: str):
|
|
| 373 |
# 2. E2E inference (ONNX — GPU if available, else CPU)
|
| 374 |
chunk_probs, _ = _run_e2e(mono_tensor)
|
| 375 |
|
| 376 |
-
# 3. Distribution-based verdict (
|
| 377 |
seg_stats = compute_stats(chunk_probs)
|
| 378 |
elapsed = time.time() - t0
|
| 379 |
|
| 380 |
# 4. Generate visualizations
|
| 381 |
-
verdict = classify(seg_stats
|
| 382 |
verdict_html = VerdictCardBuilder.build(
|
| 383 |
verdict, seg_stats, is_stereo,
|
| 384 |
duration=info["duration"], elapsed=elapsed,
|
|
|
|
| 373 |
# 2. E2E inference (ONNX — GPU if available, else CPU)
|
| 374 |
chunk_probs, _ = _run_e2e(mono_tensor)
|
| 375 |
|
| 376 |
+
# 3. Distribution-based verdict (3-tier)
|
| 377 |
seg_stats = compute_stats(chunk_probs)
|
| 378 |
elapsed = time.time() - t0
|
| 379 |
|
| 380 |
# 4. Generate visualizations
|
| 381 |
+
verdict = classify(seg_stats)
|
| 382 |
verdict_html = VerdictCardBuilder.build(
|
| 383 |
verdict, seg_stats, is_stereo,
|
| 384 |
duration=info["duration"], elapsed=elapsed,
|
core/__pycache__/proprietary.cpython-312.pyc
CHANGED
|
Binary files a/core/__pycache__/proprietary.cpython-312.pyc and b/core/__pycache__/proprietary.cpython-312.pyc differ
|
|
|
core/codec_aware.py
DELETED
|
@@ -1,32 +0,0 @@
|
|
| 1 |
-
"""Codec-aware classification module."""
|
| 2 |
-
|
| 3 |
-
from pathlib import Path
|
| 4 |
-
import numpy as np
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
def detect_codec(audio_path):
|
| 8 |
-
"""Detect if audio is lossless or lossy."""
|
| 9 |
-
if audio_path is None:
|
| 10 |
-
return 'unknown'
|
| 11 |
-
|
| 12 |
-
if isinstance(audio_path, str):
|
| 13 |
-
audio_path = Path(audio_path)
|
| 14 |
-
|
| 15 |
-
ext = audio_path.suffix.lower()
|
| 16 |
-
|
| 17 |
-
if ext in {'.wav', '.flac', '.aiff', '.aif'}:
|
| 18 |
-
return 'lossless'
|
| 19 |
-
elif ext in {'.mp3', '.aac', '.m4a', '.ogg', '.opus', '.wma'}:
|
| 20 |
-
return 'lossy'
|
| 21 |
-
else:
|
| 22 |
-
return 'unknown'
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
def get_codec_thresholds(codec_mode):
|
| 26 |
-
"""Get thresholds based on codec mode."""
|
| 27 |
-
if codec_mode == 'lossless':
|
| 28 |
-
return {'ai': 0.5, 'real': 0.5, 'name': 'Lossless (High Sensitivity)'}
|
| 29 |
-
elif codec_mode == 'lossy':
|
| 30 |
-
return {'ai': 0.8, 'real': 0.3, 'name': 'Lossy (Conservative)'}
|
| 31 |
-
else:
|
| 32 |
-
return {'ai': 0.7, 'real': 0.4, 'name': 'Unknown (Moderate)'}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
core/proprietary.py
CHANGED
|
@@ -7,10 +7,7 @@
|
|
| 7 |
|
| 8 |
import base64
|
| 9 |
import json
|
| 10 |
-
import os
|
| 11 |
-
from pathlib import Path
|
| 12 |
import numpy as np
|
| 13 |
-
from scipy import stats as sp_stats
|
| 14 |
|
| 15 |
# Encrypted parameters (XOR + Base64) - DO NOT MODIFY
|
| 16 |
_ENC_P = 'AR3tX367a8ZODq4dcKFpkRJK8EYD8i6RWAW+GXKxZ9JYUcFLOvVpyFoNrhlkrWvQElDuD2ahfsNIE74PMt4mlxZMvBd8sHnKVh+8Tz31KJpYBb4VcKFpnxtHwUkp82nIWgyuHSE='
|
|
@@ -25,9 +22,6 @@ _K = _K1 + _K2 + _K3
|
|
| 25 |
# Decryption cache (computed once)
|
| 26 |
_cache = {}
|
| 27 |
|
| 28 |
-
# LGBM model cache
|
| 29 |
-
_lgbm_model = None
|
| 30 |
-
|
| 31 |
# Obfuscated constants (decoys)
|
| 32 |
_MAGIC_A = 0x1F3D5A7B
|
| 33 |
_MAGIC_B = 0x9C8E2F41
|
|
@@ -133,135 +127,11 @@ def compute_stats(chunk_probs: list[float]) -> dict:
|
|
| 133 |
}
|
| 134 |
|
| 135 |
|
| 136 |
-
def
|
| 137 |
-
"""
|
| 138 |
-
global _lgbm_model
|
| 139 |
-
if _lgbm_model is not None:
|
| 140 |
-
return _lgbm_model
|
| 141 |
-
|
| 142 |
-
import lightgbm as lgb
|
| 143 |
-
from huggingface_hub import hf_hub_download
|
| 144 |
-
|
| 145 |
-
# Try local path first
|
| 146 |
-
local_model = Path(__file__).resolve().parent.parent / "models" / "lgbm_verdict.txt"
|
| 147 |
-
|
| 148 |
-
if local_model.exists():
|
| 149 |
-
model_path = str(local_model)
|
| 150 |
-
else:
|
| 151 |
-
# Download from HF Hub
|
| 152 |
-
model_path = hf_hub_download("intrect/artifactnet-models", "lgbm_verdict.txt")
|
| 153 |
-
|
| 154 |
-
_lgbm_model = lgb.Booster(model_file=model_path)
|
| 155 |
-
return _lgbm_model
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
def _extract_lgbm_features(seg_probs: list[float]) -> np.ndarray:
|
| 159 |
-
"""Extract LGBM features from segment probabilities (v8 2nd-stage)."""
|
| 160 |
-
arr = np.array(seg_probs, dtype=np.float64)
|
| 161 |
-
n = len(arr)
|
| 162 |
-
|
| 163 |
-
if n == 0:
|
| 164 |
-
return None
|
| 165 |
-
|
| 166 |
-
# Distribution statistics
|
| 167 |
-
features = [
|
| 168 |
-
n, # n_segments
|
| 169 |
-
arr.mean(), # mean
|
| 170 |
-
arr.std(), # std
|
| 171 |
-
np.median(arr), # median
|
| 172 |
-
arr.min(), # min
|
| 173 |
-
arr.max(), # max
|
| 174 |
-
arr.max() - arr.min(), # range
|
| 175 |
-
np.percentile(arr, 10), # p10
|
| 176 |
-
np.percentile(arr, 25), # p25
|
| 177 |
-
np.percentile(arr, 75), # p75
|
| 178 |
-
np.percentile(arr, 90), # p90
|
| 179 |
-
(arr >= 0.3).mean(), # r_03
|
| 180 |
-
(arr >= 0.5).mean(), # r_05
|
| 181 |
-
(arr >= 0.7).mean(), # r_07
|
| 182 |
-
(arr >= 0.8).mean(), # r_08
|
| 183 |
-
(arr >= 0.9).mean(), # r_09
|
| 184 |
-
float(sp_stats.skew(arr)) if n >= 3 else 0.0, # skew
|
| 185 |
-
float(sp_stats.kurtosis(arr)) if n >= 3 else 0.0, # kurtosis
|
| 186 |
-
]
|
| 187 |
-
|
| 188 |
-
# Temporal features
|
| 189 |
-
if n >= 2:
|
| 190 |
-
diffs = np.diff(arr)
|
| 191 |
-
features.append(diffs.std()) # temporal_std
|
| 192 |
-
features.append(np.abs(diffs).max()) # temporal_max_jump
|
| 193 |
-
else:
|
| 194 |
-
features.extend([0.0, 0.0])
|
| 195 |
-
|
| 196 |
-
return np.array(features, dtype=np.float32).reshape(1, -1)
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
def classify(stats: dict, seg_probs: list[float] = None, audio_path: str = None) -> str:
|
| 200 |
-
"""LGBM 2nd-stage track-level verdict with codec-aware thresholds (v8.1).
|
| 201 |
-
|
| 202 |
-
Codec-aware dual mode:
|
| 203 |
-
- Lossless (WAV/FLAC): threshold=0.5 (high sensitivity)
|
| 204 |
-
- Lossy (MP3/YouTube): threshold=0.8 (conservative, returns Uncertain for edge cases)
|
| 205 |
|
| 206 |
-
|
| 207 |
"""
|
| 208 |
-
# Detect codec mode
|
| 209 |
-
from .codec_aware import detect_codec, get_codec_thresholds
|
| 210 |
-
codec_mode = detect_codec(audio_path)
|
| 211 |
-
thresholds = get_codec_thresholds(codec_mode)
|
| 212 |
-
|
| 213 |
-
# 3-Tier quick check (strong signals, codec-independent)
|
| 214 |
-
if seg_probs is not None:
|
| 215 |
-
arr = np.array(seg_probs)
|
| 216 |
-
high_ratio = (arr >= 0.8).mean()
|
| 217 |
-
low_ratio = (arr < 0.5).mean()
|
| 218 |
-
|
| 219 |
-
if high_ratio >= 0.75:
|
| 220 |
-
return "AI Generated" # Strong AI signal
|
| 221 |
-
elif low_ratio >= 0.85:
|
| 222 |
-
return "Human-Made" # Strong Real signal
|
| 223 |
-
|
| 224 |
-
# Try LGBM for uncertain zone
|
| 225 |
-
if seg_probs is not None:
|
| 226 |
-
try:
|
| 227 |
-
model = _load_lgbm_model()
|
| 228 |
-
features = _extract_lgbm_features(seg_probs)
|
| 229 |
-
|
| 230 |
-
if features is not None:
|
| 231 |
-
pred_proba = model.predict(features)[0]
|
| 232 |
-
|
| 233 |
-
# Codec-aware thresholds
|
| 234 |
-
if codec_mode == 'lossless':
|
| 235 |
-
# High sensitivity (trained on WAV)
|
| 236 |
-
if pred_proba >= thresholds['ai']:
|
| 237 |
-
return "AI Generated"
|
| 238 |
-
else:
|
| 239 |
-
return "Human-Made"
|
| 240 |
-
|
| 241 |
-
elif codec_mode == 'lossy':
|
| 242 |
-
# Conservative (lossy artifacts can mimic AI)
|
| 243 |
-
if pred_proba >= thresholds['ai']:
|
| 244 |
-
return "AI Generated"
|
| 245 |
-
elif pred_proba <= thresholds['real']:
|
| 246 |
-
return "Human-Made"
|
| 247 |
-
else:
|
| 248 |
-
return "Uncertain" # 0.3~0.8 range
|
| 249 |
-
|
| 250 |
-
else:
|
| 251 |
-
# Unknown codec → moderate
|
| 252 |
-
if pred_proba >= thresholds['ai']:
|
| 253 |
-
return "AI Generated"
|
| 254 |
-
elif pred_proba <= thresholds['real']:
|
| 255 |
-
return "Human-Made"
|
| 256 |
-
else:
|
| 257 |
-
return "Uncertain"
|
| 258 |
-
|
| 259 |
-
except Exception as e:
|
| 260 |
-
# Fallback to 3-Tier on error
|
| 261 |
-
print(f"LGBM error (fallback to 3-Tier): {e}")
|
| 262 |
-
pass
|
| 263 |
-
|
| 264 |
-
# Fallback: 3-Tier rule (legacy)
|
| 265 |
t = _d(_ENC_T, _K)
|
| 266 |
ph = stats["pct_high"]
|
| 267 |
pa = stats["pct_above_50"]
|
|
|
|
| 7 |
|
| 8 |
import base64
|
| 9 |
import json
|
|
|
|
|
|
|
| 10 |
import numpy as np
|
|
|
|
| 11 |
|
| 12 |
# Encrypted parameters (XOR + Base64) - DO NOT MODIFY
|
| 13 |
_ENC_P = 'AR3tX367a8ZODq4dcKFpkRJK8EYD8i6RWAW+GXKxZ9JYUcFLOvVpyFoNrhlkrWvQElDuD2ahfsNIE74PMt4mlxZMvBd8sHnKVh+8Tz31KJpYBb4VcKFpnxtHwUkp82nIWgyuHSE='
|
|
|
|
| 22 |
# Decryption cache (computed once)
|
| 23 |
_cache = {}
|
| 24 |
|
|
|
|
|
|
|
|
|
|
| 25 |
# Obfuscated constants (decoys)
|
| 26 |
_MAGIC_A = 0x1F3D5A7B
|
| 27 |
_MAGIC_B = 0x9C8E2F41
|
|
|
|
| 127 |
}
|
| 128 |
|
| 129 |
|
| 130 |
+
def classify(stats: dict) -> str:
|
| 131 |
+
"""3-Tier distribution-based verdict (v8.0).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
|
| 133 |
+
Encrypted threshold-based classification using segment distribution statistics.
|
| 134 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
t = _d(_ENC_T, _K)
|
| 136 |
ph = stats["pct_high"]
|
| 137 |
pa = stats["pct_above_50"]
|
requirements.txt
CHANGED
|
@@ -7,7 +7,7 @@ huggingface_hub>=0.20.0
|
|
| 7 |
onnxruntime>=1.17.0
|
| 8 |
torch>=2.0.0
|
| 9 |
requests>=2.31.0
|
| 10 |
-
|
| 11 |
gradio>=5.20.0
|
| 12 |
fastapi>=0.104.0
|
| 13 |
uvicorn>=0.24.0
|
|
|
|
| 7 |
onnxruntime>=1.17.0
|
| 8 |
torch>=2.0.0
|
| 9 |
requests>=2.31.0
|
| 10 |
+
|
| 11 |
gradio>=5.20.0
|
| 12 |
fastapi>=0.104.0
|
| 13 |
uvicorn>=0.24.0
|