Skip to content

Instantly share code, notes, and snippets.

@Norgus
Forked from celestial-33/readme-mac.md
Last active September 14, 2025 21:49
Show Gist options
  • Select an option

  • Save Norgus/9e877924949d778db12c534ff1bafe36 to your computer and use it in GitHub Desktop.

Select an option

Save Norgus/9e877924949d778db12c534ff1bafe36 to your computer and use it in GitHub Desktop.

Revisions

  1. Norgus renamed this gist Sep 3, 2025. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  2. Norgus revised this gist Sep 3, 2025. 2 changed files with 18 additions and 37 deletions.
    34 changes: 8 additions & 26 deletions readme-mac.md
    Original file line number Diff line number Diff line change
    @@ -1,15 +1,14 @@
    # Automated Video to 3D Scan Workflow (GLOMAP + COLMAP) for macOS
    # Automated Video to 3D Scan Workflow (GLOMAP + COLMAP) for Linux

    This repository contains a Bash script for automating a photogrammetry workflow on macOS using **GLOMAP** and **COLMAP**. It takes video files as input, extracts frames via FFmpeg, and generates a 3D sparse point cloud for each video.
    This repository contains a Bash script for automating a photogrammetry workflow on Linux using **GLOMAP** and **COLMAP**. It takes video files as input, extracts frames via FFmpeg, and generates a 3D sparse point cloud for each video.

    This script is a macOS/Apple Silicon adaptation of [Polyfjord](https://www.youtube.com/@Polyfjord)’s workflow, rewritten to run smoothly on Mac systems.
    This script is a Linux adaptation of [celestial's Mac adaptation](https://gist.github.com/celestial-33/07438792a11964ee5f6f02847b6dbb03) of [Polyfjord](https://www.youtube.com/@Polyfjord)’s workflow [here](https://gist.github.com/AeroGenesiX/009654b40d8911ccebb347537d5782b6), with minimul adaptations to run smoothly on Linux systems.

    -----

    ## Features

    * **One-Command Automation:** Processes all videos in the input folder in a single run.
    * **CPU & Apple Silicon Ready:** Automatically detects CPU threads and works on M-series Macs.
    * **Error Handling:** Skips already processed videos and ensures the workflow executes step by step without breaking.
    * **Organized Output:** Keeps results clean with a structured project folder.
    * **COLMAP + GLOMAP Integration:** Extracts features, performs sequential matching, and runs sparse reconstruction automatically.
    @@ -25,18 +24,7 @@ This script is a macOS/Apple Silicon adaptation of [Polyfjord](https://www.youtu
    * **GLOMAP** – COLMAP extension for automated mapping.
    * **FFmpeg** – For frame extraction.

    ### Recommended Installation Method:

    * **Colmap and Glomap:**

    [Install by following this guide](https://youtu.be/tr5LrANp470)

    * **FFmpeg:**


    ```
    brew install ffmpeg
    ```

    ### Folder Structure

    @@ -57,15 +45,11 @@ brew install ffmpeg

    3. **Save the Script:** Place the script (`run_glomap.sh`) in the `SCRIPTS/` folder.

    4. **Update Paths:** Edit the script and update these variables to match your system installation:
    ```bash
    FFMPEG="/opt/homebrew/bin/ffmpeg"
    COLMAP="/usr/local/bin/colmap"
    GLOMAP="/usr/local/bin/glomap"
    ```
    **If you are unsure of the paths, run:**
    4. **Update Paths:** Edit the script and update these variables to match your system installation (only necessary if they aren't already on $PATH):
    ```bash
    which colmap ffmpeg glomap
    FFMPEG="$(which ffmpeg)"
    COLMAP="$(which colmap)"
    GLOMAP="$(which glomap)"
    ```

    5. **Make it Executable:**
    @@ -88,10 +72,8 @@ brew install ffmpeg

    * **Slow processing:** Reduce `--SiftExtraction.max_image_size` (e.g., from 4096 → 2048) or process shorter clips.

    * **GPU/CPU issues:** This script primarily uses CPU. M-series Macs may benefit from COLMAP GPU support if configured.

    -----

    ## Conclusion

    This script fully automates [Polyfjord](https://www.youtube.com/@Polyfjord)’s photogrammetry workflow on macOS using GLOMAP and COLMAP, making it easy to generate 3D sparse point clouds from videos. Future updates may include optimizations and support for additional Mac hardware.
    This script fully automates [Polyfjord](https://www.youtube.com/@Polyfjord)’s photogrammetry workflow on macOS using GLOMAP and COLMAP, making it easy to generate 3D sparse point clouds from videos.
    21 changes: 10 additions & 11 deletions run_glomap.sh
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,7 @@
    #!/bin/bash
    #!/usr/bin/env bash
    # ================================================================
    # BASH SCRIPT FOR AUTOMATED PHOTOGRAMMETRY TRACKING WORKFLOW (GLOMAP + COLMAP)
    # macOS / Apple Silicon version (Could work in Intel Mac - Not tested yet)
    # for Linux!
    # ================================================================

    set -e # Stop on error
    @@ -15,12 +15,14 @@ VIDEOS_DIR="$TOP/VIDEOS"
    SCENES_DIR="$TOP/SCENES"

    # --- System-wide executables ---
    FFMPEG="/opt/homebrew/bin/ffmpeg"
    COLMAP="/usr/local/bin/colmap"
    GLOMAP="/usr/local/bin/glomap"
    # replace with full paths to binaries if using one
    # not on $PATH
    FFMPEG="$(which ffmpeg)"
    COLMAP="$(which colmap)"
    GLOMAP="$(which glomap)"

    # --- CPU thread count ---
    NUM_THREADS=$(sysctl -n hw.ncpu)
    NUM_THREADS=$(nproc)

    # --- Ensure executables exist ---
    for cmd in "$FFMPEG" "$COLMAP" "$GLOMAP"; do
    @@ -73,12 +75,9 @@ for VIDEO_FILE in "$VIDEOS_DIR"/*; do

    # --- 1) Extract frames ---
    echo " [1/4] Extracting frames …"
    "$FFMPEG" -loglevel error -stats -i "$VIDEO_FILE" -qscale:v 2 "$IMG_DIR/frame_%06d.jpg"
    "$FFMPEG" -loglevel error -stats -i "$VIDEO_FILE" -qscale:v 2 "$IMG_DIR/frame_%06d.png"

    # Remove dot-underscore macOS files
    find "$IMG_DIR" -name '._*' -delete

    if ! ls "$IMG_DIR"/*.jpg &> /dev/null; then
    if ! ls "$IMG_DIR"/*.png &> /dev/null; then
    echo " × No frames extracted – skipping \"$BASE\"."
    rm -rf "$SCENE_DIR"
    continue
  3. @celestial-33 celestial-33 revised this gist Sep 2, 2025. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion readme-mac.md
    Original file line number Diff line number Diff line change
    @@ -29,7 +29,7 @@ This script is a macOS/Apple Silicon adaptation of [Polyfjord](https://www.youtu

    * **Colmap and Glomap:**

    [Install by following this guide](https://gist.github.com/celestial-33/a016465fd854d79a1b93458f167baa6a)
    [Install by following this guide](https://youtu.be/tr5LrANp470)

    * **FFmpeg:**

  4. @celestial-33 celestial-33 revised this gist Sep 2, 2025. 3 changed files with 222 additions and 186 deletions.
    186 changes: 0 additions & 186 deletions AutoTracker_v1.4.bat
    Original file line number Diff line number Diff line change
    @@ -1,186 +0,0 @@
    :: ================================================================
    :: BATCH SCRIPT FOR AUTOMATED PHOTOGRAMMETRY TRACKING WORKFLOW
    :: By polyfjord - https://youtube.com/polyfjord
    :: GLOMAP mapping (faster), COLMAP for features/matching + TXT export
    :: ================================================================
    @echo off
    setlocal EnableExtensions EnableDelayedExpansion

    :: ---------- Resolve top-level folder (one up from this .bat) -----
    pushd "%~dp0\.." >nul
    set "TOP=%cd%"

    :: ---------- Key paths -------------------------------------------
    set "SFM_DIR=%TOP%\01 GLOMAP"
    set "VIDEOS_DIR=%TOP%\02 VIDEOS"
    set "FFMPEG_DIR=%TOP%\03 FFMPEG"
    set "SCENES_DIR=%TOP%\04 SCENES"

    :: ---------- Locate ffmpeg.exe -----------------------------------
    if exist "%FFMPEG_DIR%\ffmpeg.exe" (
    set "FFMPEG=%FFMPEG_DIR%\ffmpeg.exe"
    ) else if exist "%FFMPEG_DIR%\bin\ffmpeg.exe" (
    set "FFMPEG=%FFMPEG_DIR%\bin\ffmpeg.exe"
    ) else (
    echo [ERROR] ffmpeg.exe not found inside "%FFMPEG_DIR%".
    popd & pause & goto :eof
    )

    :: ---------- Locate glomap.exe -----------------------------------
    if exist "%SFM_DIR%\glomap.exe" (
    set "GLOMAP=%SFM_DIR%\glomap.exe"
    ) else if exist "%SFM_DIR%\bin\glomap.exe" (
    set "GLOMAP=%SFM_DIR%\bin\glomap.exe"
    ) else (
    echo [ERROR] glomap.exe not found inside "%SFM_DIR%".
    popd & pause & goto :eof
    )

    :: ---------- Locate colmap.exe (DB + TXT export) ------------------
    if exist "%SFM_DIR%\colmap.exe" (
    set "COLMAP=%SFM_DIR%\colmap.exe"
    ) else if exist "%SFM_DIR%\bin\colmap.exe" (
    set "COLMAP=%SFM_DIR%\bin\colmap.exe"
    ) else (
    echo [ERROR] colmap.exe not found inside "%SFM_DIR%".
    popd & pause & goto :eof
    )

    :: ---------- Put binaries on PATH --------------------------------
    set "PATH=%SFM_DIR%;%SFM_DIR%\bin;%PATH%"

    :: ---------- Ensure required folders exist ------------------------
    if not exist "%VIDEOS_DIR%" (
    echo [ERROR] Input folder "%VIDEOS_DIR%" missing.
    popd & pause & goto :eof
    )
    if not exist "%SCENES_DIR%" mkdir "%SCENES_DIR%"

    :: ---------- Count videos for progress bar ------------------------
    for /f %%C in ('dir /b /a-d "%VIDEOS_DIR%\*" ^| find /c /v ""') do set "TOTAL=%%C"
    if "%TOTAL%"=="0" (
    echo [INFO] No video files found in "%VIDEOS_DIR%".
    popd & pause & goto :eof
    )

    echo ==============================================================
    echo Starting GLOMAP pipeline on %TOTAL% video(s) …
    echo ==============================================================

    set /a IDX=0

    for %%V in ("%VIDEOS_DIR%\*.*") do (
    set /a IDX+=1
    call :PROCESS_VIDEO "%%~fV" "%%IDX%%" "%TOTAL%"
    )

    echo --------------------------------------------------------------
    echo All jobs finished – results are in "%SCENES_DIR%".
    echo --------------------------------------------------------------
    popd
    pause
    goto :eof


    :PROCESS_VIDEO
    :: ----------------------------------------------------------------
    :: %1 = full path to video %2 = current index %3 = total
    :: ----------------------------------------------------------------
    setlocal EnableDelayedExpansion

    set "VIDEO=%~1"
    set "NUM=%~2"
    set "TOT=%~3"

    for %%I in ("%VIDEO%") do (
    set "BASE=%%~nI"
    set "EXT=%%~xI"
    )

    echo.
    echo [!NUM!/!TOT!] === Processing "!BASE!!EXT!" ===

    :: -------- Directory layout for this scene -----------------------
    set "SCENE=%SCENES_DIR%\!BASE!"
    set "IMG_DIR=!SCENE!\images"
    set "SPARSE_DIR=!SCENE!\sparse"

    :: -------- Skip if already reconstructed -------------------------
    if exist "!SCENE!" (
    echo • Skipping "!BASE!" – already reconstructed.
    goto :END
    )

    :: Clean slate ----------------------------------------------------
    mkdir "!IMG_DIR!" >nul
    mkdir "!SPARSE_DIR!" >nul

    :: -------- 1) Extract every frame --------------------------------
    echo [1/4] Extracting frames …
    "%FFMPEG%" -loglevel error -stats -i "!VIDEO!" -qscale:v 2 ^
    "!IMG_DIR!\frame_%%06d.jpg"
    if errorlevel 1 (
    echo × FFmpeg failed – skipping "!BASE!".
    goto :END
    )

    dir /b "!IMG_DIR!\*.jpg" >nul 2>&1 || (
    echo × No frames extracted – skipping "!BASE!".
    goto :END
    )

    :: -------- 2) Feature extraction (COLMAP) -------------------------
    echo [2/4] COLMAP feature_extractor …
    "%COLMAP%" feature_extractor ^
    --database_path "!SCENE!\database.db" ^
    --image_path "!IMG_DIR!" ^
    --ImageReader.single_camera 1 ^
    --SiftExtraction.use_gpu 1 ^
    --SiftExtraction.max_image_size 4096
    if errorlevel 1 (
    echo × feature_extractor failed – skipping "!BASE!".
    goto :END
    )

    :: -------- 3) Sequential matching (COLMAP) ------------------------
    echo [3/4] COLMAP sequential_matcher …
    "%COLMAP%" sequential_matcher ^
    --database_path "!SCENE!\database.db" ^
    --SequentialMatching.overlap 15
    if errorlevel 1 (
    echo × sequential_matcher failed – skipping "!BASE!".
    goto :END
    )

    :: -------- 4) Sparse reconstruction (GLOMAP) ----------------------
    echo [4/4] GLOMAP mapper …
    "%GLOMAP%" mapper ^
    --database_path "!SCENE!\database.db" ^
    --image_path "!IMG_DIR!" ^
    --output_path "!SPARSE_DIR!"
    if errorlevel 1 (
    echo × glomap mapper failed – skipping "!BASE!".
    goto :END
    )

    :: -------- Export TXT **inside the model folder** -----------------
    :: Keep TXT next to BIN so Blender can import from sparse\0 directly.
    if exist "!SPARSE_DIR!\0" (
    "%COLMAP%" model_converter ^
    --input_path "!SPARSE_DIR!\0" ^
    --output_path "!SPARSE_DIR!\0" ^
    --output_type TXT >nul
    )

    :: -------- Export TXT to parent sparse\ (for Blender auto-detect) --
    if exist "!SPARSE_DIR!\0" (
    "%COLMAP%" model_converter ^
    --input_path "!SPARSE_DIR!\0" ^
    --output_path "!SPARSE_DIR!" ^
    --output_type TXT >nul
    )

    echo ✓ Finished "!BASE!" (!NUM!/!TOT!)

    :END
    endlocal & goto :eof
    97 changes: 97 additions & 0 deletions readme-mac.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,97 @@
    # Automated Video to 3D Scan Workflow (GLOMAP + COLMAP) for macOS

    This repository contains a Bash script for automating a photogrammetry workflow on macOS using **GLOMAP** and **COLMAP**. It takes video files as input, extracts frames via FFmpeg, and generates a 3D sparse point cloud for each video.

    This script is a macOS/Apple Silicon adaptation of [Polyfjord](https://www.youtube.com/@Polyfjord)’s workflow, rewritten to run smoothly on Mac systems.

    -----

    ## Features

    * **One-Command Automation:** Processes all videos in the input folder in a single run.
    * **CPU & Apple Silicon Ready:** Automatically detects CPU threads and works on M-series Macs.
    * **Error Handling:** Skips already processed videos and ensures the workflow executes step by step without breaking.
    * **Organized Output:** Keeps results clean with a structured project folder.
    * **COLMAP + GLOMAP Integration:** Extracts features, performs sequential matching, and runs sparse reconstruction automatically.
    * **TXT Export:** Converts COLMAP models to TXT format for easier inspection or import.

    -----

    ## Requirements

    ### Software

    * **COLMAP** – Structure-from-motion and multi-view stereo pipeline.
    * **GLOMAP** – COLMAP extension for automated mapping.
    * **FFmpeg** – For frame extraction.

    ### Recommended Installation Method:

    * **Colmap and Glomap:**

    [Install by following this guide](https://gist.github.com/celestial-33/a016465fd854d79a1b93458f167baa6a)

    * **FFmpeg:**


    ```
    brew install ffmpeg
    ```

    ### Folder Structure

    ```
    Project_Folder/
    ├── VIDEOS/ # Place input video files (.mp4, .mov) here.
    ├── SCENES/ # The script saves all outputs here.
    └── SCRIPTS/ # The run_glomap.sh script lives here.
    ```

    -----

    ## How to Use

    1. **Set Up Folders:** Create the structure shown above.

    2. **Add Videos:** Copy your `.mp4` or `.mov` files into the `VIDEOS/` folder.

    3. **Save the Script:** Place the script (`run_glomap.sh`) in the `SCRIPTS/` folder.

    4. **Update Paths:** Edit the script and update these variables to match your system installation:
    ```bash
    FFMPEG="/opt/homebrew/bin/ffmpeg"
    COLMAP="/usr/local/bin/colmap"
    GLOMAP="/usr/local/bin/glomap"
    ```
    **If you are unsure of the paths, run:**
    ```bash
    which colmap ffmpeg glomap
    ```

    5. **Make it Executable:**
    ```bash
    chmod +x run_glomap.sh
    ```

    6. **Run the Script:** From inside the `SCRIPTS/` folder:
    ```bash
    ./run_glomap.sh
    ```

    -----

    ## Troubleshooting

    * **COLMAP/GLOMAP/FFmpeg not found:** Ensure the paths in the script match your system installation.

    * **No frames extracted:** Check that your video files are valid and playable by FFmpeg.

    * **Slow processing:** Reduce `--SiftExtraction.max_image_size` (e.g., from 4096 → 2048) or process shorter clips.

    * **GPU/CPU issues:** This script primarily uses CPU. M-series Macs may benefit from COLMAP GPU support if configured.

    -----

    ## Conclusion

    This script fully automates [Polyfjord](https://www.youtube.com/@Polyfjord)’s photogrammetry workflow on macOS using GLOMAP and COLMAP, making it easy to generate 3D sparse point clouds from videos. Future updates may include optimizations and support for additional Mac hardware.
    125 changes: 125 additions & 0 deletions run_glomap.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,125 @@
    #!/bin/bash
    # ================================================================
    # BASH SCRIPT FOR AUTOMATED PHOTOGRAMMETRY TRACKING WORKFLOW (GLOMAP + COLMAP)
    # macOS / Apple Silicon version (Could work in Intel Mac - Not tested yet)
    # ================================================================

    set -e # Stop on error

    # --- Resolve top-level folder (one up from this script) ---
    SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
    TOP="$(dirname "$SCRIPT_DIR")"

    # --- Key paths ---
    VIDEOS_DIR="$TOP/VIDEOS"
    SCENES_DIR="$TOP/SCENES"

    # --- System-wide executables ---
    FFMPEG="/opt/homebrew/bin/ffmpeg"
    COLMAP="/usr/local/bin/colmap"
    GLOMAP="/usr/local/bin/glomap"

    # --- CPU thread count ---
    NUM_THREADS=$(sysctl -n hw.ncpu)

    # --- Ensure executables exist ---
    for cmd in "$FFMPEG" "$COLMAP" "$GLOMAP"; do
    if [ ! -x "$cmd" ]; then
    echo "[ERROR] Executable not found: $cmd" >&2
    exit 1
    fi
    done

    # --- Ensure required folders exist ---
    if [ ! -d "$VIDEOS_DIR" ]; then
    echo "[ERROR] Input folder '$VIDEOS_DIR' missing." >&2
    exit 1
    fi
    mkdir -p "$SCENES_DIR"

    # --- Count videos ---
    TOTAL=$(find "$VIDEOS_DIR" -maxdepth 1 -type f | wc -l | tr -d ' ')
    if [ "$TOTAL" -eq 0 ]; then
    echo "[INFO] No video files found in '$VIDEOS_DIR'."
    exit 0
    fi

    echo "=============================================================="
    echo " Starting GLOMAP pipeline on $TOTAL video(s) …"
    echo "=============================================================="

    IDX=0
    for VIDEO_FILE in "$VIDEOS_DIR"/*; do
    [ -f "$VIDEO_FILE" ] || continue
    IDX=$((IDX + 1))
    BASENAME=$(basename "$VIDEO_FILE")
    BASE="${BASENAME%.*}"

    echo
    echo "[$IDX/$TOTAL] === Processing \"$BASENAME\" ==="

    SCENE_DIR="$SCENES_DIR/$BASE"
    IMG_DIR="$SCENE_DIR/images"
    SPARSE_DIR="$SCENE_DIR/sparse"

    # Skip if already reconstructed
    if [ -d "$SCENE_DIR" ]; then
    echo " • Skipping \"$BASE\" – already reconstructed."
    continue
    fi

    # Create directories
    mkdir -p "$IMG_DIR" "$SPARSE_DIR"

    # --- 1) Extract frames ---
    echo " [1/4] Extracting frames …"
    "$FFMPEG" -loglevel error -stats -i "$VIDEO_FILE" -qscale:v 2 "$IMG_DIR/frame_%06d.jpg"

    # Remove dot-underscore macOS files
    find "$IMG_DIR" -name '._*' -delete

    if ! ls "$IMG_DIR"/*.jpg &> /dev/null; then
    echo " × No frames extracted – skipping \"$BASE\"."
    rm -rf "$SCENE_DIR"
    continue
    fi

    # --- 2) Feature extraction (COLMAP) ---
    echo " [2/4] COLMAP feature_extractor …"
    "$COLMAP" feature_extractor \
    --database_path "$SCENE_DIR/database.db" \
    --image_path "$IMG_DIR" \
    --ImageReader.single_camera 1 \
    --SiftExtraction.max_image_size 4096

    # --- 3) Sequential matching (COLMAP) ---
    echo " [3/4] COLMAP sequential_matcher …"
    "$COLMAP" sequential_matcher \
    --database_path "$SCENE_DIR/database.db" \
    --SequentialMatching.overlap 15

    # --- 4) Sparse reconstruction (GLOMAP) ---
    echo " [4/4] GLOMAP mapper …"
    "$GLOMAP" mapper \
    --database_path "$SCENE_DIR/database.db" \
    --image_path "$IMG_DIR" \
    --output_path "$SPARSE_DIR"

    # --- Export TXT inside model folder ---
    if [ -d "$SPARSE_DIR/0" ]; then
    "$COLMAP" model_converter \
    --input_path "$SPARSE_DIR/0" \
    --output_path "$SPARSE_DIR/0" \
    --output_type TXT > /dev/null
    "$COLMAP" model_converter \
    --input_path "$SPARSE_DIR/0" \
    --output_path "$SPARSE_DIR" \
    --output_type TXT > /dev/null
    fi

    echo " ✓ Finished \"$BASE\" ($IDX/$TOTAL)"
    done

    echo "--------------------------------------------------------------"
    echo " All jobs finished – results are in \"$SCENES_DIR\"."
    echo "--------------------------------------------------------------"
  5. @polyfjord polyfjord created this gist Aug 20, 2025.
    186 changes: 186 additions & 0 deletions AutoTracker_v1.4.bat
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,186 @@
    :: ================================================================
    :: BATCH SCRIPT FOR AUTOMATED PHOTOGRAMMETRY TRACKING WORKFLOW
    :: By polyfjord - https://youtube.com/polyfjord
    :: GLOMAP mapping (faster), COLMAP for features/matching + TXT export
    :: ================================================================
    @echo off
    setlocal EnableExtensions EnableDelayedExpansion

    :: ---------- Resolve top-level folder (one up from this .bat) -----
    pushd "%~dp0\.." >nul
    set "TOP=%cd%"

    :: ---------- Key paths -------------------------------------------
    set "SFM_DIR=%TOP%\01 GLOMAP"
    set "VIDEOS_DIR=%TOP%\02 VIDEOS"
    set "FFMPEG_DIR=%TOP%\03 FFMPEG"
    set "SCENES_DIR=%TOP%\04 SCENES"

    :: ---------- Locate ffmpeg.exe -----------------------------------
    if exist "%FFMPEG_DIR%\ffmpeg.exe" (
    set "FFMPEG=%FFMPEG_DIR%\ffmpeg.exe"
    ) else if exist "%FFMPEG_DIR%\bin\ffmpeg.exe" (
    set "FFMPEG=%FFMPEG_DIR%\bin\ffmpeg.exe"
    ) else (
    echo [ERROR] ffmpeg.exe not found inside "%FFMPEG_DIR%".
    popd & pause & goto :eof
    )

    :: ---------- Locate glomap.exe -----------------------------------
    if exist "%SFM_DIR%\glomap.exe" (
    set "GLOMAP=%SFM_DIR%\glomap.exe"
    ) else if exist "%SFM_DIR%\bin\glomap.exe" (
    set "GLOMAP=%SFM_DIR%\bin\glomap.exe"
    ) else (
    echo [ERROR] glomap.exe not found inside "%SFM_DIR%".
    popd & pause & goto :eof
    )

    :: ---------- Locate colmap.exe (DB + TXT export) ------------------
    if exist "%SFM_DIR%\colmap.exe" (
    set "COLMAP=%SFM_DIR%\colmap.exe"
    ) else if exist "%SFM_DIR%\bin\colmap.exe" (
    set "COLMAP=%SFM_DIR%\bin\colmap.exe"
    ) else (
    echo [ERROR] colmap.exe not found inside "%SFM_DIR%".
    popd & pause & goto :eof
    )

    :: ---------- Put binaries on PATH --------------------------------
    set "PATH=%SFM_DIR%;%SFM_DIR%\bin;%PATH%"

    :: ---------- Ensure required folders exist ------------------------
    if not exist "%VIDEOS_DIR%" (
    echo [ERROR] Input folder "%VIDEOS_DIR%" missing.
    popd & pause & goto :eof
    )
    if not exist "%SCENES_DIR%" mkdir "%SCENES_DIR%"

    :: ---------- Count videos for progress bar ------------------------
    for /f %%C in ('dir /b /a-d "%VIDEOS_DIR%\*" ^| find /c /v ""') do set "TOTAL=%%C"
    if "%TOTAL%"=="0" (
    echo [INFO] No video files found in "%VIDEOS_DIR%".
    popd & pause & goto :eof
    )

    echo ==============================================================
    echo Starting GLOMAP pipeline on %TOTAL% video(s) …
    echo ==============================================================

    set /a IDX=0

    for %%V in ("%VIDEOS_DIR%\*.*") do (
    set /a IDX+=1
    call :PROCESS_VIDEO "%%~fV" "%%IDX%%" "%TOTAL%"
    )

    echo --------------------------------------------------------------
    echo All jobs finished – results are in "%SCENES_DIR%".
    echo --------------------------------------------------------------
    popd
    pause
    goto :eof


    :PROCESS_VIDEO
    :: ----------------------------------------------------------------
    :: %1 = full path to video %2 = current index %3 = total
    :: ----------------------------------------------------------------
    setlocal EnableDelayedExpansion

    set "VIDEO=%~1"
    set "NUM=%~2"
    set "TOT=%~3"

    for %%I in ("%VIDEO%") do (
    set "BASE=%%~nI"
    set "EXT=%%~xI"
    )

    echo.
    echo [!NUM!/!TOT!] === Processing "!BASE!!EXT!" ===

    :: -------- Directory layout for this scene -----------------------
    set "SCENE=%SCENES_DIR%\!BASE!"
    set "IMG_DIR=!SCENE!\images"
    set "SPARSE_DIR=!SCENE!\sparse"

    :: -------- Skip if already reconstructed -------------------------
    if exist "!SCENE!" (
    echo • Skipping "!BASE!" – already reconstructed.
    goto :END
    )

    :: Clean slate ----------------------------------------------------
    mkdir "!IMG_DIR!" >nul
    mkdir "!SPARSE_DIR!" >nul

    :: -------- 1) Extract every frame --------------------------------
    echo [1/4] Extracting frames …
    "%FFMPEG%" -loglevel error -stats -i "!VIDEO!" -qscale:v 2 ^
    "!IMG_DIR!\frame_%%06d.jpg"
    if errorlevel 1 (
    echo × FFmpeg failed – skipping "!BASE!".
    goto :END
    )

    dir /b "!IMG_DIR!\*.jpg" >nul 2>&1 || (
    echo × No frames extracted – skipping "!BASE!".
    goto :END
    )

    :: -------- 2) Feature extraction (COLMAP) -------------------------
    echo [2/4] COLMAP feature_extractor …
    "%COLMAP%" feature_extractor ^
    --database_path "!SCENE!\database.db" ^
    --image_path "!IMG_DIR!" ^
    --ImageReader.single_camera 1 ^
    --SiftExtraction.use_gpu 1 ^
    --SiftExtraction.max_image_size 4096
    if errorlevel 1 (
    echo × feature_extractor failed – skipping "!BASE!".
    goto :END
    )

    :: -------- 3) Sequential matching (COLMAP) ------------------------
    echo [3/4] COLMAP sequential_matcher …
    "%COLMAP%" sequential_matcher ^
    --database_path "!SCENE!\database.db" ^
    --SequentialMatching.overlap 15
    if errorlevel 1 (
    echo × sequential_matcher failed – skipping "!BASE!".
    goto :END
    )

    :: -------- 4) Sparse reconstruction (GLOMAP) ----------------------
    echo [4/4] GLOMAP mapper …
    "%GLOMAP%" mapper ^
    --database_path "!SCENE!\database.db" ^
    --image_path "!IMG_DIR!" ^
    --output_path "!SPARSE_DIR!"
    if errorlevel 1 (
    echo × glomap mapper failed – skipping "!BASE!".
    goto :END
    )

    :: -------- Export TXT **inside the model folder** -----------------
    :: Keep TXT next to BIN so Blender can import from sparse\0 directly.
    if exist "!SPARSE_DIR!\0" (
    "%COLMAP%" model_converter ^
    --input_path "!SPARSE_DIR!\0" ^
    --output_path "!SPARSE_DIR!\0" ^
    --output_type TXT >nul
    )

    :: -------- Export TXT to parent sparse\ (for Blender auto-detect) --
    if exist "!SPARSE_DIR!\0" (
    "%COLMAP%" model_converter ^
    --input_path "!SPARSE_DIR!\0" ^
    --output_path "!SPARSE_DIR!" ^
    --output_type TXT >nul
    )

    echo ✓ Finished "!BASE!" (!NUM!/!TOT!)

    :END
    endlocal & goto :eof