Skip to content

Feature/labimp 7672 - #42

Open
yashsuman15 wants to merge 17 commits into
mainfrom
feature/LABIMP-7672
Open

Feature/labimp 7672#42
yashsuman15 wants to merge 17 commits into
mainfrom
feature/LABIMP-7672

Conversation

@yashsuman15

Copy link
Copy Markdown
Collaborator

No description provided.

- Created a new script  to test the upload of keyframe preannotations.
- added method for pre-annotation upload to video project
- modify SDK notebook accordingly
- Created a new script `test_preannotation_api.py` to test the upload of keyframe preannotations.
- added method for pre-annotation upload to video project
- modify SDK notebook accordingly
- Created a new script  to test the upload of keyframe preannotations.
- added method for pre-annotation upload to video project
- modify SDK notebook accordingly
- Created a new script `test_preannotation_api.py` to test the upload of keyframe preannotations.
- added method for pre-annotation upload to video project
- modify SDK notebook accordingly
- Add keyframe annotation upload to Notebook
- added coco to video json converter
@github-actions

Copy link
Copy Markdown

Code Review Summary

Overview

This PR introduces video keyframe extraction and scene detection functionality with three detection algorithms (FFMPEG, SSIM, PySceneDetect), video preannotation upload capabilities, and supporting utilities.

Stats: +2,441 additions, -872 deletions across 16 files


🔴 Critical Issues

1. Hardcoded Local File Paths (Security & Portability)

Multiple files contain hardcoded Windows-specific paths that will fail in production:

  • ffmpeg_detect.py:392: r"D:\Professional\Labellerr_SDK\SDKPython\..."
  • ssim_detect.py:432: Same Windows path
  • test_preannotation_api.py:9: r"D:\Professional\Labellerr_SDK\dev.env"

Impact: Code will fail for any user without this exact directory structure. This is a deployment blocker.

2. BASE_URL Changed to Non-Production (Production Issue)

constants.py:1: Base URL changed from production (api.labellerr.com) to what appears to be a QA environment (api-gateway-qcb3iv2gaa-uc.a.run.app)

Impact: This will route all production traffic to a QA/staging environment. This must be reverted or made configurable.

3. Test File in Production Code (Code Organization)

test_preannotation_api.py is in labellerr/notebooks/ directory with hardcoded credentials loading logic.

Impact: Test files should be in tests/ directory. Having test files in production code increases package size and creates confusion.


⚠️ High Priority Issues

4. Missing Import in video_project.py

Line 3 imports VideoProject but the actual VideoProject class is defined in a different file. The import statement appears incorrect.

5. Security: Subprocess Without Input Validation (Security)

ffmpeg_detect.py and other detection modules use subprocess.run() with user-provided file paths. While there is path validation, there's no explicit sanitization against command injection if paths contain shell metacharacters.

Recommendation: Use absolute paths and avoid shell=True (which you're already doing, good!). Consider additional path sanitization.

6. Reduced Worker Pool Size (Performance)

datasets/utils.py:257: Max workers reduced from 20 to 5 (75% reduction)

Impact: This will significantly slow down parallel uploads. If this was changed to fix issues, the root cause should be documented in comments or the PR description.

7. Duplicate Imports in projects/init.py

Lines 18-19 duplicate earlier imports:

  • Line 13: from .video_project import VideoProject as LabellerrVideoProject (deleted)
  • Line 18: from .base import LabellerrProject (duplicated)
  • Line 19: from ..annotation_templates import LabellerrAnnotationTemplate (duplicated)

🟡 Medium Priority Issues

8. Incomplete Documentation

  • Method upload_keyframe_preannotations() mentions deprecated parameters in docstring but they don't exist in the signature
  • The notebook file has significant changes but lacks clear documentation of the new workflow

9. Error Handling Could Be More Specific

Multiple locations catch broad Exception and wrap in custom exceptions, losing stack traces:

  • ffmpeg_detect.py:218: Generic exception handling
  • ssim_detect.py:316: Same pattern

Recommendation: Consider logging the full exception details before re-raising.

10. Inconsistent Naming Convention

download_create_video_auto_cleanup() has a typo in the default parameter: "./Labellerr_datastets" → should be "./Labellerr_datasets" (already fixed in the code, good!)

11. Magic Numbers

  • utils.py:257: Worker count changed from 20 to 5 (no constant or config)
  • ssim_detect.py:166: Default threshold 0.3, resize_dim (320, 240) - should be named constants
  • ffmpeg_detect.py:345: 30 second timeout - should be configurable

12. Large Notebook File Changes

The Jupyter notebook has 739 additions and 353 deletions. Notebooks are difficult to review in diffs and often contain execution outputs that bloat the repository.

Recommendation: Consider adding notebook to .gitattributes to strip outputs before commit.


✅ Positive Aspects

  1. Good Exception Hierarchy: Well-structured custom exceptions for each detector
  2. Singleton Pattern: Appropriate use for detector classes
  3. Pydantic Models: Type-safe data models with proper validation
  4. Comprehensive Validation: Good input validation for file paths and formats
  5. Progress Reporting: User-friendly progress messages in batch processing
  6. Code Structure: Clean separation of concerns with distinct detector modules
  7. Cross-platform Support: Timeout handling and proper subprocess usage
  8. Docstrings: Most functions have detailed docstrings with examples

🔧 Minor Issues / Code Quality

13. Code Duplication

All three detector classes have identical _validate_video_file() and SUPPORTED_EXTENSIONS. Consider extracting to a shared base class or utility module.

14. Unused Variable

coco_to_video_json() line 353: Commented-out code for answer_id - should be removed if not needed.

15. String Formatting Inconsistency

Mix of f-strings, .format(), and % formatting. Standardize on f-strings for consistency.

16. Type Hints

Some functions lack complete type hints:

  • _save_json_mapping() returns None but not annotated
  • Some parameters could use more specific types (e.g., Union[str, Path] for paths)

🧪 Test Coverage

Concern: No new test files added for the substantial new functionality. The only test-related file (test_preannotation_api.py) is actually in the source tree, not the test directory.

Missing Tests:

  • Unit tests for all three detector classes
  • Integration tests for process_videos_batch()
  • Tests for coco_to_video_json() conversion logic
  • Tests for upload_keyframe_preannotations()

📋 Recommendations

Immediate (Block Merge):

  1. ✅ Remove/parameterize all hardcoded file paths
  2. ✅ Revert BASE_URL change or make it environment-configurable
  3. ✅ Move test file to proper location or remove from source tree
  4. ✅ Fix duplicate imports

Before Merge:

  1. ✅ Add comprehensive unit tests
  2. ✅ Document the worker pool reduction rationale
  3. ✅ Extract common validation logic to reduce duplication
  4. ✅ Add docstring for why worker count was reduced

Future Improvements:

  1. 🔄 Add configuration system for detector parameters
  2. 🔄 Implement logging instead of print statements
  3. 🔄 Consider notebook cleanup strategy
  4. 🔄 Add performance benchmarks for the detection algorithms

🎯 Verdict

Status: ⛔ CHANGES REQUIRED

This PR introduces valuable functionality but has critical issues that must be addressed:

  • Hardcoded paths will break for all users
  • BASE_URL change could route production to staging
  • Missing test coverage for substantial new code

The code architecture is solid, but the deployment-blocking issues need resolution before merge.

Estimated Risk: High (due to environment issues)
Code Quality: Good (well-structured, but needs tests)
Test Coverage: Poor (no tests for new functionality)

@@ -1,4 +1,4 @@
BASE_URL = "https://api.labellerr.com"
BASE_URL = "https://api-gateway-qcb3iv2gaa-uc.a.run.app"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Critical: Production URL Changed to QA Environment

This changes the base URL from production (https://api.labellerr.com) to what appears to be a QA/staging environment.

Issues:

  1. All SDK users will hit the QA environment instead of production
  2. This breaks existing production integrations
  3. No environment-based configuration

Recommendation:

import os

BASE_URL = os.getenv("LABELLERR_BASE_URL", "https://api.labellerr.com")

Or use a proper config system that allows override for testing while defaulting to production.



if __name__ == "__main__":
video_path = r"D:\Professional\Labellerr_SDK\SDKPython\labellerr\notebooks\Labellerr_datasets\354681d3-034a-4d66-b070-365f4bd11d8a\2a8d96ca-9161-4dee-ad3b-a5faf301bc6c.mp4"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Critical: Hardcoded Local Path

This hardcoded Windows path will fail for anyone else using this code. Remove this __main__ block or use a configurable path.

Better approach:

if __name__ == "__main__":
    import sys
    if len(sys.argv) < 2:
        print("Usage: python ffmpeg_detect.py <video_path>")
        sys.exit(1)
    video_path = sys.argv[1]
    # Rest of code...


if __name__ == "__main__":
# Example usage
video_path = r"D:\Professional\Labellerr_SDK\SDKPython\labellerr\notebooks\Labellerr_datasets\354681d3-034a-4d66-b070-365f4bd11d8a\2a8d96ca-9161-4dee-ad3b-a5faf301bc6c.mp4"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Critical: Hardcoded Local Path

Same issue as in ffmpeg_detect.py - this hardcoded Windows path should be removed or parameterized.

Comment on lines +1 to +9
import os

from dotenv import load_dotenv

from labellerr.client import LabellerrClient
from labellerr.core.projects.video_project import LabellerrProject

# Load environment variables from .env file
load_dotenv(r"D:\Professional\Labellerr_SDK\dev.env")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Critical: Test File in Wrong Location + Hardcoded Path

Issues:

  1. Test files should be in tests/ directory, not in source code
  2. Hardcoded Windows-specific path: r"D:\Professional\Labellerr_SDK\dev.env"
  3. This will be packaged with the SDK unnecessarily

Action:

  • Move to tests/integration/test_video_preannotation.py
  • Use environment variables or pytest fixtures for configuration
  • Remove hardcoded paths

Comment thread labellerr/core/datasets/utils.py Outdated
os.cpu_count() or 1, # Number of CPU cores (default to 1 if None)
len(batches), # Number of batches
20,
5,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Impact: 75% Reduction in Worker Pool

Changed from 20 to 5 workers, which will significantly slow down parallel uploads.

Questions:

  1. What issue was this change addressing?
  2. Was there a race condition or resource exhaustion?
  3. Should this be configurable by users?

Recommendation:

# Allow configuration via environment variable
DEFAULT_MAX_WORKERS = int(os.getenv("LABELLERR_MAX_WORKERS", "20"))
max_workers = min(
    os.cpu_count() or 1,
    len(batches),
    DEFAULT_MAX_WORKERS,
)

Please add a comment explaining the rationale for this change.

Comment thread labellerr/core/projects/__init__.py Outdated
from .image_project import ImageProject as LabellerrImageProject
from .video_project import VideoProject as LabellerrVideoProject
from .text_project import TextProject as LabellerrTextProject
from .base import LabellerrProject

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Duplicate Imports

Lines 18-19 duplicate imports from earlier in the file:

  • LabellerrProject imported on line 14
  • LabellerrAnnotationTemplate imported on line 10

Remove the duplicates on lines 18-19.

except Exception as e:
raise LabellerrError(f"Failed to delete key frames: {str(e)}")

def upload_keyframe_preannotations(self, video_json_file_path: str = None) -> Any:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Documentation Issue: Mentions Non-existent Parameters

The docstring mentions deprecated parameters (annotation_format, annotation_file, conf_bucket, _async) that don't exist in the function signature.

Fix:

def upload_keyframe_preannotations(self, video_json_file_path: str) -> Any:
    \"\"\"
    Uploads pre-annotations for video project in video_json format.

    :param video_json_file_path: Path to the video JSON file containing pre-annotations
    :return: Response from the API
    :raises LabellerrError: If file path is invalid or upload fails
    \"\"\"

from typing import Any, Dict, List, Optional, Union

# Try to import detectors (optional dependencies)
try:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Code Duplication: Validation Logic

All three detector classes (FFMPEGSceneDetect, SSIMSceneDetect, PySceneDetect) have identical:

  • _validate_video_file() method
  • SUPPORTED_EXTENSIONS constant

Recommendation: Extract to a shared base class or utility module:

class BaseVideoDetector(Singleton):
    SUPPORTED_EXTENSIONS = {".mp4", ".avi", ".mov", ...}
    
    def _validate_video_file(self, video_path: str) -> None:
        # Shared implementation
        ...

This would reduce duplication and ensure consistent validation across all detectors.


def download_create_video_auto_cleanup(
self, output_folder: str = "./Labellerr_datastets"
self, output_folder: str = "./Labellerr_datasets"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good Fix: Typo Corrected

Nice catch fixing the typo from "datastets" to "datasets" in the default parameter!

check=True,
capture_output=True,
text=True,
timeout=30, # 30 second timeout per frame

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔧 Magic Number: Hardcoded Timeout

30-second timeout should be a configurable constant or parameter, especially for large videos or slow systems.

Suggestion:

FRAME_EXTRACTION_TIMEOUT = 30  # seconds

# In method:
timeout=self.FRAME_EXTRACTION_TIMEOUT

}


def coco_to_video_json(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧪 Missing Test Coverage

This is a complex function with multiple edge cases (filename parsing, format conversion, grouping logic). It needs comprehensive unit tests covering:

  1. Valid COCO JSON with bbox annotations
  2. Valid COCO JSON with polygon/segmentation annotations
  3. Invalid filename formats
  4. Multiple annotations per frame
  5. Multiple frames per video
  6. Edge case: Empty annotations list
  7. Edge case: Missing required fields

Consider adding tests in tests/unit/test_video_sampling.py

raise VideoFileError(f"Video file is not readable: {video_path}")

def detect_and_extract(
self, video_path: str, threshold: float = 0.3, resize_dim: tuple = (320, 240)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔧 Magic Numbers: Hardcoded Parameters

Default threshold (0.3) and resize dimensions (320, 240) should be named constants at class level:

class SSIMSceneDetect(Singleton):
    SUPPORTED_EXTENSIONS = {...}
    DEFAULT_THRESHOLD = 0.3
    DEFAULT_RESIZE_DIM = (320, 240)
    
    def detect_and_extract(
        self, 
        video_path: str, 
        threshold: float = DEFAULT_THRESHOLD, 
        resize_dim: tuple = DEFAULT_RESIZE_DIM
    ):

This improves maintainability and makes it clear these are configurable defaults.

]

if not video_files:
print(f"⚠️ No video files found in {dataset_dir}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Consider Logging Instead of Print

For a production SDK, consider using Python's logging module instead of print() statements. This allows users to control verbosity and integrate with their logging systems:

import logging
logger = logging.getLogger(__name__)

# Instead of print():
logger.info(f"Found {len(video_files)} video files to process")
logger.warning(f"⚠️  No video files found in {dataset_dir}")
logger.error(f"✗ Failed: {str(e)}")

Users can then configure logging level as needed.

try:
# Use file_id as folder name
folder_name = self.file_id
# Use [Dataset_id]+[File_id]+[File_name] as folder name

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good Improvement: Better Folder Naming

Good enhancement to use descriptive folder names with dataset+file+name structure. This makes it much easier to identify videos in the file system.

Minor suggestion: Consider documenting this naming convention in the class docstring or module documentation for users.

video_filename = f"{self.dataset_id}+{self.file_id}+{self.file_name}"
elif self.dataset_id:
video_filename = f"{self.dataset_id}+{self.file_id}.mp4"
else:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐛 Potential Bug: String Formatting Error

Line 316 has incorrect string literal - the closing brace should be outside the string:

# Current (incorrect):
print("{'='*60}\n")  # This prints: {'='*60}

# Should be:
print(f"{'='*60}\n")  # This prints: ====================...

Or simply:

print(f"{'='*60}\n")

@github-actions

Copy link
Copy Markdown

Code Review Summary

I've completed a comprehensive review of this PR. Below are the key findings organized by category:

🔴 Critical Issues

  1. Hardcoded QA environment URL - BASE_URL changed to QA environment in production code
  2. Hardcoded test paths - Windows-specific absolute paths in test/notebook files
  3. Missing error handling - Several methods lack proper validation and error handling
  4. Thread safety concerns - Potential race conditions in concurrent operations

🟡 Code Quality Issues

  1. Inconsistent naming conventions - Mixed use of camelCase and snake_case
  2. Code duplication - Repeated folder name generation logic across multiple files
  3. Magic numbers - Hardcoded values without constants
  4. Incomplete docstrings - Several methods have outdated or incomplete documentation

🟢 Security Concerns

  1. Credentials in test files - Environment variables referenced but not properly secured
  2. Path traversal risk - Insufficient path validation in file operations
  3. Command injection risk - Unsanitized subprocess calls in video processing

📊 Performance Considerations

  1. Inefficient frame extraction - FFMPEG called multiple times instead of batch processing
  2. Memory management - Large video files processed without streaming
  3. Thread pool sizing - Hardcoded max_workers values

✅ Test Coverage

  • No unit tests added for new functionality
  • Only manual test scripts provided
  • Missing edge case coverage

📝 Additional Notes

  • Video sampling functionality is well-structured with good separation of concerns
  • Exception hierarchy is well-designed
  • Singleton pattern usage is appropriate
  • Good documentation in most detector classes

I've added inline comments on specific lines that need attention. Please address the critical issues before merging.

@@ -1,4 +1,4 @@
BASE_URL = "https://api.labellerr.com"
BASE_URL = "https://api-gateway-qcb3iv2gaa-uc.a.run.app"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Critical: Hardcoded QA Environment URL

This changes the BASE_URL to a QA environment in production code. This should:

  1. Be reverted to the production URL
  2. Use environment variables for different environments
  3. Have proper configuration management
Suggested change
BASE_URL = "https://api-gateway-qcb3iv2gaa-uc.a.run.app"
BASE_URL = os.getenv("LABELLERR_BASE_URL", "https://api.labellerr.com")

# - select filter: Only pass through I-frames (PICT_TYPE_I)
# - showinfo: Print detailed information about each frame to stderr
# - null output: Don't actually save frames, just analyze
command = [

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Security: Command Injection Risk

The subprocess call doesn't sanitize the video_path input. If the path contains special characters or is user-controlled, this could lead to command injection.

Recommendation:

# Validate and sanitize the path first
video_path = os.path.abspath(video_path)
if not video_path.startswith(expected_base_path):
    raise VideoFileError("Invalid video path")

# Using actual frame numbers ensures frames are named correctly
# (e.g., frame 250 from video → video_name+frame_250.jpg)
selected_frames = []
for idx, frame_num in enumerate(frame_numbers, 1):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance: Inefficient Frame Extraction

Each frame is extracted with a separate FFMPEG call. For videos with many I-frames, this is extremely inefficient.

Consider:

  1. Extracting all frames in a single FFMPEG command using the select filter
  2. Batch processing frames in groups
  3. Using frame extraction with output pattern like frame_%04d.jpg

This could reduce extraction time by 10-100x for videos with many keyframes.

check=True,
capture_output=True,
text=True,
timeout=30, # 30 second timeout per frame

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Code Quality: Magic Number

The 30-second timeout is hardcoded. This should be:

  1. A class constant
  2. Configurable per video size/complexity
  3. Documented why 30 seconds was chosen
FRAME_EXTRACTION_TIMEOUT = 30  # Class constant



if __name__ == "__main__":
video_path = r"D:\Professional\Labellerr_SDK\SDKPython\labellerr\notebooks\Labellerr_datasets\354681d3-034a-4d66-b070-365f4bd11d8a\2a8d96ca-9161-4dee-ad3b-a5faf301bc6c.mp4"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Critical: Hardcoded Test Path

This hardcoded Windows-specific path should not be in the repository. Remove the __main__ block or use a configurable path.

Suggested change
video_path = r"D:\Professional\Labellerr_SDK\SDKPython\labellerr\notebooks\Labellerr_datasets\354681d3-034a-4d66-b070-365f4bd11d8a\2a8d96ca-9161-4dee-ad3b-a5faf301bc6c.mp4"
if __name__ == "__main__":
import sys
if len(sys.argv) < 2:
print("Usage: python ffmpeg_detect.py <video_path>")
sys.exit(1)
video_path = sys.argv[1]

# ================================================================
# PHASE 3: Process remaining frames with SSIM detection
# ================================================================
while True:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Performance: Memory Management

The entire video is processed frame-by-frame without memory management. For large videos, this could cause memory issues.

Consider:

  1. Processing in batches with periodic garbage collection
  2. Limiting frame buffer size
  3. Adding progress callbacks for long-running operations

# ============================================================================


def _extract_video_name_and_frame(filename: str) -> tuple[str, int, int]:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Code Quality: Repeated Logic

The folder naming logic {dataset_id}+{file_id}+{video_name}+FPS{fps} is repeated across multiple files (video_file.py, init.py, etc.). This should be extracted to a utility function:

def generate_video_folder_name(dataset_id: str, file_id: str, file_name: str, fps: Optional[int] = None) -> str:
    \"\"\"Generate standardized folder name for video files.\"\"\"
    base_name = os.path.splitext(file_name)[0]
    parts = [dataset_id, file_id, base_name]
    if fps:
        parts.append(f"FPS{fps}")
    return "+".join(parts)

# ============================================================================


def _extract_video_name_and_frame(filename: str) -> tuple[str, int, int]:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Bug: Unsafe Type Annotation

The return type tuple[str, int, int] uses Python 3.9+ syntax. For compatibility with Python 3.8, use:

Suggested change
def _extract_video_name_and_frame(filename: str) -> tuple[str, int, int]:
def _extract_video_name_and_frame(filename: str) -> Tuple[str, int, int]:

And add import: from typing import Tuple

except Exception as e:
raise LabellerrError(f"Failed to delete key frames: {str(e)}")

def upload_keyframe_preannotations(self, video_json_file_path: str = None) -> Any:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Code Quality: Misleading Docstring

The docstring mentions deprecated parameters (annotation_format, annotation_file, conf_bucket, _async) that don't exist in the function signature. This is confusing.

Suggested change
def upload_keyframe_preannotations(self, video_json_file_path: str = None) -> Any:
def upload_keyframe_preannotations(self, video_json_file_path: str) -> Any:
\"\"\"
Uploads pre-annotations for video project in video JSON format.
:param video_json_file_path: Path to the video JSON file containing pre-annotations
:return: Response from the API
:raises LabellerrError: If file doesn't exist or upload fails
\"\"\"


try:
# Validate if the file exists
if not os.path.exists(file_path):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Missing Validation

The function should validate:

  1. File extension (must be .json)
  2. File is not empty
  3. JSON is valid before uploading
# Add before opening file
if not file_path.endswith('.json'):
    raise LabellerrError("File must be a JSON file")
if os.path.getsize(file_path) == 0:
    raise LabellerrError("File is empty")

from labellerr.services.video_sampling import coco_to_video_json

# Input COCO JSON file path
coco_json_path = r"D:\Professional\Labellerr_SDK\SDKPython\labellerr\notebooks\export-#huy0VWY14med4McdKd6h.json"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Critical: Hardcoded Windows Path

This test file has hardcoded Windows paths that won't work in CI/CD or on other systems. Test files should:

  1. Use relative paths
  2. Accept command-line arguments
  3. Use Path objects for cross-platform compatibility
  4. Be in a proper tests/ directory with pytest
from pathlib import Path
import sys

if len(sys.argv) > 1:
    coco_json_path = Path(sys.argv[1])
else:
    coco_json_path = Path("test_data/export.json")

from labellerr.core.projects.video_project import LabellerrProject

# Load environment variables from .env file
load_dotenv(r"D:\Professional\Labellerr_SDK\dev.env")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Critical: Hardcoded Paths and Missing Best Practices

Multiple issues:

  1. Hardcoded Windows path to .env file
  2. No error handling for missing environment variables
  3. Hardcoded project ID
  4. Should use pytest instead of main

This should be refactored into proper unit/integration tests in a tests/ directory.

# Fetch all video files
video_files = self.fetch_files()
# Fetch all video files (convert generator to list)
video_files = list(self.fetch_files())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance: Generator Converted to List

Converting the generator to a list loads all video files into memory. For large datasets, this could cause memory issues.

Consider:

  1. Keep as generator and process in batches
  2. Add pagination support
  3. Stream process the files
# Process in batches
for video_file in self.fetch_files():
    # Process one at a time
    video_file.download_create_video_auto_cleanup()

@@ -1,4 +1,4 @@
BASE_URL = "https://api.labellerr.com"
BASE_URL = "https://api-gateway-qcb3iv2gaa-uc.a.run.app"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL SECURITY ISSUE: The BASE_URL is hardcoded to a QA/staging environment (api-gateway-qcb3iv2gaa-uc.a.run.app). This should NOT be committed to the main branch as it will affect all production users.

Recommendation:

  • Revert this to the production URL (https://api.labellerr.com)
  • Use environment variables or configuration files for environment-specific URLs
  • Add a clear warning comment if this is intentionally for testing

try:
# Use file_id as folder name
folder_name = self.file_id
# Use [Dataset_id]+[File_id]+[File_name] as folder name

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Quality Issue: The folder naming logic is overly complex and repeated in multiple places (lines 124-132, 292-298, 315-322, 359-363).

Recommendation: Extract this into a private method:

def _get_folder_name(self) -> str:
    if self.dataset_id and self.file_name:
        base_name = os.path.splitext(self.file_name)[0]
        return f"{self.dataset_id}+{self.file_id}+{base_name}"
    elif self.dataset_id:
        return f"{self.dataset_id}+{self.file_id}"
    else:
        return self.file_id

This reduces code duplication and makes maintenance easier.

@property
def fps(self):
"""Get frames per second of the video."""
return self.metadata.get("fps", 25)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential Bug: Using a hardcoded default FPS of 25 could cause issues. If the actual video has different FPS and this default is used in calculations, it will result in incorrect frame timing.

Recommendation: Consider raising an error if FPS is not available in metadata rather than silently falling back to a default, or at minimum log a warning.


def download_create_video_auto_cleanup(
self, output_folder: str = "./Labellerr_datastets"
self, output_folder: str = "./Labellerr_datasets"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in default path: "Labellerr_datastets" should be "Labellerr_datasets" (missing 'a').

except Exception as e:
raise LabellerrError(f"Failed to delete key frames: {str(e)}")

def upload_keyframe_preannotations(self, video_json_file_path: str = None) -> Any:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Quality Issue: The docstring mentions deprecated parameters (annotation_format, annotation_file, conf_bucket, _async) that don't exist in the method signature. This is confusing and misleading.

Recommendation: Clean up the docstring to only document the actual parameter:

"""
Uploads pre-annotations for video project.

:param video_json_file_path: Path to the video JSON file containing pre-annotations
:return: Response from the API
"""

# Process each video file
results = []
for idx, filename in enumerate(video_files, 1):
file_path = os.path.join(dataset_path, filename)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Performance Issue: Using os.listdir() and filtering with list comprehension loads all files into memory. For directories with many files, this could be inefficient.

Recommendation: Use os.scandir() or Path.glob() for better performance:

video_files = [
    f.name for f in os.scandir(dataset_path)
    if f.is_file() and os.path.splitext(f.name)[1].lower() in VIDEO_EXTENSIONS
]

# ============================================================================


def _extract_video_name_and_frame(filename: str) -> tuple[str, int, int]:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Quality: The return type annotation uses tuple[str, int, int] (Python 3.10+ syntax) but the codebase might need to support older Python versions.

Recommendation: Verify Python version requirements. If supporting <3.10, use Tuple[str, int, int] from typing module.



if __name__ == "__main__":
video_path = r"D:\Professional\Labellerr_SDK\SDKPython\labellerr\notebooks\Labellerr_datasets\354681d3-034a-4d66-b070-365f4bd11d8a\2a8d96ca-9161-4dee-ad3b-a5faf301bc6c.mp4"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Issue: Hardcoded absolute Windows path in __main__ block. This exposes internal directory structure and won't work on other machines.

Recommendation: Remove or use a relative example path like "./test_video.mp4".


if __name__ == "__main__":
# Example usage
video_path = r"D:\Professional\Labellerr_SDK\SDKPython\labellerr\notebooks\Labellerr_datasets\354681d3-034a-4d66-b070-365f4bd11d8a\2a8d96ca-9161-4dee-ad3b-a5faf301bc6c.mp4"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Issue: Same as ffmpeg_detect.py - hardcoded absolute Windows path exposing internal directory structure.

from labellerr.services.video_sampling import coco_to_video_json

# Input COCO JSON file path
coco_json_path = r"D:\Professional\Labellerr_SDK\SDKPython\labellerr\notebooks\export-#huy0VWY14med4McdKd6h.json"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Issue: Hardcoded absolute Windows paths exposing internal directory structure. These test files should either:

  1. Use relative paths
  2. Load paths from environment variables
  3. Not be committed to the repository (should be in .gitignore)

from labellerr.core.projects.video_project import LabellerrProject

# Load environment variables from .env file
load_dotenv(r"D:\Professional\Labellerr_SDK\dev.env")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Issue: Loading environment variables from a hardcoded absolute path. This path won't exist on other machines and exposes internal directory structure.

Recommendation: Either use default .env file in current directory or make it configurable.

Comment thread SDK_test.ipynb
"from dotenv import dotenv_values\n",
"\n",
"config = dotenv_values(\".env\")\n",
"config = dotenv_values(\"../dev.env\")\n",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Quality Issue: The notebook contains hardcoded QA credentials in the config keys (QA_API_KEY, QA_API_SECRET, QA_CLIENT_ID). While these are loaded from env file, it suggests this notebook is configured for QA environment which shouldn't be in the main branch.

Recommendation: Use generic key names or document that this is a development/testing notebook.

@github-actions

Copy link
Copy Markdown

Pull Request Review Summary

This PR introduces video keyframe scene detection functionality with three detection algorithms (FFMPEG, PySceneDetect, SSIM) and COCO to Video JSON conversion utilities. While the core functionality is well-implemented, there are critical issues that must be addressed before merging.


🚨 Critical Issues (Must Fix)

1. SECURITY: Hardcoded QA/Staging URL ⚠️

  • File: labellerr/core/constants.py:1
  • Issue: BASE_URL changed from production (https://api.labellerr.com) to QA environment (api-gateway-qcb3iv2gaa-uc.a.run.app)
  • Impact: This will break production for ALL users if merged
  • Action: Revert to production URL immediately or use environment-based configuration

2. SECURITY: Exposed Internal Paths 🔒

Multiple files contain hardcoded absolute Windows paths exposing internal directory structure:

  • labellerr/services/video_sampling/ffmpeg_detect.py:392
  • labellerr/services/video_sampling/ssim_detect.py:432
  • labellerr/notebooks/test_coco_to_video.py:9
  • labellerr/notebooks/test_preannotation_api.py:9
  • SDK_test.ipynb (multiple locations)

Action: Remove or sanitize all hardcoded paths before merging.


⚠️ High Priority Issues

3. Code Duplication in video_file.py

  • Lines: 124-132, 292-298, 315-322, 359-363
  • Issue: Folder naming logic repeated 4 times
  • Recommendation: Extract into a private method _get_folder_name() to follow DRY principle

4. Potential Bug: Hardcoded Default FPS

  • File: labellerr/core/files/video_file.py:41
  • Issue: Falls back to FPS=25 silently, which could cause incorrect frame timing calculations
  • Recommendation: Log warning or raise error when FPS metadata is missing

5. Misleading Documentation

  • File: labellerr/core/projects/video_project.py:95
  • Issue: Docstring references non-existent deprecated parameters
  • Impact: Confuses developers using the API

📋 Medium Priority Issues

6. Performance: Inefficient File Listing

  • File: labellerr/services/video_sampling/__init__.py:109
  • Issue: Uses os.listdir() instead of os.scandir()
  • Impact: Poor performance with large directories
  • Recommendation: Switch to os.scandir() for better performance

7. Type Annotation Compatibility

  • File: labellerr/services/video_sampling/__init__.py:171
  • Issue: Uses tuple[str, int, int] (Python 3.10+ syntax)
  • Action: Verify Python version requirements or use Tuple from typing module

🐛 Minor Issues

8. Typos

  • labellerr/core/files/video_file.py:255: "Labellerr_datastets" → "Labellerr_datasets"
  • labellerr/core/projects/video_project.py:13: "fething" → "fetching"

✅ Positive Aspects

  1. Well-structured architecture: Clean separation of concerns with three distinct scene detection algorithms
  2. Comprehensive error handling: Custom exception classes for different error scenarios
  3. Good documentation: Detailed docstrings explaining parameters and return values
  4. Singleton pattern: Proper implementation for detector classes
  5. Thread-safe operations: Uses proper locking in concurrent frame downloads
  6. Batch processing: Efficient handling of multiple videos
  7. COCO conversion utility: Useful helper for annotation format conversion

📊 Code Statistics

  • Files Changed: 19
  • Additions: +2,634 lines
  • Deletions: -915 lines
  • Net Change: +1,719 lines

🎯 Recommendations for Next Steps

Before Merging:

  1. Fix the critical BASE_URL issue - This is a blocker
  2. Remove all hardcoded paths - Security and portability concern
  3. Add proper environment configuration - Use .env or config files for environment-specific values
  4. ⚠️ Refactor code duplication in video_file.py
  5. ⚠️ Fix documentation to match actual API

Post-Merge Improvements:

  1. Add unit tests for new scene detection algorithms
  2. Add integration tests for COCO to Video JSON conversion
  3. Add performance benchmarks for different detection algorithms
  4. Consider adding rate limiting for concurrent frame downloads
  5. Add validation for video file size limits

📝 Test Coverage Assessment

Missing Test Coverage:

  • No tests for FFMPEGSceneDetect
  • No tests for SSIMSceneDetect
  • No tests for PySceneDetect
  • No tests for coco_to_video_json conversion
  • No tests for upload_keyframe_preannotations

Recommendation: Add comprehensive test coverage before merging to ensure reliability.


🏁 Verdict

Status: ❌ CHANGES REQUESTED

This PR introduces valuable functionality but has critical issues that must be resolved:

  1. The hardcoded QA URL will break production
  2. Security concerns with exposed internal paths
  3. Code quality issues with duplication and misleading docs

Please address the critical and high-priority issues before requesting re-review.


Reviewed by: Claude Code
Review Focus: Code quality, best practices, security, performance, and maintainability

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant