line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Mojolicious::Command::Author::generate::obrazi; |
2
|
1
|
|
|
1
|
|
1066
|
use feature ':5.26'; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
133
|
|
3
|
1
|
|
|
1
|
|
8
|
use Mojo::Base Mojolicious::Command => -signatures; |
|
1
|
|
|
|
|
13
|
|
|
1
|
|
|
|
|
14
|
|
4
|
1
|
|
|
1
|
|
5085
|
use Mojo::File 'path'; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
62
|
|
5
|
1
|
|
|
1
|
|
11
|
use Mojo::Util qw(url_escape punycode_decode punycode_encode getopt encode decode dumper); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
85
|
|
6
|
1
|
|
|
1
|
|
7
|
use Mojo::Collection 'c'; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
59
|
|
7
|
1
|
|
|
1
|
|
1229
|
use Text::CSV_XS qw( csv ); |
|
1
|
|
|
|
|
13722
|
|
|
1
|
|
|
|
|
78
|
|
8
|
1
|
|
|
1
|
|
8
|
use Mojo::Loader qw(data_section); |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
53
|
|
9
|
1
|
|
|
1
|
|
1109
|
use Imager; |
|
1
|
|
|
|
|
40105
|
|
|
1
|
|
|
|
|
10
|
|
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
my $_formats = join '|', map { $_ =~ /jpe?g/i ? 'jpe?g' : $_ } sort keys %Imager::formats; |
12
|
|
|
|
|
|
|
my $FILETYPES = qr/\.(?:$_formats)$/i; |
13
|
0
|
|
|
0
|
|
|
my sub _U {'UTF-8'} |
14
|
|
|
|
|
|
|
has description => 'Generate a gallery from a directory structure with images'; |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
# our own log, used instead of 'say'. |
17
|
|
|
|
|
|
|
has log => sub { |
18
|
|
|
|
|
|
|
Mojo::Log->new(format => sub { "[$$] [$_[1]] " . join(' ', @_[2 .. $#_]) . $/ }); |
19
|
|
|
|
|
|
|
}; |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
has usage => sub { shift->extract_usage . $/ . 'Supported formats: ' . $FILETYPES . $/ }; |
22
|
|
|
|
|
|
|
has from_dir => sub { path('./')->to_abs }; |
23
|
|
|
|
|
|
|
has images => sub { |
24
|
|
|
|
|
|
|
$_[0]->matrix->grep(sub { $_->[1] =~ $FILETYPES }); |
25
|
|
|
|
|
|
|
}; |
26
|
|
|
|
|
|
|
has _linked_images => sub { |
27
|
|
|
|
|
|
|
my $c = 0; |
28
|
|
|
|
|
|
|
my $images = $_[0]->images; |
29
|
|
|
|
|
|
|
return {map { $_->[1] => {self => $_, prev => $images->[$c - 1], next => $images->[$c + 1], id => ++$c} } @$images}; |
30
|
|
|
|
|
|
|
}; |
31
|
|
|
|
|
|
|
has categories => sub { |
32
|
|
|
|
|
|
|
$_[0]->matrix->grep(sub { !$_->[-1] && !$_->[-2] && $_->[1] =~ /$_->[0]$/ }); |
33
|
|
|
|
|
|
|
}; |
34
|
|
|
|
|
|
|
has files_per_subproc => sub { |
35
|
|
|
|
|
|
|
return int(scalar(@{$_[0]->images}) / $_[0]->subprocs_num) + 1; |
36
|
|
|
|
|
|
|
}; |
37
|
|
|
|
|
|
|
has csv_filename => 'index.csv'; |
38
|
|
|
|
|
|
|
has subprocs_num => 4; |
39
|
|
|
|
|
|
|
has template_file => ''; |
40
|
|
|
|
|
|
|
has obrazec_file => ''; |
41
|
|
|
|
|
|
|
has to_dir => sub { $_[0]->app->home->child('public') }; |
42
|
|
|
|
|
|
|
has publish_url => '/obrazi.html'; |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
# Default titles and descriptions |
45
|
|
|
|
|
|
|
has defaults => sub { { |
46
|
|
|
|
|
|
|
author => 'Марио Беров', |
47
|
|
|
|
|
|
|
category_title => 'Заглавие на категорията', |
48
|
|
|
|
|
|
|
category_description => 'Описание на категорията', |
49
|
|
|
|
|
|
|
image_title => 'Заглавие на изображението', |
50
|
|
|
|
|
|
|
image_description => 'Описание на изображението' |
51
|
|
|
|
|
|
|
. $/ |
52
|
|
|
|
|
|
|
. ' Материали, размери,какво, защо - според каквото мислиш, че е важно.', |
53
|
|
|
|
|
|
|
} }; |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
# An empty Imager instance on which the read() method will be called for every |
56
|
|
|
|
|
|
|
# image we work with. |
57
|
|
|
|
|
|
|
has imager => sub { Imager->new }; |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
my @header = qw(category path title description author image thumbnail); |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
# csv file contents |
62
|
|
|
|
|
|
|
has matrix => sub { c([@header]) }; |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
# resized images |
65
|
|
|
|
|
|
|
has _processed => sub { c() }; |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
# '1000x1000' |
68
|
|
|
|
|
|
|
sub max { |
69
|
0
|
0
|
|
0
|
1
|
|
if ($_[1]) { |
70
|
0
|
0
|
0
|
|
|
|
$_[0]->{max} = $_[1] && return $_[0] if ref $_[1]; |
71
|
0
|
|
|
|
|
|
($_[0]->{max}{width}, $_[0]->{max}{height}) = $_[1] =~ /(\d+)x(\d+)/; |
72
|
0
|
|
|
|
|
|
return $_[0]; |
73
|
|
|
|
|
|
|
} |
74
|
0
|
|
0
|
|
|
|
return $_[0]->{max} //= {width => 1000, height => 1000}; |
75
|
|
|
|
|
|
|
} |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
# '100x100' |
78
|
|
|
|
|
|
|
sub thumbs { |
79
|
0
|
0
|
|
0
|
1
|
|
if ($_[1]) { |
80
|
0
|
0
|
0
|
|
|
|
$_[0]->{thumbs} = $_[1] && return $_[0] if ref $_[1]; |
81
|
0
|
|
|
|
|
|
($_[0]->{thumbs}{width}, $_[0]->{thumbs}{height}) = $_[1] =~ /(\d+)x(\d+)/; |
82
|
0
|
|
|
|
|
|
return $_[0]; |
83
|
|
|
|
|
|
|
} |
84
|
0
|
|
0
|
|
|
|
return $_[0]->{thumbs} //= {width => 100, height => 100}; |
85
|
|
|
|
|
|
|
} |
86
|
|
|
|
|
|
|
|
87
|
0
|
|
|
0
|
1
|
|
sub run ($self, @args) { |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
88
|
0
|
|
|
|
|
|
getopt \@args, |
89
|
|
|
|
|
|
|
'f|from=s' => \(my $from_dir = $self->from_dir), |
90
|
|
|
|
|
|
|
'to=s' => \(my $to_dir = $self->to_dir), |
91
|
|
|
|
|
|
|
'x|max=s' => \(my $max = $self->max), |
92
|
|
|
|
|
|
|
's|thumbs=s' => \(my $thumbs = $self->thumbs), |
93
|
|
|
|
|
|
|
'i|index=s' => \(my $csv_filename = $self->csv_filename), |
94
|
|
|
|
|
|
|
't|template=s' => \(my $template = $self->template_file), |
95
|
|
|
|
|
|
|
'o|obrazec=s' => \(my $obrazec = $self->obrazec_file), |
96
|
|
|
|
|
|
|
'u|url=s' => \(my $url = $self->publish_url), |
97
|
|
|
|
|
|
|
; |
98
|
0
|
0
|
0
|
|
|
|
if ($template ne $self->template_file and not -f $template) { |
99
|
0
|
|
|
|
|
|
Carp::croak("Template $template does not exist. " . "Please provide an existing template file."); |
100
|
|
|
|
|
|
|
} |
101
|
0
|
0
|
|
|
|
|
$self->template_file($template) if $template; |
102
|
|
|
|
|
|
|
|
103
|
0
|
0
|
0
|
|
|
|
if ($obrazec ne $self->obrazec_file and not -f $obrazec) { |
104
|
0
|
|
|
|
|
|
Carp::croak("Single image template $obrazec does not exist. " . "Please provide an existing template file."); |
105
|
|
|
|
|
|
|
} |
106
|
0
|
0
|
|
|
|
|
$self->obrazec_file($obrazec) if $obrazec; |
107
|
|
|
|
|
|
|
|
108
|
0
|
|
|
|
|
|
$self->from_dir(path($from_dir)->to_abs)->to_dir(path($to_dir)->to_abs)->max($max)->thumbs($thumbs) |
109
|
|
|
|
|
|
|
->csv_filename($csv_filename)->publish_url(decode _U, $url); |
110
|
0
|
|
|
|
|
|
return $self->_do_csv->_resize_and_copy_to_dir->_do_html; |
111
|
|
|
|
|
|
|
} |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
# Calculates the resized image dimensions according to the C<$self-Emax> |
114
|
|
|
|
|
|
|
# and C<$self-Ethumbs> gallery contraints. Accepts the utf8 decoded path |
115
|
|
|
|
|
|
|
# and the raw path to the file to be worked on. Returns two empty strings if |
116
|
|
|
|
|
|
|
# there is error reading the image and warns about the error. Returns filenames |
117
|
|
|
|
|
|
|
# for the resized image and the thumbnail image. |
118
|
0
|
|
|
0
|
1
|
|
sub calculate_max_and_thumbs ($self, $path, $raw_path) { |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
119
|
0
|
|
|
|
|
|
state $imager = $self->imager; |
120
|
0
|
|
|
|
|
|
my $log = $self->log; |
121
|
0
|
|
|
|
|
|
my $img; |
122
|
0
|
|
|
|
|
|
my $image = [$raw_path->to_array->[-1] =~ /^(.+?)\.(.\w+)$/]; |
123
|
0
|
|
|
|
|
|
$log->info('Inspecting image ', $path); |
124
|
|
|
|
|
|
|
|
125
|
0
|
|
|
|
|
|
my $max = $self->max; |
126
|
0
|
|
|
|
|
|
my $thumbs = $self->thumbs; |
127
|
0
|
|
|
|
|
|
my %size = %$max; |
128
|
0
|
|
|
|
|
|
my %thumb_size = %$thumbs; |
129
|
0
|
|
|
|
|
|
my $fh = $raw_path->open(); |
130
|
0
|
0
|
|
|
|
|
unless ($fh->binmode) { |
131
|
0
|
|
|
|
|
|
$log->warn(" !!! Skipping $path. Error: " . $!); |
132
|
0
|
|
|
|
|
|
return ('', ''); |
133
|
|
|
|
|
|
|
} |
134
|
0
|
0
|
|
|
|
|
if (not eval { $img = $imager->read(fh => $fh) }) { |
|
0
|
|
|
|
|
|
|
135
|
0
|
|
|
|
|
|
$log->warn(" !!! Skipping $path. Image error: " . $imager->errstr()); |
136
|
0
|
|
|
|
|
|
return ('', ''); |
137
|
|
|
|
|
|
|
} |
138
|
|
|
|
|
|
|
else { |
139
|
0
|
|
|
|
|
|
$image->[0] = decode _U, $image->[0]; |
140
|
0
|
|
|
|
|
|
%size = (width => $img->getwidth, height => $img->getheight); |
141
|
0
|
|
|
|
|
|
%thumb_size = %size; |
142
|
0
|
0
|
0
|
|
|
|
if ($size{width} > $max->{width} || $size{height} > $max->{height}) { |
143
|
|
|
|
|
|
|
@size{qw(x_scale y_scale width height)} |
144
|
0
|
|
|
|
|
|
= $img->scale_calculate(xpixels => $max->{width}, ypixels => $max->{height}, type => 'min'); |
145
|
|
|
|
|
|
|
} |
146
|
|
|
|
|
|
|
|
147
|
0
|
0
|
0
|
|
|
|
if ($thumb_size{width} > $thumbs->{width} || $thumb_size{height} > $thumbs->{height}) { |
148
|
|
|
|
|
|
|
@thumb_size{qw(x_scale y_scale width height)} |
149
|
0
|
|
|
|
|
|
= $img->scale_calculate(xpixels => $thumbs->{width}, ypixels => $thumbs->{height}, type => 'min'); |
150
|
|
|
|
|
|
|
} |
151
|
|
|
|
|
|
|
} |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
return ( |
154
|
0
|
|
|
|
|
|
punycode_encode($image->[0]) . "_$size{width}x$size{height}.$image->[1]", |
155
|
|
|
|
|
|
|
punycode_encode($image->[0]) . "_$thumb_size{width}x$thumb_size{height}.$image->[1]" |
156
|
|
|
|
|
|
|
); |
157
|
|
|
|
|
|
|
} |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
# Reads the `from_dir` and dumps a csv file named after the from_dir folder. |
160
|
|
|
|
|
|
|
# The file contains a table with paths and default titles and descriptions for |
161
|
|
|
|
|
|
|
# the pictures. This file can be given to the painter to add titles and |
162
|
|
|
|
|
|
|
# descriptions for the pictures using an application like LibreOffice Calc or |
163
|
|
|
|
|
|
|
# M$ Excel. |
164
|
0
|
|
|
0
|
|
|
sub _do_csv ($self, $root = $self->from_dir) { |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
165
|
0
|
|
|
|
|
|
my $csv_filepath = decode _U, $root->child($self->csv_filename); |
166
|
0
|
|
|
|
|
|
my $log = $self->log; |
167
|
0
|
0
|
|
|
|
|
if (-f $csv_filepath) { |
168
|
0
|
|
|
|
|
|
$log->info("$csv_filepath already exists.$/" |
169
|
|
|
|
|
|
|
. "\tContinuing with resizing and copying images.$/" |
170
|
|
|
|
|
|
|
. "\tSome rows may be updated with filenames for resized images and thumbnails...$/"); |
171
|
0
|
|
|
|
|
|
return $self; |
172
|
|
|
|
|
|
|
} |
173
|
0
|
|
|
|
|
|
my $category = ''; |
174
|
0
|
|
|
|
|
|
my $defaults = $self->defaults; |
175
|
0
|
|
|
|
|
|
my $matrix = $self->matrix; |
176
|
0
|
|
|
|
|
|
$root->list_tree({dir => 1})->sort->each( |
177
|
0
|
|
|
0
|
|
|
sub ($e, $num) { |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
178
|
0
|
|
|
|
|
|
$self->_make_matrix_row($root, $e, \$category, $defaults, $matrix, $log); |
179
|
|
|
|
|
|
|
} |
180
|
0
|
|
|
|
|
|
); |
181
|
0
|
|
|
|
|
|
csv(in => $matrix->to_array, enc => _U, out => \my $data, binary => 1, sep_char => ","); |
182
|
0
|
|
|
|
|
|
path($csv_filepath)->spurt($data); |
183
|
|
|
|
|
|
|
|
184
|
0
|
|
|
|
|
|
return $self; |
185
|
|
|
|
|
|
|
} |
186
|
|
|
|
|
|
|
|
187
|
0
|
|
|
0
|
|
|
sub _make_matrix_row ($self, $root, $raw_path, $category, $defaults, $matrix, $log) { |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
188
|
0
|
|
|
|
|
|
my $path = decode(_U, $_->to_string =~ s|$root/||r); |
189
|
0
|
0
|
|
|
|
|
if (-d $raw_path) { |
|
|
0
|
|
|
|
|
|
190
|
0
|
|
|
|
|
|
$log->info("Inspecting category $path"); |
191
|
0
|
|
|
|
|
|
$$category = decode(_U, $_->to_array->[-1]); |
192
|
|
|
|
|
|
|
push @$matrix, |
193
|
|
|
|
|
|
|
[ |
194
|
|
|
|
|
|
|
$$category, $path, |
195
|
|
|
|
|
|
|
"$defaults->{category_title} – $$category", |
196
|
|
|
|
|
|
|
$defaults->{category_description}, |
197
|
0
|
|
|
|
|
|
$defaults->{author}, '', '', |
198
|
|
|
|
|
|
|
]; |
199
|
|
|
|
|
|
|
} |
200
|
|
|
|
|
|
|
elsif (-f $raw_path) { |
201
|
0
|
0
|
|
|
|
|
if ($_ !~ $FILETYPES) { |
202
|
0
|
|
|
|
|
|
$log->warn("Skipping unsupported file $path…"); |
203
|
0
|
|
|
|
|
|
return; |
204
|
|
|
|
|
|
|
} |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
# for images without category - which are in the $root folder |
207
|
0
|
0
|
|
|
|
|
$$category = '' unless $path =~ /$$category/; |
208
|
|
|
|
|
|
|
push @$matrix, |
209
|
|
|
|
|
|
|
[ |
210
|
|
|
|
|
|
|
$$category, $path, |
211
|
|
|
|
|
|
|
"$defaults->{image_title} – " . path($path)->basename, |
212
|
|
|
|
|
|
|
$defaults->{image_description}, |
213
|
0
|
|
|
|
|
|
$defaults->{author}, $self->calculate_max_and_thumbs($path, $raw_path) |
214
|
|
|
|
|
|
|
]; |
215
|
|
|
|
|
|
|
} |
216
|
|
|
|
|
|
|
} |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
# Scales and resizes images to maximum width and height and generates thumbnails. |
219
|
0
|
|
|
0
|
|
|
sub _resize_and_copy_to_dir($self) { |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
220
|
0
|
|
|
|
|
|
my $matrix = $self->matrix; |
221
|
0
|
|
|
|
|
|
my $csv_filepath = decode _U, $self->from_dir->child($self->csv_filename); |
222
|
0
|
0
|
|
|
|
|
if (@$matrix == 1) { |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
# read the CSV file from disk to get calculated dimensions |
225
|
0
|
|
|
|
|
|
$matrix = c @{csv(in => $csv_filepath, enc => _U, binary => 1, sep_char => ",")}; |
|
0
|
|
|
|
|
|
|
226
|
0
|
|
|
|
|
|
$self->matrix($matrix); |
227
|
|
|
|
|
|
|
} |
228
|
|
|
|
|
|
|
|
229
|
0
|
|
|
|
|
|
my @subprocs; |
230
|
0
|
|
|
|
|
|
my $chunk_size = $self->files_per_subproc; |
231
|
0
|
|
|
0
|
|
|
my $images = $self->images->map(sub { [@$_] }); #copy |
|
0
|
|
|
|
|
|
|
232
|
0
|
|
|
|
|
|
while (my @chunk = splice @$images, 0, $chunk_size) { |
233
|
0
|
|
|
|
|
|
push @subprocs, $self->_process_chunk_of_files(\@chunk); |
234
|
|
|
|
|
|
|
} |
235
|
|
|
|
|
|
|
|
236
|
0
|
|
|
|
|
|
foreach my $s (@subprocs) { |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
# Wait for the subprocess to finish |
239
|
0
|
|
|
|
|
|
$s->wait; |
240
|
|
|
|
|
|
|
} |
241
|
0
|
|
|
|
|
|
$self->log->info('All subprocesses finished.'); |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
# Update image and thumbnail columns and write the new csv file |
244
|
0
|
|
|
|
|
|
my $_processed = $self->_processed; |
245
|
0
|
|
|
|
|
|
my $updated = 0; |
246
|
|
|
|
|
|
|
$self->matrix->each(sub { |
247
|
0
|
0
|
|
0
|
|
|
return unless $_->[1] =~ $FILETYPES; |
248
|
0
|
|
|
|
|
|
for my $i (0 .. @$_processed - 1) { |
249
|
0
|
0
|
0
|
|
|
|
if ($_->[1] eq $_processed->[$i][1] && ($_->[-1] ne $_processed->[$i][-1] || $_->[-2] ne $_processed->[$i][-2])) { |
|
|
|
0
|
|
|
|
|
250
|
0
|
|
|
|
|
|
($_) = splice @$_processed, $i, 1; |
251
|
0
|
|
|
|
|
|
$updated++; |
252
|
0
|
|
|
|
|
|
last; |
253
|
|
|
|
|
|
|
} |
254
|
|
|
|
|
|
|
} |
255
|
0
|
|
|
|
|
|
}); |
256
|
0
|
0
|
|
|
|
|
if ($updated) { |
257
|
0
|
|
|
|
|
|
csv(in => $matrix->to_array, enc => _U, out => \my $data, binary => 1, sep_char => ","); |
258
|
0
|
|
|
|
|
|
path($csv_filepath)->spurt($data); |
259
|
0
|
|
|
|
|
|
my $copied = path($csv_filepath)->copy_to($self->to_dir); |
260
|
0
|
|
|
|
|
|
$self->log->info("$csv_filepath *updated* and copied to $copied"); |
261
|
|
|
|
|
|
|
} |
262
|
|
|
|
|
|
|
else { |
263
|
0
|
|
|
|
|
|
my $copied = path($csv_filepath)->copy_to($self->to_dir); |
264
|
0
|
|
|
|
|
|
$self->log->info("$csv_filepath copied to $copied"); |
265
|
|
|
|
|
|
|
} |
266
|
0
|
|
|
|
|
|
return $self; |
267
|
|
|
|
|
|
|
} |
268
|
|
|
|
|
|
|
|
269
|
0
|
|
|
0
|
|
|
sub _process_chunk_of_files ($self, $files = []) { |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
270
|
0
|
|
|
|
|
|
my $log = $self->log; |
271
|
0
|
|
|
|
|
|
my $from_dir = $self->from_dir; |
272
|
0
|
|
|
|
|
|
my $to_dir = $self->to_dir; |
273
|
0
|
|
|
|
|
|
my $imager = $self->imager; |
274
|
0
|
|
|
0
|
|
|
Mojo::IOLoop->subprocess->run_p(sub($sub) { |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
275
|
0
|
|
|
|
|
|
my $processed = []; |
276
|
0
|
|
|
|
|
|
for my $row (@$files) { |
277
|
0
|
|
|
|
|
|
my $raw_path = $from_dir->child(encode _U, $row->[1]); |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
# Check for calculated dimensions and calculate them if missing. |
280
|
0
|
0
|
0
|
|
|
|
if (!$row->[-2] || !$row->[-1]) { |
281
|
0
|
|
|
|
|
|
($row->[-2], $row->[-1]) = $self->calculate_max_and_thumbs($row->[1], $raw_path); |
282
|
|
|
|
|
|
|
} |
283
|
0
|
|
|
|
|
|
my $to_path = $to_dir->child($row->[1])->dirname; |
284
|
0
|
|
|
|
|
|
my $sized_path = $to_path->child($row->[-2])->to_string; |
285
|
0
|
|
|
|
|
|
my $thumb_path = $to_path->child($row->[-1])->to_string; |
286
|
0
|
|
|
|
|
|
my $html_path = "$sized_path.html"; |
287
|
0
|
0
|
0
|
|
|
|
if (-s $sized_path && -s $thumb_path && -s $html_path) { |
|
|
|
0
|
|
|
|
|
288
|
0
|
|
|
|
|
|
$log->info("$row->[-2].html, $row->[-2] and $row->[-1] were already produced. Skipping ..."); |
289
|
0
|
|
|
|
|
|
push @$processed, $row; |
290
|
0
|
|
|
|
|
|
next; |
291
|
|
|
|
|
|
|
} |
292
|
0
|
|
|
|
|
|
my (%sized, %thumb); |
293
|
0
|
|
|
|
|
|
@sized{qw(xpixels ypixels)} = $row->[-2] =~ /_(\d+)x(\d+)\./; |
294
|
0
|
|
|
|
|
|
@thumb{qw(xpixels ypixels)} = $row->[-1] =~ /_(\d+)x(\d+)\./; |
295
|
0
|
|
|
|
|
|
my $img; |
296
|
0
|
0
|
|
|
|
|
unless (-s $sized_path) { |
297
|
0
|
0
|
|
|
|
|
unless (eval { $img = $imager->read(file => $raw_path) }) { |
|
0
|
|
|
|
|
|
|
298
|
0
|
|
|
|
|
|
$log->warn(" !!! Skipping $row->[1]. Image error: " . $imager->errstr()); |
299
|
0
|
|
|
|
|
|
next; |
300
|
|
|
|
|
|
|
} |
301
|
0
|
0
|
|
|
|
|
unless (eval { $to_path->make_path({mode => 0711}); 1 }) { |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
302
|
0
|
|
|
|
|
|
$log->warn("!!! Skipping $row->[1]. Error: $@"); |
303
|
0
|
|
|
|
|
|
next; |
304
|
|
|
|
|
|
|
} |
305
|
0
|
|
|
|
|
|
my $maxi = $img->scale(%sized); |
306
|
0
|
|
|
|
|
|
$maxi->settag(name => 'i_xres', value => 96); |
307
|
0
|
|
|
|
|
|
$maxi->settag(name => 'i_yres', value => 96); |
308
|
0
|
0
|
|
|
|
|
unless ($maxi->write(file => $sized_path)) { |
309
|
0
|
|
|
|
|
|
$log->warn("!!! Cannot write image $sized_path!\nError:" . $maxi->errstr); |
310
|
|
|
|
|
|
|
} |
311
|
|
|
|
|
|
|
else { |
312
|
0
|
|
|
|
|
|
$log->info("Written $sized_path"); |
313
|
|
|
|
|
|
|
} |
314
|
|
|
|
|
|
|
} |
315
|
0
|
0
|
|
|
|
|
unless (-s $thumb_path) { |
316
|
0
|
|
|
|
|
|
my $thumbi = $img->scale(%thumb); |
317
|
0
|
|
|
|
|
|
$thumbi->settag(name => 'i_xres', value => 96); |
318
|
0
|
|
|
|
|
|
$thumbi->settag(name => 'i_yres', value => 96); |
319
|
0
|
0
|
|
|
|
|
unless ($thumbi->write(file => $thumb_path)) { |
320
|
0
|
|
|
|
|
|
$log->warn("!!! Cannot write image $thumb_path!\nError:" . $thumbi->errstr); |
321
|
|
|
|
|
|
|
} |
322
|
|
|
|
|
|
|
else { |
323
|
0
|
|
|
|
|
|
$log->info("Written $thumb_path"); |
324
|
|
|
|
|
|
|
} |
325
|
|
|
|
|
|
|
} |
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
# Generate single image html file for sharing on social medias |
328
|
0
|
|
|
|
|
|
$self->render_obrazec_to_file($row, $html_path); |
329
|
0
|
|
|
|
|
|
push @$processed, $row; |
330
|
|
|
|
|
|
|
} |
331
|
0
|
|
|
|
|
|
return $$, $processed; |
332
|
0
|
|
|
|
|
|
})->then( |
333
|
0
|
|
|
0
|
|
|
sub ($pid, $processed) { |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
# Executed in the parent process where we can collect the results and write the |
336
|
|
|
|
|
|
|
# new csv file, which can be saved in the $to_dir. |
337
|
|
|
|
|
|
|
# TODO: think if this is needed or we can just copy the initially produced |
338
|
|
|
|
|
|
|
# csv file to $to_dir. |
339
|
0
|
|
|
|
|
|
$log->info("PID $pid processed " . (scalar @$processed) . ' images!'); |
340
|
0
|
|
|
|
|
|
push @{$self->_processed}, @$processed; |
|
0
|
|
|
|
|
|
|
341
|
0
|
|
|
|
|
|
return; |
342
|
|
|
|
|
|
|
} |
343
|
0
|
|
|
0
|
|
|
)->catch(sub ($err) { |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
344
|
0
|
0
|
|
|
|
|
$log->warn("Subprocess error: $err") if $err; |
345
|
0
|
|
|
|
|
|
}); |
346
|
|
|
|
|
|
|
} |
347
|
|
|
|
|
|
|
|
348
|
0
|
|
|
0
|
|
|
sub _do_html($self) { |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
349
|
0
|
|
|
|
|
|
state $app = $self->app; |
350
|
0
|
|
|
|
|
|
my $categories = $self->categories; |
351
|
0
|
|
|
|
|
|
my $processed = $self->_processed; |
352
|
0
|
|
|
|
|
|
my $template_file = $self->template_file; |
353
|
0
|
|
0
|
|
|
|
my $tpl = $template_file || 'obrazi.html'; |
354
|
0
|
|
|
|
|
|
my $css_file = 'obrazi.css'; |
355
|
0
|
|
|
|
|
|
my $js_file = 'obrazi.js'; |
356
|
0
|
|
|
|
|
|
my $vars = { |
357
|
|
|
|
|
|
|
generator => __PACKAGE__, |
358
|
|
|
|
|
|
|
categories => $categories, |
359
|
|
|
|
|
|
|
processed => $processed, |
360
|
|
|
|
|
|
|
app => $app, |
361
|
|
|
|
|
|
|
thumbs => $self->thumbs, |
362
|
|
|
|
|
|
|
css_file => $css_file, |
363
|
|
|
|
|
|
|
js_file => $js_file, |
364
|
|
|
|
|
|
|
linked => $self->_linked_images, |
365
|
|
|
|
|
|
|
self => $self, |
366
|
|
|
|
|
|
|
}; |
367
|
|
|
|
|
|
|
|
368
|
0
|
|
|
|
|
|
$self->write_file($self->to_dir->child($css_file), $self->render_data($css_file => $vars)); |
369
|
0
|
|
|
|
|
|
$self->write_file($self->to_dir->child($js_file), $self->render_data($js_file => $vars)); |
370
|
0
|
0
|
|
|
|
|
if ($template_file) { |
371
|
|
|
|
|
|
|
|
372
|
0
|
|
|
|
|
|
my $html = Mojo::Template->new($self->template)->name($template_file)->render_file($template_file => $vars); |
373
|
0
|
|
|
|
|
|
$self->to_dir->child(path($template_file)->basename =~ s/\.ep$//r)->spurt(encode _U, $html); |
374
|
|
|
|
|
|
|
} |
375
|
|
|
|
|
|
|
else { |
376
|
0
|
|
|
|
|
|
$self->write_file($self->to_dir->child($tpl), encode _U, $self->render_data($tpl => $vars)); |
377
|
|
|
|
|
|
|
} |
378
|
|
|
|
|
|
|
|
379
|
0
|
|
|
|
|
|
return $self; |
380
|
|
|
|
|
|
|
} |
381
|
|
|
|
|
|
|
|
382
|
0
|
|
|
0
|
0
|
|
sub render_obrazec_to_file ($self, $img, $html_path) { |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
383
|
0
|
|
|
|
|
|
state $obrazec_file = $self->obrazec_file; |
384
|
0
|
|
|
|
|
|
state $obrazec_data = 'obrazec.html'; |
385
|
0
|
|
0
|
|
|
|
state $mt = Mojo::Template->new(vars => 1, name => $obrazec_file || $obrazec_data); |
386
|
0
|
|
|
|
|
|
state $parsed = 0; |
387
|
0
|
|
|
|
|
|
my $categories = $self->categories; |
388
|
|
|
|
|
|
|
|
389
|
0
|
0
|
0
|
|
|
|
if ($obrazec_file && !$parsed) { |
|
|
0
|
0
|
|
|
|
|
390
|
0
|
|
|
|
|
|
$mt->parse(decode _U, path($obrazec_file)->slurp); |
391
|
0
|
|
|
|
|
|
$parsed = 1; |
392
|
|
|
|
|
|
|
} |
393
|
|
|
|
|
|
|
elsif (!$obrazec_file && !$parsed) { |
394
|
0
|
|
|
|
|
|
$mt->parse(data_section(ref $self, $obrazec_data)); |
395
|
0
|
|
|
|
|
|
$parsed = 1; |
396
|
|
|
|
|
|
|
} |
397
|
|
|
|
|
|
|
|
398
|
0
|
|
|
|
|
|
my $html = $mt->process( |
399
|
|
|
|
|
|
|
{ |
400
|
|
|
|
|
|
|
generator => __PACKAGE__, |
401
|
|
|
|
|
|
|
self => $self, |
402
|
|
|
|
|
|
|
app => $self->app, |
403
|
|
|
|
|
|
|
img => $img, |
404
|
|
|
|
|
|
|
categories => $categories, |
405
|
|
|
|
|
|
|
linked => $self->_linked_images |
406
|
|
|
|
|
|
|
} |
407
|
|
|
|
|
|
|
); |
408
|
0
|
0
|
|
|
|
|
path($html_path)->spurt(encode _U, $html) && $self->log->info("Written $html_path"); |
409
|
0
|
|
|
|
|
|
return; |
410
|
|
|
|
|
|
|
} |
411
|
|
|
|
|
|
|
1; |
412
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
=encoding utf8 |
414
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
=head1 NAME |
416
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
Mojolicious::Command::Author::generate::obrazi - Обраꙁи for your site – a |
418
|
|
|
|
|
|
|
gallery generator command |
419
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
=head1 SYNOPSIS |
421
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
Usage: APPLICATION generate obrazi [OPTIONS] |
423
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
Examples: |
425
|
|
|
|
|
|
|
./myapp.pl help generate obrazi # This help |
426
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
mojo generate obrazi --from ~/Pictures/summer-2021 \ |
428
|
|
|
|
|
|
|
--to /opt/myapp/public/summer-2021 |
429
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
mojo generate obrazi --from ~/Pictures/summer-2021 \ |
431
|
|
|
|
|
|
|
--to /opt/some/static/site/albums/summer-2021 \ |
432
|
|
|
|
|
|
|
-x 800x600 -s 96x96 -t ./some/custom_template.html.ep |
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
Options: |
435
|
|
|
|
|
|
|
-h, --help Show this summary of available options |
436
|
|
|
|
|
|
|
-f, --from Root of directory structure from which the images |
437
|
|
|
|
|
|
|
will be taken. Defaults to ./. |
438
|
|
|
|
|
|
|
--to Root directory where the gallery will be put. Defaults to ./. |
439
|
|
|
|
|
|
|
-x, --max Maximum image dimesnions in pixels in format 'widthxheight'. |
440
|
|
|
|
|
|
|
Defaults to 1000x1000. |
441
|
|
|
|
|
|
|
-s, --thumbs Thumbnails maximal dimensions. Defaults to 100x100 pixels. |
442
|
|
|
|
|
|
|
-i, --index Name of the CSV index file to be generated and then read |
443
|
|
|
|
|
|
|
in the --from directory. |
444
|
|
|
|
|
|
|
-t, --template Path to template file. Defaults to embedded template |
445
|
|
|
|
|
|
|
obrazi.html. |
446
|
|
|
|
|
|
|
-o, --obrazec Path to single image template file for sharing on social |
447
|
|
|
|
|
|
|
media. Defaults to embedded template obrazec.html |
448
|
|
|
|
|
|
|
-u, --url Url where the gallery will be published. |
449
|
|
|
|
|
|
|
Defaults to '/obrazi.html'. |
450
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
=head1 DESCRIPTION |
452
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
L generates a gallery from a |
454
|
|
|
|
|
|
|
directory structure, containing images. The produced gallery is a static html |
455
|
|
|
|
|
|
|
file which body content can be easily taken, modified, and embedded into a page |
456
|
|
|
|
|
|
|
in any site. |
457
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
In addition the command generates a csv file on the first traversal of the |
459
|
|
|
|
|
|
|
directory structure, describing the images. This file can be edited. Titles and |
460
|
|
|
|
|
|
|
descriptions can be added for each image and then the command can be run again |
461
|
|
|
|
|
|
|
to regenerate the gallery with the modified titles and descriptions. This file |
462
|
|
|
|
|
|
|
can be further used by a helper — L to |
463
|
|
|
|
|
|
|
produce and embed the HTML for the galery into a L application. |
464
|
|
|
|
|
|
|
Please note that the helper is not yet implemented. |
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
The word B<обраꙁъ>(singular) means L
|
467
|
|
|
|
|
|
|
template, etc.|https://histdict.uni-sofia.bg/dictionary/show/d_05616> |
468
|
|
|
|
|
|
|
in OCS/OS/OBG language. The name of the plugin is the plural variant in |
469
|
|
|
|
|
|
|
nominative case (обраꙁи). |
470
|
|
|
|
|
|
|
|
471
|
|
|
|
|
|
|
=head1 WORKFLOW |
472
|
|
|
|
|
|
|
|
473
|
|
|
|
|
|
|
1. Images' owner and producer gives the direcory (probably zipped) to the |
474
|
|
|
|
|
|
|
command runner (a human yet). |
475
|
|
|
|
|
|
|
|
476
|
|
|
|
|
|
|
2. The runner runs the command as shown in the SYNOPSIS. |
477
|
|
|
|
|
|
|
|
478
|
|
|
|
|
|
|
3. The runner gives back the produced csv file to the images' producer. Fixes |
479
|
|
|
|
|
|
|
problems with ICC profiles etc. Notifies the producer for eventual naming |
480
|
|
|
|
|
|
|
convetions, possible problems. The producer fills in the description and titles |
481
|
|
|
|
|
|
|
in the comfort of L
|
482
|
|
|
|
|
|
|
Calc|https://www.libreoffice.org/discover/calc/> or MS Excel and returns the |
483
|
|
|
|
|
|
|
file to the command-runner. This may take some time. |
484
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
4. The runner runs again the command with the modified csv file, reviews the |
486
|
|
|
|
|
|
|
produced file. Takes the HTML and puts it in a page on the Web. |
487
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
5. The images' owner/producer enjoys the gallery, prise him/herself with it or |
489
|
|
|
|
|
|
|
goes back to the runner to report problems. |
490
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
6. DONE or go to some of the previous steps. |
492
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
=head1 FEATURES |
494
|
|
|
|
|
|
|
|
495
|
|
|
|
|
|
|
Recursively traverses subdirectories and scales images (four at a time) to |
496
|
|
|
|
|
|
|
given width and height and produces thumbnails for those images. Thumbnail |
497
|
|
|
|
|
|
|
sizes can also be set. |
498
|
|
|
|
|
|
|
|
499
|
|
|
|
|
|
|
Produces an index CSV file which can be edited to add titles and descriptions |
500
|
|
|
|
|
|
|
for the images. |
501
|
|
|
|
|
|
|
|
502
|
|
|
|
|
|
|
Produces an HTML file with fully functional lightbox-like gallery, implemented |
503
|
|
|
|
|
|
|
only using jQuery and CSS – no jQ-plugins. Left/right keyboard buttons navigation |
504
|
|
|
|
|
|
|
to next and previous image. |
505
|
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
=head1 ATTRIBUTES |
507
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
L inherits all attributes from |
509
|
|
|
|
|
|
|
L and implements the following new ones. |
510
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
=head2 categories |
512
|
|
|
|
|
|
|
|
513
|
|
|
|
|
|
|
my $cat = $self->categories->first(sub { $_->[0] eq $img->[0] }); |
514
|
|
|
|
|
|
|
|
515
|
|
|
|
|
|
|
A L instance, containing rows from the CSV file which are |
516
|
|
|
|
|
|
|
categories (directories). For this attribute to return meaningful data, |
517
|
|
|
|
|
|
|
C<$self-Ematrix> must be already filled in from CSV file. |
518
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
=head2 csv_filename |
521
|
|
|
|
|
|
|
|
522
|
|
|
|
|
|
|
my $filename = $self->csv_filename; # index.csv |
523
|
|
|
|
|
|
|
my $обраꙁи = $self->csv_filename('gallery.csv'); |
524
|
|
|
|
|
|
|
|
525
|
|
|
|
|
|
|
The name of the CSV file which will be created in L. This file, |
526
|
|
|
|
|
|
|
after being edited and after the images are processed, will be copied together |
527
|
|
|
|
|
|
|
with the images to L. Defaults to C. Can be passed on the |
528
|
|
|
|
|
|
|
command-line via the C<--index> argument. |
529
|
|
|
|
|
|
|
|
530
|
|
|
|
|
|
|
=head2 defaults |
531
|
|
|
|
|
|
|
|
532
|
|
|
|
|
|
|
my $defaults_hashref = $обраꙁи->defaults; |
533
|
|
|
|
|
|
|
$обраꙁи->defaults->{category_title} = 'Def. Cat. title'; |
534
|
|
|
|
|
|
|
$обраꙁи->defaults->{category_description} = 'Def. Cat. description.'; |
535
|
|
|
|
|
|
|
$обраꙁи->defaults->{image_title} = 'Def. Image Title'; |
536
|
|
|
|
|
|
|
$обраꙁи->defaults->{image_description} = 'Def. Image description.'; |
537
|
|
|
|
|
|
|
$обраꙁи->defaults->{author} = 'John Smith'; |
538
|
|
|
|
|
|
|
|
539
|
|
|
|
|
|
|
These values go to the folowing columns in the produced CSV file. C
|
540
|
|
|
|
|
|
|
description, author>. They are supposed to be replaced by editing the produced |
541
|
|
|
|
|
|
|
file. TODO: Maybe allow these to be passed on the command line via an argument |
542
|
|
|
|
|
|
|
C<--defaults>. |
543
|
|
|
|
|
|
|
|
544
|
|
|
|
|
|
|
=head2 description |
545
|
|
|
|
|
|
|
|
546
|
|
|
|
|
|
|
my $description = $обраꙁи->description; |
547
|
|
|
|
|
|
|
$self = $обраꙁи->description('Foo'); |
548
|
|
|
|
|
|
|
|
549
|
|
|
|
|
|
|
Short description of this command, used for the application's command list. |
550
|
|
|
|
|
|
|
|
551
|
|
|
|
|
|
|
=head2 files_per_subproc |
552
|
|
|
|
|
|
|
|
553
|
|
|
|
|
|
|
my $files_num = $обраꙁи->files_per_subproc; |
554
|
|
|
|
|
|
|
$self = $обраꙁи->files_per_subproc(10); |
555
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
Number of files to be processed by one subprocess. Defaults to |
557
|
|
|
|
|
|
|
Csubprocs_num) +1>. The last chunk of files is |
558
|
|
|
|
|
|
|
the remainder — usually smaller than the previous chunks. |
559
|
|
|
|
|
|
|
|
560
|
|
|
|
|
|
|
=head2 from_dir |
561
|
|
|
|
|
|
|
|
562
|
|
|
|
|
|
|
$self = $обраꙁи->from_dir(path('./')); |
563
|
|
|
|
|
|
|
my $root_folder_abs_path = $обраꙁи->from_dir; |
564
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
Holds a L instance — absolute path to the directory from which the |
566
|
|
|
|
|
|
|
pictures will be taken. This is where the CSV file describing the directory |
567
|
|
|
|
|
|
|
structure will be generated too. The value is taken from the commandline |
568
|
|
|
|
|
|
|
argument C<--from_dir>. Defaults to C<./> — current directory — where the |
569
|
|
|
|
|
|
|
command is executed. |
570
|
|
|
|
|
|
|
|
571
|
|
|
|
|
|
|
=head2 imager |
572
|
|
|
|
|
|
|
|
573
|
|
|
|
|
|
|
my $img = $обраꙁи->imager->read(file=>'path/to/image.jpg') |
574
|
|
|
|
|
|
|
|| die $обраꙁи->imager->errstr; |
575
|
|
|
|
|
|
|
|
576
|
|
|
|
|
|
|
my $self = $обраꙁи->imager(Imager->new); |
577
|
|
|
|
|
|
|
|
578
|
|
|
|
|
|
|
An L instance. This is the images-processing engine, used by the |
579
|
|
|
|
|
|
|
command. |
580
|
|
|
|
|
|
|
|
581
|
|
|
|
|
|
|
=head2 images |
582
|
|
|
|
|
|
|
|
583
|
|
|
|
|
|
|
my $images = $self->images->map(sub { [@$_] }); #copy |
584
|
|
|
|
|
|
|
|
585
|
|
|
|
|
|
|
A L instance, containing rows from the CSV file which are |
586
|
|
|
|
|
|
|
image files, supported by L and will be processed. For this attribute |
587
|
|
|
|
|
|
|
to return meaningful data, C<$self-Ematrix> must be already filled in from |
588
|
|
|
|
|
|
|
CSV file. |
589
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
=head2 log |
591
|
|
|
|
|
|
|
|
592
|
|
|
|
|
|
|
my $log = $self->log; |
593
|
|
|
|
|
|
|
my $self = $self->log(Mojo::Log->new) |
594
|
|
|
|
|
|
|
|
595
|
|
|
|
|
|
|
A L instance. By default it is not the same as |
596
|
|
|
|
|
|
|
C<$self-Eapp-Elog>. Used to output info, warnings and errors to the |
597
|
|
|
|
|
|
|
terminal or the application log. |
598
|
|
|
|
|
|
|
|
599
|
|
|
|
|
|
|
=head2 matrix |
600
|
|
|
|
|
|
|
|
601
|
|
|
|
|
|
|
my $matrix = $self->matrix; |
602
|
|
|
|
|
|
|
|
603
|
|
|
|
|
|
|
# add an image |
604
|
|
|
|
|
|
|
push @$matrix, |
605
|
|
|
|
|
|
|
[ |
606
|
|
|
|
|
|
|
$category, $path, |
607
|
|
|
|
|
|
|
$defaults->{image_title}, $defaults->{image_description}, |
608
|
|
|
|
|
|
|
$defaults->{author}, $self->calculate_max_and_thumbs($path, $raw_path) |
609
|
|
|
|
|
|
|
]; |
610
|
|
|
|
|
|
|
|
611
|
|
|
|
|
|
|
# add a category |
612
|
|
|
|
|
|
|
push @$matrix, [ |
613
|
|
|
|
|
|
|
$category, $path, "$defaults->{category_title} – $category", |
614
|
|
|
|
|
|
|
$defaults->{category_description}, $defaults->{author}, '', '' |
615
|
|
|
|
|
|
|
]; |
616
|
|
|
|
|
|
|
|
617
|
|
|
|
|
|
|
$matrix->each(sub{...}); |
618
|
|
|
|
|
|
|
|
619
|
|
|
|
|
|
|
|
620
|
|
|
|
|
|
|
csv(in => $matrix->to_array, enc => 'UTF-8', out => \my $data, binary => 1, sep_char => ","); |
621
|
|
|
|
|
|
|
path($csv_filepath)->spurt($data); |
622
|
|
|
|
|
|
|
|
623
|
|
|
|
|
|
|
A L instance. First row contains the headers. This matrix is |
624
|
|
|
|
|
|
|
filled in while recursively searching in the L for images. Then it |
625
|
|
|
|
|
|
|
is dumped into the index CSV file. If the CSV file is already present, the data |
626
|
|
|
|
|
|
|
is read directly from it. |
627
|
|
|
|
|
|
|
|
628
|
|
|
|
|
|
|
=head2 max |
629
|
|
|
|
|
|
|
|
630
|
|
|
|
|
|
|
my $max_sizes = $self->max; # {width => 1000, height => 1000} |
631
|
|
|
|
|
|
|
$self = $self->max({width => 1000, height => 1000}); |
632
|
|
|
|
|
|
|
$self = $self->max('1000x1000'); |
633
|
|
|
|
|
|
|
|
634
|
|
|
|
|
|
|
A hash reference with keys C and C. Defaults to C<{width => |
635
|
|
|
|
|
|
|
1000, height => 1000}>. Can be changed via the command line argument C<--max>. |
636
|
|
|
|
|
|
|
|
637
|
|
|
|
|
|
|
=head2 obrazec_file |
638
|
|
|
|
|
|
|
|
639
|
|
|
|
|
|
|
Path to template file for single html pages. Defaults to embedded template |
640
|
|
|
|
|
|
|
obrazec.html. Can be passed as argument on the command-line via C<--obrazec>. |
641
|
|
|
|
|
|
|
|
642
|
|
|
|
|
|
|
=head2 publish_url |
643
|
|
|
|
|
|
|
|
644
|
|
|
|
|
|
|
$обраꙁи->publish_url($string); # $self |
645
|
|
|
|
|
|
|
$обраꙁи->publish_url; # $string |
646
|
|
|
|
|
|
|
|
647
|
|
|
|
|
|
|
String. Url path or preferably full url, where the gallery will reside. Needed |
648
|
|
|
|
|
|
|
for link from individual images to the common gallery page and OpenGraph meta |
649
|
|
|
|
|
|
|
data. Can be passed as argument on the command-line via C<--url>. Defaults |
650
|
|
|
|
|
|
|
to C – rarely what you need. |
651
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
=head2 subprocs_num |
653
|
|
|
|
|
|
|
|
654
|
|
|
|
|
|
|
$self->subprocs_num; #4 |
655
|
|
|
|
|
|
|
$self = $self->subprocs_num(5); |
656
|
|
|
|
|
|
|
|
657
|
|
|
|
|
|
|
Integer, used to split the number of files found into equal chunks, each of |
658
|
|
|
|
|
|
|
which will be processed in a separate subprocess in parallel. Defaults to 4. |
659
|
|
|
|
|
|
|
See also L. |
660
|
|
|
|
|
|
|
|
661
|
|
|
|
|
|
|
=head2 template_file |
662
|
|
|
|
|
|
|
|
663
|
|
|
|
|
|
|
my $self = $self->template_file('path/to/template.html.ep'); |
664
|
|
|
|
|
|
|
my $tpl = $self->template_file; |
665
|
|
|
|
|
|
|
|
666
|
|
|
|
|
|
|
Path to template file to be used for generating the HTML for the gallery. |
667
|
|
|
|
|
|
|
Defaults to embedded template obrazi.html. Can be passed as argument on the |
668
|
|
|
|
|
|
|
command-line via C<--template>. |
669
|
|
|
|
|
|
|
|
670
|
|
|
|
|
|
|
=head2 thumbs |
671
|
|
|
|
|
|
|
|
672
|
|
|
|
|
|
|
my $thumbs_sizes = $self->thumbs; # {width => 100, height => 100} |
673
|
|
|
|
|
|
|
$self = $self->thumbs({width => 100, height => 100}); |
674
|
|
|
|
|
|
|
$self = $self->thumbs('1000x1000'); |
675
|
|
|
|
|
|
|
|
676
|
|
|
|
|
|
|
A hash reference with keys C and C. Defaults to C<{width => |
677
|
|
|
|
|
|
|
100, height => 100}>. Can be changed via the command line argument |
678
|
|
|
|
|
|
|
C<--thumbs>. |
679
|
|
|
|
|
|
|
|
680
|
|
|
|
|
|
|
=head2 to_dir |
681
|
|
|
|
|
|
|
|
682
|
|
|
|
|
|
|
$self->to_dir # path($app/public) |
683
|
|
|
|
|
|
|
$self = $self->to_dir(path('/some/folder')); |
684
|
|
|
|
|
|
|
|
685
|
|
|
|
|
|
|
A L instance. Directory, where the folder with the processed images |
686
|
|
|
|
|
|
|
will be put. Defaults to the C forlder of the current application. |
687
|
|
|
|
|
|
|
Can be changed via the command line argument C<--to_dir>. It is recommended to |
688
|
|
|
|
|
|
|
pass this value unless all your images are into one root folder. The L |
689
|
|
|
|
|
|
|
directory is the root of your images' galery. |
690
|
|
|
|
|
|
|
|
691
|
|
|
|
|
|
|
=head2 usage |
692
|
|
|
|
|
|
|
|
693
|
|
|
|
|
|
|
my $usage = $обраꙁи->usage; |
694
|
|
|
|
|
|
|
$self = $обраꙁи->usage('Foo'); |
695
|
|
|
|
|
|
|
|
696
|
|
|
|
|
|
|
Usage information for this command, used for the help screen. At the bottom are |
697
|
|
|
|
|
|
|
shown the supported by L image formats. |
698
|
|
|
|
|
|
|
|
699
|
|
|
|
|
|
|
=head1 METHODS |
700
|
|
|
|
|
|
|
|
701
|
|
|
|
|
|
|
L inherits all methods from |
702
|
|
|
|
|
|
|
L and implements the following new ones. |
703
|
|
|
|
|
|
|
|
704
|
|
|
|
|
|
|
=head2 calculate_max_and_thumbs |
705
|
|
|
|
|
|
|
|
706
|
|
|
|
|
|
|
# img_1000x1000.jpg, img_100x100.jpg |
707
|
|
|
|
|
|
|
my ($img_filename, $thumb_filename) = $self->calculate_max_and_thumbs($decoded_path, $raw_path); |
708
|
|
|
|
|
|
|
|
709
|
|
|
|
|
|
|
Calculates the resized image dimensions according to the C<$self-Emax> |
710
|
|
|
|
|
|
|
and C<$self-Ethumbs> gallery constraints. Accepts the utf8 decoded path |
711
|
|
|
|
|
|
|
and the raw path to the file to be worked on. Returns two empty strings if |
712
|
|
|
|
|
|
|
there is error reading the image and warns about the error. Returns filenames |
713
|
|
|
|
|
|
|
for the resized image and the thumbnail image. See also |
714
|
|
|
|
|
|
|
L. |
715
|
|
|
|
|
|
|
|
716
|
|
|
|
|
|
|
=head2 run |
717
|
|
|
|
|
|
|
|
718
|
|
|
|
|
|
|
$обраꙁи = $обраꙁи->run(@ARGV); |
719
|
|
|
|
|
|
|
|
720
|
|
|
|
|
|
|
Run this command. |
721
|
|
|
|
|
|
|
|
722
|
|
|
|
|
|
|
=head2 TEMPLATES |
723
|
|
|
|
|
|
|
|
724
|
|
|
|
|
|
|
L contains four embedded |
725
|
|
|
|
|
|
|
templates: |
726
|
|
|
|
|
|
|
|
727
|
|
|
|
|
|
|
obrazi.html — template for a single page gallery, containing all images |
728
|
|
|
|
|
|
|
obrazec.html — template for single image html pages |
729
|
|
|
|
|
|
|
obrazi.css — template for CSS rules used in both html templates |
730
|
|
|
|
|
|
|
obrazi.js — JavaScript (jQuery) code for handling navigation and effecs |
731
|
|
|
|
|
|
|
in obrazi.html |
732
|
|
|
|
|
|
|
|
733
|
|
|
|
|
|
|
TODO: Maybe make the templates inflatable. |
734
|
|
|
|
|
|
|
|
735
|
|
|
|
|
|
|
=head1 DEMOS |
736
|
|
|
|
|
|
|
|
737
|
|
|
|
|
|
|
Here is a hopefully growing list of URLs of galleries produced with this |
738
|
|
|
|
|
|
|
software. |
739
|
|
|
|
|
|
|
|
740
|
|
|
|
|
|
|
L<Творби на Марио Беров|https://слово.бг/хѫдожьство/mario_berov.bg.html> — a |
741
|
|
|
|
|
|
|
gallery, presenting works of the Bulgarian Orthodox icon painter Mario Berov. |
742
|
|
|
|
|
|
|
|
743
|
|
|
|
|
|
|
=head1 SEE ALSO |
744
|
|
|
|
|
|
|
|
745
|
|
|
|
|
|
|
L, L |
746
|
|
|
|
|
|
|
L, L, L, |
747
|
|
|
|
|
|
|
|
748
|
|
|
|
|
|
|
L, |
749
|
|
|
|
|
|
|
|
750
|
|
|
|
|
|
|
L. |
751
|
|
|
|
|
|
|
|
752
|
|
|
|
|
|
|
=cut |
753
|
|
|
|
|
|
|
|
754
|
|
|
|
|
|
|
__DATA__ |