python-libphash 1.0.3__cp313-cp313-macosx_11_0_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
libphash/__init__.py ADDED
@@ -0,0 +1,24 @@
1
+ from __future__ import annotations
2
+
3
+ from .exceptions import (
4
+ PhashError,
5
+ AllocationError,
6
+ DecodeError,
7
+ InvalidArgumentError,
8
+ )
9
+ from .types import Digest, HashMethod
10
+ from .context import ImageContext
11
+ from .utils import hamming_distance, get_hash, compare_images
12
+
13
+ __all__ = [
14
+ "PhashError",
15
+ "AllocationError",
16
+ "DecodeError",
17
+ "InvalidArgumentError",
18
+ "Digest",
19
+ "HashMethod",
20
+ "ImageContext",
21
+ "hamming_distance",
22
+ "get_hash",
23
+ "compare_images",
24
+ ]
libphash/_build.py ADDED
@@ -0,0 +1,97 @@
1
+ import os
2
+ import glob
3
+ from cffi import FFI
4
+
5
+ ffibuilder = FFI()
6
+
7
+ # 1. Define the C definitions exposed to Python
8
+ # We strip macros like PH_API for CFFI parsing
9
+ ffibuilder.cdef("""
10
+ // Constants
11
+ #define PH_DIGEST_MAX_BYTES 64
12
+
13
+ // Error Codes
14
+ typedef enum {
15
+ PH_SUCCESS = 0,
16
+ PH_ERR_ALLOCATION_FAILED = -1,
17
+ PH_ERR_DECODE_FAILED = -2,
18
+ PH_ERR_INVALID_ARGUMENT = -3,
19
+ PH_ERR_NOT_IMPLEMENTED = -4,
20
+ PH_ERR_EMPTY_IMAGE = -5,
21
+ ...
22
+ } ph_error_t;
23
+
24
+ // Types
25
+ typedef struct ph_context ph_context_t;
26
+
27
+ typedef struct {
28
+ uint8_t data[PH_DIGEST_MAX_BYTES];
29
+ uint8_t size;
30
+ uint8_t reserved[7];
31
+ } ph_digest_t;
32
+
33
+ // Lifecycle
34
+ const char *ph_version(void);
35
+ ph_error_t ph_create(ph_context_t **out_ctx);
36
+ void ph_free(ph_context_t *ctx);
37
+ void ph_context_set_gamma(ph_context_t *ctx, float gamma);
38
+
39
+ // Loading
40
+ ph_error_t ph_load_from_file(ph_context_t *ctx, const char *filepath);
41
+ ph_error_t ph_load_from_memory(ph_context_t *ctx, const uint8_t *buffer, size_t length);
42
+
43
+ // uint64 Hashes
44
+ ph_error_t ph_compute_ahash(ph_context_t *ctx, uint64_t *out_hash);
45
+ ph_error_t ph_compute_dhash(ph_context_t *ctx, uint64_t *out_hash);
46
+ ph_error_t ph_compute_phash(ph_context_t *ctx, uint64_t *out_hash);
47
+ ph_error_t ph_compute_whash(ph_context_t *ctx, uint64_t *out_hash);
48
+ ph_error_t ph_compute_mhash(ph_context_t *ctx, uint64_t *out_hash);
49
+
50
+ // Digest Hashes
51
+ ph_error_t ph_compute_bmh(ph_context_t *ctx, ph_digest_t *out_digest);
52
+ ph_error_t ph_compute_color_hash(ph_context_t *ctx, ph_digest_t *out_digest);
53
+ ph_error_t ph_compute_radial_hash(ph_context_t *ctx, ph_digest_t *out_digest);
54
+
55
+ // Comparison
56
+ int ph_hamming_distance(uint64_t hash1, uint64_t hash2);
57
+ int ph_hamming_distance_digest(const ph_digest_t *a, const ph_digest_t *b);
58
+ double ph_l2_distance(const ph_digest_t *a, const ph_digest_t *b);
59
+ """)
60
+
61
+ # 2. Configure the Source Compilation
62
+ # We need to find all .c files in the native directory
63
+ curr_dir = os.path.dirname(os.path.abspath(__file__))
64
+ project_root = os.path.abspath(os.path.join(curr_dir, "../../"))
65
+ native_dir = os.path.abspath(os.path.join(project_root, "native", "libphash"))
66
+
67
+ sources = []
68
+ sources.extend(glob.glob(os.path.join(native_dir, "src", "*.c")))
69
+ sources.extend(glob.glob(os.path.join(native_dir, "src", "hashes", "*.c")))
70
+
71
+ include_dirs = ["native/libphash/include"]
72
+ source_files = [
73
+ "native/libphash/src/core.c",
74
+ "native/libphash/src/image.c",
75
+ "native/libphash/src/hashes/ahash.c",
76
+ "native/libphash/src/hashes/bmh.c",
77
+ "native/libphash/src/hashes/color_hash.c",
78
+ "native/libphash/src/hashes/common.c",
79
+ "native/libphash/src/hashes/dhash.c",
80
+ "native/libphash/src/hashes/mhash.c",
81
+ "native/libphash/src/hashes/phash.c",
82
+ "native/libphash/src/hashes/radial.c",
83
+ "native/libphash/src/hashes/whash.c",
84
+ ]
85
+
86
+ ffibuilder.set_source(
87
+ "libphash._native",
88
+ '#include "libphash.h"',
89
+ sources=source_files,
90
+ include_dirs=include_dirs,
91
+ libraries=["m"] if os.name == "posix" else [],
92
+ extra_compile_args=["-O3", "-Wall", "-fPIC"]
93
+ if os.name == "posix"
94
+ else ["/O2", "/W3"],
95
+ )
96
+ if __name__ == "__main__":
97
+ ffibuilder.compile(verbose=True)
Binary file
libphash/_native.pyi ADDED
@@ -0,0 +1,39 @@
1
+ # src/libphash/_native.pyi
2
+ from typing import Any
3
+
4
+ class FFI:
5
+ def new(self, type_name: str, value: Any = None) -> Any: ...
6
+ def buffer(self, ptr: Any, size: int) -> Any: ...
7
+ def memmove(self, dest: Any, src: Any, n: int) -> None: ...
8
+
9
+ class Lib:
10
+ # Lifecycle
11
+ def ph_create(self, ctx: Any) -> int: ...
12
+ def ph_free(self, ctx: Any) -> None: ...
13
+
14
+ # Loading
15
+ def ph_load_from_file(self, ctx: Any, path: bytes) -> int: ...
16
+ def ph_load_from_memory(self, ctx: Any, data: bytes, size: int) -> int: ...
17
+
18
+ # Settings
19
+ def ph_context_set_gamma(self, ctx: Any, gamma: float) -> None: ...
20
+
21
+ # Hashes (uint64)
22
+ def ph_compute_ahash(self, ctx: Any, out: Any) -> int: ...
23
+ def ph_compute_dhash(self, ctx: Any, out: Any) -> int: ...
24
+ def ph_compute_phash(self, ctx: Any, out: Any) -> int: ...
25
+ def ph_compute_whash(self, ctx: Any, out: Any) -> int: ...
26
+ def ph_compute_mhash(self, ctx: Any, out: Any) -> int: ...
27
+
28
+ # Hashes (Digests)
29
+ def ph_compute_bmh(self, ctx: Any, out: Any) -> int: ...
30
+ def ph_compute_color_hash(self, ctx: Any, out: Any) -> int: ...
31
+ def ph_compute_radial_hash(self, ctx: Any, out: Any) -> int: ...
32
+
33
+ # Distances
34
+ def ph_hamming_distance(self, h1: int, h2: int) -> int: ...
35
+ def ph_hamming_distance_digest(self, d1: Any, d2: Any) -> int: ...
36
+ def ph_l2_distance(self, d1: Any, d2: Any) -> float: ...
37
+
38
+ lib: Lib
39
+ ffi: FFI
libphash/context.py ADDED
@@ -0,0 +1,94 @@
1
+ from __future__ import annotations
2
+ from pathlib import Path
3
+ from typing import Any, final, Callable
4
+ from ._native import ffi, lib
5
+ from .exceptions import check_error
6
+ from .types import Digest
7
+
8
+
9
+ @final
10
+ class ImageContext:
11
+ _ptr: Any
12
+ _ctx_ptr_ptr: Any
13
+
14
+ def __init__(
15
+ self, path: str | Path | None = None, bytes_data: bytes | None = None
16
+ ) -> None:
17
+ self._ctx_ptr_ptr = ffi.new("ph_context_t **")
18
+ check_error(lib.ph_create(self._ctx_ptr_ptr))
19
+ self._ptr = self._ctx_ptr_ptr[0]
20
+
21
+ try:
22
+ if path is not None:
23
+ self.load_from_file(path)
24
+ elif bytes_data is not None:
25
+ self.load_from_memory(bytes_data)
26
+ except Exception:
27
+ self.close()
28
+ raise
29
+
30
+ def close(self) -> None:
31
+ if hasattr(self, "_ptr") and self._ptr is not None:
32
+ lib.ph_free(self._ptr)
33
+ self._ptr = None
34
+
35
+ def __enter__(self) -> ImageContext:
36
+ return self
37
+
38
+ def __exit__(self, exc_type: type[BaseException] | None, *args: Any) -> None:
39
+ self.close()
40
+
41
+ def load_from_file(self, path: str | Path) -> None:
42
+ path_obj = Path(path).resolve()
43
+ if not path_obj.exists():
44
+ raise FileNotFoundError(f"File not found: {path_obj}")
45
+ check_error(lib.ph_load_from_file(self._ptr, str(path_obj).encode()))
46
+
47
+ def load_from_memory(self, data: bytes) -> None:
48
+ check_error(lib.ph_load_from_memory(self._ptr, data, len(data)))
49
+
50
+ def set_gamma(self, gamma: float) -> None:
51
+ lib.ph_context_set_gamma(self._ptr, float(gamma))
52
+
53
+ # Внутренние хелперы теперь типизированы через Callable
54
+ def _uint64_prop(self, func: Callable[[Any, Any], int]) -> int:
55
+ out = ffi.new("uint64_t *")
56
+ check_error(func(self._ptr, out))
57
+ return int(out[0])
58
+
59
+ def _digest_prop(self, func: Callable[[Any, Any], int]) -> Digest:
60
+ out = ffi.new("ph_digest_t *")
61
+ check_error(func(self._ptr, out))
62
+ return Digest.from_c_struct(out[0])
63
+
64
+ @property
65
+ def ahash(self) -> int:
66
+ return self._uint64_prop(lib.ph_compute_ahash)
67
+
68
+ @property
69
+ def dhash(self) -> int:
70
+ return self._uint64_prop(lib.ph_compute_dhash)
71
+
72
+ @property
73
+ def phash(self) -> int:
74
+ return self._uint64_prop(lib.ph_compute_phash)
75
+
76
+ @property
77
+ def whash(self) -> int:
78
+ return self._uint64_prop(lib.ph_compute_whash)
79
+
80
+ @property
81
+ def mhash(self) -> int:
82
+ return self._uint64_prop(lib.ph_compute_mhash)
83
+
84
+ @property
85
+ def bmh(self) -> Digest:
86
+ return self._digest_prop(lib.ph_compute_bmh)
87
+
88
+ @property
89
+ def color_hash(self) -> Digest:
90
+ return self._digest_prop(lib.ph_compute_color_hash)
91
+
92
+ @property
93
+ def radial_hash(self) -> Digest:
94
+ return self._digest_prop(lib.ph_compute_radial_hash)
libphash/exceptions.py ADDED
@@ -0,0 +1,36 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ class PhashError(Exception):
5
+ """Base exception for libphash library."""
6
+
7
+
8
+ class AllocationError(PhashError):
9
+ """Raised when memory allocation fails in the C layer."""
10
+
11
+
12
+ class DecodeError(PhashError):
13
+ """Raised when image decoding fails (stb_image error)."""
14
+
15
+
16
+ class InvalidArgumentError(PhashError):
17
+ """Raised when an invalid argument is passed to the C function."""
18
+
19
+
20
+ def check_error(err_code: int) -> None:
21
+ """Map C return codes to Python exceptions."""
22
+ if err_code == 0:
23
+ return
24
+
25
+ errors: dict[int, tuple[type[PhashError], str]] = {
26
+ -1: (AllocationError, "Memory allocation failed in libphash"),
27
+ -2: (DecodeError, "Failed to decode image (stb_image error)"),
28
+ -3: (InvalidArgumentError, "Invalid argument provided to libphash"),
29
+ -4: (PhashError, "Feature not implemented"),
30
+ -5: (PhashError, "Image is empty or invalid"),
31
+ }
32
+
33
+ exc_class, msg = errors.get(
34
+ err_code, (PhashError, f"Unknown error code: {err_code}")
35
+ )
36
+ raise exc_class(msg)
libphash/py.typed ADDED
File without changes
libphash/types.py ADDED
@@ -0,0 +1,58 @@
1
+ from __future__ import annotations
2
+ from enum import Enum
3
+ from typing import Any, final
4
+ from ._native import ffi, lib
5
+
6
+ PH_DIGEST_MAX_BYTES: int = 64
7
+
8
+
9
+ class HashMethod(Enum):
10
+ AHASH = "ahash"
11
+ DHASH = "dhash"
12
+ PHASH = "phash"
13
+ WHASH = "whash"
14
+ MHASH = "mhash"
15
+
16
+
17
+ @final
18
+ class Digest:
19
+ _data: bytes
20
+ _size: int
21
+
22
+ def __init__(self, data: bytes, size: int) -> None:
23
+ if len(data) > PH_DIGEST_MAX_BYTES:
24
+ raise ValueError(f"Data exceeds max size ({PH_DIGEST_MAX_BYTES})")
25
+ self._data = data
26
+ self._size = size
27
+
28
+ @property
29
+ def data(self) -> bytes:
30
+ return self._data
31
+
32
+ @property
33
+ def size(self) -> int:
34
+ return self._size
35
+
36
+ @classmethod
37
+ def from_c_struct(cls, c_digest: Any) -> Digest:
38
+ # Pyright теперь знает, что у c_digest могут быть поля,
39
+ # так как мы описали это в .pyi как Any (динамический указатель)
40
+ size = int(c_digest.size)
41
+ raw_buffer = ffi.buffer(c_digest.data, size)
42
+ return cls(bytes(raw_buffer), size)
43
+
44
+ def to_c_struct(self) -> Any:
45
+ c_ptr = ffi.new("ph_digest_t *")
46
+ c_ptr.size = self._size
47
+ ffi.memmove(c_ptr.data, self._data, len(self._data))
48
+ return c_ptr
49
+
50
+ def distance_hamming(self, other: Digest) -> int:
51
+ if self._size != other._size:
52
+ raise ValueError("Digests must have the same size")
53
+ return lib.ph_hamming_distance_digest(self.to_c_struct(), other.to_c_struct())
54
+
55
+ def distance_l2(self, other: Digest) -> float:
56
+ if self._size != other._size:
57
+ raise ValueError("Digests must have the same size")
58
+ return lib.ph_l2_distance(self.to_c_struct(), other.to_c_struct())
libphash/utils.py ADDED
@@ -0,0 +1,28 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import cast
5
+ from ._native import lib # type: ignore
6
+ from .context import ImageContext
7
+ from .types import HashMethod
8
+
9
+
10
+ def hamming_distance(h1: int, h2: int) -> int:
11
+ """Calculate Hamming distance between two uint64 hashes."""
12
+ return int(lib.ph_hamming_distance(int(h1), int(h2))) # type: ignore
13
+
14
+
15
+ def get_hash(path: str | Path, method: HashMethod = HashMethod.PHASH) -> int:
16
+ """Convenience function to get a hash for a file."""
17
+ with ImageContext(path=path) as ctx:
18
+ return cast(int, getattr(ctx, method.value))
19
+
20
+
21
+ def compare_images(
22
+ path1: str | Path, path2: str | Path, method: HashMethod = HashMethod.PHASH
23
+ ) -> int:
24
+ """Convenience function to compare two images."""
25
+ with ImageContext(path=path1) as ctx1, ImageContext(path=path2) as ctx2:
26
+ h1 = cast(int, getattr(ctx1, method.value))
27
+ h2 = cast(int, getattr(ctx2, method.value))
28
+ return hamming_distance(h1, h2)
@@ -0,0 +1,124 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-libphash
3
+ Version: 1.0.3
4
+ Summary: High-performance perceptual hashing library (CFFI bindings)
5
+ Author-email: gudoshnikovn <gudoshnikov-na@yandex.ru>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/gudoshnikovn/python-libphash
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: C
10
+ Classifier: Topic :: Multimedia :: Graphics
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: cffi>=1.15.0
15
+ Provides-Extra: dev
16
+ Requires-Dist: pytest; extra == "dev"
17
+ Requires-Dist: mypy; extra == "dev"
18
+ Requires-Dist: ruff; extra == "dev"
19
+ Dynamic: license-file
20
+
21
+ # python-libphash
22
+
23
+ High-performance Python bindings for [libphash](https://github.com/gudoshnikovn/libphash), a C library for perceptual image hashing.
24
+
25
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
26
+ [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
27
+
28
+ ## Overview
29
+
30
+ `libphash` provides multiple algorithms to generate "perceptual hashes" of images. Unlike cryptographic hashes (like MD5 or SHA256), perceptual hashes change only slightly if the image is resized, compressed, or has minor color adjustments. This makes them ideal for finding duplicate or similar images.
31
+
32
+ ### Supported Algorithms
33
+
34
+ * **64-bit Hashes (uint64):**
35
+ * `ahash`: Average Hash
36
+ * `dhash`: Difference Hash
37
+ * `phash`: Perceptual Hash (DCT based)
38
+ * `whash`: Wavelet Hash
39
+ * `mhash`: Median Hash
40
+ * **Digest Hashes (Multi-byte):**
41
+ * `bmh`: Block Mean Hash
42
+ * `color_hash`: Color Moment Hash
43
+ * `radial_hash`: Radial Variance Hash
44
+
45
+ ## Installation
46
+
47
+ ### Prerequisites
48
+ * A C compiler (GCC/Clang or MSVC)
49
+ * Python 3.8 or higher
50
+
51
+ ### Install from source
52
+ ```bash
53
+ git clone --recursive https://github.com/yourusername/python-libphash.git
54
+ cd python-libphash
55
+ pip install .
56
+ ```
57
+
58
+ ## Quick Start
59
+
60
+ ### Basic Usage
61
+ ```python
62
+ from libphash import ImageContext, HashMethod, hamming_distance
63
+
64
+ # Use the context manager for automatic memory management
65
+ with ImageContext("photo.jpg") as ctx:
66
+ # Get standard 64-bit hashes
67
+ phash_val = ctx.phash
68
+ dhash_val = ctx.dhash
69
+
70
+ print(f"pHash: {phash_val:016x}")
71
+ print(f"dHash: {dhash_val:016x}")
72
+
73
+ # Compare two images
74
+ from libphash import compare_images
75
+ distance = compare_images("image1.jpg", "image2.jpg", method=HashMethod.PHASH)
76
+ print(f"Hamming Distance: {distance}")
77
+ ```
78
+
79
+ ### Working with Digests (Advanced Hashes)
80
+ Algorithms like Radial Hash or Color Hash return a `Digest` object instead of a single integer.
81
+
82
+ ```python
83
+ with ImageContext("photo.jpg") as ctx:
84
+ digest = ctx.radial_hash
85
+ print(f"Digest size: {digest.size} bytes")
86
+ print(f"Raw data: {digest.data.hex()}")
87
+
88
+ # Comparing digests
89
+ with ImageContext("photo_v2.jpg") as ctx2:
90
+ digest2 = ctx2.radial_hash
91
+
92
+ # Hamming distance for bit-wise comparison
93
+ h_dist = digest.distance_hamming(digest2)
94
+
95
+ # L2 (Euclidean) distance for similarity
96
+ l2_dist = digest.distance_l2(digest2)
97
+ ```
98
+
99
+ ## API Reference
100
+
101
+ ### `ImageContext`
102
+ The main class for loading images and computing hashes.
103
+ * `__init__(path=None, bytes_data=None)`: Load an image from a file path or memory.
104
+ * `set_gamma(gamma: float)`: Set gamma correction (useful for Radial Hash).
105
+ * **Properties**: `ahash`, `dhash`, `phash`, `whash`, `mhash` (returns `int`).
106
+ * **Properties**: `bmh`, `color_hash`, `radial_hash` (returns `Digest`).
107
+
108
+ ### `Digest`
109
+ * `data`: The raw `bytes` of the hash.
110
+ * `size`: Length of the hash in bytes.
111
+ * `distance_hamming(other)`: Calculates bit-wise distance.
112
+ * `distance_l2(other)`: Calculates Euclidean distance.
113
+
114
+ ### Utilities
115
+ * `hamming_distance(h1: int, h2: int)`: Returns the number of differing bits between two 64-bit integers.
116
+ * `get_hash(path, method)`: Quick way to get a hash without manual context management.
117
+ * `compare_images(path1, path2, method)`: Returns the Hamming distance between two image files.
118
+
119
+ ## Performance
120
+ Since the core logic is implemented in C and uses `stb_image` for decoding, `libphash` is significantly faster than pure-Python alternatives. It also uses CFFI's "out-of-line" mode for minimal overhead.
121
+
122
+ ## License
123
+ This project is licensed under the MIT License - see the LICENSE file for details.
124
+
@@ -0,0 +1,14 @@
1
+ libphash/__init__.py,sha256=tqi8ZA7pA1gksrE5pkTYBbIdsB6nFAwNwrYNM_7dpR0,495
2
+ libphash/_build.py,sha256=3JRNepyK7gyYlkD_U0RX505Z-Spn2dJ_wtja0Z7wrM4,3279
3
+ libphash/types.py,sha256=r1gnlvBwCSFgUY5UWe4ZIcm0Zbi4omE9nF8LwIfcV2A,1772
4
+ libphash/_native.abi3.so,sha256=Q_uCYFeYebG33n67He4K7jPA03I_9ymgCBxvkt08kJI,227664
5
+ libphash/context.py,sha256=eCKZNuuadStVmpnI8UcyF_SbKzNaWFTmjv9ryA8zG0c,2879
6
+ libphash/utils.py,sha256=BGYIXyyWluBHlUmPVcStDxP0hTXIFHe1HNd4eoWaQsE,995
7
+ libphash/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ libphash/exceptions.py,sha256=HOo1nWh9jucqgH1c1rkxhZJoYO8EvmO6g3aENrbGn8U,1071
9
+ libphash/_native.pyi,sha256=BUSKo-ZUJyWu-3ftjmVe3nfII-6bOMvte_TCE0kn8lE,1406
10
+ python_libphash-1.0.3.dist-info/RECORD,,
11
+ python_libphash-1.0.3.dist-info/WHEEL,sha256=KreXLeNnYSLDPpk7qnNyKd0DQEhtY-je-mdlEpkBMmo,109
12
+ python_libphash-1.0.3.dist-info/top_level.txt,sha256=_Iys95-wvePR99MGJ1ikgjscpcotnUJ2m3JzmLvCG7w,9
13
+ python_libphash-1.0.3.dist-info/METADATA,sha256=JLoS7kRLWhDRukE0gU8xmJ3VvXfkvmKpAgK7WUubb1c,4377
14
+ python_libphash-1.0.3.dist-info/licenses/LICENSE,sha256=rRPeBnIMO5vVEJAMWDi-pAR4_AD3id6WwDG02zUnYzQ,1069
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: false
4
+ Tag: cp313-cp313-macosx_11_0_arm64
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 gudoshnikovn
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ libphash