gphoto2-cffi-0.3~a1/doc/000775 001750 001750 00000000000 12656073631 016515 5ustar00aigariusaigarius000000 000000 gphoto2-cffi-0.3~a1/gphoto2cffi/__init__.py000664 001750 001750 00000000264 12656073631 022275 0ustar00aigariusaigarius000000 000000 from .gphoto2 import (Camera, list_cameras, supported_cameras, get_library_version) __all__ = [Camera, list_cameras, supported_cameras, get_library_version] gphoto2-cffi-0.3~a1/doc/index.rst000664 001750 001750 00000004155 12656073631 020363 0ustar00aigariusaigarius000000 000000 ============= gphoto2-cffi ============= Python bindings for `libgphoto2`_ with an interface that strives to be idiomatic. In contrast to other bindings for Python, gphoto2-cffi hides most of the lower-level abstractions and reduces the API surface while still offering access to most of the library's features. .. code:: python import gphoto2 as gp # List all attached cameras that are supported cams = gp.list_cameras() # Get a camera instance by specifying a USB bus and device number my_cam = gp.Camera(bus=4, device=1) # Get an instance for the first supported camera my_cam = gp.Camera() # or my_cam = next(gp.list_cameras()) # Capture an image to the camera's RAM and get its data imgdata = my_cam.capture() # Grab a preview from the camera previewdata = my_cam.get_preview() # Get a list of files on the camera files = tuple(my_cam.list_all_files()) # Iterate over a file's content with open("image.jpg", "wb") as fp: for chunk in my_cam.files[0].iter_data(): fp.write(chunk) # Get a configuration value image_quality = my_cam.config['capturesettings']['imagequality'].value # Set a configuration value my_cam.config['capturesettings']['imagequality'].set("JPEG Fine") Currently only Python 2.7 and 3.4 (CPython and PyPy) are supported, however support for 2.6 and 3.3 is planned for the future. .. _libgphoto2: http://www.gphoto.org/proj/libgphoto2/ .. _PyPy: http://pypy.org/ .. _cffi: https://cffi.readthedocs.org/ Requirements ------------ * libgphoto2 with development headers * A working C compiler * cffi Installation ------------ From Source:: $ pip install git+https://github.com/jbaiter/gphoto2-cffi.git Similar projects ---------------- * `piggyphoto `_: Uses ctypes * `python-gphoto2 `_: Uses SWIG API Reference ============= .. automodule:: gphoto2.gphoto2 :members: list_cameras, Camera, Directory, File, ConfigItem, VideoCaptureContext, Range, ImageDimensions, UsbInformation :undoc-members: gphoto2-cffi-0.3~a1/setup.py000664 001750 001750 00000002361 12656073631 017464 0ustar00aigariusaigarius000000 000000 import os import sys from setuptools import setup REQUIRES = ['cffi >= 1.4'] if sys.version_info < (3, 4): REQUIRES.append('enum34 >= 1.0.3') if os.path.exists('README.rst'): if sys.version_info > (3,): description_long = open('README.rst', encoding="utf-8").read() else: description_long = open('README.rst').read() else: description_long = """ Python bindings for libgphoto2 with an interface that strives to be idiomatic. In contrast to other bindings for Python, gphoto2-cffi hides most of the lower-level abstractions and reduces the API surface while still offering access to most of the library's features. """ setup( name='gphoto2-cffi', version="0.3a1", description=("libgphoto2 bindings with an interface that strives to be " "idiomatic"), description_long=description_long, author="Johannes Baiter", url="http://github.com/jbaiter/gphoto2-cffi.git", author_email="johannes.baiter@gmail.com", license='LGPLv3', packages=['gphoto2cffi'], package_data={'gphoto2cffi': ['gphoto2.cdef']}, include_package_data=True, setup_requires=['cffi >= 1.4'], cffi_modules=['gphoto2cffi/backend_build.py:ffi'], install_requires=REQUIRES, zip_safe=False, ) gphoto2-cffi-0.3~a1/.gitignore000664 001750 001750 00000000104 12656073631 017733 0ustar00aigariusaigarius000000 000000 __pycache__ *.pyc *.so dist *.log *.egg-info doc/_build *.diff core gphoto2-cffi-0.3~a1/gphoto2cffi/errors.py000664 001750 001750 00000005557 12656073631 022064 0ustar00aigariusaigarius000000 000000 def error_from_code(errcode): from .backend import ffi, lib if errcode == lib.GP_ERROR_CORRUPTED_DATA: return CameraIOError(errcode, "Corrupted data received.") elif errcode == lib.GP_ERROR_FILE_EXISTS: return CameraIOError(errcode, "File already exists.") elif errcode == lib.GP_ERROR_FILE_NOT_FOUND: return CameraIOError(errcode, "File not found.") elif errcode == lib.GP_ERROR_DIRECTORY_NOT_FOUND: return CameraIOError(errcode, "Directory not found.") elif errcode == lib.GP_ERROR_DIRECTORY_EXISTS: return CameraIOError(errcode, "Directory already exists.") elif errcode == lib.GP_ERROR_NO_SPACE: return CameraIOError(errcode, "Not enough space.") elif errcode == lib.GP_ERROR_MODEL_NOT_FOUND: return UnsupportedDevice(errcode) elif errcode == lib.GP_ERROR_CAMERA_BUSY: return CameraBusy(errcode) elif errcode == lib.GP_ERROR_PATH_NOT_ABSOLUTE: return ValueError("Specified path is not absolute.") elif errcode == lib.GP_ERROR_CANCEL: return OperationCancelled(errcode) elif errcode == lib.GP_ERROR_CAMERA_ERROR: return CameraError(errcode, "Unspecified camera error.") elif errcode == lib.GP_ERROR_OS_FAILURE: return OSError("Unspecified failure of the operation system.") else: return GPhoto2Error(errcode, ffi.string(lib.gp_result_as_string(errcode))) class GPhoto2Error(Exception): def __init__(self, errcode=None, message=None): """ Generic exception type for all errors that originate in libgphoto2. Converts libgphoto2 error codes to their human readable message. :param errcode: The libgphoto2 error code """ self.error_code = errcode if message: super(GPhoto2Error, self).__init__(message) class CameraIOError(GPhoto2Error, IOError): """ IOError on the camera itself. """ pass class UnsupportedDevice(GPhoto2Error): """ Specified camera model was not found. The specified model could not be found. This error is reported when the user specified a model that does not seem to be supported by any driver. """ pass class CameraBusy(GPhoto2Error): """ The camera is already busy. Camera I/O or a command is in progress. """ pass class OperationCancelled(GPhoto2Error): """ Cancellation successful. A cancellation requestion by the frontend via progress callback and GP_CONTEXT_FEEDBACK_CANCEL was successful and the transfer has been aborted. """ pass class CameraError(GPhoto2Error): """ Unspecified camera error The camera reported some kind of error. This can be either a photographic error, such as failure to autofocus, underexposure, or violating storage permission, anything else that stops the camera from performing the operation. """ pass gphoto2-cffi-0.3~a1/LICENSE000664 001750 001750 00000016743 12656073631 016770 0ustar00aigariusaigarius000000 000000 GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. gphoto2-cffi-0.3~a1/000775 001750 001750 00000000000 12656073631 015750 5ustar00aigariusaigarius000000 000000 gphoto2-cffi-0.3~a1/gphoto2cffi/backend_build.py000664 001750 001750 00000000626 12656073631 023306 0ustar00aigariusaigarius000000 000000 import os from cffi import FFI with open(os.path.join(os.path.dirname(__file__), 'gphoto2.cdef')) as fp: CDEF = fp.read() SOURCE = """ #include "gphoto2/gphoto2-version.h" #include "gphoto2/gphoto2-context.h" #include "gphoto2/gphoto2-camera.h" #include """ ffi = FFI() ffi.set_source("_backend", SOURCE, libraries=['gphoto2']) ffi.cdef(CDEF) if __name__ == "__main__": ffi.compile() gphoto2-cffi-0.3~a1/gphoto2cffi/gphoto2.py000664 001750 001750 00000077400 12656073631 022126 0ustar00aigariusaigarius000000 000000 from __future__ import unicode_literals, division, absolute_import import functools import itertools import logging import math import os import re import string import sys import time from collections import namedtuple from datetime import datetime from . import errors, backend from .backend import ffi, lib from .util import SimpleNamespace, get_string, get_ctype, new_gp_object if sys.version_info > (3,): basestring = str def get_library_version(): """ Get the version number of the underlying gphoto2 library. :return: The version :rtype: tuple of (major, minor, patch) version numbers """ version_str = ffi.string(lib.gp_library_version(True)[0]).decode() return tuple(int(x) for x in version_str.split('.')) def list_cameras(): """ List all attached USB cameras that are supported by libgphoto2. :return: All recognized cameras :rtype: list of :py:class:`Camera` """ ctx = lib.gp_context_new() camlist_p = new_gp_object("CameraList") port_list_p = new_gp_object("GPPortInfoList") lib.gp_port_info_list_load(port_list_p) abilities_list_p = new_gp_object("CameraAbilitiesList") lib.gp_abilities_list_load(abilities_list_p, ctx) lib.gp_abilities_list_detect(abilities_list_p, port_list_p, camlist_p, ctx) out = [] for idx in range(lib.gp_list_count(camlist_p)): name = get_string(lib.gp_list_get_name, camlist_p, idx) value = get_string(lib.gp_list_get_value, camlist_p, idx) bus_no, device_no = (int(x) for x in re.match(r"usb:(\d+),(\d+)", value).groups()) abilities = ffi.new("CameraAbilities*") ability_idx = lib.gp_abilities_list_lookup_model( abilities_list_p, name.encode()) lib.gp_abilities_list_get_abilities(abilities_list_p, ability_idx, abilities) if abilities.device_type == lib.GP_DEVICE_STILL_CAMERA: out.append(Camera(bus_no, device_no, lazy=True, _abilities=abilities)) lib.gp_list_free(camlist_p) lib.gp_port_info_list_free(port_list_p) lib.gp_abilities_list_free(abilities_list_p) return out def supported_cameras(): """ List the names of all cameras supported by libgphoto2, grouped by the name of their driver. """ ctx = lib.gp_context_new() abilities_list_p = new_gp_object("CameraAbilitiesList") lib.gp_abilities_list_load(abilities_list_p, ctx) abilities = ffi.new("CameraAbilities*") out = [] for idx in range(lib.gp_abilities_list_count(abilities_list_p)): lib.gp_abilities_list_get_abilities(abilities_list_p, idx, abilities) if abilities.device_type == lib.GP_DEVICE_STILL_CAMERA: libname = os.path.basename(ffi.string(abilities.library) .decode()) out.append((ffi.string(abilities.model).decode(), libname)) lib.gp_abilities_list_free(abilities_list_p) key_func = lambda name, driver: driver out = sorted(out, key=key_func) return {k: tuple(x[0] for x in v) for k, v in itertools.groupby(out, key_func)} return out def exit_after(meth=None, cam_struc=None): if meth is None: return functools.partial(exit_after, cam_struc=cam_struc) @functools.wraps(meth) def wrapped(self, *args, **kwargs): if not isinstance(self, Camera): cam, ctx = self._cam._cam, self._cam._ctx else: cam, ctx = self._cam, self._ctx rval = meth(self, *args, **kwargs) lib.gp_camera_exit(cam, ctx) return rval return wrapped class Range(namedtuple("Range", ('min', 'max', 'step'))): """ Specifies a range of values (:py:attr:`max`, :py:attr:`min`, :py:attr:`step`) """ pass class ImageDimensions(namedtuple("ImageDimensions", ('width', 'height'))): """ Describes the dimension of an image (:py:attr:`width`, :py:attr:`height`) """ pass class UsbInformation(namedtuple( "UsbInformation", ('vendor', 'product', 'devclass', 'subclass', 'protocol'))): """ Information about a USB device. (:py:attr:`vendor`, :py:attr:`product`, :py:attr:`devclass`, :py:attr:`subclass`) """ pass class VideoCaptureContext(object): """ Context object that allows the stopping of a video capture via the :py:meth:`start` method. Can also be used as a context manager, where the capture will be stopped upon leaving. Get the resulting videofile by accessing the :py:attr:`videofile` attribute. """ def __init__(self, camera): #: Camera the capture is running on self.camera = camera #: Resulting video :py:class:`File`, only available after stopping #: the capture self.videofile = None target = self.camera.config['settings']['capturetarget'] self._old_captarget = target.value if self._old_captarget != "Memory card": target.set("Memory card") self.camera._get_config()['actions']['movie'].set(True) def stop(self): """ Stop the capture. """ self.camera._get_config()['actions']['movie'].set(False) self.videofile = self.camera._wait_for_event( event_type=lib.GP_EVENT_FILE_ADDED) if self._old_captarget != "Memory card": self.camera.config['settings']['capturetarget'].set( self._old_captarget) def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): if self.videofile is None: self.stop() lib.gp_camera_exit(self.camera._cam, self.camera._ctx) class Directory(object): """ A directory on the camera. """ def __init__(self, name, parent, camera): self.name = name self.parent = parent self._file_ops = camera._abilities.file_operations self._dir_ops = camera._abilities.folder_operations self._cam = camera @property def path(self): """ Absolute path to the directory on the camera's filesystem. """ if self.parent is None: return "/" else: return os.path.join(self.parent.path, self.name) @property def supported_operations(self): """ All directory operations supported by the camera. """ return tuple(op for op in backend.DIR_OPS if self._dir_ops & op) @property def exists(self): """ Check whether the directory exists on the camera. """ if self.name in ("", "/") and self.parent is None: return True else: return self in self.parent.directories @property @exit_after def files(self): """ Get a generator that yields all files in the directory. """ filelist_p = new_gp_object("CameraList") lib.gp_camera_folder_list_files(self._cam._cam, self.path.encode(), filelist_p, self._cam._ctx) for idx in range(lib.gp_list_count(filelist_p)): fname = get_string(lib.gp_list_get_name, filelist_p, idx) yield File(name=fname, directory=self, camera=self._cam) lib.gp_list_free(filelist_p) @property @exit_after def directories(self): """ Get a generator that yields all subdirectories in the directory. """ dirlist_p = new_gp_object("CameraList") lib.gp_camera_folder_list_folders(self._cam._cam, self.path.encode(), dirlist_p, self._cam._ctx) for idx in range(lib.gp_list_count(dirlist_p)): name = os.path.join( self.path, get_string(lib.gp_list_get_name, dirlist_p, idx)) yield Directory(name=name, parent=self, camera=self._cam) lib.gp_list_free(dirlist_p) @exit_after def create(self): """ Create the directory. """ lib.gp_camera_folder_make_dir( self._cam._cam, self.parent.path.encode(), self.name.encode(), self._cam._ctx) @exit_after def remove(self): """ Remove the directory. """ lib.gp_camera_folder_remove_dir( self._cam._cam, self.parent.path.encode(), self.name.encode(), self._cam._ctx) @exit_after def upload(self, local_path): """ Upload a file to the camera's permanent storage. :param local_path: Path to file to copy :type local_path: str/unicode """ camerafile_p = ffi.new("CameraFile**") with open(local_path, 'rb') as fp: lib.gp_file_new_from_fd(camerafile_p, fp.fileno()) lib.gp_camera_folder_put_file( self._cam._cam, self.path.encode() + b"/", os.path.basename(local_path).encode(), backend.FILE_TYPES['normal'], camerafile_p[0], self._cam.ctx) def __eq__(self, other): return (self.name == other.name and self.parent == other.parent and self._cam == other._cam) def __repr__(self): return "Directory(\"{0}\")".format(self.path) class File(object): """ A file on the camera. """ def __init__(self, name, directory, camera): self.name = name self.directory = directory self._cam = camera self._operations = camera._abilities.file_operations self.__info = None @property def supported_operations(self): """ All file operations supported by the camera. """ return tuple(op for op in backend.FILE_OPS if self._operations & op) @property def size(self): """ File size in bytes. :rtype: int """ return int(self._info.file.size) @property def mimetype(self): """ MIME type of the file. :rtype: str """ return ffi.string(self._info.file.type).decode() @property def dimensions(self): """ Dimensions of the image. :rtype: :py:class:`ImageDimensions` """ return ImageDimensions(self._info.file.width, self._info.file.height) @property def permissions(self): """ Permissions of the file. Can be "r-" (read-only), "-w" (write-only), "rw" (read-write) or "--" (no rights). :rtype: str """ can_read = self._info.file.permissions & lib.GP_FILE_PERM_READ can_write = self._info.file.permissions & lib.GP_FILE_PERM_DELETE return "{0}{1}".format("r" if can_read else "-", "w" if can_write else "-") @property def last_modified(self): """ Date of last modification. :rtype: :py:class:`datetime.datetime` """ return datetime.fromtimestamp(self._info.file.mtime) @exit_after def save(self, target_path, ftype='normal'): """ Save file content to a local file. :param target_path: Path to save remote file as. :type target_path: str/unicode :param ftype: Select 'view' on file. :type ftype: str """ camfile_p = ffi.new("CameraFile**") with open(target_path, 'wb') as fp: lib.gp_file_new_from_fd(camfile_p, fp.fileno()) lib.gp_camera_file_get( self._cam._cam, self.directory.path.encode(), self.name.encode(), backend.FILE_TYPES[ftype], camfile_p[0], self._cam._ctx) @exit_after def get_data(self, ftype='normal'): """ Get file content as a bytestring. :param ftype: Select 'view' on file. :type ftype: str :return: File content :rtype: bytes """ camfile_p = ffi.new("CameraFile**") lib.gp_file_new(camfile_p) lib.gp_camera_file_get( self._cam._cam, self.directory.path.encode(), self.name.encode(), backend.FILE_TYPES[ftype], camfile_p[0], self._cam._ctx) data_p = ffi.new("char**") length_p = ffi.new("unsigned long*") lib.gp_file_get_data_and_size(camfile_p[0], data_p, length_p) return ffi.buffer(data_p[0], length_p[0])[:] @exit_after def iter_data(self, chunk_size=2**16, ftype='normal'): """ Get an iterator that yields chunks of the file content. :param chunk_size: Size of yielded chunks in bytes :type chunk_size: int :param ftype: Select 'view' on file. :type ftype: str :return: Iterator """ self._check_type_supported(ftype) buf_p = ffi.new("char[{0}]".format(chunk_size)) size_p = ffi.new("uint64_t*") offset_p = ffi.new("uint64_t*") for chunk_idx in range(int(math.ceil(self.size/chunk_size))): size_p[0] = chunk_size lib.gp_camera_file_read( self._cam._cam, self.directory.path.encode(), self.name.encode(), backend.FILE_TYPES[ftype], offset_p[0], buf_p, size_p, self._cam._ctx) yield ffi.buffer(buf_p, size_p[0])[:] @exit_after def remove(self): """ Remove file from device. """ lib.gp_camera_file_delete(self._cam._cam, self.directory.path.encode(), self.name.encode(), self._cam._ctx) @property def _info(self): if self.__info is None: self.__info = ffi.new("CameraFileInfo*") try: lib.gp_camera_file_get_info( self._cam._cam, self.directory.path.encode(), self.name.encode(), self.__info, self._cam._ctx) lib.gp_camera_exit(self._cam._cam, self._cam._ctx) except errors.GPhoto2Error: raise ValueError("Could not get file info, are you sure the " "file exists on the device?") return self.__info def __eq__(self, other): return (self.name == other.name and self.directory == other.directory and self._cam == other._cam) def __repr__(self): return "File(\"{0}/{1}\")".format(self.directory.path.rstrip("/"), self.name) class ConfigItem(object): """ A configuration option on the device. """ def __init__(self, widget, camera): self._widget = widget root_p = ffi.new("CameraWidget**") lib.gp_widget_get_root(self._widget, root_p) self._root = root_p[0] self._cam = camera #: Short name self.name = get_string(lib.gp_widget_get_name, widget) typenum = get_ctype("CameraWidgetType*", lib.gp_widget_get_type, widget) #: Type of option, can be one of `selection`, `text`, `range`, #: `toggle` or `date`. self.type = backend.WIDGET_TYPES[typenum] #: Human-readable label self.label = get_string(lib.gp_widget_get_label, widget) #: Information about the widget self.info = get_string(lib.gp_widget_get_info, widget) #: Current value self.value = None value_fn = lib.gp_widget_get_value if self.type in ('selection', 'text'): self.value = get_string(value_fn, widget) elif self.type == 'range': self.value = get_ctype("float*", value_fn, widget) #: Valid range for value, only present when :py:attr:`type` is #: `range`. self.range = self._read_range() elif self.type in ('toggle', 'date'): val = get_ctype("int*", value_fn, widget) if self.type == 'date': self.value = val else: self.value = None if val == 2 else bool(val) else: raise ValueError("Unsupported widget type for ConfigItem: {0}" .format(self.type)) if self.type == 'selection': #: Valid choices for value, only present when :py:attr:`type` #: is `selection`. self.choices = self._read_choices() #: Whether the value can be written to or not self.readonly = bool(get_ctype( "int*", lib.gp_widget_get_readonly, widget)) @exit_after def set(self, value): """ Update value of the option. Only possible for options with :py:attr:`readonly` set to `False`. If :py:attr:`type` is `choice`, the value must be one of the :py:attr:`choices`. If :py:attr:`type` is `range`, the value must be in the range described by :py:attr:`range`. :param value: Value to set """ if self.readonly: raise ValueError("Option is read-only.") val_p = None if self.type == 'selection': if value not in self.choices: raise ValueError("Invalid choice (valid: {0})".format( repr(self.choices))) val_p = ffi.new("const char[]", value.encode()) elif self.type == 'text': if not isinstance(value, basestring): raise ValueError("Value must be a string.") val_p = ffi.new("char**") val_p[0] = ffi.new("char[]", value.encode()) elif self.type == 'range': if value < self.range.min or value > self.range.max: raise ValueError("Value exceeds valid range ({0}-{1}." .format(self.range.min, self.range.max)) if value % self.range.step: raise ValueError("Value can only be changed in steps of {0}." .format(self.range.step)) val_p = ffi.new("float*") val_p[0] = value elif self.type == 'toggle': if not isinstance(value, bool): raise ValueError("Value must be bool.") val_p = ffi.new("int*") val_p[0] = int(value) elif self.type == 'date': val_p = ffi.new("int*") val_p[0] = value lib.gp_widget_set_value(self._widget, val_p) lib.gp_camera_set_config(self._cam._cam, self._root, self._cam._ctx) def _read_choices(self): if self.type != 'selection': raise ValueError("Can only read choices for items of type " "'selection'.") choices = [] for idx in range(lib.gp_widget_count_choices(self._widget)): choices.append(get_string(lib.gp_widget_get_choice, self._widget, idx)) return choices def _read_range(self): rmin = ffi.new("float*") rmax = ffi.new("float*") rinc = ffi.new("float*") lib.gp_widget_get_range(self._widget, rmin, rmax, rinc) return Range(rmin[0], rmax[0], rinc[0]) def __repr__(self): return ("ConfigItem('{0}', {1}, {2}, r{3})" .format(self.label, self.type, repr(self.value), "o" if self.readonly else "w")) class Camera(object): """ A camera device. The specific device can be auto-detected or set manually by specifying the USB bus and device number. :param bus: USB bus number :param device: USB device number :param lazy: Only initialize the device when needed """ def __init__(self, bus=None, device=None, lazy=False, _abilities=None): self._logger = logging.getLogger() # NOTE: It is not strictly neccessary to create a context for every # device, however it is significantly (>500ms) faster when # actions are to be performed simultaneously. self._ctx = lib.gp_context_new() self._usb_address = (bus, device) self.__abilities = _abilities self.__cam = None if not lazy: # Trigger the property self._cam @property def supported_operations(self): """ All operations supported by the camera. """ return tuple(op for op in backend.CAM_OPS if self._abilities.operations & op) @property def usb_info(self): """ The camera's USB information. """ return UsbInformation(self._abilities.usb_vendor, self._abilities.usb_product, self._abilities.usb_class, self._abilities.usb_subclass, self._abilities.usb_protocol) @property def model_name(self): """ Camera model name as specified in the gphoto2 driver. """ return ffi.string(self._abilities.model).decode() @property def config(self): """ Writeable configuration parameters. :rtype: dict """ config = self._get_config() return {section: {itm.name: itm for itm in config[section].values() if not itm.readonly} for section in config if 'settings' in section or section == 'other'} @property def status(self): """ Status information (read-only). :rtype: :py:class:`SimpleNamespace` """ config = self._get_config() is_hex = lambda name: (len(name) == 4 and all(c in string.hexdigits for c in name)) out = SimpleNamespace() for sect in config: for itm in config[sect].values(): if (itm.readonly or sect == 'status') and not is_hex(itm.name): setattr(out, itm.name, itm.value) return out @property def filesystem(self): """ The camera's root directory. """ return Directory(name="/", parent=None, camera=self) @property @exit_after def storage_info(self): """ Information about the camera's storage. """ info_p = ffi.new("CameraStorageInformation**") num_info_p = ffi.new("int*") lib.gp_camera_get_storageinfo(self._cam, info_p, num_info_p, self._ctx) infos = [] for idx in range(num_info_p[0]): out = SimpleNamespace() struc = (info_p[0] + idx) fields = struc.fields if lib.GP_STORAGEINFO_BASE & fields: out.directory = next( (d for d in self.list_all_directories() if d.path == ffi.string(struc.basedir).decode()), None) if lib.GP_STORAGEINFO_LABEL & fields: out.label = ffi.string(struc.label).decode() if lib.GP_STORAGEINFO_DESCRIPTION & fields: out.description = ffi.string(struc.description).decode() if lib.GP_STORAGEINFO_STORAGETYPE & fields: stype = struc.type if lib.GP_STORAGEINFO_ST_FIXED_ROM & stype: out.type = 'fixed_rom' elif lib.GP_STORAGEINFO_ST_REMOVABLE_ROM & stype: out.type = 'removable_rom' elif lib.GP_STORAGEINFO_ST_FIXED_RAM & stype: out.type = 'fixed_ram' elif lib.GP_STORAGEINFO_ST_REMOVABLE_RAM & stype: out.type = 'removable_ram' else: out.type = 'unknown' if lib.GP_STORAGEINFO_ACCESS & fields: if lib.GP_STORAGEINFO_AC_READWRITE & struc.access: out.access = 'read-write' elif lib.GP_STORAGEINFO_AC_READONLY & struc.access: out.access = 'read-only' elif lib.GP_STORAGEINFO_AC_READONLY_WITH_DELETE & struc.access: out.access = 'read-delete' if lib.GP_STORAGEINFO_MAXCAPACITY & fields: out.capacity = int(struc.capacitykbytes) if lib.GP_STORAGEINFO_FREESPACEKBYTES & fields: out.free_space = int(struc.freekbytes) if lib.GP_STORAGEINFO_FREESPACEIMAGES & fields: out.remaining_images = int(struc.freeimages) infos.append(out) return infos def list_all_files(self): """ Utility method that yields all files on the device's file systems. """ def list_files_recursively(directory): f_gen = itertools.chain( directory.files, *tuple(list_files_recursively(d) for d in directory.directories)) for f in f_gen: yield f return list_files_recursively(self.filesystem) def list_all_directories(self): """ Utility method that yields all directories on the device's file systems. """ def list_dirs_recursively(directory): if directory == self.filesystem: yield directory d_gen = itertools.chain( directory.directories, *tuple(list_dirs_recursively(d) for d in directory.directories)) for d in d_gen: yield d return list_dirs_recursively(self.filesystem) @exit_after def capture(self, to_camera_storage=False): """ Capture an image. Some cameras (mostly Canon and Nikon) support capturing to internal RAM. On these devices, you have to specify `to_camera_storage` if you want to save the images to the memory card. On devices that do not support saving to RAM, the only difference is that the file is automatically downloaded and deleted when set to `False`. :param to_camera_storage: Save image to the camera's internal storage :type to_camera_storage: bool :return: A :py:class:`File` if `to_camera_storage` was `True`, otherwise the captured image as a bytestring. :rtype: :py:class:`File` or bytes """ target = self.config['settings']['capturetarget'] if to_camera_storage and target.value != "Memory card": target.set("Memory card") elif not to_camera_storage and target.value != "Internal RAM": target.set("Internal RAM") lib.gp_camera_trigger_capture(self._cam, self._ctx) fobj = self._wait_for_event(event_type=lib.GP_EVENT_FILE_ADDED) if to_camera_storage: self._logger.info("File written to storage at {0}.".format(fobj)) return fobj else: data = fobj.get_data() try: fobj.remove() except errors.CameraIOError: # That probably means the file is already gone from RAM, # so nothing to worry about. pass return data def capture_video_context(self): """ Get a :py:class:`VideoCaptureContext` object. This allows the user to control when to stop the video capture. :rtype: :py:class:`VideoCaptureContext` """ return VideoCaptureContext(self) @exit_after def capture_video(self, length): """ Capture a video. This always writes to the memory card, since internal RAM is likely to run out of space very quickly. Currently this only works with Nikon cameras. :param length: Length of the video to capture in seconds. :type length: int :return: Video file :rtype: :py:class:`File` """ with self.capture_video_context() as ctx: time.sleep(length) return ctx.videofile @exit_after def get_preview(self): """ Get a preview from the camera's viewport. This will usually be a JPEG image with the dimensions depending on the camera. :return: The preview image as a bytestring :rtype: bytes """ camfile_p = ffi.new("CameraFile**") lib.gp_file_new(camfile_p) lib.gp_camera_capture_preview(self._cam, camfile_p[0], self._ctx) data_p = ffi.new("char**") length_p = ffi.new("unsigned long*") lib.gp_file_get_data_and_size(camfile_p[0], data_p, length_p) return ffi.buffer(data_p[0], length_p[0])[:] @property def _cam(self): if self.__cam is None: self.__cam = new_gp_object("Camera") if self._usb_address != (None, None): port_name = ("usb:{0:03},{1:03}".format(*self._usb_address) .encode()) port_list_p = new_gp_object("GPPortInfoList") lib.gp_port_info_list_load(port_list_p) port_info_p = ffi.new("GPPortInfo*") lib.gp_port_info_new(port_info_p) port_num = lib.gp_port_info_list_lookup_path( port_list_p, port_name) lib.gp_port_info_list_get_info(port_list_p, port_num, port_info_p) lib.gp_camera_set_port_info(self.__cam, port_info_p[0]) lib.gp_camera_init(self.__cam, self._ctx) else: try: lib.gp_camera_init(self.__cam, self._ctx) except errors.UnsupportedDevice as e: raise errors.UnsupportedDevice( e.error_code, "Could not find any supported devices.") return self.__cam @property def _abilities(self): if self.__abilities is None: self.__abilities = ffi.new("CameraAbilities*") lib.gp_camera_get_abilities(self._cam, self.__abilities) return self.__abilities def _wait_for_event(self, event_type=None, duration=0): if event_type is None and not duration: raise ValueError("Please specifiy either `event_type` or " "`duration!`") start_time = time.time() event_type_p = ffi.new("CameraEventType*") event_data_p = ffi.new("void**", ffi.NULL) while True: lib.gp_camera_wait_for_event(self._cam, 1000, event_type_p, event_data_p, self._ctx) if event_type_p[0] == lib.GP_EVENT_CAPTURE_COMPLETE: self._logger.info("Capture completed.") elif event_type_p[0] == lib.GP_EVENT_FILE_ADDED: self._logger.info("File added.") elif event_type_p[0] == lib.GP_EVENT_TIMEOUT: self._logger.debug("Timeout while waiting for event.") continue do_break = (event_type_p[0] == event_type or ((time.time() - start_time > duration) if duration else False)) if do_break: break if event_type == lib.GP_EVENT_FILE_ADDED: camfile_p = ffi.cast("CameraFilePath*", event_data_p[0]) dirname = ffi.string(camfile_p[0].folder).decode() directory = next(f for f in self.list_all_directories() if f.path == dirname) return File(name=ffi.string(camfile_p[0].name).decode(), directory=directory, camera=self) @exit_after def _get_config(self): def _widget_to_dict(cwidget): out = {} for idx in range(lib.gp_widget_count_children(cwidget)): child_p = ffi.new("CameraWidget**") lib.gp_widget_get_child(cwidget, idx, child_p) key = get_string(lib.gp_widget_get_name, child_p[0]) typenum = get_ctype("CameraWidgetType*", lib.gp_widget_get_type, child_p[0]) if typenum in (lib.GP_WIDGET_WINDOW, lib.GP_WIDGET_SECTION): out[key] = _widget_to_dict(child_p[0]) else: item = ConfigItem(child_p[0], self) out[key] = item return out root_widget = ffi.new("CameraWidget**") lib.gp_camera_get_config(self._cam, root_widget, self._ctx) return _widget_to_dict(root_widget[0]) def __repr__(self): return "".format( self.model_name, *self._usb_address) def __del__(self): if self.__cam is not None: lib.gp_camera_exit(self.__cam, self._ctx) lib.gp_camera_free(self.__cam) gphoto2-cffi-0.3~a1/doc/Makefile000664 001750 001750 00000015166 12656073631 020166 0ustar00aigariusaigarius000000 000000 # Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/chdkptppy.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/chdkptppy.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/chdkptppy" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/chdkptppy" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." gphoto2-cffi-0.3~a1/gphoto2cffi/util.py000664 001750 001750 00000004000 12656073631 021503 0ustar00aigariusaigarius000000 000000 from . import backend class SimpleNamespace(object): """ A simple :class:`object` subclass that provides attribute access to its namespace, as well as a meaningful repr. Unlike :class:`object`, with ``SimpleNamespace`` you can add and remove attributes. If a ``SimpleNamespace`` object is initialized with keyword arguments, those are directly added to the underlying namespace. This is a backport from Python 3.3. """ def __init__(self, **kwargs): self.__dict__.update(kwargs) def __repr__(self): keys = sorted(self.__dict__) items = ("{}={!r}".format(k, self.__dict__[k]) for k in keys) return "{}({})".format(type(self).__name__, ", ".join(items)) def __eq__(self, other): return self.__dict__ == other.__dict__ def get_string(cfunc, *args): """ Call a C function and return its return value as a Python string. :param cfunc: C function to call :param args: Arguments to call function with :rtype: str """ cstr = get_ctype("const char**", cfunc, *args) return backend.ffi.string(cstr).decode() if cstr else None def get_ctype(rtype, cfunc, *args): """ Call a C function that takes a pointer as its last argument and return the C object that it contains after the function has finished. :param rtype: C data type is filled by the function :param cfunc: C function to call :param args: Arguments to call function with :return: A pointer to the specified data type """ val_p = backend.ffi.new(rtype) args = args + (val_p,) cfunc(*args) return val_p[0] def new_gp_object(typename): """ Create an indirect pointer to a GPhoto2 type, call its matching constructor function and return the pointer to it. :param typename: Name of the type to create. :return: A pointer to the specified data type. """ obj_p = backend.ffi.new("{0}**".format(typename)) backend.CONSTRUCTORS[typename](obj_p) return obj_p[0] gphoto2-cffi-0.3~a1/gphoto2cffi/000775 001750 001750 00000000000 12656073631 020162 5ustar00aigariusaigarius000000 000000 gphoto2-cffi-0.3~a1/doc/requirements.txt000664 001750 001750 00000000007 12656073631 021776 0ustar00aigariusaigarius000000 000000 enum34 gphoto2-cffi-0.3~a1/README.rst000664 001750 001750 00000003600 12656073631 017436 0ustar00aigariusaigarius000000 000000 gphoto2-cffi ============ Python bindings for `libgphoto2`_ with an interface that strives to be idiomatic. In contrast to other bindings for Python, gphoto2-cffi hides most of the lower-level abstractions and reduces the API surface while still offering access to most of the library's features. .. code:: python import gphoto2 as gp # List all attached cameras that are supported cams = gp.list_cameras() # Get a camera instance by specifying a USB bus and device number my_cam = gp.Camera(bus=4, device=1) # Get an instance for the first supported camera my_cam = gp.Camera() # or my_cam = next(gp.list_cameras()) # Capture an image to the camera's RAM and get its data imgdata = my_cam.capture() # Grab a preview from the camera previewdata = my_cam.get_preview() # Get a list of files on the camera files = tuple(my_cam.list_all_files()) # Iterate over a file's content with open("image.jpg", "wb") as fp: for chunk in my_cam.files[0].iter_data(): fp.write(chunk) # Get a configuration value image_quality = my_cam.config['capturesettings']['imagequality'].value # Set a configuration value my_cam.config['capturesettings']['imagequality'].set("JPEG Fine") Currently only Python 2.7 and 3.4 (CPython and PyPy) are supported, however support for 2.6 and 3.3 is planned for the future. .. _libgphoto2: http://www.gphoto.org/proj/libgphoto2/ .. _PyPy: http://pypy.org/ .. _cffi: https://cffi.readthedocs.org/ Requirements ------------ * libgphoto2 with development headers * A working C compiler * cffi Installation ------------ From Source:: $ pip install git+https://github.com/jbaiter/gphoto2-cffi.git Similar projects ---------------- * `piggyphoto `_: Uses ctypes * `python-gphoto2 `_: Uses SWIG gphoto2-cffi-0.3~a1/gphoto2cffi/gphoto2.cdef000664 001750 001750 00000040653 12656073631 022377 0ustar00aigariusaigarius000000 000000 // vim: ft=c /* ======= Needed by all ======= */ typedef ... Camera; typedef ... CameraFile; /* ======= Errors ======== */ #define GP_ERROR_CORRUPTED_DATA ... #define GP_ERROR_FILE_EXISTS ... #define GP_ERROR_MODEL_NOT_FOUND ... #define GP_ERROR_DIRECTORY_NOT_FOUND ... #define GP_ERROR_FILE_NOT_FOUND ... #define GP_ERROR_DIRECTORY_EXISTS ... #define GP_ERROR_CAMERA_BUSY ... #define GP_ERROR_PATH_NOT_ABSOLUTE ... #define GP_ERROR_CANCEL ... #define GP_ERROR_CAMERA_ERROR ... #define GP_ERROR_OS_FAILURE ... #define GP_ERROR_NO_SPACE ... /* ====== Context ====== */ typedef long time_t; // Dangerous, might not be portable typedef ... GPContext; GPContext* gp_context_new (void); const char* gp_result_as_string (int result); /* ======= Port Info ======= */ typedef enum { GP_PORT_NONE = 0, GP_PORT_SERIAL = 1, GP_PORT_USB = 4, GP_PORT_DISK = 8, GP_PORT_PTPIP = 16, GP_PORT_USB_DISK_DIRECT = 32, GP_PORT_USB_SCSI = 64 } GPPortType; struct _GPPortInfo; typedef struct _GPPortInfo *GPPortInfo; typedef ... GPPortInfoList; int gp_port_info_new (GPPortInfo* info); int gp_port_info_get_name (GPPortInfo info, char** name); int gp_port_info_set_name (GPPortInfo info, const char* name); int gp_port_info_get_path (GPPortInfo info, char** path); int gp_port_info_set_path (GPPortInfo info, const char* path); int gp_port_info_get_type (GPPortInfo info, GPPortType* type); int gp_port_info_set_type (GPPortInfo info, const GPPortType type); int gp_port_info_list_new (GPPortInfoList** list); int gp_port_info_list_free (GPPortInfoList* list); int gp_port_info_list_append (GPPortInfoList* list, GPPortInfo info); int gp_port_info_list_load (GPPortInfoList* list); int gp_port_info_list_count (GPPortInfoList* list); int gp_port_info_list_lookup_path (GPPortInfoList* list, const char* path); int gp_port_info_list_lookup_name (GPPortInfoList* list, const char* name); int gp_port_info_list_get_info (GPPortInfoList* list, int n, GPPortInfo* info); /* ====== Lists ====== */ typedef ... CameraList; int gp_list_new (CameraList** list); int gp_list_free (CameraList* list); int gp_list_count (CameraList* list); int gp_list_append (CameraList* list, const char* name, const char* value); int gp_list_reset (CameraList* list); int gp_list_sort (CameraList* list); int gp_list_get_name (CameraList* list, int index, const char** name); int gp_list_get_value (CameraList* list, int index, const char** value); int gp_list_set_name (CameraList* list, int index, const char* name); int gp_list_set_value (CameraList* list, int index, const char* value); int gp_list_populate (CameraList* list, const char* format, int count); /* ===== Abilities ====== */ typedef enum { GP_DRIVER_STATUS_PRODUCTION, GP_DRIVER_STATUS_TESTING, GP_DRIVER_STATUS_EXPERIMENTAL, GP_DRIVER_STATUS_DEPRECATED } CameraDriverStatus; typedef enum { GP_DEVICE_STILL_CAMERA = 0, GP_DEVICE_AUDIO_PLAYER = 1 } GphotoDeviceType; typedef enum { GP_OPERATION_NONE = 0, GP_OPERATION_CAPTURE_IMAGE = 1, GP_OPERATION_CAPTURE_VIDEO = 2, GP_OPERATION_CAPTURE_AUDIO = 4, GP_OPERATION_CAPTURE_PREVIEW = 8, GP_OPERATION_CONFIG = 16, GP_OPERATION_TRIGGER_CAPTURE = 32 } CameraOperation; typedef enum { GP_FILE_OPERATION_NONE = 0, GP_FILE_OPERATION_DELETE = 2, GP_FILE_OPERATION_PREVIEW = 8, GP_FILE_OPERATION_RAW = 16, GP_FILE_OPERATION_AUDIO = 32, GP_FILE_OPERATION_EXIF = 64 } CameraFileOperation; typedef enum { GP_FOLDER_OPERATION_NONE = 0, GP_FOLDER_OPERATION_DELETE_ALL = 1, GP_FOLDER_OPERATION_PUT_FILE = 2, GP_FOLDER_OPERATION_MAKE_DIR = 4, GP_FOLDER_OPERATION_REMOVE_DIR = 8 } CameraFolderOperation; typedef struct { char model [128]; CameraDriverStatus status; GPPortType port; int speed[64]; CameraOperation operations; CameraFileOperation file_operations; CameraFolderOperation folder_operations; int usb_vendor; int usb_product; int usb_class; int usb_subclass; int usb_protocol; char library [1024]; char id [1024]; GphotoDeviceType device_type; ...; } CameraAbilities; typedef ... CameraAbilitiesList; int gp_abilities_list_new (CameraAbilitiesList** list); int gp_abilities_list_free (CameraAbilitiesList* list); int gp_abilities_list_load (CameraAbilitiesList* list, GPContext* context); int gp_abilities_list_detect (CameraAbilitiesList* list, GPPortInfoList* info_list, CameraList* l, GPContext* context); int gp_abilities_list_count (CameraAbilitiesList* list); int gp_abilities_list_get_abilities (CameraAbilitiesList* list, int index, CameraAbilities* abilities); int gp_abilities_list_lookup_model (CameraAbilitiesList* list, const char* model); /* ===== Widgets ===== */ typedef enum { GP_WIDGET_WINDOW, GP_WIDGET_SECTION, GP_WIDGET_TEXT, GP_WIDGET_RANGE, GP_WIDGET_TOGGLE, GP_WIDGET_RADIO, GP_WIDGET_MENU, GP_WIDGET_BUTTON, GP_WIDGET_DATE } CameraWidgetType; typedef ... CameraWidget; typedef int (* CameraWidgetCallback) (Camera*, CameraWidget*, GPContext*); int gp_widget_new (CameraWidgetType type, const char* label, CameraWidget** widget); int gp_widget_free (CameraWidget* widget); int gp_widget_append (CameraWidget* widget, CameraWidget* child); int gp_widget_prepend (CameraWidget* widget, CameraWidget* child); int gp_widget_get_child (CameraWidget* widget, int child_number, CameraWidget** child); int gp_widget_get_child_by_label (CameraWidget* widget, const char* label, CameraWidget** child); int gp_widget_get_child_by_id (CameraWidget* widget, int id, CameraWidget** child); int gp_widget_get_child_by_name (CameraWidget* widget, const char* name, CameraWidget** child); int gp_widget_count_children (CameraWidget* widget); int gp_widget_get_root (CameraWidget* widget, CameraWidget**root); int gp_widget_get_parent (CameraWidget* widget, CameraWidget**parent); int gp_widget_set_value (CameraWidget* widget, const void* value); int gp_widget_get_value (CameraWidget* widget, void* value); int gp_widget_set_name (CameraWidget* widget, const char* name); int gp_widget_get_name (CameraWidget* widget, const char** name); int gp_widget_set_info (CameraWidget* widget, const char* info); int gp_widget_get_info (CameraWidget* widget, const char** info); int gp_widget_get_id (CameraWidget* widget, int* id); int gp_widget_get_type (CameraWidget* widget, CameraWidgetType* type); int gp_widget_get_label (CameraWidget* widget, const char** label); int gp_widget_set_range (CameraWidget* range, float low, float high, float increment); int gp_widget_get_range (CameraWidget* range, float* min, float* max, float* increment); int gp_widget_add_choice (CameraWidget* widget, const char* choice); int gp_widget_count_choices (CameraWidget* widget); int gp_widget_get_choice (CameraWidget* widget, int choice_number, const char** choice); int gp_widget_changed (CameraWidget* widget); int gp_widget_set_changed (CameraWidget* widget, int changed); int gp_widget_set_readonly (CameraWidget* widget, int readonly); int gp_widget_get_readonly (CameraWidget* widget, int* readonly); /* ===== Filesystem ===== */ typedef enum { GP_FILE_INFO_NONE = 0, GP_FILE_INFO_TYPE = 1, GP_FILE_INFO_SIZE = 4, GP_FILE_INFO_WIDTH = 8, GP_FILE_INFO_HEIGHT = 16, GP_FILE_INFO_PERMISSIONS = 32, GP_FILE_INFO_STATUS = 64, GP_FILE_INFO_MTIME = 128, GP_FILE_INFO_ALL = 0xFF } CameraFileInfoFields; typedef enum { GP_FILE_PERM_NONE = 0, GP_FILE_PERM_READ = 1, GP_FILE_PERM_DELETE = 2, GP_FILE_PERM_ALL = 0xFF } CameraFilePermissions; typedef enum { GP_FILE_STATUS_NOT_DOWNLOADED, GP_FILE_STATUS_DOWNLOADED } CameraFileStatus; typedef struct _CameraFileInfoFile { CameraFileInfoFields fields; CameraFileStatus status; uint64_t size; char type[64]; uint32_t width; uint32_t height; CameraFilePermissions permissions; time_t mtime; } CameraFileInfoFile; typedef struct _CameraFileInfoPreview { CameraFileInfoFields fields; CameraFileStatus status; uint64_t size; char type[64]; uint32_t width; uint32_t height; } CameraFileInfoPreview; typedef struct _CameraFileInfoAudio { CameraFileInfoFields fields; CameraFileStatus status; uint64_t size; char type[64]; } CameraFileInfoAudio; typedef struct _CameraFileInfo { CameraFileInfoPreview preview; CameraFileInfoFile file; CameraFileInfoAudio audio; } CameraFileInfo; typedef enum { GP_STORAGEINFO_BASE = 1, GP_STORAGEINFO_LABEL = 2, GP_STORAGEINFO_DESCRIPTION = 4, GP_STORAGEINFO_ACCESS = 8, GP_STORAGEINFO_STORAGETYPE = 16, GP_STORAGEINFO_FILESYSTEMTYPE = 32, GP_STORAGEINFO_MAXCAPACITY = 64, GP_STORAGEINFO_FREESPACEKBYTES = 128, GP_STORAGEINFO_FREESPACEIMAGES = 256 } CameraStorageInfoFields; typedef enum { GP_STORAGEINFO_ST_UNKNOWN = 0, GP_STORAGEINFO_ST_FIXED_ROM = 1, GP_STORAGEINFO_ST_REMOVABLE_ROM = 2, GP_STORAGEINFO_ST_FIXED_RAM = 3, GP_STORAGEINFO_ST_REMOVABLE_RAM = 4 } CameraStorageType; typedef enum { GP_STORAGEINFO_AC_READWRITE = 0, GP_STORAGEINFO_AC_READONLY = 1, GP_STORAGEINFO_AC_READONLY_WITH_DELETE = 2 } CameraStorageAccessType; typedef enum { GP_STORAGEINFO_FST_UNDEFINED = 0, GP_STORAGEINFO_FST_GENERICFLAT = 1, GP_STORAGEINFO_FST_GENERICHIERARCHICAL = 2, GP_STORAGEINFO_FST_DCF = 3 } CameraStorageFilesystemType; typedef struct _CameraStorageInformation { CameraStorageInfoFields fields; char basedir[256]; char label[256]; char description[256]; CameraStorageType type; CameraStorageFilesystemType fstype; CameraStorageAccessType access; uint64_t capacitykbytes; uint64_t freekbytes; uint64_t freeimages; } CameraStorageInformation; /* ====== Files ====== */ typedef enum { GP_FILE_TYPE_PREVIEW, GP_FILE_TYPE_NORMAL, GP_FILE_TYPE_RAW, GP_FILE_TYPE_AUDIO, GP_FILE_TYPE_EXIF, GP_FILE_TYPE_METADATA } CameraFileType; typedef enum { GP_FILE_ACCESSTYPE_MEMORY, GP_FILE_ACCESSTYPE_FD, GP_FILE_ACCESSTYPE_HANDLER } CameraFileAccessType; int gp_file_new (CameraFile** file); int gp_file_new_from_fd (CameraFile** file, int fd); int gp_file_free (CameraFile* file); int gp_file_set_name (CameraFile* file, const char* name); int gp_file_get_name (CameraFile* file, const char** name); int gp_file_set_mime_type (CameraFile* file, const char* mime_type); int gp_file_get_mime_type (CameraFile* file, const char** mime_type); int gp_file_get_mtime (CameraFile* file, time_t* mtime); int gp_file_detect_mime_type (CameraFile* file); int gp_file_adjust_name_for_mime_type (CameraFile* file); int gp_file_get_name_by_type (CameraFile* file, const char* basename, CameraFileType type, char** newname); int gp_file_set_data_and_size (CameraFile*, char* data, unsigned long int size); int gp_file_get_data_and_size (CameraFile*, const char** data, unsigned long int* size); /* ======= Camera ======= */ typedef struct { char text [32768]; } CameraText; typedef struct { char name [128]; char folder [1024]; } CameraFilePath; typedef enum { GP_CAPTURE_IMAGE, GP_CAPTURE_MOVIE, GP_CAPTURE_SOUND } CameraCaptureType; typedef enum { GP_EVENT_UNKNOWN, GP_EVENT_TIMEOUT, GP_EVENT_FILE_ADDED, GP_EVENT_FOLDER_ADDED, GP_EVENT_CAPTURE_COMPLETE } CameraEventType; int gp_camera_new (Camera** camera); int gp_camera_get_abilities (Camera* camera, CameraAbilities* abilities); int gp_camera_autodetect (CameraList* list, GPContext* context); int gp_camera_init (Camera* camera, GPContext* context); int gp_camera_exit (Camera* camera, GPContext* context); int gp_camera_ref (Camera* camera); int gp_camera_unref (Camera* camera); int gp_camera_free (Camera* camera); int gp_camera_set_port_info (Camera* camera, GPPortInfo info); int gp_camera_get_config (Camera* camera, CameraWidget** window, GPContext* context); int gp_camera_set_config (Camera* camera, CameraWidget* window, GPContext* context); int gp_camera_get_summary (Camera* camera, CameraText* summary, GPContext* context); int gp_camera_get_manual (Camera* camera, CameraText* manual, GPContext* context); int gp_camera_get_about (Camera* camera, CameraText* about, GPContext* context); int gp_camera_capture (Camera* camera, CameraCaptureType type, CameraFilePath* path, GPContext* context); int gp_camera_trigger_capture (Camera* camera, GPContext* context); int gp_camera_capture_preview (Camera* camera, CameraFile* file, GPContext* context); int gp_camera_wait_for_event (Camera* camera, int timeout, CameraEventType* eventtype, void** eventdata, GPContext* context); int gp_camera_get_storageinfo (Camera* camera, CameraStorageInformation**, int*, GPContext* context); int gp_camera_folder_list_files (Camera* camera, const char* folder, CameraList* list, GPContext* context); int gp_camera_folder_list_folders (Camera* camera, const char* folder, CameraList* list, GPContext* context); int gp_camera_folder_delete_all (Camera* camera, const char* folder, GPContext* context); int gp_camera_folder_put_file (Camera* camera, const char* folder, const char* filename, CameraFileType type, CameraFile* file, GPContext* context); int gp_camera_folder_make_dir (Camera* camera, const char* folder, const char* name, GPContext* context); int gp_camera_folder_remove_dir (Camera* camera, const char* folder, const char* name, GPContext* context); int gp_camera_file_get_info (Camera* camera, const char* folder, const char* file, CameraFileInfo* info, GPContext* context); int gp_camera_file_set_info (Camera* camera, const char* folder, const char* file, CameraFileInfo info, GPContext* context); int gp_camera_file_get (Camera* camera, const char* folder, const char* file, CameraFileType type, CameraFile* camera_file, GPContext* context); int gp_camera_file_read (Camera* camera, const char* folder, const char* file, CameraFileType type, uint64_t offset, char* buf, uint64_t* size, GPContext* context); int gp_camera_file_delete (Camera* camera, const char* folder, const char* file, GPContext* context); /* ====== Logging ====== */ typedef enum { GP_LOG_ERROR = 0, GP_LOG_VERBOSE = 1, GP_LOG_DEBUG = 2, GP_LOG_DATA = 3 } GPLogLevel; typedef void (*GPLogFunc)(GPLogLevel level, const char* domain, const char* str, void *data); int gp_log_add_func (GPLogLevel level, GPLogFunc func, void *data); /* ====== Other ====== */ const char ** gp_library_version(int verbose); gphoto2-cffi-0.3~a1/doc/conf.py000664 001750 001750 00000003062 12656073631 020015 0ustar00aigariusaigarius000000 000000 # -*- coding: utf-8 -*- import sys sys.path.insert(0, '../') class Mock(object): def __init__(self, *args, **kwargs): pass def __call__(self, *args, **kwargs): return Mock() @classmethod def __getattr__(cls, name): print "Getting mock: ", name if name in ('__file__', '__path__'): return '/dev/null' # Special case for CFFI elif name in ('FFI', 'Verifier'): return Mock() elif name[0] == name[0].upper(): mockType = type(name, (), {}) mockType.__module__ = __name__ return mockType else: return Mock() MOCK_MODULES = ['cffi', 'cffi.verifier'] for mod_name in MOCK_MODULES: sys.modules[mod_name] = Mock() extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] templates_path = ['_templates'] source_suffix = '.rst' master_doc = 'index' project = u'gphoto2-cffi' copyright = u'2014, Johannes Baiter' version = '0.1' release = '0.1' exclude_patterns = ['_build'] pygments_style = 'sphinx' html_static_path = ['_static'] htmlhelp_basename = 'gphoto2-cffidoc' latex_elements = { 'preamble': '', } latex_documents = [ ('index', 'gphoto2-cffi.tex', u'gphoto2-cffi Documentation', u'Johannes Baiter', 'manual'), ] man_pages = [ ('index', 'gphoto2-cffi', u'gphoto2-cffi Documentation', [u'Johannes Baiter'], 1) ] texinfo_documents = [ ('index', 'gphoto2-cffi', u'gphoto2-cffi Documentation', u'Johannes Baiter', 'gphoto2-cffi', 'One line description of project.', 'Miscellaneous'), ] gphoto2-cffi-0.3~a1/gphoto2cffi/backend.py000664 001750 001750 00000010173 12656073631 022125 0ustar00aigariusaigarius000000 000000 import logging from enum import IntEnum from . import errors from _backend import ffi, lib as _lib #: Root logger that all other libgphoto2 loggers are children of LOGGER = logging.getLogger("libgphoto2") #: Mapping from libgphoto2 file type constants to human-readable strings. FILE_TYPES = { 'normal': _lib.GP_FILE_TYPE_NORMAL, 'exif': _lib.GP_FILE_TYPE_EXIF, 'metadata': _lib.GP_FILE_TYPE_METADATA, 'preview': _lib.GP_FILE_TYPE_PREVIEW, 'raw': _lib.GP_FILE_TYPE_RAW, 'audio': _lib.GP_FILE_TYPE_AUDIO} #: Mapping from libgphoto2 types to their appropriate constructor functions. CONSTRUCTORS = { "Camera": _lib.gp_camera_new, "GPPortInfo": _lib.gp_port_info_new, "CameraList": _lib.gp_list_new, "CameraAbilitiesList": _lib.gp_abilities_list_new, "GPPortInfoList": _lib.gp_port_info_list_new} #: Mapping from libgphoto2 widget type constants to human-readable strings WIDGET_TYPES = { _lib.GP_WIDGET_MENU: "selection", _lib.GP_WIDGET_RADIO: "selection", _lib.GP_WIDGET_TEXT: "text", _lib.GP_WIDGET_RANGE: "range", _lib.GP_WIDGET_DATE: "date", _lib.GP_WIDGET_TOGGLE: "toggle", _lib.GP_WIDGET_WINDOW: "window", _lib.GP_WIDGET_SECTION: "section"} #: Mapping from libgphoto2 logging levels to Python logging levels. LOG_LEVELS = { _lib.GP_LOG_ERROR: logging.ERROR, _lib.GP_LOG_VERBOSE: logging.INFO, _lib.GP_LOG_DEBUG: logging.DEBUG} FILE_OPS = IntEnum(b'FileOperations', { 'remove': _lib.GP_FILE_OPERATION_DELETE, 'extract_preview': _lib.GP_FILE_OPERATION_PREVIEW, 'extract_raw': _lib.GP_FILE_OPERATION_RAW, 'extract_audio': _lib.GP_FILE_OPERATION_AUDIO, 'extract_exif': _lib.GP_FILE_OPERATION_EXIF}) CAM_OPS = IntEnum(b'CameraOperations', { 'capture_image': _lib.GP_OPERATION_CAPTURE_IMAGE, 'capture_video': _lib.GP_OPERATION_CAPTURE_VIDEO, 'capture_audio': _lib.GP_OPERATION_CAPTURE_AUDIO, 'capture_preview': _lib.GP_OPERATION_CAPTURE_PREVIEW, 'update_config': _lib.GP_OPERATION_CONFIG, 'trigger_capture': _lib.GP_OPERATION_TRIGGER_CAPTURE}) DIR_OPS = IntEnum(b'DirectoryOperations', { 'remove': _lib.GP_FOLDER_OPERATION_REMOVE_DIR, 'create': _lib.GP_FOLDER_OPERATION_MAKE_DIR, 'delete_all': _lib.GP_FOLDER_OPERATION_DELETE_ALL, 'upload': _lib.GP_FOLDER_OPERATION_PUT_FILE}) def _logging_callback(level, domain, message, data): """ Callback that outputs libgphoto2's logging message via Python's standard logging facilities. :param level: libgphoto2 logging level :param domain: component the message originates from :param message: logging message :param data: Other data in the logging record (unused) """ domain = ffi.string(domain).decode() message = ffi.string(message).decode() logger = LOGGER.getChild(domain) if level not in LOG_LEVELS: return logger.log(LOG_LEVELS[level], message) class LibraryWrapper(object): NO_ERROR_CHECK = ( "gp_log_add_func", "gp_context_new", "gp_list_count", "gp_result_as_string", "gp_library_version",) def __init__(self, to_wrap): """ Wrapper around our FFI object that performs error checking. Wraps functions inside an anonymous function that checks the inner function's return code for libgphoto2 errors and throws a :py:class:`gphoto2.errors.GPhoto2Error` if needed. :param to_wrap: FFI library to wrap """ self._lib = to_wrap @staticmethod def _check_error(rval): """ Check a return value for a libgphoto2 error. """ if rval < 0: raise errors.error_from_code(rval) else: return rval def __getattr__(self, name): val = getattr(self._lib, name) if not isinstance(val, int) and name not in self.NO_ERROR_CHECK: return lambda *a, **kw: self._check_error(val(*a, **kw)) else: return val # Register logging callback with FFI logging_cb = ffi.callback( "void(GPLogLevel, const char*, const char*, void*)", _logging_callback) #: The wrapped library lib = LibraryWrapper(_lib)