jim-easterbrook / python-gphoto2

Python interface to libgphoto2
GNU Lesser General Public License v3.0
357 stars 59 forks source link

[Question] Is there any way to specify a port for the ptpip connection? #131

Closed vimusov closed 2 years ago

vimusov commented 2 years ago

Hello!

Recently I bought a camera Canon EOS M200 and connected it via Wi-Fi to my archlinux workstation. gphoto2 works perfectly, I can list all the files on camera and download them. Command line looks like that:

gphoto2 --port 'ptpip:192.168.0.5' --list-files
gphoto2 --port 'ptpip:192.168.0.5' --get-all-files

I'm going to develop a daemon which will look after the camera and sync all new files from it. And in fact I've already wrote such code:

https://gist.github.com/vimusov/b46cdbee06d4e4d83d8d7362752ffa36

It works flawlessly but looks very ugly. I want to improve it by using this Python bindings to libgphoto2.

And there is a problem - I suppose this bindings does not provide any functions which can be used to specify the port in the form of "ptpip:".

I've already googled a lot but with no luck. All examples I found are targeted on simple wire connection. And none of them are usable for ptpip.

Please give me some piece of advice is there any solution for such case? Or I need to add some new exports for libgphoto2 functions gp_port_info_*?

Thanks in advance!

jim-easterbrook commented 2 years ago

Have a look at the choose-camera.py example. I expect gp.Camera.autodetect() won't see your camera (as it's not USB) but what about the result of port_info_list.load()?

The documentation of port_info_list is here: http://www.gphoto.org/doc/api/gphoto2-port-info-list_8h.html One of those functions should enable you to specify a port. I know nothing about ptpip cameras. You should ask this question on the gphoto2 mailing list.

vimusov commented 2 years ago

what about the result of port_info_list.load()?

I wrote minimal example:

from gphoto2 import PortInfoList

pi_list = PortInfoList()
pi_list.load()
for pi in pi_list:
    print(pi.get_type(), '|', pi.get_name(), '|', pi.get_path())

The result:

16 | PTP/IP Connection | ptpip:
128 | IP Connection | ip:
1 | Serial Port 0 | serial:/dev/ttyS0
<... the same lines here for other serial ports from 2 to 30...>
1 | Serial Port 31 | serial:/dev/ttyS31
1 | Serial Port Device | serial:
4 | Universal Serial Bus | usb:003,002

In the other words it's the same as the output of gphoto2 --list-ports.

One of those functions should enable you to specify a port.

Yes, thanks. The only thing is that all these functions are not exported in this bindings.

You should ask this question on the gphoto2 mailing list.

OK, thanks! I'm going to ask there too.

jim-easterbrook commented 2 years ago

Isn't the first one on that list the ptpip port you want? That's the port you can use when initialising a camera.

The Python interface doesn't include functions used internally by libgphoto2 but should include the ones that user programs need. Which ones do you think are missing?

vimusov commented 2 years ago

Isn't the first one on that list the ptpip port you want?

Yes, it looks so. But this object is read-only. I can't change its path to new value 192.168.0.5.

Which ones do you think are missing?

I think I miss at least gp_port_info_set_path. Or maybe I need to create the whole object GPPortType from scratch with gp_port_info_new - I've never used libgphoto2 API so I have no idea what's better.

jim-easterbrook commented 2 years ago

The documentation for gp_port_info_set_path explicitly says "This is a libgphoto2_port internal function." so I assume it's not how you specify the IP address to a ptp port. But I don't know what is.

If you can find the relevant bit in the sources of the gphoto2 command line tool then you should be able to copy what it does in Python. See https://github.com/gphoto/gphoto2

jim-easterbrook commented 2 years ago

Looking at https://github.com/gphoto/gphoto2/blob/master/gphoto2/actions.c I see action_camera_set_port calls gp_port_info_list_lookup_path. The documentation for gp_port_info_list_lookup_path says it's called by gp_camera_init and calls gp_port_info_new and gp_port_info_set_path amongst others.

jim-easterbrook commented 2 years ago

In commit 320ef11 I've modified the choose-camera example so you can specify a port address on the command line. It works with my USB cameras but I'm not able to test it with PTPIP as I don't have a camera that does it.

vimusov commented 2 years ago

I've modified the choose-camera example so you can specify a port address

Thanks for help, I appreciate that! Unfortunately it does not work with the same error:

WARNING: gphoto2: (gp_context_error) Could not detect any camera
Traceback (most recent call last):
  File "/home/rics/test.py", line 80, in <module>
    sys.exit(main())
  File "/home/rics/test.py", line 64, in main
    camera.init()
gphoto2.GPhoto2Error: [-105] Unknown model

I will try to write a minimal program in C. I hope it'll help to understand how to port it to Python and which functions are needed for that.

jim-easterbrook commented 2 years ago

What port address did you use? It should be the same format as understood by the gphoto2 command, so you'd do something like

python3 examples/choose-camera.py ptpip:192.168.0.5
jim-easterbrook commented 2 years ago

One last thought for today. Check libgphoto2_port has been built with ptpip support. python3 -m gphoto2 should show you. For example, I get this:

python-gphoto2 version: 2.3.1
libgphoto2 version: ['2.5.27', 'standard camlibs (SKIPPING docupen)', 'gcc', 'ltdl', 'EXIF']
libgphoto2_port version: ['0.12.0', 'iolibs: disk ptpip serial usb1 usbdiskdirect usbscsi vusb', 'gcc', 'ltdl', 'EXIF', 'USB', 'serial without locking']
python-gphoto2 examples: /usr/lib64/python3.6/site-packages/gphoto2/examples
vimusov commented 2 years ago

It should be the same format as understood by the gphoto2

I tried both 192.168.0.5 and ptpip:192.168.0.5 - the same error.

python3 -m gphoto2 should show you

This command returned error: /usr/bin/python: No module named gphoto2.__main__; 'gphoto2' is a package and cannot be directly executed

But that's what I see on gphoto2 --version:

gphoto2 2.5.27

Copyright (c) 2000-2021 Marcus Meissner and others

gphoto2 comes with NO WARRANTY, to the extent permitted by law. You may
redistribute copies of gphoto2 under the terms of the GNU General Public
License. For more information about these matters, see the files named COPYING.

This version of gphoto2 is using the following software versions and options:
gphoto2         2.5.27         gcc, popt(m), exif, no cdk, no aa, jpeg, readline
libgphoto2      2.5.27         standard camlibs, gcc, ltdl, EXIF
libgphoto2_port 0.12.0         iolibs: disk ptpip serial usb1 usbdiskdirect usbscsi, gcc, ltdl, EXIF, USB, serial without locking

And bindings version:

$ pacman -Qi python-gphoto2
Name            : python-gphoto2
Version         : 2.2.4-4
vimusov commented 2 years ago

And here we go! I wrote a minimal example in C which shows summary info about camera connected via Wi-Fi: https://gist.github.com/vimusov/c9f5903e6eea83a63256210151ce39fb It works like a charm. Now I need to port it to this Python binding.

vimusov commented 2 years ago

Wheee!!! I did it!!! It finally works! Here is my sketch:

import logging
from argparse import ArgumentParser

from gphoto2 import (
    Camera, CameraAbilitiesList, CameraList, Context, GP_ERROR_UNKNOWN_PORT, PortInfoList,
    check_result, use_python_logging,
)

def main():
    parser = ArgumentParser()
    parser.add_argument('-p', '--port', required=True, help='Camera address (e.g. "ptpip:192.168.0.1").')
    args = parser.parse_args()
    port: str = args.port

    logging.basicConfig(level=logging.DEBUG)
    check_result(use_python_logging())

    ctx = Context()
    camera = Camera()

    pi_list = PortInfoList()
    pi_list.load()
    port_idx = pi_list.lookup_path(port)
    if port_idx == GP_ERROR_UNKNOWN_PORT:
        raise RuntimeError(f'Unknown port value {port!r}:')
    camera.set_port_info(pi_list.get_info(port_idx))

    abs_list = CameraAbilitiesList()
    abs_list.load(ctx)

    cam_list: CameraList = abs_list.detect(pi_list, ctx)
    cam_cnt = cam_list.count()
    if cam_cnt != 1:
        raise RuntimeError('Unable to detect camera:')
    model = cam_list.get_name(0)
    cam_idx = abs_list.lookup_model(model)
    if cam_idx < 0:
        raise RuntimeError(f'Unable to find camera index for {model=!r}:')
    camera.set_abilities(abs_list.get_abilities(cam_idx))

    summary = camera.get_summary(ctx)
    print(summary)

    camera.exit(ctx)

if __name__ == '__main__':
    main()

The same code in gist: https://gist.github.com/vimusov/3125a97b96f99a3de6190ba15b985ace

vimusov commented 2 years ago

So the issue is solved. Thanks for your attention!

jim-easterbrook commented 2 years ago

I'm glad you got it working. Comparing your solution with choose-camera.py it seems the important bit is setting the camera abilities as well as the port info. I'll update choose-camera.py accordingly.

Going back in this thread, I'd forgotten that python3 -m gphoto2 was introduced as recently as python-gphoto2 version 2.3.1. The gphoto2_version.py example is older. If you install python-gphoto2 as a binary wheel it has its own copy of libgphoto2, which may be a different version from the one used by the gphoto2 command.