3.3.1. Picture Fetcher

This module is made of a single class Pygphoto defined in the file pygphoto.py. It allows to interact with a USB camera using the gphoto2 tool. Common actions include : listing the names of the photos present in the camera, watching for new files, downloading photos individually etc. Interface with the gphoto2 command-line interface

The module uses directly the gphoto2 command-line interface for prototyping. It would be wiser to create a C module that uses the C API of gphoto2 called libgphoto2 and offer a interface for all the needed operations. This C module would be compiled as a shared library and imported in Python with ctypes.

However, the current implementation makes extended use of the command-line interface provided by gphoto2, through hard-coded calls and output parsing.

The calls to gphoto2 are made with the subprocess Python module.

Simple calls are done like so:

command = ["gphoto2", "--get-all-files"]
return_code = subprocess.call(command)

When the output need to be parsed, it is necessary to use the blocking subprocess.check_output() function and to decode the binary stream as an UTF-8 string:

command = ["gphoto2", "--summary"]
output_string = subprocess.check_output(command).decode("utf-8") Active watching of the camera

When instantiated, the Pygphoto class create a daemon thread that actively watch for new camera connections/disconnections and pictures creation/deletion. The two associated signals onCameraConnection and onContentChanged are the only signals emitted by Pygphoto.

The CameraWatcher internal class is responsible for the active watching. The threading is made by moving the instance of CameraWatcher to a QThread at initialization. Using QThread allows for easy asynchronous communication with the CameraWatcher‘s thread through the use of Qt‘s signals.

Note: As the CameraWatcher class is internal, Pygphoto must forward every signals emitted by CameraWatcher to the exterior. The same goes for slots connections. As a consequence, every slot and signal of CameraWatcher is duplicated in the the parent Pygphoto class. Camera locking

The camera device can be busy for several reasons. This usually end up throwing the following error message

*** Error ***
An error occurred in the io-library ('Could not lock the device'): Camera is already in use.
*** Error (-60: 'Could not lock the device') ***

Sometimes it comes from other processes or daemons accessing the camera. We do not do anything about them and we did not explore this issue further. Sometimes, however, the lock comes from simultaneous calls to gphoto2 made by the application. To avoid such problems, we use a thread synchronization lock every time we make a call to gphoto2:

self._camera_lock = threading.Lock()
command = [Pygphoto._GPHOTO, "--auto-detect"]
with self._camera_lock:
    output = subprocess.check_output(command).decode("utf-8")
# The call has returned, the lock is released
# Continue working with output...

The lock is internal to the Pygphoto class, the only class that can make calls to gphoto2. The lock issue is thus hidden from the exterior. It is important to note that the Pygphoto class should not be instantiated twice, and could actually be implemented as a singleton class.