Skip to content
This repository has been archived by the owner on Oct 4, 2020. It is now read-only.

Commit

Permalink
Merge eb8ac0d into e2530c7
Browse files Browse the repository at this point in the history
  • Loading branch information
SamWhited committed Jun 7, 2015
2 parents e2530c7 + eb8ac0d commit 439e4d8
Show file tree
Hide file tree
Showing 10 changed files with 191 additions and 87 deletions.
12 changes: 0 additions & 12 deletions libraw/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +0,0 @@
import libraw.bindings

libraw = libraw.bindings.libraw
"""
A handle to the LibRaw binary installed on the end users machine.
This should always be imported first by anything that wants to use `libraw`:
.. sourcecode:: python
from libraw import libraw
"""
99 changes: 89 additions & 10 deletions libraw/bindings.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,94 @@
from ctypes import * # noqa
from ctypes import util

from libraw import errors
from libraw.structs import libraw_data_t, libraw_processed_image_t

# TODO: This is necessary because Travis CI is still using Ubuntu 12.04
try:
# TODO: This will do bad things if the API version isn't 10
libraw = cdll.LoadLibrary(util.find_library('raw')) # pragma: no cover
libraw.libraw_init.restype = POINTER(libraw_data_t) # pragma: no cover
libraw.libraw_dcraw_make_mem_image.restype = POINTER( # pragme: no cover
libraw_processed_image_t # pragma: no cover
) # pragma: no cover
except: # pragma: no cover
libraw = cdll.LoadLibrary('') # pragma: no cover

class LibRaw(CDLL):

"""
A :class:`ctypes.CDLL` that links against `libraw.so` (or the equivalent on
your platform).
"""

@staticmethod
def check_call(exit_code, func, arguments):
"""
Throws a Python error which corresponds to the given LibRaw exit code.
:param exit_code: the exit code returned by a LibRaw function
:type exit_code: :class:`int`
:returns: Returns :param:`exit_code` or throws an error from
:class:`libraw.errors`
:rtype: :class:`type(exit_code)`
"""

if isinstance(exit_code, int) and exit_code is not 0:
raise {
-1: errors.UnspecifiedError,
-2: errors.FileUnsupported,
-3: errors.RequestForNonexistentImage,
-4: errors.OutOfOrderCall,
-5: errors.NoThumbnail,
-6: errors.UnsupportedThumbnail,
-7: errors.InputClosed,
-100007: errors.InsufficientMemory,
-100008: errors.DataError,
-100009: errors.IOError,
-100010: errors.CancelledByCallback,
-100011: errors.BadCrop
}[exit_code]

return exit_code

def __init__(self): # pragma: no cover
# TODO: This hack is required because Travis doesn't have libraw10
try:
super(LibRaw, self).__init__(util.find_library('raw'))
self.libraw_init.restype = POINTER(libraw_data_t)
self.libraw_dcraw_make_mem_image.restype = POINTER(
libraw_processed_image_t
)
self.libraw_version.restype = c_char_p
except:
super(LibRaw, self).__init__(util.find_library(''))

@property
def version_number(self):
"""
A numeric representation of the version of LibRaw which we have linked
against. eg. ::
(0, 16, 1)
:returns: The version number
:rtype: :class:`3 tuple`
"""
v = self.libraw_versionNumber()
return ((v >> 16) & 0x0000ff, (v >> 8) & 0x0000ff, v & 0x0000ff)

@property
def version(self):
"""
A string representation of the version of LibRaw which we have linked
against. eg. ::
"0.16.1-Release"
:returns: The version
:rtype: :class:`basestring`
"""
return self.libraw_version().decode('utf-8')

def __getitem__(self, name):
if name.startswith('libraw_'):
func = super(LibRaw, self).__getitem__(name)

errexcludes = ('libraw_cameraCount', 'libraw_versionNumber')

if name not in errexcludes:
func.errcheck = LibRaw.check_call

# return func
return func
25 changes: 0 additions & 25 deletions libraw/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,28 +88,3 @@ class BadCrop(Exception):
The cropping coordinates specified are invalid (eg. the top left corner of
the cropping rectangle is outside the image).
"""


def check_call(exit_code):
"""
Throws a Python error which corresponds to the given LibRaw exit code.
:param exit_code: the exit code returned by a LibRaw function
:type exit_code: :class:`int`
"""

if exit_code is not 0:
raise {
-1: UnspecifiedError,
-2: FileUnsupported,
-3: RequestForNonexistentImage,
-4: OutOfOrderCall,
-5: NoThumbnail,
-6: UnsupportedThumbnail,
-7: InputClosed,
-100007: InsufficientMemory,
-100008: DataError,
-100009: IOError,
-100010: CancelledByCallback,
-100011: BadCrop
}[exit_code]
7 changes: 7 additions & 0 deletions rawkit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,10 @@
"""
The current version of the `rawkit` package.
"""

from libraw.bindings import LibRaw

_libraw = LibRaw()
"""
A handle to the LibRaw binary installed on the end users machine.
"""
34 changes: 15 additions & 19 deletions rawkit/raw.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@

import ctypes

from libraw import libraw
from libraw import errors as e

from rawkit import _libraw
from rawkit.metadata import Metadata
from rawkit.options import Options

Expand Down Expand Up @@ -35,10 +33,8 @@ class Raw(object):

def __init__(self, filename=None):
"""Initializes a new Raw object."""
self.data = libraw.libraw_init(0)
e.check_call(
libraw.libraw_open_file(self.data, filename.encode('ascii'))
)
self.data = _libraw.libraw_init(0)
_libraw.libraw_open_file(self.data, filename.encode('ascii'))

self.options = Options()

Expand All @@ -55,24 +51,24 @@ def __exit__(self, exc_type, exc_value, traceback):

def close(self):
"""Free the underlying raw representation."""
e.check_call(libraw.libraw_close(self.data))
_libraw.libraw_close(self.data)

def unpack(self):
"""Unpack the raw data."""
if not self.image_unpacked:
e.check_call(libraw.libraw_unpack(self.data))
_libraw.libraw_unpack(self.data)
self.image_unpacked = True

def unpack_thumb(self):
"""Unpack the thumbnail data."""
if not self.thumb_unpacked:
e.check_call(libraw.libraw_unpack_thumb(self.data))
_libraw.libraw_unpack_thumb(self.data)
self.thumb_unpacked = True

def process(self):
"""Process the raw data based on self.options"""
self.options._map_to_libraw_params(self.data.contents.params)
e.check_call(libraw.libraw_dcraw_process(self.data))
_libraw.libraw_dcraw_process(self.data)

def save(self, filename=None, filetype='ppm'):
"""
Expand All @@ -89,8 +85,8 @@ def save(self, filename=None, filetype='ppm'):
self.unpack()
self.process()

e.check_call(libraw.libraw_dcraw_ppm_tiff_writer(
self.data, filename.encode('ascii')))
_libraw.libraw_dcraw_ppm_tiff_writer(
self.data, filename.encode('ascii'))

def save_thumb(self, filename=None):
"""
Expand All @@ -101,8 +97,8 @@ def save_thumb(self, filename=None):
"""
self.unpack_thumb()

e.check_call(libraw.libraw_dcraw_thumb_writer(
self.data, filename.encode('ascii')))
_libraw.libraw_dcraw_thumb_writer(
self.data, filename.encode('ascii'))

def to_buffer(self):
"""
Expand All @@ -114,13 +110,13 @@ def to_buffer(self):
self.unpack()
self.process()

processed_image = libraw.libraw_dcraw_make_mem_image(self.data)
processed_image = _libraw.libraw_dcraw_make_mem_image(self.data)
data_pointer = ctypes.cast(
processed_image.contents.data,
ctypes.POINTER(ctypes.c_byte * processed_image.contents.data_size)
)
data = bytearray(data_pointer.contents)
e.check_call(libraw.libraw_dcraw_clear_mem(processed_image))
_libraw.libraw_dcraw_clear_mem(processed_image)

return data

Expand All @@ -133,13 +129,13 @@ def thumbnail_to_buffer(self):
"""
self.unpack_thumb()

processed_image = libraw.libraw_dcraw_make_mem_thumb(self.data)
processed_image = _libraw.libraw_dcraw_make_mem_thumb(self.data)
data_pointer = ctypes.cast(
processed_image.contents.data,
ctypes.POINTER(ctypes.c_byte * processed_image.contents.data_size)
)
data = bytearray(data_pointer.contents)
e.check_call(libraw.libraw_dcraw_clear_mem(processed_image))
_libraw.libraw_dcraw_clear_mem(processed_image)

return data

Expand Down
17 changes: 9 additions & 8 deletions rawkit/util.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import ctypes
import os

from libraw import libraw
from rawkit import _libraw


def discover(path):
Expand All @@ -11,16 +11,16 @@ def discover(path):
:type path: :class:`basestring`
"""
file_list = []
raw = libraw.libraw_init(0)
raw = _libraw.libraw_init(0)

for root, _, files in os.walk(path):
for file_name in files:
file_path = os.path.join(root, file_name).encode('ascii')
if libraw.libraw_open_file(raw, file_path) == 0:
if _libraw.libraw_open_file(raw, file_path) == 0:
file_list.append(file_path)
libraw.libraw_recycle(raw)
_libraw.libraw_recycle(raw)

libraw.libraw_close(raw)
_libraw.libraw_close(raw)
return file_list


Expand All @@ -32,8 +32,9 @@ def camera_list():
:returns: A list of supported cameras
:rtype: :class:`basestring tuple`
"""
libraw.libraw_cameraList.restype = ctypes.POINTER(
ctypes.c_char_p * libraw.libraw_cameraCount()

_libraw.libraw_cameraList.restype = ctypes.POINTER(
ctypes.c_char_p * _libraw.libraw_cameraCount()
)
data_pointer = libraw.libraw_cameraList()
data_pointer = _libraw.libraw_cameraList()
return [x.decode('ascii') for x in data_pointer.contents]
64 changes: 64 additions & 0 deletions tests/unit/bindings_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import mock
import pytest

from libraw.bindings import LibRaw


@pytest.fixture
def libraw():
return LibRaw()


def test_libraw_sets_method_name(libraw):
"""
Ensure that the method name is set when calling a LibRaw function.
"""

libraw._FuncPtr = mock.Mock()
libraw.libraw_init()

assert libraw.libraw_init.__name__ == 'libraw_init'


def test_error_checking(libraw):
"""
Check that libraw methods are assigned an error checker (unless they're on
the white list).
"""

libraw._FuncPtr = mock.Mock()

libraw.libraw_cameraCount()
assert libraw.libraw_cameraCount.errcheck != LibRaw.check_call

libraw.libraw_something()
assert libraw.libraw_something.errcheck == LibRaw.check_call


def test_version_number_calculation(libraw):
"""
Check that the version tuple is calculated correctly.
"""

libraw.libraw_versionNumber = lambda: 4097
assert libraw.version_number == (0, 16, 1)


def test_version(libraw):
"""
Check that the version method actually calls `LibRaw::version()`.
"""

libraw.libraw_version = mock.Mock()
libraw.version
libraw.libraw_version.assert_called_once_with()


def test_get_non_libraw_method(libraw):
"""
Tets getting a method from an instance of `LibRaw` that does not exist in
the DLL (eg. that does not start with `libraw_`) and is not an instance
method. Expected behavior is to return `None`.
"""

assert libraw.test is None
6 changes: 3 additions & 3 deletions tests/unit/error_test.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import pytest

from libraw.errors import check_call
from libraw.bindings import LibRaw
from libraw.errors import UnspecifiedError


def test_check_call_success():
check_call(0)
LibRaw.check_call(0, None, None)


def test_check_call_error():
with pytest.raises(UnspecifiedError):
check_call(-1)
LibRaw.check_call(-1, None, None)

0 comments on commit 439e4d8

Please sign in to comment.