pHash 1.0.0
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.
- data/.gitignore +12 -0
- data/LICENSE.txt +20 -0
- data/README.markdown +53 -0
- data/audiophash.diff +17 -0
- data/lib/phash.rb +44 -0
- data/lib/phash/all.rb +3 -0
- data/lib/phash/audio.rb +116 -0
- data/lib/phash/image.rb +59 -0
- data/lib/phash/text.rb +100 -0
- data/lib/phash/video.rb +55 -0
- data/pHash.gemspec +20 -0
- data/spec/data/audiophash.cpp-0.9.3.txt +571 -0
- data/spec/data/audiophash.cpp-0.9.4.txt +572 -0
- data/spec/data/audiophash.h-0.9.3.txt +111 -0
- data/spec/data/audiophash.h-0.9.4.txt +108 -0
- data/spec/data/hal9000-m.mp3 +0 -0
- data/spec/data/hal9000-o.mp3 +0 -0
- data/spec/data/jug-0-10.jpg +0 -0
- data/spec/data/jug-0-120.png +0 -0
- data/spec/data/jug-0-50.jpg +0 -0
- data/spec/data/jug-0-70.jpg +0 -0
- data/spec/data/jug-1-10.jpg +0 -0
- data/spec/data/jug-1-120.png +0 -0
- data/spec/data/jug-1-50.jpg +0 -0
- data/spec/data/jug-1-70.jpg +0 -0
- data/spec/data/jug-120.mp4 +0 -0
- data/spec/data/jug-150.mp4 +0 -0
- data/spec/data/jug-180.mp4 +0 -0
- data/spec/data/jug-2-10.jpg +0 -0
- data/spec/data/jug-2-120.png +0 -0
- data/spec/data/jug-2-50.jpg +0 -0
- data/spec/data/jug-2-70.jpg +0 -0
- data/spec/data/mouse-0-10.jpg +0 -0
- data/spec/data/mouse-0-120.png +0 -0
- data/spec/data/mouse-0-50.jpg +0 -0
- data/spec/data/mouse-0-70.jpg +0 -0
- data/spec/data/mouse-1-10.jpg +0 -0
- data/spec/data/mouse-1-120.png +0 -0
- data/spec/data/mouse-1-50.jpg +0 -0
- data/spec/data/mouse-1-70.jpg +0 -0
- data/spec/data/mouse-120.mp4 +0 -0
- data/spec/data/mouse-150.mp4 +0 -0
- data/spec/data/mouse-180.mp4 +0 -0
- data/spec/data/mouse-2-10.jpg +0 -0
- data/spec/data/mouse-2-120.png +0 -0
- data/spec/data/mouse-2-50.jpg +0 -0
- data/spec/data/mouse-2-70.jpg +0 -0
- data/spec/data/scream-m.mp3 +0 -0
- data/spec/data/scream-o.mp3 +0 -0
- data/spec/data/vader-m.mp3 +0 -0
- data/spec/data/vader-o.mp3 +0 -0
- data/spec/phash_spec.rb +43 -0
- data/spec/spec_helper.rb +10 -0
- metadata +186 -0
data/.gitignore
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Ivan Kuchin
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# pHash
|
2
|
+
|
3
|
+
Interface to [pHash](http://pHash.org/).
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
gem install pHash
|
8
|
+
|
9
|
+
Audio hash functions needs to be compiled with C linkage, so if you get `FFI::NotFoundError` check names of methods in `libpHash`. Tiny patch for pHash 0.9.4 is in `audiophash.diff`.
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
Compare two mp3s:
|
14
|
+
|
15
|
+
require 'phash/audio'
|
16
|
+
|
17
|
+
a = Phash::Audio.new('first.mp3')
|
18
|
+
b = Phash::Audio.new('second.mp3')
|
19
|
+
a.similarity(b)
|
20
|
+
|
21
|
+
or just
|
22
|
+
|
23
|
+
a % b
|
24
|
+
|
25
|
+
Get bunch of comparators and work with them:
|
26
|
+
|
27
|
+
audios = Phash::Audio.for_paths(Dir['**/*.{mp3,wav}'])
|
28
|
+
audios.combination(2) do |a, b|
|
29
|
+
similarity = a % b
|
30
|
+
# work with similarity
|
31
|
+
end
|
32
|
+
|
33
|
+
Videos:
|
34
|
+
|
35
|
+
require 'phash/video'
|
36
|
+
|
37
|
+
Phash::Video.new('first.mp4') % Phash::Video.new('second.mp4')
|
38
|
+
|
39
|
+
Images:
|
40
|
+
|
41
|
+
require 'phash/image'
|
42
|
+
|
43
|
+
Phash::Image.new('first.jpg') % Phash::Image.new('second.png')
|
44
|
+
|
45
|
+
Texts:
|
46
|
+
|
47
|
+
require 'phash/text'
|
48
|
+
|
49
|
+
Phash::Text.new('first.txt') % Phash::Text.new('second.txt')
|
50
|
+
|
51
|
+
## Copyright
|
52
|
+
|
53
|
+
Copyright (c) 2011 Ivan Kuchin. See LICENSE.txt for details.
|
data/audiophash.diff
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
--- src/audiophash.h.orig 2011-12-13 14:47:08.000000000 +0100
|
2
|
+
+++ src/audiophash.h 2011-12-13 14:47:12.000000000 +0100
|
3
|
+
@@ -34,7 +34,6 @@
|
4
|
+
|
5
|
+
extern "C" {
|
6
|
+
#include "ph_fft.h"
|
7
|
+
-}
|
8
|
+
|
9
|
+
/* /brief count number of samples in file
|
10
|
+
*
|
11
|
+
@@ -105,4 +104,6 @@
|
12
|
+
*/
|
13
|
+
double* ph_audio_distance_ber(uint32_t *hash_a , const int Na, uint32_t *hash_b, const int Nb, const float threshold, const int block_size, int &Nc);
|
14
|
+
|
15
|
+
+}
|
16
|
+
+
|
17
|
+
#endif
|
data/lib/phash.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
|
3
|
+
module Phash
|
4
|
+
class HashData
|
5
|
+
attr_reader :data, :length
|
6
|
+
def initialize(data, length = nil)
|
7
|
+
@data, @length = data, length
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class FileHash
|
12
|
+
attr_reader :path
|
13
|
+
|
14
|
+
# File path
|
15
|
+
def initialize(path)
|
16
|
+
@path = path
|
17
|
+
end
|
18
|
+
|
19
|
+
# Init multiple image instances
|
20
|
+
def self.for_paths(paths, *args)
|
21
|
+
paths.map do |path|
|
22
|
+
new(path, *args)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Cached hash of text
|
27
|
+
def phash
|
28
|
+
@phash ||= compute_phash
|
29
|
+
end
|
30
|
+
|
31
|
+
def %(other)
|
32
|
+
similarity(other)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
extend FFI::Library
|
37
|
+
|
38
|
+
ffi_lib(ENV['PHASH_LIB'] || Dir['/{usr,usr/local,opt/local}/lib/libpHash.{dylib,so}'].first)
|
39
|
+
|
40
|
+
autoload :Audio, 'phash/audio'
|
41
|
+
autoload :Image, 'phash/image'
|
42
|
+
autoload :Text, 'phash/text'
|
43
|
+
autoload :Video, 'phash/video'
|
44
|
+
end
|
data/lib/phash/all.rb
ADDED
data/lib/phash/audio.rb
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'phash'
|
2
|
+
|
3
|
+
module Phash
|
4
|
+
# read audio
|
5
|
+
#
|
6
|
+
# param filename - path and name of audio file to read
|
7
|
+
# param sr - sample rate conversion
|
8
|
+
# param channels - nb channels to convert to (always 1) unused
|
9
|
+
# param buf - preallocated buffer
|
10
|
+
# param buflen - (in/out) param for buf length
|
11
|
+
# param nbsecs - float value for duration (in secs) to read from file
|
12
|
+
#
|
13
|
+
# return float* - float pointer to start of buffer - one channel of audio, NULL if error
|
14
|
+
#
|
15
|
+
# float* ph_readaudio(const char *filename, int sr, int channels, float *sigbuf, int &buflen, const float nbsecs = 0);
|
16
|
+
#
|
17
|
+
attach_function :ph_readaudio, [:string, :int, :int, :pointer, :pointer, :float], :pointer
|
18
|
+
|
19
|
+
# audio hash calculation
|
20
|
+
# purpose: hash calculation for each frame in the buffer.
|
21
|
+
# Each value is computed from successive overlapping frames of the input buffer.
|
22
|
+
# The value is based on the bark scale values of the frame fft spectrum. The value
|
23
|
+
# computed from temporal and spectral differences on the bark scale.
|
24
|
+
#
|
25
|
+
# param buf - pointer to start of buffer
|
26
|
+
# param N - length of buffer
|
27
|
+
# param sr - sample rate on which to base the audiohash
|
28
|
+
# param nb_frames - (out) number of frames in audio buf and length of audiohash buffer returned
|
29
|
+
#
|
30
|
+
# return uint32 pointer to audio hash, NULL for error
|
31
|
+
#
|
32
|
+
# uint32_t* ph_audiohash(float *buf, int nbbuf, const int sr, int &nbframes);
|
33
|
+
#
|
34
|
+
attach_function :ph_audiohash, [:pointer, :int, :int, :pointer], :pointer
|
35
|
+
|
36
|
+
# distance function between two hashes
|
37
|
+
#
|
38
|
+
# param hash_a - first hash
|
39
|
+
# param Na - length of first hash
|
40
|
+
# param hash_b - second hash
|
41
|
+
# param Nb - length of second hash
|
42
|
+
# param threshold - threshold value to compare successive blocks, 0.25, 0.30, 0.35
|
43
|
+
# param block_size - length of block_size, 256
|
44
|
+
# param Nc - (out) length of confidence score vector
|
45
|
+
#
|
46
|
+
# return double - ptr to confidence score vector
|
47
|
+
#
|
48
|
+
# double* ph_audio_distance_ber(uint32_t *hash_a, const int Na, uint32_t *hash_b, const int Nb, const float threshold, const int block_size, int &Nc);
|
49
|
+
#
|
50
|
+
attach_function :ph_audio_distance_ber, [:pointer, :int, :pointer, :int, :float, :int, :pointer], :pointer
|
51
|
+
|
52
|
+
class << self
|
53
|
+
class AudioHash < HashData; end
|
54
|
+
|
55
|
+
# Read audio file specified by path and optional length using <tt>ph_readaudio</tt> and get its hash using <tt>ph_audiohash</tt>
|
56
|
+
def audio_hash(path, length = 0)
|
57
|
+
sample_rate = 8000
|
58
|
+
audio_data_length_p = FFI::MemoryPointer.new :int
|
59
|
+
if audio_data = ph_readaudio(path.to_s, sample_rate, 1, nil, audio_data_length_p, length.to_f)
|
60
|
+
audio_data_length = audio_data_length_p.get_int(0)
|
61
|
+
audio_data_length_p.free
|
62
|
+
|
63
|
+
hash_data_length_p = FFI::MemoryPointer.new :int
|
64
|
+
if hash_data = ph_audiohash(audio_data, audio_data_length, sample_rate, hash_data_length_p)
|
65
|
+
hash_data_length = hash_data_length_p.get_int(0)
|
66
|
+
hash_data_length_p.free
|
67
|
+
|
68
|
+
AudioHash.new(hash_data, hash_data_length)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Get distance between two audio hashes using <tt>ph_audio_distance_ber</tt>
|
74
|
+
def audio_distance_ber(hash_a, hash_b, threshold = 0.25, block_size = 256)
|
75
|
+
hash_a.is_a?(AudioHash) or raise ArgumentError.new('hash_a is not an AudioHash')
|
76
|
+
hash_b.is_a?(AudioHash) or raise ArgumentError.new('hash_b is not an AudioHash')
|
77
|
+
|
78
|
+
distance_vector_length_p = FFI::MemoryPointer.new :int
|
79
|
+
block_size = [block_size.to_i, hash_a.length, hash_b.length].min
|
80
|
+
if distance_vector = ph_audio_distance_ber(hash_a.data, hash_a.length, hash_b.data, hash_b.length, threshold.to_f, block_size, distance_vector_length_p)
|
81
|
+
distance_vector_length = distance_vector_length_p.get_int(0)
|
82
|
+
distance_vector_length_p.free
|
83
|
+
|
84
|
+
distance = distance_vector.get_array_of_double(0, distance_vector_length)
|
85
|
+
distance_vector.free
|
86
|
+
distance
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Get similarity from audio_distance_ber
|
91
|
+
def audio_similarity(hash_a, hash_b, *args)
|
92
|
+
audio_distance_ber(hash_a, hash_b, *args).max
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Class to store audio file hash and compare to other
|
97
|
+
class Audio < FileHash
|
98
|
+
attr_reader :length
|
99
|
+
|
100
|
+
# Audio path and optional length in seconds to read
|
101
|
+
def initialize(path, length = 0)
|
102
|
+
@path, @length = path, length
|
103
|
+
end
|
104
|
+
|
105
|
+
# Similarity with other audio
|
106
|
+
def similarity(other, *args)
|
107
|
+
Phash.audio_similarity(phash, other.phash, *args)
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def compute_phash
|
113
|
+
Phash.audio_hash(@path, @length)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
data/lib/phash/image.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'phash'
|
2
|
+
|
3
|
+
module Phash
|
4
|
+
# compute dct robust image hash
|
5
|
+
#
|
6
|
+
# param file string variable for name of file
|
7
|
+
# param hash of type ulong64 (must be 64-bit variable)
|
8
|
+
# return int value - -1 for failure, 1 for success
|
9
|
+
#
|
10
|
+
# int ph_dct_imagehash(const char* file, ulong64 &hash);
|
11
|
+
#
|
12
|
+
attach_function :ph_dct_imagehash, [:string, :pointer], :int
|
13
|
+
|
14
|
+
# no info in pHash.h
|
15
|
+
#
|
16
|
+
# int ph_hamming_distance(const ulong64 hash1,const ulong64 hash2);
|
17
|
+
#
|
18
|
+
attach_function :ph_hamming_distance, [:uint64, :uint64], :int
|
19
|
+
|
20
|
+
class << self
|
21
|
+
# Get image file hash using <tt>ph_dct_imagehash</tt>
|
22
|
+
def image_hash(path)
|
23
|
+
hash_p = FFI::MemoryPointer.new :ulong_long
|
24
|
+
if -1 != ph_dct_imagehash(path.to_s, hash_p)
|
25
|
+
hash = hash_p.get_uint64(0)
|
26
|
+
hash_p.free
|
27
|
+
|
28
|
+
hash
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Get distance between two image hashes using <tt>ph_hamming_distance</tt>
|
33
|
+
def image_hamming_distance(hash_a, hash_b)
|
34
|
+
hash_a.is_a?(Integer) or raise ArgumentError.new('hash_a is not an Integer')
|
35
|
+
hash_b.is_a?(Integer) or raise ArgumentError.new('hash_b is not an Integer')
|
36
|
+
|
37
|
+
ph_hamming_distance(hash_a, hash_b)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Get similarity from hamming_distance
|
41
|
+
def image_similarity(hash_a, hash_b)
|
42
|
+
1 - image_hamming_distance(hash_a, hash_b) / 64.0
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Class to store image file hash and compare to other
|
47
|
+
class Image < FileHash
|
48
|
+
# Similarity with other image
|
49
|
+
def similarity(other)
|
50
|
+
Phash.image_similarity(phash, other.phash)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def compute_phash
|
56
|
+
Phash.image_hash(@path)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/phash/text.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'phash'
|
2
|
+
|
3
|
+
module Phash
|
4
|
+
class TxtHashPoint < FFI::Struct
|
5
|
+
layout :hash, :uint64,
|
6
|
+
:index, :off_t
|
7
|
+
end
|
8
|
+
|
9
|
+
class TxtMatch < FFI::Struct
|
10
|
+
layout :index_a, :off_t,
|
11
|
+
:index_b, :off_t,
|
12
|
+
:length, :uint32
|
13
|
+
end
|
14
|
+
|
15
|
+
# textual hash for file
|
16
|
+
#
|
17
|
+
# param filename - char* name of file
|
18
|
+
# param nbpoints - int length of array of return value (out)
|
19
|
+
# return TxtHashPoint* array of hash points with respective index into file.
|
20
|
+
#
|
21
|
+
# TxtHashPoint* ph_texthash(const char *filename, int *nbpoints);
|
22
|
+
#
|
23
|
+
attach_function :ph_texthash, [:string, :pointer], :pointer
|
24
|
+
|
25
|
+
# compare 2 text hashes
|
26
|
+
#
|
27
|
+
# param hash1 -TxtHashPoint
|
28
|
+
# param N1 - int length of hash1
|
29
|
+
# param hash2 - TxtHashPoint
|
30
|
+
# param N2 - int length of hash2
|
31
|
+
# param nbmatches - int number of matches found (out)
|
32
|
+
# return TxtMatch* - list of all matches
|
33
|
+
#
|
34
|
+
# TxtMatch* ph_compare_text_hashes(TxtHashPoint *hash1, int N1, TxtHashPoint *hash2, int N2, int *nbmatches);
|
35
|
+
#
|
36
|
+
attach_function :ph_compare_text_hashes, [:pointer, :int, :pointer, :int, :pointer], :pointer
|
37
|
+
|
38
|
+
class << self
|
39
|
+
class TextHash < HashData; end
|
40
|
+
|
41
|
+
# Get text file hash using <tt>ph_texthash</tt>
|
42
|
+
def text_hash(path)
|
43
|
+
hash_data_length_p = FFI::MemoryPointer.new :int
|
44
|
+
if hash_data = ph_texthash(path.to_s, hash_data_length_p)
|
45
|
+
hash_data_length = hash_data_length_p.get_int(0)
|
46
|
+
hash_data_length_p.free
|
47
|
+
|
48
|
+
TextHash.new(hash_data, hash_data_length)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Get distance between two text hashes using <tt>text_distance</tt>
|
53
|
+
def text_hash_matches(hash_a, hash_b)
|
54
|
+
hash_a.is_a?(TextHash) or raise ArgumentError.new('hash_a is not a TextHash')
|
55
|
+
hash_b.is_a?(TextHash) or raise ArgumentError.new('hash_b is not a TextHash')
|
56
|
+
|
57
|
+
matches_length_p = FFI::MemoryPointer.new :int
|
58
|
+
if data = ph_compare_text_hashes(hash_a.data, hash_a.length, hash_b.data, hash_b.length, matches_length_p)
|
59
|
+
matches_length = matches_length_p.get_int(0)
|
60
|
+
matches_length_p.free
|
61
|
+
|
62
|
+
matches = matches_length.times.map{ |i| TxtMatch.new(data + i * TxtMatch.size) }
|
63
|
+
data.free
|
64
|
+
matches
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def text_similarity(hash_a, hash_b)
|
69
|
+
matches = text_hash_matches(hash_a, hash_b)
|
70
|
+
# p [hash_a.length, hash_b.length, matches.length]
|
71
|
+
matched_a = Array.new(hash_a.length)
|
72
|
+
matched_b = Array.new(hash_b.length)
|
73
|
+
matches.each do |match|
|
74
|
+
index_a = match[:index_a]
|
75
|
+
index_b = match[:index_b]
|
76
|
+
match[:length].times do |i|
|
77
|
+
matched_a[index_a + i] = true
|
78
|
+
matched_b[index_b + i] = true
|
79
|
+
end
|
80
|
+
end
|
81
|
+
coverage_a = matched_a.nitems / hash_a.length.to_f
|
82
|
+
coverage_b = matched_b.nitems / hash_b.length.to_f
|
83
|
+
(coverage_a + coverage_b) * 0.5
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Class to store text file hash and compare to other
|
88
|
+
class Text < FileHash
|
89
|
+
# Distance from other file, for now bit useless thing
|
90
|
+
def similarity(other)
|
91
|
+
Phash.text_similarity(phash, other.phash)
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def compute_phash
|
97
|
+
Phash.text_hash(@path)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
data/lib/phash/video.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'phash'
|
2
|
+
|
3
|
+
module Phash
|
4
|
+
# no info in pHash.h
|
5
|
+
#
|
6
|
+
# ulong64* ph_dct_videohash(const char *filename, int &Length);
|
7
|
+
#
|
8
|
+
attach_function :ph_dct_videohash, [:string, :pointer], :pointer
|
9
|
+
|
10
|
+
# no info in pHash.h
|
11
|
+
#
|
12
|
+
# double ph_dct_videohash_dist(ulong64 *hashA, int N1, ulong64 *hashB, int N2, int threshold=21);
|
13
|
+
#
|
14
|
+
attach_function :ph_dct_videohash_dist, [:pointer, :int, :pointer, :int, :int], :double
|
15
|
+
|
16
|
+
class << self
|
17
|
+
class VideoHash < HashData; end
|
18
|
+
|
19
|
+
# Get video hash using <tt>ph_dct_videohash</tt>
|
20
|
+
def video_hash(path)
|
21
|
+
hash_data_length_p = FFI::MemoryPointer.new :int
|
22
|
+
if hash_data = ph_dct_videohash(path.to_s, hash_data_length_p)
|
23
|
+
hash_data_length = hash_data_length_p.get_int(0)
|
24
|
+
hash_data_length_p.free
|
25
|
+
|
26
|
+
VideoHash.new(hash_data, hash_data_length)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Get distance between two video hashes using <tt>ph_dct_videohash_dist</tt>
|
31
|
+
def video_dct_distance(hash_a, hash_b, threshold = 21)
|
32
|
+
hash_a.is_a?(VideoHash) or raise ArgumentError.new('hash_a is not a VideoHash')
|
33
|
+
hash_b.is_a?(VideoHash) or raise ArgumentError.new('hash_b is not a VideoHash')
|
34
|
+
|
35
|
+
ph_dct_videohash_dist(hash_a.data, hash_a.length, hash_b.data, hash_b.length, threshold.to_i)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Get similarity from video_dct_distance
|
39
|
+
alias_method :video_similarity, :video_dct_distance
|
40
|
+
end
|
41
|
+
|
42
|
+
# Class to store video file hash and compare to other
|
43
|
+
class Video < FileHash
|
44
|
+
# Similarity with other video
|
45
|
+
def similarity(other, *args)
|
46
|
+
Phash.video_similarity(phash, other.phash, *args)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def compute_phash
|
52
|
+
Phash.video_hash(@path)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|