ElBeh commited on
Commit
035e180
Β·
verified Β·
1 Parent(s): 472dc50

Upload 4 files

Browse files
tabs/tab_info.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Help Tab
5
+ Created on Sat Nov 8 11:58:29 2025
6
+ @author: standarduser
7
+ """
8
+
9
+ import gradio as gr
10
+
11
+
12
+ def create_tab_info(tab_label):
13
+ """Creates a tab for help text"""
14
+ with gr.TabItem(tab_label):
15
+ gr.Markdown("""
16
+ # SVAT - Synthetic Video Analyze Tool
17
+ ## Quick Start
18
+ 1. **Load Video:** Go to "Video-Frames" tab and upload a video
19
+ 2. **Navigate Frames:** Use slider or buttons to move through frames
20
+ 3. **Apply Transformations:** Click transformation buttons to analyze frames
21
+ 4. **Annotate:** Draw on frames in "Annotations" tab
22
+ 5. **Analyze Video:** Use "Video Analysis" tab for global analysis
23
+ ## Frame Transformations
24
+ ### Laplacian High-Pass
25
+ Emphasizes high-frequency details and edges. Useful for detecting sharpness artifacts.
26
+ ### FFT Spectrum
27
+ Shows frequency domain representation with viridis colormap (blue-green-yellow).
28
+ Reveals periodic patterns and compression artifacts.
29
+ ### Error Level Analysis (ELA)
30
+ Detects JPEG compression artifacts by re-compressing the image.
31
+ Lower quality = more visible differences in manipulated areas.
32
+ ### Wavelet Decomposition
33
+ Multi-scale frequency analysis showing LL, LH, HL, HH subbands.
34
+ Reveals different frequency components.
35
+ ### Noise Extraction
36
+ Isolates high-frequency noise via high-pass filtering.
37
+ Shows noise patterns that might indicate generation artifacts.
38
+ ### YCbCr Channels
39
+ Separates luminance (Y) and chrominance (Cb, Cr) channels.
40
+ Useful for detecting color space artifacts.
41
+ ### Gradient Magnitude
42
+ Visualizes edge strength using Sobel operator.
43
+ Shows edge consistency.
44
+ ### Histogram Stretching (CLAHE)
45
+ Adaptive contrast enhancement that preserves local details.
46
+ ## Video Analysis
47
+ ### Mean FFT
48
+ Calculates average FFT across all frames to detect:
49
+ - Consistent frequency patterns in AI-generated videos
50
+ - Generator-specific fingerprints
51
+ - Temporal artifacts
52
+ ## Annotation Modes
53
+ **Per Frame (A):** Separate drawings for each frame
54
+ **Global (B):** One drawing overlaid on all frames
55
+ ## Tips for AI Detection
56
+ - Look for **repeating patterns** in FFT spectrum
57
+ - Check **ELA** for inconsistent compression levels
58
+ - Use **Mean FFT** to find generator fingerprints
59
+ - Compare **noise patterns** between frames
60
+ - Watch for **unnatural frequency distributions**
61
+ ## Keyboard Shortcuts
62
+ *Navigation:*
63
+ - Use frame slider for quick navigation
64
+ - Click β—€/β–Ά buttons for precise frame control
65
+ ## System Requirements
66
+ - Python 3.8+
67
+ - Gradio 6.x
68
+ - OpenCV
69
+ - NumPy 2.x
70
+ - Pillow
71
+ - Matplotlib
72
+ ## About
73
+ SVAT is designed to help identify synthetic/AI-generated video content through various image analysis techniques.
74
+ Version: 0.5
75
+ Updated:
76
+ - 29.10.2025 Initial version
77
+ - 13.01.2026 added "Classify Image" Tab and classify function with XGBoost via image statistics
78
+ """)
tabs/tab_video_analysis.py CHANGED
@@ -2,7 +2,6 @@
2
  # -*- coding: utf-8 -*-
3
  """
4
  Video Analysis Tab - Global analysis functions for entire videos
5
-
6
  @author: standarduser
7
  """
8
  import gradio as gr
 
2
  # -*- coding: utf-8 -*-
3
  """
4
  Video Analysis Tab - Global analysis functions for entire videos
 
5
  @author: standarduser
6
  """
7
  import gradio as gr
tabs/tab_videoframes.py CHANGED
@@ -2,13 +2,17 @@
2
  # -*- coding: utf-8 -*-
3
  """
4
  Created on Sat Nov 8 09:54:54 2025
5
-
6
  @author: standarduser
7
  """
8
  import gradio as gr
9
  import cv2
10
  import numpy as np
11
  from PIL import Image
 
 
 
 
 
12
 
13
  # CSS for box styling
14
  css = """
@@ -151,6 +155,56 @@ def create_comparison_slider(frame, transformation, quality, process_image_func)
151
  return (original, transformed)
152
 
153
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  def update_frame_display(frame_idx, frames, fps, annotations, global_annotation, annotation_mode, transformation, quality, process_image_func):
155
  """Updates frame display"""
156
  if not frames or frame_idx >= len(frames):
@@ -205,7 +259,7 @@ def go_to_next_frame(current_idx, steps, frames, fps, annotations, global_annota
205
  def load_video_frames(video_path):
206
  """Loads all frames from a video"""
207
  if video_path is None:
208
- return [], 0, gr.update(maximum=0, value=0), "No video loaded", 0, 0, {}, None
209
 
210
  cap = cv2.VideoCapture(video_path)
211
  frames = []
@@ -220,7 +274,7 @@ def load_video_frames(video_path):
220
  cap.release()
221
 
222
  if len(frames) == 0:
223
- return [], 0, gr.update(maximum=0, value=0), "No frames found", 0, 0, {}, None
224
 
225
  duration = len(frames) / fps if fps > 0 else 0
226
 
@@ -232,7 +286,8 @@ def load_video_frames(video_path):
232
  duration,
233
  fps,
234
  {},
235
- None
 
236
  )
237
 
238
 
@@ -309,6 +364,7 @@ def create_tab_videoframes(tab_label, process_image, shared_video_frames=None):
309
  annotation_mode = gr.State("A")
310
  selected_transformation = gr.State("None")
311
  ela_quality = gr.State(90)
 
312
 
313
 
314
  # Row 1: raw video
@@ -355,6 +411,7 @@ def create_tab_videoframes(tab_label, process_image, shared_video_frames=None):
355
  with gr.Column(scale=1, min_width=1):
356
  frame_info = gr.Textbox(label="Frame Info", value="No video loaded", interactive=False, scale=2)
357
  video_time_display = gr.Textbox(label="Video Time", value="--:--", interactive=False, scale=1)
 
358
  gr.Markdown("---")
359
 
360
  # Accordion-based transformation selection
@@ -410,6 +467,21 @@ def create_tab_videoframes(tab_label, process_image, shared_video_frames=None):
410
  with gr.Column(visible=False) as content_histogram:
411
  gr.Markdown("Extreme contrast enhancement")
412
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
413
  # Row: Frame navigation
414
  with gr.Row():
415
  gr.Markdown("---")
@@ -456,6 +528,13 @@ def create_tab_videoframes(tab_label, process_image, shared_video_frames=None):
456
  btn_histogram
457
  ]
458
 
 
 
 
 
 
 
 
459
  # Accordion button clicks
460
  btn_laplacian.click(
461
  fn=lambda current: toggle_accordion("Laplacian High-Pass", current),
@@ -548,46 +627,70 @@ def create_tab_videoframes(tab_label, process_image, shared_video_frames=None):
548
  outputs=[sketch_output, comparison_slider, frame_info, video_time_display]
549
  )
550
 
551
- # Video Upload
552
  video_input.change(
553
  fn=load_video_frames,
554
  inputs=[video_input],
555
- outputs=[video_frames, current_frame_idx, frame_slider, frame_info, video_duration, video_fps, frame_annotations, global_annotation]
556
  ).then(
557
  fn=lambda idx, frames, fps, annots, glob_annot, mode, trans, quality: update_frame_display(idx, frames, fps, annots, glob_annot, mode, trans, quality, process_image),
558
  inputs=[current_frame_idx, video_frames, video_fps, frame_annotations, global_annotation, annotation_mode, selected_transformation, ela_quality],
559
  outputs=[sketch_output, comparison_slider, frame_info, video_time_display]
 
 
 
 
560
  )
561
 
562
- # Frame Navigation
563
  frame_slider.release(
564
  fn=lambda idx, frames, fps, annots, glob_annot, mode, trans, quality: update_frame_display(idx, frames, fps, annots, glob_annot, mode, trans, quality, process_image),
565
  inputs=[frame_slider, video_frames, video_fps, frame_annotations, global_annotation, annotation_mode, selected_transformation, ela_quality],
566
  outputs=[sketch_output, comparison_slider, frame_info, video_time_display]
 
 
 
 
567
  )
568
 
569
  btn_prev_frame.click(
570
  fn=lambda idx, frames, fps, annots, glob_annot, mode, trans, quality: go_to_prev_frame(idx, 1, frames, fps, annots, glob_annot, mode, trans, quality, process_image),
571
  inputs=[frame_slider, video_frames, video_fps, frame_annotations, global_annotation, annotation_mode, selected_transformation, ela_quality],
572
  outputs=[frame_slider, sketch_output, comparison_slider, frame_info, video_time_display]
 
 
 
 
573
  )
574
 
575
  btn_next_frame.click(
576
  fn=lambda idx, frames, fps, annots, glob_annot, mode, trans, quality: go_to_next_frame(idx, 1, frames, fps, annots, glob_annot, mode, trans, quality, process_image),
577
  inputs=[frame_slider, video_frames, video_fps, frame_annotations, global_annotation, annotation_mode, selected_transformation, ela_quality],
578
  outputs=[frame_slider, sketch_output, comparison_slider, frame_info, video_time_display]
 
 
 
 
579
  )
580
 
581
  btn_prev10_frame.click(
582
  fn=lambda idx, frames, fps, annots, glob_annot, mode, trans, quality: go_to_prev_frame(idx, 10, frames, fps, annots, glob_annot, mode, trans, quality, process_image),
583
  inputs=[frame_slider, video_frames, video_fps, frame_annotations, global_annotation, annotation_mode, selected_transformation, ela_quality],
584
  outputs=[frame_slider, sketch_output, comparison_slider, frame_info, video_time_display]
 
 
 
 
585
  )
586
 
587
  btn_next10_frame.click(
588
  fn=lambda idx, frames, fps, annots, glob_annot, mode, trans, quality: go_to_next_frame(idx, 10, frames, fps, annots, glob_annot, mode, trans, quality, process_image),
589
  inputs=[frame_slider, video_frames, video_fps, frame_annotations, global_annotation, annotation_mode, selected_transformation, ela_quality],
590
  outputs=[frame_slider, sketch_output, comparison_slider, frame_info, video_time_display]
 
 
 
 
591
  )
592
 
593
  # Sketchpad Change - Saves drawing
 
2
  # -*- coding: utf-8 -*-
3
  """
4
  Created on Sat Nov 8 09:54:54 2025
 
5
  @author: standarduser
6
  """
7
  import gradio as gr
8
  import cv2
9
  import numpy as np
10
  from PIL import Image
11
+ import tempfile
12
+ import os
13
+
14
+ # Import classification function
15
+ from tabs.tab_classify_image import predict_from_space
16
 
17
  # CSS for box styling
18
  css = """
 
155
  return (original, transformed)
156
 
157
 
158
+ # NEW: Classification functions
159
+ def classify_current_frame(frame_idx, frames, existing_classifications):
160
+ """Classify current frame and cache result"""
161
+ frame_idx = int(frame_idx)
162
+
163
+ # Check if already classified
164
+ if frame_idx in existing_classifications:
165
+ return (
166
+ existing_classifications[frame_idx],
167
+ f"βœ“ Cached result (Frame {frame_idx + 1})",
168
+ existing_classifications
169
+ )
170
+
171
+ if not frames or frame_idx >= len(frames):
172
+ return None, "βœ— No frame available", existing_classifications
173
+
174
+ frame = frames[frame_idx]
175
+
176
+ # Save temp file
177
+ with tempfile.NamedTemporaryFile(suffix='.jpg', delete=False) as tmp:
178
+ Image.fromarray(frame).save(tmp.name, 'JPEG', quality=95)
179
+ tmp_path = tmp.name
180
+
181
+ try:
182
+ result = predict_from_space(tmp_path)
183
+
184
+ # Cache result
185
+ new_classifications = existing_classifications.copy()
186
+ new_classifications[frame_idx] = result
187
+
188
+ return result, f"βœ“ Frame {frame_idx + 1} classified", new_classifications
189
+
190
+ except Exception as e:
191
+ return None, f"βœ— API Error: {str(e)}", existing_classifications
192
+
193
+ finally:
194
+ if os.path.exists(tmp_path):
195
+ os.unlink(tmp_path)
196
+
197
+
198
+ def update_classification_display(frame_idx, classifications):
199
+ """Update classification display when switching frames"""
200
+ frame_idx = int(frame_idx)
201
+
202
+ if frame_idx in classifications:
203
+ return classifications[frame_idx], f"βœ“ Frame {frame_idx + 1} (cached)"
204
+ else:
205
+ return None, "Not classified yet"
206
+
207
+
208
  def update_frame_display(frame_idx, frames, fps, annotations, global_annotation, annotation_mode, transformation, quality, process_image_func):
209
  """Updates frame display"""
210
  if not frames or frame_idx >= len(frames):
 
259
  def load_video_frames(video_path):
260
  """Loads all frames from a video"""
261
  if video_path is None:
262
+ return [], 0, gr.update(maximum=0, value=0), "No video loaded", 0, 0, {}, None, {} # Added {} for frame_classifications
263
 
264
  cap = cv2.VideoCapture(video_path)
265
  frames = []
 
274
  cap.release()
275
 
276
  if len(frames) == 0:
277
+ return [], 0, gr.update(maximum=0, value=0), "No frames found", 0, 0, {}, None, {} # Added {} for frame_classifications
278
 
279
  duration = len(frames) / fps if fps > 0 else 0
280
 
 
286
  duration,
287
  fps,
288
  {},
289
+ None,
290
+ {} # Reset frame_classifications
291
  )
292
 
293
 
 
364
  annotation_mode = gr.State("A")
365
  selected_transformation = gr.State("None")
366
  ela_quality = gr.State(90)
367
+ frame_classifications = gr.State({}) # NEW: Store classification results
368
 
369
 
370
  # Row 1: raw video
 
411
  with gr.Column(scale=1, min_width=1):
412
  frame_info = gr.Textbox(label="Frame Info", value="No video loaded", interactive=False, scale=2)
413
  video_time_display = gr.Textbox(label="Video Time", value="--:--", interactive=False, scale=1)
414
+
415
  gr.Markdown("---")
416
 
417
  # Accordion-based transformation selection
 
467
  with gr.Column(visible=False) as content_histogram:
468
  gr.Markdown("Extreme contrast enhancement")
469
 
470
+ # Row: Frame Classification
471
+ with gr.Row():
472
+ gr.Markdown("---")
473
+
474
+ with gr.Accordion("Frame Classification - (optimized model for ai images)", open=False):
475
+ with gr.Row():
476
+ with gr.Column(scale=1):
477
+ with gr.Row():
478
+ btn_classify_frame = gr.Button("πŸ” Classify Current Frame", size="sm", variant="primary")
479
+ btn_classify_all = gr.Button("πŸ”¬ Classify All Frames (Coming Soon)", size="sm", interactive=False)
480
+ with gr.Column(scale=2):
481
+ classification_result = gr.Label(num_top_classes=2, label="Result")
482
+ with gr.Column(scale=1):
483
+ classification_status = gr.Textbox(label="Status", value="Not classified yet", interactive=False)
484
+
485
  # Row: Frame navigation
486
  with gr.Row():
487
  gr.Markdown("---")
 
528
  btn_histogram
529
  ]
530
 
531
+ # NEW: Classification button event
532
+ btn_classify_frame.click(
533
+ fn=classify_current_frame,
534
+ inputs=[frame_slider, video_frames, frame_classifications],
535
+ outputs=[classification_result, classification_status, frame_classifications]
536
+ )
537
+
538
  # Accordion button clicks
539
  btn_laplacian.click(
540
  fn=lambda current: toggle_accordion("Laplacian High-Pass", current),
 
627
  outputs=[sketch_output, comparison_slider, frame_info, video_time_display]
628
  )
629
 
630
+ # Video Upload - MODIFIED: Added frame_classifications to outputs
631
  video_input.change(
632
  fn=load_video_frames,
633
  inputs=[video_input],
634
+ outputs=[video_frames, current_frame_idx, frame_slider, frame_info, video_duration, video_fps, frame_annotations, global_annotation, frame_classifications]
635
  ).then(
636
  fn=lambda idx, frames, fps, annots, glob_annot, mode, trans, quality: update_frame_display(idx, frames, fps, annots, glob_annot, mode, trans, quality, process_image),
637
  inputs=[current_frame_idx, video_frames, video_fps, frame_annotations, global_annotation, annotation_mode, selected_transformation, ela_quality],
638
  outputs=[sketch_output, comparison_slider, frame_info, video_time_display]
639
+ ).then(
640
+ fn=lambda: (None, "Not classified yet"), # Reset classification display
641
+ inputs=[],
642
+ outputs=[classification_result, classification_status]
643
  )
644
 
645
+ # Frame Navigation - MODIFIED: Added classification display update
646
  frame_slider.release(
647
  fn=lambda idx, frames, fps, annots, glob_annot, mode, trans, quality: update_frame_display(idx, frames, fps, annots, glob_annot, mode, trans, quality, process_image),
648
  inputs=[frame_slider, video_frames, video_fps, frame_annotations, global_annotation, annotation_mode, selected_transformation, ela_quality],
649
  outputs=[sketch_output, comparison_slider, frame_info, video_time_display]
650
+ ).then(
651
+ fn=update_classification_display,
652
+ inputs=[frame_slider, frame_classifications],
653
+ outputs=[classification_result, classification_status]
654
  )
655
 
656
  btn_prev_frame.click(
657
  fn=lambda idx, frames, fps, annots, glob_annot, mode, trans, quality: go_to_prev_frame(idx, 1, frames, fps, annots, glob_annot, mode, trans, quality, process_image),
658
  inputs=[frame_slider, video_frames, video_fps, frame_annotations, global_annotation, annotation_mode, selected_transformation, ela_quality],
659
  outputs=[frame_slider, sketch_output, comparison_slider, frame_info, video_time_display]
660
+ ).then(
661
+ fn=update_classification_display,
662
+ inputs=[frame_slider, frame_classifications],
663
+ outputs=[classification_result, classification_status]
664
  )
665
 
666
  btn_next_frame.click(
667
  fn=lambda idx, frames, fps, annots, glob_annot, mode, trans, quality: go_to_next_frame(idx, 1, frames, fps, annots, glob_annot, mode, trans, quality, process_image),
668
  inputs=[frame_slider, video_frames, video_fps, frame_annotations, global_annotation, annotation_mode, selected_transformation, ela_quality],
669
  outputs=[frame_slider, sketch_output, comparison_slider, frame_info, video_time_display]
670
+ ).then(
671
+ fn=update_classification_display,
672
+ inputs=[frame_slider, frame_classifications],
673
+ outputs=[classification_result, classification_status]
674
  )
675
 
676
  btn_prev10_frame.click(
677
  fn=lambda idx, frames, fps, annots, glob_annot, mode, trans, quality: go_to_prev_frame(idx, 10, frames, fps, annots, glob_annot, mode, trans, quality, process_image),
678
  inputs=[frame_slider, video_frames, video_fps, frame_annotations, global_annotation, annotation_mode, selected_transformation, ela_quality],
679
  outputs=[frame_slider, sketch_output, comparison_slider, frame_info, video_time_display]
680
+ ).then(
681
+ fn=update_classification_display,
682
+ inputs=[frame_slider, frame_classifications],
683
+ outputs=[classification_result, classification_status]
684
  )
685
 
686
  btn_next10_frame.click(
687
  fn=lambda idx, frames, fps, annots, glob_annot, mode, trans, quality: go_to_next_frame(idx, 10, frames, fps, annots, glob_annot, mode, trans, quality, process_image),
688
  inputs=[frame_slider, video_frames, video_fps, frame_annotations, global_annotation, annotation_mode, selected_transformation, ela_quality],
689
  outputs=[frame_slider, sketch_output, comparison_slider, frame_info, video_time_display]
690
+ ).then(
691
+ fn=update_classification_display,
692
+ inputs=[frame_slider, frame_classifications],
693
+ outputs=[classification_result, classification_status]
694
  )
695
 
696
  # Sketchpad Change - Saves drawing