Bololoco
Published

The OpenEXIF Sidecar

A hot-shoe Edge-AI sidecar using voice & a lens-facing OCR camera to auto-inject vintage lens metadata into digital camera EXIF data.

IntermediateWork in progress10
The OpenEXIF Sidecar

Things used in this project

Hardware components

Raspberry Pi 5
Raspberry Pi 5
×1
Raspberry Pi Camera Module 3
Raspberry Pi Camera Module 3
×1
Microphone, Omnidirectional
Microphone, Omnidirectional
×1
Adafruit PowerBoost 1000C
Adafruit PowerBoost 1000C
×1
Optocoupler, Transistor Output
Optocoupler, Transistor Output
×1
USB Cable, USB Type C Plug
USB Cable, USB Type C Plug
×1
Audio / Video Cable Assembly, 3.5mm Slim Stereo Plug to 3.5mm Slim Stereo Jack
Audio / Video Cable Assembly, 3.5mm Slim Stereo Plug to 3.5mm Slim Stereo Jack
×1

Software apps and online services

Funtoo Linux for Beaglebone AI and Beagleboard X15
Suzie Linux Funtoo Linux for Beaglebone AI and Beagleboard X15

Hand tools and fabrication machines

Hot glue gun (generic)
Hot glue gun (generic)

Story

Read more

Code

openexif_sidecar_engine.py

Python
What it is: A Python engine that orchestrates the dual-input metadata system, linking a lens-tracking OCR camera and voice inputs to the physical shutter mechanisms.
How to use it: The script runs as a background service on the Raspberry Pi 5. It continuously parses data from the lens-facing camera loop and microphone, and then instantly executes both the USB-PTP and opto-isolated hardware shutter commands when the "Snap" trigger word is recognized.
python
#!/usr/bin/env python3
import time

print("="*60)
print(" THE OPENEXIF SIDECAR - EMBEDDED ENGINE INITIALISED")
print(" SYSTEM RUNNING ON RASPBERRY PI 5 - VINTAGE FLASH ENCLOSURE")
print("="*60)

# --- SECTION 1: HARDWARE INTERFACE WRAPPERS ---

def trigger_mechanical_shutter_lead():
    """
    Pulsing the physical GPIO line to close the circuit on the PC817 optocoupler.
    This triggers purely manual/mechanical cameras via the 2.5mm/3.5mm assembly cable.
    """
    print("[HARDWARE ACTUATOR] GPIO Pin 17 -> HIGH (3.3V)")
    print("[HARDWARE ACTUATOR] PC817 Optocoupler closed remote release loop.")
    time.sleep(0.1) # Shutter hold time
    print("[HARDWARE ACTUATOR] GPIO Pin 17 -> LOW")
    print("[STATUS] Mechanical shutter activation pulse complete.")

def trigger_digital_usb_ptp():
    """
    Wrapper command executing 'gphoto2 --trigger-capture' over the USB-C link.
    Used exclusively for modern digital bodies.
    """
    print("[USB HOST] Initialising PTP (Picture Transfer Protocol) connection over USB-C...")
    print("[gPHOTO2 CMD] gphoto2 --trigger-capture --quiet")
    # In a real environment, this returns the name of the newly written image file
    mock_captured_filename = "IMAGE_001.RAW"
    print(f"[STATUS] Digital camera triggered. Image saved to camera storage as: {mock_captured_filename}")
    return mock_captured_filename

def write_exif_metadata(target_file, metadata_packet):
    """
    Utilises exiv2 / piexif wrappers to inject the text-parsed data 
    directly into the camera file fields.
    """
    print(f"[METADATA INJECTOR] Intercepting {target_file} over USB host...")
    print(f"[EXIF WRITE] Injecting Lens Model:  {metadata_packet['lens_model']}")
    print(f"[EXIF WRITE] Injecting Aperture:    {metadata_packet['aperture']}")
    print(f"[EXIF WRITE] Injecting Subject Tag: {metadata_packet['subject_name']}")
    print("[STATUS] EXIF payload successfully written to file non-volatile memory.")


# --- SECTION 2: AI MULTIMODAL LOOP (MOCK) ---

def run_openexif_engine():
    # Cache to store incoming voice/vision telemetry
    fused_metadata = {
        "lens_model": "Vintage Manual Lens (Default)",
        "aperture": "f/0.0 (Unread)",
        "subject_name": "Ambient Scene"
    }
    
    print("\n[AI CORE] Local Voice Pipeline (Vosk/PocketSphinx) listening...")
    print("[AI CORE] Lens Camera (Module 3 + EasyOCR) continuous stream running...\n")

    while True:
        # Simulate an interactive command session for testing the logic architecture
        user_input = input("Simulate input (e.g., 'Voice: Subject Sarah', 'Vision: f/8', 'Snap'): ").strip()
        
        # Scenario A: Photographer updates creative metadata vocally
        if user_input.lower().startswith("voice:"):
            voice_string = user_input.split(":", 1)[1].strip()
            if "subject" in voice_string.lower():
                fused_metadata["subject_name"] = voice_string.replace("Subject", "").strip().capitalize()
                print(f" -> [ASR SUCCESS] Subject context locked as: '{fused_metadata['subject_name']}'")
        
        # Scenario B: Lens-facing camera automatically registers physical ring changes
        elif user_input.lower().startswith("vision:"):
            vision_ocr_reading = user_input.split(":", 1)[1].strip()
            fused_metadata["aperture"] = vision_ocr_reading
            print(f" -> [OCR SUCCESS] Visual track updated lens ring reading to: {fused_metadata['aperture']}")
            
        # Scenario C: Shutter Command Fired
        elif user_input.lower() == "snap":
            print("\n" + "-"*40)
            print(" EXECUTION COMMAND TRIGGERED: 'SNAP'")
            print("-"*40)
            
            # Fire both mechanical lead and digital protocols simultaneously
            trigger_mechanical_shutter_lead()
            filename = trigger_digital_usb_ptp()
            
            # Commit the fused voice/vision data directly to the file
            write_exif_metadata(filename, fused_metadata)
            print("-"*40)
            print("SYSTEM ARMED AND READY FOR NEXT FRAME.\n")
            break
            
        else:
            print("Unknown simulation string. Use 'Voice: [text]', 'Vision: [text]', or 'Snap'")

if __name__ == "__main__":
    run_openexif_engine()

Credits

Bololoco
1 project • 0 followers
Full of ideas. Doing my best to make them a reality, although that's a long road.

Comments