Spaces:
Running on Zero
Running on Zero
Download app.py from Rubli/deepfake-detector-api: direct link, hf CLI and curl.
- Browser
- Download file 16 kB
-
https://huggingface.co/spaces/Rubli/deepfake-detector-api/resolve/main/app.py
- Command line
-
hf download hf://spaces/Rubli/deepfake-detector-api/app.py
-
curl -L -o app.py https://huggingface.co/spaces/Rubli/deepfake-detector-api/resolve/main/app.py
16 kB
| import os | |
| import sys | |
| import gc | |
| from functools import lru_cache | |
| import gradio as gr | |
| import torch | |
| import torch.nn as nn | |
| import numpy as np | |
| import spaces | |
| import librosa | |
| import torchaudio | |
| import torchaudio.transforms as T | |
| from PIL import Image | |
| from transformers import ( | |
| pipeline, | |
| AutoModelForAudioClassification, | |
| AutoFeatureExtractor, | |
| ) | |
| from huggingface_hub import hf_hub_download | |
| import importlib.util | |
| # ============================================================ | |
| # CONFIGURATION | |
| # ============================================================ | |
| _DEVICE = "cpu" | |
| IMAGE_MODEL_REPO = "xRayon/convnext-ai-images-detector" | |
| IMAGE_CHECKPOINT = "AI Images Detector/checkpoints/checkpoint_phase2.pth" | |
| IMAGE_ARCHITECTURE = "AI Images Detector/model/convnext.py" | |
| IMAGE_TRANSFORMS = "AI Images Detector/transforms.py" | |
| TEXT_MODEL_ID = "Rubli/intent_classifier" | |
| URDU_AUDIO_MODEL = ( | |
| "JuniKhan123/urdu-deepfake-detector-unified_for_gradio_app" | |
| ) | |
| ENGLISH_AUDIO_MODEL = "mo-thecreator/Deepfake-audio-detection" | |
| def _dummy_gpu_check(): | |
| return {"status": "ok"} | |
| # ============================================================ | |
| # 1. IMAGE DETECTION (PRETRAINED CONVNEXT MODEL) | |
| # ============================================================ | |
| def _load_image_detector(): | |
| """ | |
| Load the existing pretrained ConvNeXt model once per Space process. | |
| This does not train or fine-tune the model. | |
| """ | |
| architecture_path = hf_hub_download( | |
| repo_id=IMAGE_MODEL_REPO, | |
| filename=IMAGE_ARCHITECTURE, | |
| ) | |
| checkpoint_path = hf_hub_download( | |
| repo_id=IMAGE_MODEL_REPO, | |
| filename=IMAGE_CHECKPOINT, | |
| ) | |
| transforms_path = hf_hub_download( | |
| repo_id=IMAGE_MODEL_REPO, | |
| filename=IMAGE_TRANSFORMS, | |
| ) | |
| # Load the architecture file from the model repository. | |
| spec = importlib.util.spec_from_file_location( | |
| "sentinelzone_convnext_architecture", | |
| architecture_path, | |
| ) | |
| if spec is None or spec.loader is None: | |
| raise RuntimeError("Could not load the ConvNeXt architecture file.") | |
| architecture_module = importlib.util.module_from_spec(spec) | |
| sys.modules[spec.name] = architecture_module | |
| spec.loader.exec_module(architecture_module) | |
| build_model = getattr(architecture_module, "build_model", None) | |
| if build_model is None: | |
| raise RuntimeError( | |
| "The downloaded convnext.py does not define build_model()." | |
| ) | |
| model = build_model() | |
| checkpoint = torch.load( | |
| checkpoint_path, | |
| map_location=torch.device(_DEVICE), | |
| weights_only=False, | |
| ) | |
| if not isinstance(checkpoint, dict) or "model" not in checkpoint: | |
| raise RuntimeError( | |
| "Unexpected checkpoint format: expected a dictionary with a 'model' key." | |
| ) | |
| model.load_state_dict(checkpoint["model"]) | |
| model.to(torch.device(_DEVICE)) | |
| model.eval() | |
| # Import the author's own test transforms so preprocessing stays exact. | |
| transform_spec = importlib.util.spec_from_file_location( | |
| "sentinelzone_convnext_transforms", | |
| transforms_path, | |
| ) | |
| if transform_spec is None or transform_spec.loader is None: | |
| raise RuntimeError("Could not load the ConvNeXt preprocessing file.") | |
| transform_module = importlib.util.module_from_spec(transform_spec) | |
| sys.modules[transform_spec.name] = transform_module | |
| transform_spec.loader.exec_module(transform_module) | |
| test_transforms = getattr(transform_module, "test_transforms", None) | |
| if test_transforms is None: | |
| raise RuntimeError( | |
| "The downloaded transforms.py does not define test_transforms()." | |
| ) | |
| transform = test_transforms() | |
| return model, transform | |
| def detect_image(image): | |
| if image is None: | |
| return { | |
| "verdict": "No image", | |
| "confidence": 0.0, | |
| } | |
| try: | |
| model, transform = _load_image_detector() | |
| image = image.convert("RGB") | |
| image_tensor = transform(image).unsqueeze(0).to(_DEVICE) | |
| with torch.inference_mode(): | |
| logits = model(image_tensor) | |
| probabilities = torch.softmax(logits.float(), dim=1)[0] | |
| # Model card/dataset mapping: class 0 = real, class 1 = fake. | |
| real_probability = float(probabilities[0].item()) | |
| fake_probability = float(probabilities[1].item()) | |
| verdict = ( | |
| "AI-Generated" | |
| if fake_probability >= 0.50 | |
| else "Real" | |
| ) | |
| confidence = ( | |
| fake_probability if verdict == "AI-Generated" | |
| else real_probability | |
| ) * 100 | |
| return { | |
| "verdict": verdict, | |
| "confidence": round(confidence, 2), | |
| "ai_probability": round(fake_probability * 100, 2), | |
| "real_probability": round(real_probability * 100, 2), | |
| "model": IMAGE_MODEL_REPO, | |
| "predicted_label": "fake" if verdict == "AI-Generated" else "real", | |
| } | |
| except Exception as e: | |
| print(f"ConvNeXt image detection error: {type(e).__name__}: {e}") | |
| return { | |
| "verdict": "Error", | |
| "confidence": 0.0, | |
| "error": f"{type(e).__name__}: {str(e)}", | |
| "model": IMAGE_MODEL_REPO, | |
| } | |
| # ============================================================ | |
| # 2. TEXT HARASSMENT DETECTION | |
| # ============================================================ | |
| def _load_text_classifier(): | |
| return pipeline( | |
| "text-classification", | |
| model=TEXT_MODEL_ID, | |
| device=-1, | |
| ) | |
| def detect_text(text): | |
| if not text or not text.strip(): | |
| return { | |
| "label": "Safe", | |
| "score": 0.0, | |
| "risk": "Low", | |
| } | |
| text_lower = text.lower().strip() | |
| SAFE_WORDS = [ | |
| "hello", "hi", "hey", "ok", "okay", | |
| "thanks", "thank you", "friend", "bhai", | |
| "bro", "sister", "dost", "kya haal hai", | |
| "kasy ho", "kasi ho", | |
| ] | |
| # Exact phrase matching avoids some substring false positives. | |
| if ( | |
| len(text_lower.split()) <= 4 | |
| and text_lower in SAFE_WORDS | |
| ): | |
| return { | |
| "label": "Safe", | |
| "score": 0.0, | |
| "risk": "Low", | |
| } | |
| BAD_WORDS = [ | |
| "harami", "haramkhor", "gandi", "gndi", | |
| "gaali", "kamini", "kutta", "kutty", "bc", | |
| "mc", "madarchod", "bhenchod", "behanchod", | |
| "randi", "raand", "sex", "sexy", "mazy", | |
| "nudes", "nangi", "besharam", "tharki", | |
| "lund", "phudi", "chut", "gashti", "kanjar", | |
| "kanjri", "jahil", "badtameez", "pagal", | |
| "zaleel", "mot", | |
| ] | |
| THREAT_WORDS = [ | |
| "maar dunga", "jaan se", "kill", "murder", | |
| "tabah", "barbaad", "dekh lunga", | |
| "chhor dunga", "chhod dunga", "destroy", | |
| "jaan", "shadi ni krny", | |
| "mnh dehkany k qabal ni", | |
| ] | |
| has_bad = any( | |
| word in text_lower for word in BAD_WORDS | |
| ) | |
| has_threat = any( | |
| word in text_lower for word in THREAT_WORDS | |
| ) | |
| if has_bad or has_threat: | |
| return { | |
| "label": "Harassment", | |
| "score": 90.0, | |
| "risk": "High", | |
| } | |
| try: | |
| classifier = _load_text_classifier() | |
| result = classifier(text)[0] | |
| label = str(result["label"]) | |
| score_percent = float(result["score"]) * 100 | |
| # Confirm the model's label mapping before production use. | |
| is_harassment = label.upper() == "LABEL_1" | |
| if is_harassment: | |
| if score_percent >= 85: | |
| risk = "High" | |
| elif score_percent >= 50: | |
| risk = "Medium" | |
| else: | |
| risk = "Low" | |
| return { | |
| "label": ( | |
| "Harassment" | |
| if score_percent >= 50 | |
| else "Safe" | |
| ), | |
| "score": round(score_percent, 2), | |
| "risk": risk, | |
| } | |
| return { | |
| "label": "Safe", | |
| "score": round(score_percent, 2), | |
| "risk": "Low", | |
| } | |
| except Exception as e: | |
| print(f"Text detection error: {e}") | |
| return { | |
| "label": "Error", | |
| "score": 0.0, | |
| "risk": "Unknown", | |
| "error": str(e), | |
| } | |
| # ============================================================ | |
| # 3. AUDIO DEEPFAKE DETECTION (URDU & ENGLISH) | |
| # ============================================================ | |
| def _load_audio_model(model_id): | |
| """ | |
| Cache the feature extractor and model. | |
| Check for uninitialized meta parameters before inference. | |
| """ | |
| feature_extractor = AutoFeatureExtractor.from_pretrained( | |
| model_id | |
| ) | |
| model = AutoModelForAudioClassification.from_pretrained( | |
| model_id, | |
| low_cpu_mem_usage=False, | |
| device_map=None, | |
| torch_dtype=torch.float32, | |
| ) | |
| meta_params = [ | |
| name | |
| for name, param in model.named_parameters() | |
| if param.is_meta | |
| ] | |
| meta_buffers = [ | |
| name | |
| for name, buffer in model.named_buffers() | |
| if buffer.is_meta | |
| ] | |
| if meta_params or meta_buffers: | |
| examples = (meta_params + meta_buffers)[:8] | |
| raise RuntimeError( | |
| "Audio model has uninitialized meta tensors. " | |
| f"Examples: {examples}. " | |
| "Check Transformers, Accelerate, PyTorch, and " | |
| "checkpoint compatibility in requirements.txt." | |
| ) | |
| model = model.to(torch.device("cpu")) | |
| model.eval() | |
| return feature_extractor, model | |
| def _is_fake_label(label): | |
| label = str(label).strip().lower().replace("_", "-") | |
| return any( | |
| word in label | |
| for word in ( | |
| "fake", | |
| "deepfake", | |
| "synthetic", | |
| "ai-generated", | |
| "ai generated", | |
| ) | |
| ) | |
| def _is_real_label(label): | |
| label = str(label).strip().lower().replace("_", "-") | |
| return any( | |
| word in label | |
| for word in ( | |
| "real", | |
| "genuine", | |
| "bonafide", | |
| "bona-fide", | |
| "human", | |
| ) | |
| ) | |
| def detect_audio(audio_path, language="Urdu"): | |
| if not audio_path: | |
| return { | |
| "verdict": "No audio", | |
| "confidence": 0.0, | |
| } | |
| model_id = ( | |
| URDU_AUDIO_MODEL | |
| if language == "Urdu" | |
| else ENGLISH_AUDIO_MODEL | |
| ) | |
| try: | |
| import soundfile as sf | |
| # Read and convert audio to mono. | |
| try: | |
| waveform, sr = sf.read( | |
| audio_path, | |
| dtype="float32", | |
| ) | |
| if waveform.ndim > 1: | |
| waveform = np.mean( | |
| waveform, | |
| axis=1, | |
| dtype=np.float32, | |
| ) | |
| if waveform.size == 0: | |
| return { | |
| "verdict": "Error", | |
| "confidence": 0.0, | |
| "error": "The uploaded audio file is empty.", | |
| } | |
| if sr != 16000: | |
| waveform = librosa.resample( | |
| waveform, | |
| orig_sr=sr, | |
| target_sr=16000, | |
| ) | |
| except Exception as read_error: | |
| print( | |
| "SoundFile read failed; trying librosa:", | |
| read_error, | |
| ) | |
| waveform, sr = librosa.load( | |
| audio_path, | |
| sr=16000, | |
| mono=True, | |
| ) | |
| waveform = np.asarray( | |
| waveform, | |
| dtype=np.float32, | |
| ).reshape(-1) | |
| if waveform.size == 0: | |
| return { | |
| "verdict": "Error", | |
| "confidence": 0.0, | |
| "error": ( | |
| "Audio is empty or could not be decoded." | |
| ), | |
| } | |
| feature_extractor, model = _load_audio_model( | |
| model_id | |
| ) | |
| inputs = feature_extractor( | |
| waveform, | |
| sampling_rate=16000, | |
| return_tensors="pt", | |
| padding=True, | |
| ) | |
| inputs = { | |
| key: value.to("cpu") | |
| for key, value in inputs.items() | |
| } | |
| with torch.inference_mode(): | |
| outputs = model(**inputs) | |
| logits = outputs.logits | |
| probs = torch.softmax( | |
| logits.float(), | |
| dim=-1, | |
| )[0] | |
| pred_idx = int(torch.argmax(probs).item()) | |
| confidence = float( | |
| probs[pred_idx].item() * 100 | |
| ) | |
| id2label = ( | |
| getattr(model.config, "id2label", {}) or {} | |
| ) | |
| label = id2label.get( | |
| pred_idx, | |
| id2label.get( | |
| str(pred_idx), | |
| f"LABEL_{pred_idx}", | |
| ), | |
| ) | |
| if _is_fake_label(label): | |
| verdict = "Fake (AI-Generated)" | |
| elif _is_real_label(label): | |
| verdict = "Real" | |
| else: | |
| # Fallback only for generic labels. | |
| # Verify the Urdu checkpoint's class mapping | |
| # against its model card/configuration. | |
| if language == "English": | |
| verdict = ( | |
| "Fake (AI-Generated)" | |
| if pred_idx == 0 | |
| else "Real" | |
| ) | |
| else: | |
| verdict = ( | |
| "Fake (AI-Generated)" | |
| if pred_idx == 1 | |
| else "Real" | |
| ) | |
| return { | |
| "verdict": verdict, | |
| "confidence": round(confidence, 2), | |
| "model": model_id, | |
| "predicted_label": str(label), | |
| } | |
| except Exception as e: | |
| print( | |
| f"Audio detection error ({model_id}): " | |
| f"{type(e).__name__}: {e}" | |
| ) | |
| return { | |
| "verdict": "Error", | |
| "confidence": 0.0, | |
| "error": f"{type(e).__name__}: {str(e)}", | |
| "model": model_id, | |
| } | |
| # ============================================================ | |
| # 4. GRADIO INTERFACE | |
| # ============================================================ | |
| with gr.Blocks(title="SentinelZone AI") as demo: | |
| gr.Markdown( | |
| "# SentinelZone - AI Detection API\n" | |
| "Image, text harassment, and audio deepfake detection." | |
| ) | |
| # IMAGE TAB | |
| with gr.Tab("Image Detection"): | |
| img_input = gr.Image( | |
| type="pil", | |
| label="Upload Image", | |
| ) | |
| img_output = gr.JSON( | |
| label="Image Analysis Result" | |
| ) | |
| img_btn = gr.Button("Analyze Image") | |
| img_btn.click( | |
| fn=detect_image, | |
| inputs=img_input, | |
| outputs=img_output, | |
| api_name="predict_image", | |
| ) | |
| # TEXT TAB | |
| with gr.Tab("Text Harassment"): | |
| txt_input = gr.Textbox( | |
| label="Enter Text", | |
| placeholder="Enter the message to analyze...", | |
| lines=4, | |
| ) | |
| txt_output = gr.JSON( | |
| label="Text Analysis Result" | |
| ) | |
| txt_btn = gr.Button("Analyze Text") | |
| txt_btn.click( | |
| fn=detect_text, | |
| inputs=txt_input, | |
| outputs=txt_output, | |
| api_name="predict_text", | |
| ) | |
| # AUDIO TAB | |
| with gr.Tab("Audio Deepfake"): | |
| aud_input = gr.Audio( | |
| type="filepath", | |
| label="Upload Audio File", | |
| ) | |
| aud_lang = gr.Radio( | |
| choices=["Urdu", "English"], | |
| label="Select Language", | |
| value="Urdu", | |
| ) | |
| aud_output = gr.JSON( | |
| label="Audio Analysis Result" | |
| ) | |
| aud_btn = gr.Button("Analyze Audio") | |
| aud_btn.click( | |
| fn=detect_audio, | |
| inputs=[aud_input, aud_lang], | |
| outputs=aud_output, | |
| api_name="predict_audio", | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() | |