photo-cook 1.1.0 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/README.md +15 -0
- data/app/assets/javascripts/photo-cook/photo-cook.js.erb +26 -55
- data/lib/photo-cook/assemble.rb +82 -12
- data/lib/photo-cook/command.rb +20 -6
- data/lib/photo-cook/dimensions.rb +7 -7
- data/lib/photo-cook/dirs.rb +8 -0
- data/lib/photo-cook/engine.rb +0 -4
- data/lib/photo-cook/logging.rb +48 -6
- data/lib/photo-cook/middleware.rb +59 -42
- data/lib/photo-cook/pixel-ratio.rb +11 -4
- data/lib/photo-cook/resize-api.rb +92 -0
- data/lib/photo-cook/resizer.rb +15 -23
- data/lib/photo-cook/version.rb +1 -1
- data/lib/photo-cook.rb +4 -4
- metadata +5 -6
- data/Gemfile.lock +0 -19
- data/lib/photo-cook/callbacks.rb +0 -8
- data/lib/photo-cook/paths.rb +0 -12
- data/lib/photo-cook/resizing.rb +0 -68
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 134a21bfc6697b25c4b682afee2ec145c0e29f38
|
4
|
+
data.tar.gz: e090d4eb4009cac709d78e50e2e5caa72f1dd5f7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 378835a2a6cf41cf6160be4c8e7b607fd343cf08b9dbf972f20e19c3242fc842ec83c276e01c82d156c87ff359f4c39e0177c51073e45b5c91b17dcc0279ae2c
|
7
|
+
data.tar.gz: 4c7274814608e4250c4a400cf9b109c44abb7f6b62ab39c14ee3d2dbc00a6dd70935af1f7c2471a994632bac5df06fb9110cc17eddf5db74456085ab6edad493
|
data/.gitignore
CHANGED
data/README.md
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
```ruby
|
2
|
+
# application
|
3
|
+
# public
|
4
|
+
# uploads
|
5
|
+
# photos
|
6
|
+
# 1
|
7
|
+
# car.png
|
8
|
+
# resize-cache -> ../../../resize-cache/uploads/photos/1
|
9
|
+
# resize-cache
|
10
|
+
# uploads
|
11
|
+
# photos
|
12
|
+
# 1
|
13
|
+
# width=auto&height=640&pixel_ratio=1&crop=yes
|
14
|
+
# car.png
|
15
|
+
```
|
@@ -1,13 +1,15 @@
|
|
1
1
|
(function(root) {
|
2
2
|
var PhotoCook = {
|
3
|
-
|
3
|
+
cacheDir: <%= PhotoCook.cache_dir.to_json %>,
|
4
4
|
|
5
|
-
commandRegex:
|
5
|
+
commandRegex: /^$width=auto|\d{1,5}&height=auto|\d{1,5}&pixel_ratio=[1234]&crop=yes|no$/,
|
6
6
|
|
7
7
|
initialize: function() {
|
8
8
|
PhotoCook.persistPixelRatio();
|
9
9
|
},
|
10
10
|
|
11
|
+
// Returns device pixel ratio (float)
|
12
|
+
// If no ratio could be determined will return normal ratio (1.0)
|
11
13
|
pixelRatio: (function () {
|
12
14
|
// https://gist.github.com/marcedwards/3446599
|
13
15
|
var mediaQuery = [
|
@@ -22,11 +24,11 @@
|
|
22
24
|
// and if so return 2x ratio
|
23
25
|
if (ratio == null && typeof window.matchMedia === 'function') {
|
24
26
|
if (window.matchMedia(mediaQuery).matches) {
|
25
|
-
ratio = 2;
|
27
|
+
ratio = 2.0;
|
26
28
|
}
|
27
29
|
}
|
28
30
|
|
29
|
-
return parseFloat(ratio
|
31
|
+
return parseFloat(ratio) || 1.0;
|
30
32
|
})(),
|
31
33
|
|
32
34
|
persistPixelRatio: function() {
|
@@ -35,66 +37,35 @@
|
|
35
37
|
// Expires in 1 year
|
36
38
|
date.setTime(date.getTime() + 365 * 24 * 60 * 60 * 1000);
|
37
39
|
var expires = 'expires=' + date.toUTCString();
|
38
|
-
document.cookie =
|
40
|
+
document.cookie = 'PhotoCookPixelRatio=' + PhotoCook.pixelRatio + '; ' + expires;
|
39
41
|
},
|
40
42
|
|
41
43
|
resize: function (path, width, height, options) {
|
42
|
-
var crop
|
43
|
-
var ratio
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
if (index < 0) { index = path.lastIndexOf('?'); }
|
52
|
-
if (index < 0) { index = path.lastIndexOf('#'); }
|
53
|
-
|
54
|
-
var leftPart = path.slice(0, Math.max(path.lastIndexOf('/'), 0));
|
55
|
-
var rightPart = index >= 0 ? path.slice(index) : '';
|
56
|
-
|
57
|
-
var basename = path.slice(
|
58
|
-
leftPart ? leftPart.length + 1 : 0,
|
59
|
-
rightPart ? 0 - rightPart.length : path.length
|
60
|
-
);
|
61
|
-
|
62
|
-
return (leftPart ? leftPart + '/' : leftPart)
|
63
|
-
+ PhotoCook.resizeDir
|
64
|
-
+ '/'
|
65
|
-
+ basename
|
66
|
-
+ command
|
67
|
-
+ rightPart;
|
44
|
+
var crop = typeof options === 'boolean' ? !!options : !!(options && options.crop);
|
45
|
+
var ratio = Math.ceil(Math.max(0, options && options.pixelRatio) || PhotoCook.pixelRatio);
|
46
|
+
var command = 'width=' + (+width || 'auto') +
|
47
|
+
'&height=' + (+height || 'auto') +
|
48
|
+
'&pixel_ratio=' + (+ratio || 1) +
|
49
|
+
'&crop=' + (crop ? 'yes' : 'no');
|
50
|
+
var els = path.split('/');
|
51
|
+
els.splice(-1, 0, PhotoCook.cacheDir, command);
|
52
|
+
return els.join('/');
|
68
53
|
},
|
69
54
|
|
70
|
-
strip: function (
|
71
|
-
var
|
72
|
-
var
|
73
|
-
if (
|
74
|
-
return path;
|
75
|
-
}
|
76
|
-
|
77
|
-
var rightIndex; rightIndex = path.lastIndexOf('.');
|
78
|
-
if (rightIndex < 0) { rightIndex = path.lastIndexOf('?'); }
|
79
|
-
if (rightIndex < 0) { rightIndex = path.lastIndexOf('#'); }
|
80
|
-
|
81
|
-
var basename = path.slice(
|
82
|
-
leftIndex + token.length,
|
83
|
-
rightIndex >= 0 ? 0 - path.length + rightIndex : path.length
|
84
|
-
);
|
55
|
+
strip: function (uri) {
|
56
|
+
var sections = uri.split('/');
|
57
|
+
var length = sections.length;
|
58
|
+
if (length < 3) { return uri; }
|
85
59
|
|
86
|
-
var
|
60
|
+
var cacheDir = sections[length - 3];
|
61
|
+
var command = sections[length - 2];
|
87
62
|
|
88
|
-
|
89
|
-
|
90
|
-
if (oldLength === basename.length) {
|
91
|
-
return path;
|
63
|
+
if (PhotoCook.cacheDir !== cacheDir || PhotoCook.commandRegex.test(command) == false) {
|
64
|
+
return uri;
|
92
65
|
}
|
93
66
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
return leftPart + basename + rightPart;
|
67
|
+
sections.splice(length - 3, 2)
|
68
|
+
return sections.join('/');
|
98
69
|
},
|
99
70
|
|
100
71
|
uriRegex: /^[-a-z]+:\/\/|^(?:cid|data):|^\/\//i,
|
data/lib/photo-cook/assemble.rb
CHANGED
@@ -1,26 +1,96 @@
|
|
1
1
|
module PhotoCook
|
2
2
|
module Assemble
|
3
3
|
|
4
|
-
#
|
4
|
+
# Returns URI which points to PhotoCook::Middleware
|
5
|
+
#
|
6
|
+
# Arguments:
|
7
|
+
# source_uri => /uploads/photos/1/car.png
|
8
|
+
# width => :auto
|
9
|
+
# height => 640
|
10
|
+
# pixel_ratio => 1
|
11
|
+
# crop => true
|
12
|
+
#
|
13
|
+
# Returns /uploads/photos/1/resized/width=auto&height=640&pixel_ratio=1&crop=yes/car.png
|
14
|
+
#
|
5
15
|
# NOTE: This method performs no validation
|
6
|
-
def
|
7
|
-
|
8
|
-
assemble_command(width, height, pixel_ratio, crop), File.basename(uri)
|
9
|
-
uri.start_with?('/') ? '/' + result : result
|
16
|
+
def assemble_resize_uri(source_uri, width, height, pixel_ratio, crop)
|
17
|
+
source_uri.split('/').insert(-2, cache_dir, assemble_command(width, height, pixel_ratio, crop)).join('/')
|
10
18
|
end
|
11
19
|
|
12
|
-
#
|
20
|
+
# Strips resize command from URI. Inverse of +assemble_resize_uri+
|
21
|
+
#
|
22
|
+
# Arguments:
|
23
|
+
# resize_uri => /uploads/photos/1/resized/width=auto&height=640&pixel_ratio=1&crop=yes/car.png
|
24
|
+
#
|
25
|
+
# Returns /uploads/photos/1/car.png
|
26
|
+
#
|
13
27
|
# NOTE: This method performs no validation
|
14
|
-
def
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
28
|
+
def disassemble_resize_uri(resize_uri)
|
29
|
+
# Take URI:
|
30
|
+
# /uploads/photos/1/resized/width=auto&height=640&pixel_ratio=1&crop=yes/car.png
|
31
|
+
#
|
32
|
+
# Split by separator:
|
33
|
+
# ["", "uploads", "photos", "1", "resized", "width=auto&height=640&pixel_ratio=1&crop=yes", "car.png"]
|
34
|
+
#
|
35
|
+
sections = resize_uri.split('/')
|
36
|
+
|
37
|
+
# Delete PhotoCook directory:
|
38
|
+
# ["", "uploads", "photos", "1", "width=auto&height=640&pixel_ratio=1&crop=yes", "car.png"]
|
39
|
+
sections.delete_at(-3)
|
40
|
+
|
41
|
+
# Delete command string:
|
42
|
+
# ["", "uploads", "photos", "1", "car.png"]
|
43
|
+
sections.delete_at(-2)
|
44
|
+
|
45
|
+
sections.join('/')
|
46
|
+
end
|
47
|
+
|
48
|
+
# Path where source photo is stored
|
49
|
+
#
|
50
|
+
# Arguments:
|
51
|
+
# root => /application
|
52
|
+
# resize_uri => /uploads/photos/1/resized/width=auto&height=640&pixel_ratio=1&crop=yes/car.png
|
53
|
+
#
|
54
|
+
# Returns /application/public/uploads/photos/1/car.png
|
55
|
+
#
|
56
|
+
# NOTE: This method performs no validation
|
57
|
+
def assemble_source_path(root, resize_uri)
|
58
|
+
uri = disassemble_resize_uri(resize_uri)
|
59
|
+
uri.gsub!('/', '\\') if File::SEPARATOR != '/'
|
60
|
+
File.join(assemble_public_path(root), uri)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Path where resized photo is stored
|
64
|
+
#
|
65
|
+
# Arguments:
|
66
|
+
# root => /application
|
67
|
+
# source_path => /application/public/uploads/photos/1/car.png
|
68
|
+
# assembled_command => width=auto&height=640&pixel_ratio=1&crop=yes
|
69
|
+
#
|
70
|
+
# Returns /application/public/resized/uploads/photos/1/width=auto&height=640&pixel_ratio=1&crop=yes/car.png
|
71
|
+
#
|
72
|
+
# NOTE: This method performs no validation
|
73
|
+
def assemble_store_path(root, source_path, assembled_command)
|
74
|
+
public = assemble_public_path(root)
|
75
|
+
photo_location = dirname_or_blank(source_path.split(public).last)
|
76
|
+
File.join public, cache_dir, photo_location, assembled_command, File.basename(source_path)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Path to public directory
|
80
|
+
#
|
81
|
+
# Arguments:
|
82
|
+
# root => /application
|
83
|
+
#
|
84
|
+
# Returns /application/public
|
85
|
+
#
|
86
|
+
# NOTE: This method performs no validation
|
87
|
+
def assemble_public_path(root)
|
88
|
+
File.join(root, public_dir)
|
19
89
|
end
|
20
90
|
|
21
91
|
private
|
22
92
|
def dirname_or_blank(path)
|
23
|
-
|
93
|
+
File.dirname(path).sub(/\A\.\z/, '')
|
24
94
|
end
|
25
95
|
end
|
26
96
|
extend Assemble
|
data/lib/photo-cook/command.rb
CHANGED
@@ -8,28 +8,42 @@ module PhotoCook
|
|
8
8
|
# http://www.canbike.org/CSSpixels/
|
9
9
|
def command_regex
|
10
10
|
@command_regex ||= %r{
|
11
|
+
\A
|
11
12
|
width= (?<width> auto|\d{1,4}) &
|
12
13
|
height= (?<height> auto|\d{1,4}) &
|
13
14
|
pixel_ratio= (?<pixel_ratio>[1234]) &
|
14
15
|
crop= (?<crop> yes|no)
|
16
|
+
\z
|
15
17
|
}x
|
16
18
|
end
|
17
19
|
|
18
20
|
# NOTE: This method performs no validation
|
19
21
|
def assemble_command(width, height, pixel_ratio, crop)
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
22
|
+
'width=' + encode_dimension(width) +
|
23
|
+
'&height=' + encode_dimension(height) +
|
24
|
+
'&pixel_ratio=' + encode_pixel_ratio(pixel_ratio) +
|
25
|
+
'&crop=' + encode_crop_option(crop)
|
24
26
|
end
|
25
27
|
|
26
|
-
def
|
28
|
+
def extract_command(resize_uri)
|
29
|
+
resize_uri.split('/')[-2].match(command_regex)
|
30
|
+
end
|
31
|
+
|
32
|
+
def decode_crop_option(crop)
|
27
33
|
crop == 'yes'
|
28
34
|
end
|
29
35
|
|
30
|
-
def
|
36
|
+
def encode_crop_option(crop)
|
31
37
|
crop ? 'yes' : 'no'
|
32
38
|
end
|
39
|
+
|
40
|
+
def encode_dimension(val)
|
41
|
+
val == 0 ? 'auto' : val.to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
def encode_pixel_ratio(ratio)
|
45
|
+
ratio.ceil.to_s
|
46
|
+
end
|
33
47
|
end
|
34
48
|
extend Command
|
35
49
|
end
|
@@ -9,27 +9,27 @@ module PhotoCook
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def check_dimensions!(width, height)
|
12
|
-
raise
|
13
|
-
raise
|
14
|
-
raise
|
12
|
+
raise WidthOutOfBounds if width < 0 || width > 9999
|
13
|
+
raise HeightOutOfBounds if height < 0 || height > 9999
|
14
|
+
raise NoConcreteDimensions if width + height == 0
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
-
class
|
18
|
+
class WidthOutOfBounds < ArgumentError
|
19
19
|
def initialize
|
20
20
|
super 'Width must be positive integer number (0...9999)'
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
-
class
|
24
|
+
class HeightOutOfBounds < ArgumentError
|
25
25
|
def initialize
|
26
26
|
super 'Height must be positive integer number (0...9999)'
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
class
|
30
|
+
class NoConcreteDimensions < ArgumentError
|
31
31
|
def initialize
|
32
|
-
super
|
32
|
+
super 'Both width and height specified as :auto'
|
33
33
|
end
|
34
34
|
end
|
35
35
|
extend Dimensions
|
data/lib/photo-cook/engine.rb
CHANGED
data/lib/photo-cook/logging.rb
CHANGED
@@ -1,8 +1,23 @@
|
|
1
1
|
module PhotoCook
|
2
|
-
|
3
|
-
def
|
4
|
-
|
5
|
-
|
2
|
+
class Logger
|
3
|
+
def initialize(logger)
|
4
|
+
@logger = logger
|
5
|
+
end
|
6
|
+
|
7
|
+
def info(msg)
|
8
|
+
@logger.info(msg)
|
9
|
+
end
|
10
|
+
|
11
|
+
def matched_resize_uri(uri)
|
12
|
+
info %{
|
13
|
+
[PhotoCook] Matched resize URI.
|
14
|
+
#{uri}
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
def performed_resize(photo, w, h, px_ratio, crop, msec)
|
19
|
+
info %{
|
20
|
+
[PhotoCook] Performed resize.
|
6
21
|
Source file: #{photo.source_path}
|
7
22
|
Resized file: #{photo.resized_path}
|
8
23
|
Width: #{w == 0 ? 'auto': "#{w}px"}
|
@@ -11,8 +26,35 @@ module PhotoCook
|
|
11
26
|
Pixel ratio: #{px_ratio}
|
12
27
|
Completed in: #{msec.round(1)}ms
|
13
28
|
}
|
14
|
-
rails_env? ? Rails.logger.info(msg) : print(msg)
|
15
29
|
end
|
30
|
+
|
31
|
+
def will_symlink_cache_dir(cmd)
|
32
|
+
info %{
|
33
|
+
[PhotoCook] Will symlink cache directory.
|
34
|
+
Command: #{cmd}
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
def symlink_cache_dir_success
|
39
|
+
info %{
|
40
|
+
[PhotoCook] Successfully symlink cache directory.
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
def symlink_cache_dir_failure
|
45
|
+
info %{
|
46
|
+
[PhotoCook] Failed to symlink cache directory.
|
47
|
+
}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class STDOut
|
52
|
+
def info(msg)
|
53
|
+
print(msg)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.logger
|
58
|
+
@logger ||= Logger.new(rails_env? ? Rails.logger : STDOut.new)
|
16
59
|
end
|
17
|
-
extend Logging
|
18
60
|
end
|
@@ -8,49 +8,55 @@ module PhotoCook
|
|
8
8
|
@app, @root = app, root
|
9
9
|
end
|
10
10
|
|
11
|
-
# Consider we have car.png in /uploads/photos/1
|
11
|
+
# Consider we have car.png in /uploads/photos/1/car.png
|
12
12
|
# We want to resize it with the following params:
|
13
|
-
# Width:
|
14
|
-
# Height:
|
15
|
-
#
|
16
|
-
#
|
13
|
+
# Width: choose automatically
|
14
|
+
# Height: exactly 640px
|
15
|
+
# Pixel ratio: 1
|
16
|
+
# Crop: yes
|
17
17
|
#
|
18
18
|
# Middleware will handle this URI:
|
19
|
-
# /
|
19
|
+
# /uploads/photos/1/resized/width=auto&height=640&pixel_ratio=1&crop=yes/car.png
|
20
20
|
#
|
21
21
|
def call(env)
|
22
22
|
uri = extract_uri(env)
|
23
23
|
|
24
24
|
return default_actions(env) if
|
25
25
|
|
26
|
-
# Check if
|
27
|
-
|
28
|
-
|
29
|
-
# Check if uri has valid resize command.
|
30
|
-
# uri.split('/')[-2] => width:auto&height:640&crop:true&pixel_ratio:1
|
31
|
-
nil == (uri.split('/')[-2] =~ PhotoCook.command_regex) ||
|
26
|
+
# Check if URI contains PhotoCook resize indicators
|
27
|
+
PhotoCook.resize_uri?(uri) == false ||
|
32
28
|
|
33
29
|
# If for some reasons file exists but request went to Ruby app
|
34
|
-
|
30
|
+
requested_file_exists?(uri)
|
35
31
|
|
36
32
|
# At this point we are sure that this request is targeting to resize photo
|
37
33
|
|
34
|
+
PhotoCook.logger.matched_resize_uri(uri)
|
35
|
+
|
36
|
+
# Matched data: width=auto&height=640&pixel_ratio=1&crop=yes
|
37
|
+
command = PhotoCook.extract_command(uri)
|
38
|
+
|
38
39
|
# Assemble path of the source photo:
|
39
|
-
# => /uploads/photos/1/car.png
|
40
|
-
source_path = assemble_source_path(uri)
|
40
|
+
# => /application/public/uploads/photos/1/car.png
|
41
|
+
source_path = PhotoCook.assemble_source_path(@root, uri)
|
41
42
|
|
42
|
-
#
|
43
|
-
|
43
|
+
# Assemble path of the resized photo:
|
44
|
+
# => /application/public/resized/uploads/photos/1/COMMAND/car.png
|
45
|
+
store_path = PhotoCook.assemble_store_path(@root, source_path, command.to_s)
|
44
46
|
|
45
47
|
# Map crop option values: 'yes' => true, 'no' => false
|
46
|
-
crop
|
48
|
+
crop = PhotoCook.decode_crop_option(command[:crop])
|
47
49
|
|
48
50
|
# Finally resize photo
|
49
51
|
# Resized photo will appear in resize directory
|
50
|
-
photo = PhotoCook.
|
51
|
-
|
52
|
+
photo = PhotoCook.perform_resize(
|
53
|
+
source_path, store_path,
|
54
|
+
command[:width], command[:height],
|
55
|
+
pixel_ratio: command[:pixel_ratio], crop: crop
|
56
|
+
)
|
52
57
|
|
53
58
|
if photo
|
59
|
+
symlink_cache_dir(source_path, store_path)
|
54
60
|
respond_with_file(env)
|
55
61
|
else
|
56
62
|
default_actions(env)
|
@@ -58,31 +64,14 @@ module PhotoCook
|
|
58
64
|
end
|
59
65
|
|
60
66
|
private
|
61
|
-
|
62
|
-
|
63
|
-
# Take URI:
|
64
|
-
# /resized/uploads/photos/1/width:auto&height:640&crop:true&pixel_ratio:1/car.png
|
65
|
-
#
|
66
|
-
# Split by file separator:
|
67
|
-
# ["", "resized", "uploads", "photos", "1", "width:auto&height:640&crop:true&pixel_ratio:1", "car.png"]
|
68
|
-
#
|
69
|
-
els = resize_uri.split('/')
|
70
|
-
|
71
|
-
# Delete PhotoCook directory:
|
72
|
-
# ["", "uploads", "photos", "1", "width:auto&height:640&crop:true&pixel_ratio:1", "car.png"]
|
73
|
-
els.delete_at(1)
|
74
|
-
|
75
|
-
# Delete command string:
|
76
|
-
# ["", "uploads", "photos", "1", "car.png"]
|
77
|
-
els.delete_at(-2)
|
78
|
-
|
79
|
-
URI.decode File.join(@root, PhotoCook.public_dir, els)
|
67
|
+
def public
|
68
|
+
@public ||= PhotoCook.assemble_public_path(@root)
|
80
69
|
end
|
81
70
|
|
82
71
|
def requested_file_exists?(uri)
|
83
72
|
# Check if file exists:
|
84
|
-
# /application/public/
|
85
|
-
File.exists? File.join(
|
73
|
+
# /application/public/uploads/photos/1/resized/width=auto&height=640&pixel_ratio=1&crop=yes/car.png
|
74
|
+
File.exists? File.join(public, uri)
|
86
75
|
end
|
87
76
|
|
88
77
|
def extract_uri(env)
|
@@ -97,9 +86,37 @@ module PhotoCook
|
|
97
86
|
def respond_with_file(env)
|
98
87
|
# http://rubylogs.com/writing-rails-middleware/
|
99
88
|
# https://viget.com/extend/refactoring-patterns-the-rails-middleware-response-handler
|
100
|
-
status, headers, body = Rack::File.new(
|
89
|
+
status, headers, body = Rack::File.new(public).call(env)
|
101
90
|
response = Rack::Response.new(body, status, headers)
|
102
91
|
response.finish
|
103
92
|
end
|
93
|
+
|
94
|
+
def symlink_cache_dir(source_path, store_path)
|
95
|
+
# /application/public/uploads/photos/1
|
96
|
+
p1 = Pathname.new(File.dirname(source_path))
|
97
|
+
|
98
|
+
# /application/public/resized/uploads/photos/1
|
99
|
+
p2 = Pathname.new(File.dirname(File.dirname(store_path)))
|
100
|
+
|
101
|
+
# ../../../resized/uploads/photos/1
|
102
|
+
relative = p2.relative_path_from(p1)
|
103
|
+
|
104
|
+
# Guess cache directory (must be same as PhotoCook.cache_dir)
|
105
|
+
cache_dir = relative.to_s.split(File::SEPARATOR).find { |el| !(el =~ /\A\.\.?\z/) }
|
106
|
+
|
107
|
+
unless Dir.exists?(p1.join(cache_dir))
|
108
|
+
cmd = "cd #{p1} && rm -rf #{cache_dir} && ln -rs #{relative} #{cache_dir}"
|
109
|
+
|
110
|
+
PhotoCook.logger.will_symlink_cache_dir(cmd)
|
111
|
+
|
112
|
+
%x{ #{cmd} }
|
113
|
+
|
114
|
+
if $?.success?
|
115
|
+
PhotoCook.logger.symlink_cache_dir_success
|
116
|
+
else
|
117
|
+
PhotoCook.logger.symlink_cache_dir_failure
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
104
121
|
end
|
105
122
|
end
|
@@ -3,8 +3,8 @@ module PhotoCook
|
|
3
3
|
def parse_and_check_pixel_ratio(unsafe_ratio)
|
4
4
|
pixel_ratio = unsafe_ratio.to_f
|
5
5
|
|
6
|
-
raise
|
7
|
-
raise
|
6
|
+
raise PixelRatioInvalidOrInfinite if pixel_ratio.nan? || pixel_ratio.infinite?
|
7
|
+
raise PixelRatioOutOfBounds if pixel_ratio < 1 || pixel_ratio > 4
|
8
8
|
|
9
9
|
pixel_ratio
|
10
10
|
end
|
@@ -12,15 +12,22 @@ module PhotoCook
|
|
12
12
|
def valid_pixel_ratio?(ratio)
|
13
13
|
!ratio.nan? && !ratio.infinite? && ratio >= 1 && ratio <= 4
|
14
14
|
end
|
15
|
+
|
16
|
+
# Do not produce various number of pixel ratios:
|
17
|
+
# 2.5 => 3
|
18
|
+
# 2.1 => 3
|
19
|
+
def unify_pixel_ratio(px_ratio)
|
20
|
+
px_ratio.ceil
|
21
|
+
end
|
15
22
|
end
|
16
23
|
|
17
|
-
class
|
24
|
+
class PixelRatioInvalidOrInfinite < ArgumentError
|
18
25
|
def initialize
|
19
26
|
super 'Given pixel ratio is invalid number or infinite'
|
20
27
|
end
|
21
28
|
end
|
22
29
|
|
23
|
-
class
|
30
|
+
class PixelRatioOutOfBounds < ArgumentError
|
24
31
|
def initialize
|
25
32
|
super 'Pixel ratio must be positive number: 1 <= pixel_ratio <= 4'
|
26
33
|
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module PhotoCook
|
2
|
+
module API
|
3
|
+
module Magick
|
4
|
+
# Performs photo resizing with ImageMagick:
|
5
|
+
# perform_resize('/application/public/uploads/car.png', '/application/public/resized_car.png', 280, 280)
|
6
|
+
#
|
7
|
+
# Source file: /application/public/uploads/car.png
|
8
|
+
# Result file: /application/public/resized_car.png
|
9
|
+
#
|
10
|
+
# NOTE: This method will perform validation
|
11
|
+
def perform_resize(source_path, store_path, width, height, options = {})
|
12
|
+
started = Time.now
|
13
|
+
|
14
|
+
width, height = parse_and_check_dimensions(width, height)
|
15
|
+
pixel_ratio, crop = open_options(options)
|
16
|
+
# Explicit # Default
|
17
|
+
pixel_ratio = unify_pixel_ratio(parse_and_check_pixel_ratio(pixel_ratio || 1))
|
18
|
+
photo = Resizer.instance.resize(source_path, store_path, width, height, pixel_ratio, !!crop)
|
19
|
+
|
20
|
+
finished = Time.now
|
21
|
+
logger.performed_resize(photo, width, height, pixel_ratio, !!crop, (finished - started) * 1000.0)
|
22
|
+
photo
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module URI
|
27
|
+
# Builds URI which points to PhotoCook::Middleware:
|
28
|
+
# resize('/uploads/car.png', 280, 280, pixel_ratio: 2.5)
|
29
|
+
# => /uploads/resized/width=280&height=280&pixel_ratio=3&crop=no/car.png
|
30
|
+
#
|
31
|
+
# NOTE: This method will perform validation
|
32
|
+
def resize(uri, width, height, options = {})
|
33
|
+
width, height = parse_and_check_dimensions(width, height)
|
34
|
+
pixel_ratio, crop = open_options(options)
|
35
|
+
# Explicit # From cookies # Default
|
36
|
+
pixel_ratio = parse_and_check_pixel_ratio(pixel_ratio || PhotoCook.client_pixel_ratio || 1)
|
37
|
+
assemble_resize_uri(uri, width, height, unify_pixel_ratio(pixel_ratio), !!crop)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Inverse of PhotoCook#resize (see ^):
|
41
|
+
# strip('/uploads/resized/width=280&height=280&pixel_ratio=3&crop=no/car.png')
|
42
|
+
# => /uploads/car.png
|
43
|
+
#
|
44
|
+
# NOTE: This method will perform validation
|
45
|
+
def strip(uri, check = false)
|
46
|
+
# TODO Implement check
|
47
|
+
disassemble_resize_uri(uri)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Shorthand: hresize('/uploads/car.png', 280) <=> resize('/uploads/car.png', 280, nil)
|
51
|
+
#
|
52
|
+
# NOTE: This method will perform validation
|
53
|
+
def hresize(uri, width, options = {})
|
54
|
+
resize(uri, width, :auto, options)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Shorthand: vresize('/uploads/car.png', 280) <=> resize('/uploads/car.png', nil, 280)
|
58
|
+
def vresize(uri, height, options = {})
|
59
|
+
resize(uri, :auto, height, options)
|
60
|
+
end
|
61
|
+
|
62
|
+
def resize_uri?(uri)
|
63
|
+
sections = uri.split('/')
|
64
|
+
|
65
|
+
# Check if PhotoCook cache directory exists:
|
66
|
+
# sections[-3] => resized
|
67
|
+
sections[-3] == cache_dir &&
|
68
|
+
|
69
|
+
# Check if valid resize command exists:
|
70
|
+
# sections[-2] => width=auto&height=640&pixel_ratio=1&crop=yes
|
71
|
+
(sections[-2] =~ command_regex) == 0
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
module Tools
|
76
|
+
def open_options(options)
|
77
|
+
case options
|
78
|
+
when Hash
|
79
|
+
[options[:pixel_ratio], options.fetch(:crop, false)]
|
80
|
+
when Symbol
|
81
|
+
[nil, options == :crop]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
private :open_options
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
extend API::Magick
|
90
|
+
extend API::URI
|
91
|
+
extend API::Tools
|
92
|
+
end
|
data/lib/photo-cook/resizer.rb
CHANGED
@@ -5,11 +5,11 @@ module PhotoCook
|
|
5
5
|
CENTER_GRAVITY = 'Center'.freeze
|
6
6
|
TRANSPARENT_BACKGROUND = 'rgba(255,255,255,0.0)'.freeze
|
7
7
|
|
8
|
-
def resize(
|
8
|
+
def resize(src_path, store_path, w, h, px_ratio = 1, crop = false)
|
9
9
|
if crop
|
10
|
-
resize_to_fill(
|
10
|
+
resize_to_fill(src_path, store_path, w, h, px_ratio)
|
11
11
|
else
|
12
|
-
resize_to_fit(
|
12
|
+
resize_to_fit(src_path, store_path, w, h, px_ratio)
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
@@ -18,12 +18,11 @@ module PhotoCook
|
|
18
18
|
# - new dimensions will be not larger then the specified
|
19
19
|
#
|
20
20
|
# https://github.com/carrierwaveuploader/carrierwave/blob/71cb18bba4a2078524d1ea683f267d3a97aa9bc8/lib/carrierwave/processing/mini_magick.rb#L131
|
21
|
-
def resize_to_fit(
|
21
|
+
def resize_to_fit(source_path, store_path, width, height, pixel_ratio)
|
22
22
|
|
23
23
|
# Do nothing if photo is not valid so exceptions will be not thrown
|
24
|
-
return unless (photo = open(
|
24
|
+
return unless (photo = open(source_path)) && photo.valid?
|
25
25
|
|
26
|
-
store_path = assemble_store_path(photo_path, width, height, pixel_ratio, false)
|
27
26
|
width, height = multiply_dimensions(width, height, pixel_ratio)
|
28
27
|
|
29
28
|
photo.combine_options { |cmd| cmd.resize "#{literal_dimensions(width, height)}>" }
|
@@ -36,12 +35,11 @@ module PhotoCook
|
|
36
35
|
# - the photo will be cropped if necessary
|
37
36
|
#
|
38
37
|
# https://github.com/carrierwaveuploader/carrierwave/blob/71cb18bba4a2078524d1ea683f267d3a97aa9bc8/lib/carrierwave/processing/mini_magick.rb#L176
|
39
|
-
def resize_to_fill(
|
38
|
+
def resize_to_fill(source_path, store_path, width, height, pixel_ratio)
|
40
39
|
|
41
40
|
# Do nothing if photo is not valid so exceptions will be not thrown
|
42
|
-
return unless (photo = open(
|
41
|
+
return unless (photo = open(source_path)) && photo.valid?
|
43
42
|
|
44
|
-
store_path = assemble_store_path(photo_path, width, height, pixel_ratio, true)
|
45
43
|
cols, rows = photo[:dimensions]
|
46
44
|
mwidth, mheight = multiply_dimensions(width, height, pixel_ratio)
|
47
45
|
|
@@ -78,32 +76,26 @@ module PhotoCook
|
|
78
76
|
|
79
77
|
protected
|
80
78
|
|
81
|
-
def open(
|
79
|
+
def open(source_path)
|
82
80
|
begin
|
83
81
|
# MiniMagick::Image.open creates a temporary file for us and protects original
|
84
|
-
photo = MagickPhoto.open(
|
85
|
-
photo.source_path =
|
82
|
+
photo = MagickPhoto.open(source_path)
|
83
|
+
photo.source_path = source_path
|
86
84
|
photo
|
87
85
|
rescue
|
88
86
|
nil
|
89
87
|
end
|
90
88
|
end
|
91
89
|
|
92
|
-
def store(resized_photo,
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
resized_photo.write(path_to_store_at)
|
97
|
-
resized_photo.resized_path = path_to_store_at
|
90
|
+
def store(resized_photo, store_path)
|
91
|
+
FileUtils.mkdir_p(File.dirname(store_path))
|
92
|
+
resized_photo.write(store_path)
|
93
|
+
resized_photo.resized_path = store_path
|
98
94
|
resized_photo
|
99
95
|
end
|
100
96
|
|
101
97
|
def literal_dimensions(width, height)
|
102
|
-
"#{width
|
103
|
-
end
|
104
|
-
|
105
|
-
def assemble_store_path(path, width, height, pixel_ratio, crop)
|
106
|
-
PhotoCook.assemble_store_path(path, width, height, pixel_ratio, crop)
|
98
|
+
"#{width if width != 0}x#{height if height != 0}"
|
107
99
|
end
|
108
100
|
|
109
101
|
def multiply_dimensions(width, height, ratio)
|
data/lib/photo-cook/version.rb
CHANGED
data/lib/photo-cook.rb
CHANGED
@@ -1,25 +1,25 @@
|
|
1
1
|
require 'fileutils'
|
2
|
+
require 'pathname'
|
2
3
|
require 'rake'
|
3
4
|
require 'mini_magick'
|
4
5
|
|
5
6
|
module PhotoCook
|
6
7
|
def self.rails_env?
|
7
|
-
defined?(Rails)
|
8
|
+
@rails.nil? ? @rails = !!defined?(Rails) : @rails
|
8
9
|
end
|
9
10
|
end
|
10
11
|
|
11
12
|
require 'photo-cook/dimensions'
|
12
13
|
require 'photo-cook/pixel-ratio'
|
13
|
-
require 'photo-cook/
|
14
|
+
require 'photo-cook/dirs'
|
14
15
|
require 'photo-cook/command'
|
15
16
|
require 'photo-cook/assemble'
|
16
|
-
require 'photo-cook/callbacks'
|
17
17
|
require 'photo-cook/logging'
|
18
18
|
require 'photo-cook/resizer'
|
19
19
|
require 'photo-cook/middleware'
|
20
20
|
require 'photo-cook/magick-photo'
|
21
21
|
require 'photo-cook/carrierwave'
|
22
|
-
require 'photo-cook/
|
22
|
+
require 'photo-cook/resize-api'
|
23
23
|
|
24
24
|
if PhotoCook.rails_env?
|
25
25
|
require 'photo-cook/engine'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: photo-cook
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yaroslav Konoplov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-12-
|
11
|
+
date: 2015-12-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -47,25 +47,24 @@ extra_rdoc_files: []
|
|
47
47
|
files:
|
48
48
|
- ".gitignore"
|
49
49
|
- Gemfile
|
50
|
-
-
|
50
|
+
- README.md
|
51
51
|
- Rakefile
|
52
52
|
- app/assets/javascripts/photo-cook.js
|
53
53
|
- app/assets/javascripts/photo-cook/photo-cook.js.erb
|
54
54
|
- lib/photo-cook.rb
|
55
55
|
- lib/photo-cook/assemble.rb
|
56
|
-
- lib/photo-cook/callbacks.rb
|
57
56
|
- lib/photo-cook/carrierwave.rb
|
58
57
|
- lib/photo-cook/command.rb
|
59
58
|
- lib/photo-cook/dimensions.rb
|
59
|
+
- lib/photo-cook/dirs.rb
|
60
60
|
- lib/photo-cook/engine.rb
|
61
61
|
- lib/photo-cook/logging.rb
|
62
62
|
- lib/photo-cook/magick-photo.rb
|
63
63
|
- lib/photo-cook/middleware.rb
|
64
|
-
- lib/photo-cook/paths.rb
|
65
64
|
- lib/photo-cook/pixel-ratio-spy.rb
|
66
65
|
- lib/photo-cook/pixel-ratio.rb
|
66
|
+
- lib/photo-cook/resize-api.rb
|
67
67
|
- lib/photo-cook/resizer.rb
|
68
|
-
- lib/photo-cook/resizing.rb
|
69
68
|
- lib/photo-cook/version.rb
|
70
69
|
- photo-cook.gemspec
|
71
70
|
homepage: http://github.com/yivo/photo-cook
|
data/Gemfile.lock
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
photo-cook (1.0.2)
|
5
|
-
mini_magick (~> 4.0)
|
6
|
-
rack (~> 1.5)
|
7
|
-
|
8
|
-
GEM
|
9
|
-
remote: https://rubygems.org/
|
10
|
-
specs:
|
11
|
-
mini_magick (4.1.0)
|
12
|
-
rack (1.6.4)
|
13
|
-
|
14
|
-
PLATFORMS
|
15
|
-
ruby
|
16
|
-
|
17
|
-
DEPENDENCIES
|
18
|
-
mini_magick
|
19
|
-
photo-cook!
|
data/lib/photo-cook/callbacks.rb
DELETED
data/lib/photo-cook/paths.rb
DELETED
data/lib/photo-cook/resizing.rb
DELETED
@@ -1,68 +0,0 @@
|
|
1
|
-
module PhotoCook
|
2
|
-
module Resizing
|
3
|
-
|
4
|
-
# Performs photo resizing:
|
5
|
-
# resize_photo('/application/public/uploads/car.png', 280, 280)
|
6
|
-
#
|
7
|
-
# NOTE: This method will perform validation
|
8
|
-
def resize_photo(path, width, height, options = {})
|
9
|
-
started = Time.now
|
10
|
-
|
11
|
-
width, height = parse_and_check_dimensions(width, height)
|
12
|
-
pixel_ratio, crop = open_options(options)
|
13
|
-
# Explicit # Default
|
14
|
-
pixel_ratio = unify_pixel_ratio(parse_and_check_pixel_ratio(pixel_ratio || 1))
|
15
|
-
photo = Resizer.instance.resize(path, width, height, pixel_ratio, !!crop)
|
16
|
-
|
17
|
-
finished = Time.now
|
18
|
-
log_resize(photo, width, height, pixel_ratio, !!crop, (finished - started) * 1000.0)
|
19
|
-
photo
|
20
|
-
end
|
21
|
-
|
22
|
-
# Builds URI for resizing:
|
23
|
-
# resize('/uploads/car.png', 280, 280, pixel_ratio: 2.5)
|
24
|
-
# => /resized/uploads/width:280&height:280&crop:0&pixel_ratio:3
|
25
|
-
#
|
26
|
-
# NOTE: This method will perform validation
|
27
|
-
def build_resize_uri(uri, width, height, options = {})
|
28
|
-
width, height = parse_and_check_dimensions(width, height)
|
29
|
-
pixel_ratio, crop = open_options(options)
|
30
|
-
# Explicit # From cookies # Default
|
31
|
-
pixel_ratio = parse_and_check_pixel_ratio(pixel_ratio || PhotoCook.client_pixel_ratio || 1)
|
32
|
-
assemble_uri(uri, width, height, unify_pixel_ratio(pixel_ratio), !!crop)
|
33
|
-
end
|
34
|
-
|
35
|
-
alias resize build_resize_uri
|
36
|
-
|
37
|
-
# Shorthand: hresize('/uploads/car.png', 280) <=> resize('/uploads/car.png', 280, nil)
|
38
|
-
def hresize(uri, width, options = {})
|
39
|
-
resize(uri, width, :auto, options)
|
40
|
-
end
|
41
|
-
|
42
|
-
# Shorthand: vresize('/uploads/car.png', 280) <=> resize('/uploads/car.png', nil, 280)
|
43
|
-
def vresize(uri, height, options = {})
|
44
|
-
resize(uri, :auto, height, options)
|
45
|
-
end
|
46
|
-
|
47
|
-
private
|
48
|
-
def open_options(options)
|
49
|
-
case options
|
50
|
-
when Hash
|
51
|
-
[options[:pixel_ratio], options.fetch(:crop, false)]
|
52
|
-
when Symbol
|
53
|
-
[nil, options == :crop]
|
54
|
-
else
|
55
|
-
[nil, nil]
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
# Do not produce various number of pixel ratios:
|
60
|
-
# 2.5 => 3
|
61
|
-
# 2.1 => 3
|
62
|
-
def unify_pixel_ratio(px_ratio)
|
63
|
-
px_ratio.ceil
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
extend Resizing
|
68
|
-
end
|