%doc>
Utility template multi_illustration.mc finds or creates (use Image::Magick)
requested alternate versions of /jpeg/jpg/gif/png/ images.
If the original image bitmap has changed after reupload then
all existing alt versions are regenerated and republished if ->publishing.
Usage example
(in story subelement template)
<%perl>
my $rel_med=$element->get_related_media;
if ($rel_media->get_element_type->get_name eq "multiillustration"){
my @versions_required=(qw/thumbnail_image slideshow_image/);
$m->comp("/util/multi_illustration.mc",
media_doc=> $rel_media,
versions_required => \@versions_required,
);
my ($thumb) = $rel_media->get_elements(('thumbnail_image'));_
my ($slideshow) = $rel_media->get_elements(('slideshow_image'));
my $thumb_img_uri = $thumb->get_related_media->get_uri;
my $slideshow_img_uri = $slideshow->get_related_media->get_uri;
%perl>
% }
The code is based on image_resize_and_relate.mc with the following differences/enchancements:
1. Image media documents that allow alternate versions must have dedicated type (for example multiillustration)
2. Alternate media documents containing resized versions of original image get related to the original
media document instead of story document.
multiillustration elem. type must accept subelement types (with related madia enabled),
one for each alternate version you want use.
3. Image alternate media documents have common type (for example auto_generated_image).
4. All newly created alt media documents have URIs that do not depend on orig media file name:
It is of the form
${orig_media_doc_uri_path}/${alt_media_common_prefix}_${orig_media_doc_id}_$postfix.$ext
so it does not change when user reuploads orig media bitmap unless image format has changed ($ext has changed then)
or MEDIA...UNIQUE_FILE_NAME is enabled in conf. In the last case template will probably not work without modification.
5. When alternate image version is requested than the template searches for a suitable alt media document.
If search has failed then
a. new alt media document is created,
b. resized image version is generated and uploaded,
c. orig media image fs path is stored in alt media description field.
d. a container dedicated for that alt version is created and added to the orig media doc
e. newly created alt media document gets related to this container
Note: fs path contains image version number so it changes whenever user reuploads a new image to the orig media document.
If search has been successful then alt media document description field
is compared to the orig media doc image fs path.
If they are nor equal then alt image is regenerated, reuploaded and
orig media image fs path is stored in desc field again
6. Whenever story is published than all atl media docs it refers to are also republished if ->needs_publish happens.
A side effect: if you don't use orig image URIs on your pages (just alternate ones as in my case)
then after the first publish of any story that orig media (with reuploaded bitmap) is related to
all pages on your publish server which show these alternates do show their uptodate versions (without republishing!).
I am not quite sure if this a feature or !feature. In the later case it may be easily corrected by changing the way
in which alt media docs URIs are generated below.
7. Only required alternate versions are created (not all admissible ones) but
if orig media image has changed before then all previousely created alt versions
are out of date then they are refreshed (and republished if ->publishing).
8. this code is not PUBLISH_MODE safe - it throws_errors without hesitation.
Configuration
1. define element types for each alternate version of the image (with related media enabled)
(for example article_image, thumbnail_image etc.)
2. define Image element type for images with resized versions (for example multiillustration)
let this element type have subelement types (defined in 1.) with max=1 constrain
3. edit the definition of %versions_admissible in <%once> section below.
It should have the form ( key_name => with_of_resized_image,...) where key_name is any of those defined in 1.
%doc>
<%perl>
$burner->throw_error("File a feature request if you really want to resize this one") unless $media_doc;
use Image::Magick;
#
$user= $story->get_user__id || Bric::App::Session->get_user_id;
# make sure $media_doc contains an image
$format=$1 if $media_doc->get_media_type->get_name =~ m#.*/(jpg|jpeg|gif|png)#;
$burner->throw_error("Can't resize ", $media_doc->get_media_type->get_name, " media") unless $format;
# cleanup $media_doc first
# Move bad containers (dedicated for alt medias) to %trash and then remove them all from $media_doc
# A container is bad if : it is a duplicate of previousely found one or
# has no related media or it's related media is not an image or
# it's related media image is out of date and needs to be regenerated
# (ie. user has reuploaded a new bitmap of $media_doc)
my %trash;
my %duplicates;
my $media_doc_modified=0; # flag
my @empty_containers=();
#$m->out("Media: ",$media_doc->get_uri," ver: " ,$media_doc->get_version,"
");
#warn("\nMedia: ",$media_doc->get_uri," ver: " ,$media_doc->get_version,"\n\n");
foreach my $wc ($media_doc->get_elements(keys %versions_admissible)){
#remove containers that are duplicates of the ones seen before
if (exists($duplicates{$wc->get_key_name})){
$trash{$wc->get_id}=$wc;
next;
}
#remove containers that do not have media related
if (! $wc->get_related_media){
$trash{$wc->get_id}=$wc;
next
};
#remove containers that have related media but they are not images
if ($wc->get_related_media->get_media_type->get_name !~ m#.*/(jpg|jpeg|gif|png)#){
$trash{$wc->get_id}=$wc;
next;
}
# When alt media is created then the path to the original bitmap (containing version number)
# is stored in alt media description field (dedicated field may be used if defined)
# if this path differs from $media_doc->get_path (ie orig bitmap has changed) than the container is removed
# it will be recreated later (not nice but simple)
if ($wc->get_related_media->get_description ne $media_doc->get_path){
$trash{$wc->get_id}=$wc;
# if this out of date alternate media has been found here then it must have been required somewhere else
# so make this alt version required
push(@versions_required,$wc->get_key_name);
} else {
# ok this one is good, will not touch it any more.
$duplicates{$wc->get_key_name}=1
};
};
#remove bad containers
my @to_trash= values %trash;
if (@to_trash > 0 ){
$media_doc->get_element->delete_elements( \@to_trash);
$media_doc_modified++;
#$media_doc->save
};
# remove dupes from @versions_required
%duplicates=();
@versions_required = grep ++$duplicates{$_} < 2, @versions_required;
# create required but missing containers;
foreach my $container_type (@versions_required) {
my ($wc)= $media_doc->get_elements( ($container_type) );
unless ($wc){
#create required container type
my $required_container_type = Bric::Biz::ElementType->lookup({ 'key_name' => $container_type });
# create alt version container and remember it to relate alternate media doc later
push(@empty_containers, $media_doc->add_container($required_container_type));
$media_doc_modified++;
#$media_doc->save;
}
}
# Save changes to the database if it is necessary and possible
if ($media_doc_modified && $media_doc->get_checked_out){
$burner->throw_error("Can't modify checked_out version of " , $media_doc->get_uri)
} else {
$media_doc->save if $media_doc_modified;
}
# Now create or find required alt media docs and relete them with suitable empty containers
foreach my $empty_container (@empty_containers){
# there is dedicated element type for all new alt media docs: $et_key_name
my $target_element_type = Bric::Biz::ElementType->lookup({key_name => $et_key_name});
my $alt_media_doc_title = $media_doc->get_title . '-' . $empty_container->get_key_name;
# in the code below all those checkins , desks , workflows, user (bric specific stuff) are still
# sort of black magic to me
# but the code works without visible problems (I don't see error pages in gui except the "throw_error" ones
# I generate myself.
# It probably does not make mess in the database or it does but I'll notice that later (when I am online)
# I've added some checkout checks to the original code of image_resize_and_relate.mc because
# I managed to do some document versions mess while using that.
#
my %target_initial_state = (
'user__id' => $user,
'active' => 1,
'priority' => $story->get_priority,
'title' => $alt_media_doc_title,
## remember current path/version of $media_doc bitmap
'description' => $media_doc->get_path,
'workflow_id' => $media_workflow->get_id,
'element_type' => $target_element_type,
'site_id' => $story->get_site_id,
'source__id' => $media_doc->get_source__id,
'cover_date' => $media_doc->get_cover_date,
# 'media_type_id' => $media_doc->get_media_type->get_id,
'category__id' => $media_doc->get_category__id
);
# build alt media uri
# it will be equal to $media_doc uri except it's file name part
# original media doc id is used rather than its modified file name
# when user reuploads original bitmap then the uri of $media_doc changes but
# we will still use the same slave alt media docs.
my $new_file_name = "${alt_media_common_prefix}_".$media_doc->get_id."_".$versions_admissible{$empty_container->get_key_name}."px.$format";
my $new_uri = $media_doc->get_uri;
$new_uri=~s|/[^/]+$|/$new_file_name|;
#check if there is already an image with the same URI in the Bricolage library.
# If there is, open that media document. Otherwise create a new one.
# $m->out('Looking for an existing media document with this uri:
' . $new_uri .'
');
my @existing_media_document = Bric::Biz::Asset::Business::Media->list({
'uri' => $new_uri,
'active' => 1
});
if ($existing_media_document[0]) {
# $m->out('
Found existing one. URI is ' . $existing_media_document[0]->get_primary_uri . '
');
$alt_media_document = $existing_media_document[0];
# $m->out("
New Media:", $alt_media_document->get_uri, ":: checkout: ". $alt_media_document->get_checked_out);
if ($alt_media_document->get_checked_out){
# should probably checkin, save and checkout cos nobody should touch auto_generated_images
$burner->throw_error("I will not modify checked_out version of " , $alt_media_document->get_uri)
} else {
$alt_media_document->checkout({ user__id => $user });
}
$alt_media_document->set_workflow_id($media_workflow->get_id);
$alt_media_document->save;
$publish_desk->accept({ asset => $alt_media_document });
#refresh description to contain current bitmap's path
$alt_media_document->set_description($media_doc->get_path);
# update $mew_media_doc title or you will quickly got lost on search media doc pages
$alt_media_document->set_title($alt_media_doc_title);
$publish_desk->save;
$alt_media_document->checkin;
} else {
# Not found - create one
$alt_media_document = Bric::Biz::Asset::Business::Media::Image->new(\%target_initial_state);
$alt_media_document->set_workflow_id($media_workflow->get_id);
$alt_media_document->save;
$publish_desk->accept({ asset => $alt_media_document });
$publish_desk->save;
$alt_media_document->checkin;
}
#Here we go, making the new image
my $temp_file = "/tmp/$new_file_name";
my $new_image = new Image::Magick;
$new_image->Read($media_doc->get_path);
# I do not want corpping
# if ($needs_cropping{'height_aspect'}) {
# $m->out('
Going to crop!');
# my $target_height = int(($needs_cropping{'height_aspect'} * $media_doc->get_value('width')) / $needs_cropping{'width_aspect'});
# $new_image->Crop( geometry => $media_doc->get_value('width')."x$target_height+0+0" );
# } else {
# $m->out('
NOT going to crop!');
# }
$new_image->Thumbnail($versions_admissible{$empty_container->get_key_name});
$new_image->Set(colorspace=>'RGB');
$new_image->Write($temp_file);
# I can't make Image Magic to generate optimized png's (maybe Imager does the right job - must check )
# optimize png to make it smaller
`optipng -fix $temp_file` if $temp_file=~/\.png$/;
undef $new_image;
#now upload our new image
my $file_handler;
open ($file_handler, "<$temp_file");
$alt_media_document->upload_file($file_handler, $new_file_name, $media_doc->get_media_type->get_name);
#no need to close filehandler because upload_file does it automatically
#delete the temp image here
unlink ($temp_file);
#save and check in if required
$alt_media_document->save;
if ($alt_media_document->get_checked_out) {
# $publish_desk->checkin($alt_media_document);
$alt_media_document->checkin;
}
# from image_image_resize and_relate.mc
if ($publish_images_on_story_preview eq 'Yes') {
if ($alt_media_document->needs_publish) {
# $m->out('
Trying to publish "' . $alt_media_document->get_title . '".
');
$burner->publish_another($alt_media_document);
}
}
#relate the image to the container
$empty_container->set_related_media($alt_media_document);
$empty_container->save;
$media_doc->save;
$burner->blaze_another($media_doc);
if ($burner->get_mode == PREVIEW_MODE){$burner->preview_another($alt_media_document)};
}
foreach my $wc ($media_doc->get_elements(@versions_required)) {
my $rm=$wc->get_related_media;
$burner->publish_another($rm) if ($rm->needs_publish && ($burner->get_mode == PUBLISH_MODE));
$burner->preview_another($rm) if ($burner->get_mode == PREVIEW_MODE);
}
%perl>
<%args>
$media_doc => undef
@versions_required => ('article_image')
$publish_images_on_story_preview => 'No'
%args>
<%init>
my ($format, $user, $alt_media_document);
# sanity check
foreach (@versions_required){
$burner->throw_error("Required version '$_' is not admissible") unless exists($versions_admissible{$_})
}
%init>
<%once>
# the hash below has the form: ( container_key_name => width_of_releted_alt_version)
my %versions_admissible = (
'thumbnail_image' => 100,
'slideshow_image' => 200,
'article_image' => 350,
);
my $alt_media_common_prefix='alt_media';
my $et_key_name = 'auto_generated_image'; # element type for alternate media documents
my $media_workflow = Bric::Biz::Workflow->lookup({ name => 'Media' });
my $publish_desk = Bric::Biz::Workflow::Parts::Desk->lookup({ name => 'Publish' });
# I do not crop images so do not use %ratio in the code
# my %ratio = (
# 'height_aspect' => 2,
# 'width_aspect' => 3
# );
# my %needs_cropping=%ratio;
%once>