From fcb7b1530cd42442b191ba5e46c18c270c72b386 Mon Sep 17 00:00:00 2001 From: Gregor Schmidt Date: Wed, 13 Apr 2016 11:22:49 +0200 Subject: [PATCH] Render PDF thumbnail using ImageMagick/GhostScript If GhostScript is available, ImageMagick can also handle PDF files. The Thumbnails will only show the first page ([0] at the end of source parameter) and they will be stored in PNG format (png: at beginning of target parameter). Also adding separate row to admin/info to show status of PDF thumbnail support. --- app/controllers/admin_controller.rb | 3 ++- app/controllers/attachments_controller.rb | 13 ++++++++++--- app/models/attachment.rb | 4 ++-- config/locales/de.yml | 1 + config/locales/en-GB.yml | 1 + config/locales/en.yml | 1 + lib/redmine/thumbnail.rb | 31 +++++++++++++++++++++++++------ 7 files changed, 42 insertions(+), 12 deletions(-) diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index 944e60c..88d6081 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -78,7 +78,8 @@ class AdminController < ApplicationController [:text_file_repository_writable, File.writable?(Attachment.storage_path)], ["#{l :text_plugin_assets_writable} (./public/plugin_assets)", File.writable?(Redmine::Plugin.public_directory)], [:text_rmagick_available, Object.const_defined?(:Magick)], - [:text_convert_available, Redmine::Thumbnail.convert_available?] + [:text_convert_available, Redmine::Thumbnail.convert_available?], + [:text_gs_available, Redmine::Thumbnail.gs_available?] ] end end diff --git a/app/controllers/attachments_controller.rb b/app/controllers/attachments_controller.rb index ea45397..95895fb 100644 --- a/app/controllers/attachments_controller.rb +++ b/app/controllers/attachments_controller.rb @@ -68,7 +68,7 @@ class AttachmentsController < ApplicationController if stale?(:etag => tbnail) send_file tbnail, :filename => filename_for_content_disposition(@attachment.filename), - :type => detect_content_type(@attachment), + :type => detect_content_type(@attachment, true), :disposition => 'inline' end else @@ -184,12 +184,19 @@ class AttachmentsController < ApplicationController @attachment.deletable? ? true : deny_access end - def detect_content_type(attachment) + def detect_content_type(attachment, is_thumb = false) content_type = attachment.content_type if content_type.blank? || content_type == "application/octet-stream" content_type = Redmine::MimeType.of(attachment.filename) end - content_type.to_s + content_type = content_type.to_s + + if is_thumb and content_type == "application/pdf" + # PDF previews are stored in PNG format + content_type = "image/png" + end + + content_type end def disposition(attachment) diff --git a/app/models/attachment.rb b/app/models/attachment.rb index 4bc674f..d2940fb 100644 --- a/app/models/attachment.rb +++ b/app/models/attachment.rb @@ -198,7 +198,7 @@ class Attachment < ActiveRecord::Base end def thumbnailable? - image? + image? || (is_pdf? && Redmine::Thumbnail.gs_available?) end # Returns the full path the attachment thumbnail, or nil @@ -218,7 +218,7 @@ class Attachment < ActiveRecord::Base target = File.join(self.class.thumbnails_storage_path, "#{id}_#{digest}_#{size}.thumb") begin - Redmine::Thumbnail.generate(self.diskfile, target, size) + Redmine::Thumbnail.generate(self.diskfile, target, size, is_pdf?) rescue => e logger.error "An error occured while generating thumbnail for #{disk_filename} to #{target}\nException was: #{e.message}" if logger return nil diff --git a/config/locales/de.yml b/config/locales/de.yml index 98e3047..da33a5e 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -1063,6 +1063,7 @@ de: text_caracters_minimum: "Muss mindestens %{count} Zeichen lang sein." text_comma_separated: Mehrere Werte erlaubt (durch Komma getrennt). text_convert_available: ImageMagick-Konvertierung verfügbar (optional) + text_gs_available: ImageMagick PDF-Unterstützung verfügbar (optional) text_custom_field_possible_values_info: 'Eine Zeile pro Wert' text_default_administrator_account_changed: Administrator-Passwort geändert text_destroy_time_entries: Gebuchte Aufwände löschen diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index d9e36c2..fee55a3 100644 --- a/config/locales/en-GB.yml +++ b/config/locales/en-GB.yml @@ -1098,6 +1098,7 @@ en-GB: current password setting_mail_handler_excluded_filenames: Exclude attachments by name text_convert_available: ImageMagick convert available (optional) + text_gs_available: ImageMagick PDF support available (optional) label_link: Link label_only: only label_drop_down_list: drop-down list diff --git a/config/locales/en.yml b/config/locales/en.yml index 9dd0356..a92e0aa 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1107,6 +1107,7 @@ en: text_plugin_assets_writable: Plugin assets directory writable text_rmagick_available: RMagick available (optional) text_convert_available: ImageMagick convert available (optional) + text_gs_available: ImageMagick PDF support available (optional) text_destroy_time_entries_question: "%{hours} hours were reported on the issues you are about to delete. What do you want to do?" text_destroy_time_entries: Delete reported hours text_assign_time_entries_to_project: Assign reported hours to the project diff --git a/lib/redmine/thumbnail.rb b/lib/redmine/thumbnail.rb index 9321c7b..07e44d4 100644 --- a/lib/redmine/thumbnail.rb +++ b/lib/redmine/thumbnail.rb @@ -25,19 +25,28 @@ module Redmine CONVERT_BIN = (Redmine::Configuration['imagemagick_convert_command'] || 'convert').freeze # Generates a thumbnail for the source image to target - def self.generate(source, target, size) + def self.generate(source, target, size, is_pdf = false) return nil unless convert_available? + return nil if is_pdf && !gs_available? unless File.exists?(target) - # Make sure we only invoke Imagemagick if this is actually an image - unless File.open(source) {|f| MimeMagic.by_magic(f).try(:image?)} - return nil - end + # Make sure we only invoke Imagemagick if assumed type matches content + mime_magic = File.open(source) {|f| MimeMagic.by_magic(f) } + return nil if mime_magic.nil? + return nil if is_pdf && mime_magic.type != "application/pdf" + return nil if !is_pdf && !mime_magic.image? + directory = File.dirname(target) unless File.exists?(directory) FileUtils.mkdir_p directory end size_option = "#{size}x#{size}>" - cmd = "#{shell_quote CONVERT_BIN} #{shell_quote source} -thumbnail #{shell_quote size_option} #{shell_quote target}" + + if is_pdf + cmd = "#{shell_quote CONVERT_BIN} #{shell_quote "#{source}[0]"} -thumbnail #{shell_quote size_option} #{shell_quote "png:#{target}"}" + else + cmd = "#{shell_quote CONVERT_BIN} #{shell_quote source} -thumbnail #{shell_quote size_option} #{shell_quote target}" + end + unless system(cmd) logger.error("Creating thumbnail failed (#{$?}):\nCommand: #{cmd}") return nil @@ -53,6 +62,16 @@ module Redmine @convert_available end + def self.gs_available? + return @gs_available if defined?(@gs_available) + + @gs_available = system("gs -version") rescue false + @gs_available ||= system("gswin32 -version") rescue false + @gs_available ||= system("gswin64 -version") rescue false + + @gs_available + end + def self.logger Rails.logger end -- 2.8.0