Using USB API (MTP) with libghoto2 and Python bindings on MacOS, Raspberry Pi, Linux, ROS

Moderator Note: The title of the topic was changed on 9/25/2020 to reflect the evolving discussion that eventually settled on using libgphoto2 and was expanded to include other OS platforms. The original question was only for MacOS.

Original Post by Original Author Below

Has anyone had any luck using the USB api on mac? I have installed libmtp but the camera is not recognized. Is there a ptp lib that works? Ideally there would be a python lib I could use but looking for any working ‘hello world’ example at this point.

Alternatively, is it possible to use libusb rather than ptp? I do see the device using libusb.


Have you tried libmtp on Mac with Python bindings?

I think I did get it working on Linux with the Python bindings. However, everyone seemed to be writing bash scripts, so I stopped. I think there were a few glitches with the Python bindings.

Does libptp not compile on Mac?

People have gotten it running in the past.

I did get libmtp and pymtp installed but it is not seeing the camera:

>>> import pymtp
>>> pymtp.MTP().detect_devices()


>>> mtp-detect
libmtp version: 1.1.16

Listing raw device(s)
  No raw devices found.

There are no Theta cameras listed as supported by libmtp.

libptp is proving difficult to build but I will try to get that working. Thanks!

1 Like

For prototyping, can you use Vagrant or straight VirtualBox and run Linux inside the VM?

That way, you can test out your app concepts quickly instead of diving into compiling the library.

Maybe you can even deploy with Linux in a VM?

gphoto2 works well on linux and it seems to be installable on MacOs.
I haven’t a Mac but on Linux Raspbian I need to uninstall sort of automount to make it work



Additional tests with Automount

FWIW I have had the best luck with gphoto2. Older versions (2.5.4) will not set shutter speed but I am having luck with the latest (2.5.23).

List all shutter speeds:

gphoto2 --get-config=d00f

Set shutter speed iso and fstop:

gphoto2 --set-config-index=d00f=0 --set-config=500f=80 --set-config=5007=560

Thanks for the report. It’s surprising that there’s a difference between 2.5.4 and 2.5.23. This information should be helpful to other people. :slight_smile:

Here is some code using the gphoto2 python module…I have a UI that uses this to shoot HDRI exposures…an example in _unittest(). Not totally polished but maybe useful to others. Works with Z1:

USB api for added performance over http

Theta api reference:

Unable to get mtp or ptp to connect to the camera; After some pain was able to get gphoto2 working

import os
import time

import gphoto2 as gp

# Properties
F_NUMBER = '5007'

# milliseconds

def wait_for_event(camera, timeout=TIMEOUT, event_type=gp.GP_EVENT_TIMEOUT):
    Wait for event_type to to be triggered.
    :param camera:
    :param timeout:
    :param event_type:
    :return: event_data
    while True:
        _event_type, event_data = camera.wait_for_event(timeout)
        if _event_type == gp.GP_EVENT_TIMEOUT:
        if _event_type == event_type:
            return event_data

def set_config_by_index(config, index):
    """Set config using choice index"""
    value = config.get_choice(index)

    return config

# def list_files(camera, path='/'):
#     result = []
#     # get files
#     for name, value in camera.folder_list_files(path):
#         result.append(os.path.join(path, name))
#     # read folders
#     folders = []
#     for name, value in camera.folder_list_folders(path):
#         folders.append(name)
#     # recurse over subfolders
#     for name in folders:
#         result.extend(list_files(camera, os.path.join(path, name)))
#     return result
# def get_file_info(camera, path):
#     folder, name = os.path.split(path)
#     return camera.file_get_info(folder, name)

class CameraUsb(object):
    Define API for multiple exposure
    def __init__(self, verbose=False):
        self.verbose = verbose = gp.Camera()

        self.camera_config = None
        self.status_config = None
        self.other_config = None
        self.shutter_speed_config = None
        self.shutter_speed_options = []

    def init(self):
        Set manual exposure and other defaults
        :return: config
            self.camera_config =
        except gp.GPhoto2Error:
            raise RuntimeError("Unable to connect to Camera")

        self.other_config = self.camera_config.get_child_by_name('other')

        # Manual/f-stop/iso
        exposure_program_mode = self.other_config.get_child_by_name(EXPOSURE_PROGRAM_MODE)
        if not exposure_program_mode.get_value() == '1':
            print('Setting camera to Manual exposure program')

            # When switching exposure program, we need to refresh the configs
            self.camera_config =
            self.other_config = self.camera_config.get_child_by_name('other')

        self.status_config = self.camera_config.get_child_by_name('status')

        self.shutter_speed_config = self.other_config.get_child_by_name(SHUTTER_SPEED)
        self.shutter_speed_options = [str(x) for x in self.shutter_speed_config.get_choices()]
        if len(self.shutter_speed_options) != 61:
            raise RuntimeError('Unble to determine shutter speed options; restart app')

        fstop = self.other_config.get_child_by_name(F_NUMBER)

        iso = self.other_config.get_child_by_name(EXPOSURE_INDEX)

    def get_info(self):
        :return: Dict containing serialnumber, batterylevel, remainingpictures, etc
        if not self.camera_config:

        battery_level = self.status_config.get_child_by_name('batterylevel').get_value()
        # Convert '67%' to int
        battery_level = int(''.join([x for x in battery_level if x.isdigit()]))

        info = {'serialnumber': self.status_config.get_child_by_name('serialnumber').get_value(),
                'cameramodel': self.status_config.get_child_by_name('cameramodel').get_value(),
                'deviceversion': self.status_config.get_child_by_name('deviceversion').get_value(),
                'batterylevel': battery_level,
                'remainingpictures': int([0].freeimages)}
        return info

    def take_picture(self, shutter_speed_index=None, color_temperature=None, volume=None):
        Set camera options and take picture
        :param shutter_speed_index: int in range 0-60 (0 fastest shutter)
        :param color_temperature: in in range 2500-10000 by 100 increment
        :param volume: int in range 0-100
        :return: (jpg_path, dng_path)
        t1 = time.time()
        if not self.camera_config:

        if shutter_speed_index is not None:

        if color_temperature is not None:

        if volume is not None:
        # We need this even though no event is triggered

        gp_jpg_path =

        gp_dng_path = wait_for_event(, timeout=TIMEOUT_CAPTURE_DNG, event_type=gp.GP_EVENT_FILE_ADDED)
        if not gp_dng_path:
            raise RuntimeError('Unable to copy DNG')

        jpg_path = os.path.join(gp_jpg_path.folder,
        dng_path = os.path.join(gp_dng_path.folder,

        print('Capture took %0.03f sec' % (time.time() - t1, ))
        return jpg_path, dng_path

    def download_file(self, src_path, dst_path, delete=True):
        """Copy the file from the camera src_path to local dst_path"""
        t1 = time.time()

        src_folder, src_name = os.path.split(src_path)
        src_file =, src_name, gp.GP_FILE_TYPE_NORMAL)
        print('Download %s ->\n\t%s' % (src_path, dst_path))
        print('Download took %0.03f sec' % (time.time() - t1, ))

        if delete:
            t1 = time.time()
            print('Delete %s' % src_path)
  , src_name)
            print('Delete took %0.03f sec' % (time.time() - t1, ))

def _unittest():
    """test a short exposure sequence"""
    # temporary directory
    dst_template = '/tmp/theta/capture.%04d.%s'

    t1 = time.time()
    camera = CameraUsb()



    frame = 1
    jpg_path, dng_path = camera.take_picture(0)
    print(jpg_path, dng_path)
    camera.download_file(dng_path, dst_template % (frame, 'dng'))
    frame += 1

    jpg_path, dng_path = camera.take_picture(24)
    print(jpg_path, dng_path)
    camera.download_file(dng_path, dst_template % (frame, 'dng'))
    frame += 1

    jpg_path, dng_path = camera.take_picture(42)
    print(jpg_path, dng_path)
    camera.download_file(dng_path, dst_template % (frame, 'dng'))
    frame += 1
    print('Done in %0.03f sec' % (time.time() - t1, ))

if __name__ == "__main__":



Changelog in Gphoto2 indicates support of Theta ShutterSpeed in 2.5.10
But after some tests
Gphoto2 2.5.23 (fresh compilation) on raspbian + Theta V => shutterSpeed (on video mode, i.e. shutter is less than a second) is not working (no real change after a set-config)

Maybe it’s work on other cameras.For now, ptpcam still needed for shutterspeed configuration in USB

I found a way to change shutterspeed on the Theta V with gphoto2 (on linux raspbian but it shoud work on others platforms)
Since version 2.5.10, Ricoh Shutterspeed in implemented in libghoto2 but it was not working with the Theta V (at least for me).
After a few tries I found that the VendorID reported on the Theta V is 0x00000006, this number is MICROSOFT and not PENTAX (the ricoh shutterspeed is only for PENTAX VendorID)

I add this ligne in config.c (libgphoto2-2.5.23\camlibs\ptp2)

{ N_(“Shutter Speed”), “shutterspeed”, PTP_DPC_RICOH_ShutterSpeed, PTP_VENDOR_MICROSOFT, PTP_DTC_UINT64, _get_Ricoh_ShutterSpeed, _put_Ricoh_ShutterSpeed },

just after this one :

{ N_(“Shutter Speed”), “shutterspeed”, PTP_DPC_RICOH_ShutterSpeed, PTP_VENDOR_PENTAX, PTP_DTC_UINT64, _get_Ricoh_ShutterSpeed, _put_Ricoh_ShutterSpeed },

You need to compile libgphoto2 and now you can use
gphoto2 --get-config shutterspeed
gphoto2 --set-config-index shutterspeed=10

(with the property 0xD00F it’s not working)

If ExposureProgramMode=2 (Normal)
I get:

Label: Shutter Speed
Readonly: 0
Current: Auto
Choice: 0 Auto

if I switch to ExposureProgramMode=4 (Shutter priority program)
(gphoto2 --set-config=/main/other/500e=4 )
I get

Label: Shutter Speed
Readonly: 0
Current: 1/60
Choice: 0 1/25000
Choice: 1 1/20000
Choice: 2 1/16000
Choice: 3 1/12500
Choice: 4 1/10000
Choice: 5 1/8000
Choice: 6 1/6400
Choice: 7 1/5000
Choice: 8 1/4000
Choice: 9 1/3200
Choice: 10 1/2500
Choice: 11 1/2000
Choice: 12 1/1600
Choice: 13 1/1250
Choice: 14 1/1000
Choice: 15 1/800
Choice: 16 1/640
Choice: 17 1/500
Choice: 18 1/400
Choice: 19 1/320
Choice: 20 1/250
Choice: 21 1/200
Choice: 22 1/160
Choice: 23 1/125
Choice: 24 1/100
Choice: 25 1/80
Choice: 26 1/60
Choice: 27 1/50
Choice: 28 1/40
Choice: 29 1/30

Bye bye ptpcam (the main camera led was blinking during ptpcam shutterspeed modification - not very good - and sometimes the camera was disconnected from usb)


1 Like

FWIW I set ExposureProgramMode=1 (Manual) before setting shutter_speed

Mr. @mhenrie, is this code on Github?
If you know the Git URL when I’m helping you with this code, you can fork it or whatever, which will come in handy for contributing to the community and all that.
I’d appreciate it if you could let me know.

1 Like

Sorry I do not have this code on github or elsewhere but you are free to use what is posted here.


Copy that.
When I create a derived program, I’ll indicate your display name and the URL of this site.


If you put your code up on GitHub, please put the link here. I’m going to run tests with gphoto2 myself next week. Thanks.

Update Sept 9, 2020

I need advice on how to set the values of the camera with the Python API. If I change the camera to video mode, the value changes, but the camera does not actually shift into video mode.

The same command works from the command line. I think I need help with the basic syntax of using the gphoto2 Python module.

import gphoto2 as gp

MODE = '5013'
MOVIE =  str(int('0x8002', 0))

camera = gp.Camera()

mode = camera.get_single_config(MODE)
1 Like

It’s been a while, @mhenrie.
I finally tried your code after all sorts of messes, but I got an error.
Can you guess the cause?

Setting camera to Manual exposure program
Traceback (most recent call last):
File “/”, line 234, in
File “/”, line 210, in _unittest
File “/”, line 116, in init
raise RuntimeError(‘Unable to determine shutter speed options; restart app’)
RuntimeError: Unable to determine shutter speed options; restart app

Unfortunately, @craig’s code also gives me an error, although I don’t know why.
Maybe the path is wrong?

Traceback (most recent call last):
File “/”, line 10, in
mode = camera.get_single_config(MODE)
gphoto2.GPhoto2Error: [-6] Unsupported operation

Can you run the code in the examples directory of python-gphoto2 on GitHub?

In particular, does this work?

$ python 
WARNING: gphoto2: (gp_port_set_error [gphoto2-port.c:1190]) The supplied vendor or product id (0x0,0x0) is not valid.
Manufacturer: Ricoh Company, Ltd.
  Version: 1.50.1
  Serial Number: 10010104
Vendor Extension ID: 0x6 (1.10)

Hugues mentioned that a modification to this file might help.

I have not tried his fix yet.

Some of the commands seem to work.

$ python 
WARNING: gphoto2: (gp_port_set_error [gphoto2-port.c:1190]) The supplied vendor or product id (0x0,0x0) is not valid.
File list

Information on building libgphoto2 from source is here:

I was able to build it from source.

At the current time, I am using ptpcam, which seems to work okay for my uses.

My goodness, that’s a lot more sample programs than I checked!
I might be able to develop some of this myself.

Aside from that, I ran and it looked like below.

WARNING: gphoto2: (foreach_func [gphoto2-port-info-list.c:237]) Error during assembling of port list: 'Unspecified error' (-1).
WARNING: gphoto2: (gp_port_set_error [gphoto2-port.c:1186]) The supplied vendor or product id (0x0,0x0) is not valid.
Manufacturer: Ricoh Company, Ltd.
  Version: 3.40.1
  Serial Number: 00153573
Vendor Extension ID: 0x6 (1.10)

Capture Formats: 
Display Formats: Association/Directory, JPEG, MP4, Firmware
Supported MTP Object Properties:
	Association/Directory/3001: dc01/StorageID dc02/ObjectFormat dc04/ObjectSize dc0b/ParentObject dc41/PersistantUniqueObjectIdentifier dc44/Name
	JPEG/3801: dc01/StorageID dc02/ObjectFormat dc03/ProtectionStatus dc04/ObjectSize dc07/ObjectFileName dc08/DateCreated dc09/DateModified dc0b/ParentObject dc41/PersistantUniqueObjectIdentifier dc44/Name dc87/Width dc88/Height
	MP4/b982: dc01/StorageID dc02/ObjectFormat dc03/ProtectionStatus dc04/ObjectSize dc07/ObjectFileName dc08/DateCreated dc09/DateModified dc0b/ParentObject dc41/PersistantUniqueObjectIdentifier dc44/Name dc87/Width dc88/Height
	Firmware/b802: dc01/StorageID dc02/ObjectFormat dc04/ObjectSize dc0b/ParentObject dc41/PersistantUniqueObjectIdentifier dc44/Name

Device Capabilities:
	File Download, File Deletion, No File Upload
	Generic Image Capture, Open Capture, No vendor specific capture

Storage Devices Summary:
	StorageDescription: None
	VolumeLabel: None
	Storage Type: Builtin RAM
	Filesystemtype: Digital Camera Layout (DCIM)
	Access Capability: Read-Write
	Maximum Capability: 21088788480 (20111 MB)
	Free Space (Bytes): 19082989568 (18198 MB)
	Free Space (Images): 4549

Device Property Summary:
Battery Level(0x5001):(read only) (type=0x2) Range [0 - 100, step 1] value: 100% (100)
Functional Mode(0x5002):(read only) (type=0x4) Enumeration [0] value: 0
Image Size(0x5003):(readwrite) (type=0xffff) Enumeration [
	] value: '5376x2688'
White Balance(0x5005):(readwrite) (type=0x4) Enumeration [2,4,32769,32770,6,32800,32771,32772,32773,32774,32775,32776] value: Automatic (2)
Exposure Program Mode(0x500e):(readwrite) (type=0x4) Enumeration [1,2,4,32771] value: P (2)
Exposure Index (film speed ISO)(0x500f):(readwrite) (type=0x4) Enumeration [65535] value: ISO 65535 (65535)
Exposure Bias Compensation(0x5010):(readwrite) (type=0x3) Enumeration [2000,1700,1300,1000,700,300,0,-300,-700,-1000,-1300,-1700,-2000] value: 0.0 stops (0)
Date & Time(0x5011):(readwrite) (type=0xffff) '20200925T020641+0900'
Pre-Capture Delay(0x5012):(readwrite) (type=0x6) Range [0 - 10000, step 1000] value: 0.0s (0)
Still Capture Mode(0x5013):(readwrite) (type=0x4) Enumeration [1,3,32770,32772,32773,32774,32775] value: Single Shot (1)
Timelapse Number(0x501a):(readwrite) (type=0x4) Range [0 - 9999, step 1] value: 0
Timelapse Interval(0x501b):(readwrite) (type=0x6) Range [4000 - 3600000, step 1000] value: 4000
Audio Volume(0x502c):(readwrite) (type=0x6) Range [0 - 100, step 1] value: 100
Property 0xd006:(read only) (type=0x6) 0
Property 0xd00f:(readwrite) (type=0x8) Enumeration [0] value: 0
Perceived Device Type(0xd407):(read only) (type=0x6) 1
Property 0xd801:(readwrite) (type=0xffff) '(null)'
Property 0xd803:(readwrite) (type=0x4) Range [0 - 65534, step 1] value: 180
Property 0xd805:(readwrite) (type=0xffff) 'THETAYL00153573.OSC'
Property 0xd806:(readwrite) (type=0xffff) '00153573'
Property 0xd808:(read only) (type=0x2) Enumeration [0,1,2,3,4] value: 0
Property 0xd809:(read only) (type=0x4) Range [0 - 1499, step 1] value: 0
Property 0xd80a:(read only) (type=0x4) Range [0 - 1500, step 1] value: 0
Property 0xd80b:(readwrite) (type=0x2) Enumeration [0,1,2,3,4] value: 0
Property 0xd80c:(read only) (type=0x2) Enumeration [0,1,2] value: 1
Property 0xd80d:(read only) (type=0x4) 8098
Property 0xd80e:(readwrite) (type=0x2) Enumeration [0,1] value: 0
Property 0xd812:(readwrite) (type=0x0) Undefined
Property 0xd813:(readwrite) (type=0x4) Range [2500 - 10000, step 100] value: 5000
Property 0xd814:(readwrite) (type=0x2) Enumeration [0,1] value: 0
Property 0xd815:(readwrite) (type=0xffff) 'THETAYL00153573'
Property 0xd816:(readwrite) (type=0xffff) '00153573'
Property 0xd817:(readwrite) (type=0x4) Enumeration [1] value: 1
Property 0xd818:(readwrite) (type=0x4) Enumeration [1,2] value: 1
Property 0xd81a:(readwrite) (type=0x2) Enumeration [0,1] value: 0
Property 0xd81b:(readwrite) (type=0x6) Range [0 - 2592000, step 60] value: 64980
Property 0xd81c:(readwrite) (type=0x2) Enumeration [0,1,2] value: 0
Property 0xd81d:(readwrite) (type=0x2) Enumeration [0,1] value: 0
Property 0xd81f:(readwrite) (type=0x2) Enumeration [0,1,2] value: 0
Property 0xd820:(readwrite) (type=0x2) Enumeration [0,1,2,3,4,5,6,7,8] value: 0
Property 0xd821:(readwrite) (type=0x2) Enumeration [0] value: 0
Property 0xd822:(read only) (type=0x4) 0
Property 0xd823:(readwrite) (type=0x4) Enumeration [300,1500] value: 1500
Property 0xd825:(readwrite) (type=0x2) Enumeration [0,1,2,3,4] value: 0
Property 0xd826:(readwrite) (type=0x4) Enumeration [200,250,320,400,500,640,800,1000,1250,1600,2000,2500,3200] value: 1600
Property 0xd829:(readwrite) (type=0x4) Enumeration [0] value: 0
Property 0xd82a:(readwrite) (type=0x2) Enumeration [0,1] value: 1
Property 0xd82c:(readwrite) (type=0x2) Enumeration [1,2,3] value: 2
Property 0xd831:(read only) (type=0xffff) 'ProgressRate:000'

and ran

WARNING: gphoto2: (foreach_func [gphoto2-port-info-list.c:237]) Error during assembling of port list: 'Unspecified error' (-1).
WARNING: gphoto2: (gp_port_set_error [gphoto2-port.c:1186]) The supplied vendor or product id (0x0,0x0) is not valid.
File list
File info
image dimensions: 1920 960
image type: video/mp4
file mtime: 2020-09-02 19:55:11

Without any modifications, “gphoto2 --set-config-index shutterspeed=10” could not be executed correctly.

*** Error (-110: 'I/O in progress') ***                                        

For debugging messages, please use the --debug option.
Debugging messages may help finding a solution to your problem.
If you intend to send any error or debug messages to the gphoto
developer mailing list <>, please run
gphoto2 as follows:

    env LANG=C gphoto2 --debug --debug-logfile=my-logfile.txt --get-config shutterspeed

Please make sure there is sufficient quoting around the arguments.

Is this something that can be fixed by modifying as @Hugues pointed out, or did @Hugues get the same error?
Also, have you already put up an issue in Git with a fix for this issue? If you haven’t already, I can get it up. While doing the quote.
Or shall I fork it and make a pull request?