diff --git a/Gemfile b/Gemfile index a7c99af1c226ea9c1d2b84a0b83478f7f998ef71..126ab31b806ec50ee14e13efa29bdec37715ed4f 100644 --- a/Gemfile +++ b/Gemfile @@ -60,7 +60,7 @@ gem 'bootstrap-sass' gem 'select2-rails' #Gruff (graphs and charts) -#gem 'rmagick' +gem 'rmagick' #gem 'gruff' #RSolr - Search Engine used by DSpace @@ -127,3 +127,6 @@ gem 'slim' # CUrl gem 'curb', '~> 0.8.8' + +# libArchive (Zip, Rar, ...) +gem 'libarchive', '~> 0.1.2' diff --git a/app/workers/thumbnail_generator_worker.rb b/app/workers/thumbnail_generator_worker.rb index 75b809f3f6141524b8a98ee833ccf87476ebb84a..a9eb16364b825cdb3581c0109465d59b0a3e5963 100644 --- a/app/workers/thumbnail_generator_worker.rb +++ b/app/workers/thumbnail_generator_worker.rb @@ -1,58 +1,10 @@ class ThumbnailGeneratorWorker include Sidekiq::Worker - include RepositoriesProxy - include Thumbnail::Creation - include Thumbnail::Formats + include Thumbnail::Generate def perform(learning_object_id) - - item = learning_object_repository.find(learning_object_id) - filename = item.get_bitstream_filename_of "ORIGINAL" - - size = "530x298" - - unless accepted_formats.include? file_format filename - item.thumbnail = default_thumbnail - else - begin - retrieve_link = item.get_bitstream_retrievelink_of "ORIGINAL" - file = download_bitstream(retrieve_link,filename) - rescue - puts "ERROR!!! Some error occurred during file download." - else - item.thumbnail = generate_thumbnail(file,filename,size) - delete_downloaded_bitstream(file) - end - end - - learning_object_repository.update_property(item,"thumbnail",item.thumbnail) - - end - - private - - def delete_downloaded_bitstream(file) - File.unlink(file) if File.exist?(file) - end - - def download_bitstream(url, filename) - output_dir = "/tmp" - - output_file = "#{output_dir}/#{filename}" - - c = Curl::Easy.new(url) - c.ssl_verify_peer = false - c.ssl_verify_host = false - File.open(output_file, 'wb') do |f| - # c.on_progress { |dl_total, dl_now, ul_total, ul_now| - # puts "#{dl_total}, #{dl_now}, #{ul_total}, #{ul_now}"; true - # } - c.on_body {|data| f << data; data.size } - c.perform - end - - return output_file + generate_thumbnail(learning_object_id) end end diff --git a/lib/tasks/thumbnail.rake b/lib/tasks/thumbnail.rake index 634ff332df43e82355d617274b2469b6a4f3c3c7..480b499107da08182184d6e92428cd84d22455c3 100644 --- a/lib/tasks/thumbnail.rake +++ b/lib/tasks/thumbnail.rake @@ -25,13 +25,16 @@ namespace :thumbnail do # Terminate loop if there are no more items to import break if items.empty? - # Increment offset, to get new items on next iteration - offset = offset + limit - items.each do |item| - ThumbnailGeneratorWorker.perform_async(item.id) + if item.thumbnail.nil? + ThumbnailGeneratorWorker.perform_async(item.id) + elsif item.thumbnail.empty? + ThumbnailGeneratorWorker.perform_async(item.id) + end end + # Increment offset, to get new items on next iteration + offset += limit end end end diff --git a/lib/thumbnail/formats.rb b/lib/thumbnail/accepted_formats.rb similarity index 50% rename from lib/thumbnail/formats.rb rename to lib/thumbnail/accepted_formats.rb index c7ac2c2aa9ed9c413070c934f4432d802d428fc6..bec7f427de5d0aea0ceba8a80ba69c471a4ab230 100644 --- a/lib/thumbnail/formats.rb +++ b/lib/thumbnail/accepted_formats.rb @@ -1,30 +1,53 @@ module Thumbnail - module Formats + module AcceptedFormats - def file_format file + def get_file_basename file unless file.nil? - file_format = File.extname(file) + file_format = File.basename(file) else file_format = "" end file_format end + def get_file_extname file + unless file.nil? + file_name = File.extname(file) + else + file_name = "" + end + file_name + end + + def array_to_upcase array + upcase_array = [] + array.each do |a| + upcase_array << a.upcase + end + upcase_array + end + + def accepted_archive_formats + lower = [".zip", ".gzip", ".bzip", ".tar"] + upper = array_to_upcase lower + return lower + upper + end + def accepted_video_formats lower = [".mp4", ".wmv", ".3gp", ".asf", ".avi", ".flv", ".swf", ".mov", ".mpg", ".mpeg", ".rmvb", ".rm", ".vob", ".webm"] - upper = [".MP4", ".WMV", ".3GP", ".ASF", ".AVI", ".FLV", ".SWF", ".MOV", ".MPG", ".MPEG", ".RMVB", ".RM", ".VOB", ".WEBM"] + upper = array_to_upcase lower return lower + upper end def accepted_image_formats lower = [".jpg", ".jpeg", ".gif", ".png", ".bmp", ".tif", ".wmf"] - upper = [".JPG", ".JPEG", ".GIF", ".PNG", ".BMP", ".TIF", ".WMF"] + upper = array_to_upcase lower return lower + upper end def accepted_other_formats lower = [".pdf", ".pps"] - upper = [".PDF", ".PPS"] + upper = array_to_upcase lower return lower + upper end diff --git a/lib/thumbnail/creation.rb b/lib/thumbnail/creation.rb deleted file mode 100644 index aebc32bd9815aee17d1876640fabbb243828c838..0000000000000000000000000000000000000000 --- a/lib/thumbnail/creation.rb +++ /dev/null @@ -1,69 +0,0 @@ -module Thumbnail - module Creation - - include Formats - - def generate_thumbnail(input, filename, size) - unless accepted_formats.include? file_format(filename) - return default_thumbnail - else - thumbnail = thumbnail_path(filename, size) - output = "#{root_dir}#{thumbnail}" - begin - if accepted_video_formats.include? File.extname(input) - generate_video_thumbnail(input, output, size) - else - generate_image_thumbnail(input, output, size) - end - rescue - return default_thumbnail - else - return thumbnail - end - end - - end - - def default_thumbnail - @default_thumbnail ||= nil - end - - private - - def generate_video_thumbnail(input,output,size) - movie = FFMPEG::Movie.new(input) - frame = (movie.duration * 25/100).floor - movie.screenshot(output, - { seek_time: frame, resolution: size }, - preserve_aspect_ratio: :width) - end - - def generate_image_thumbnail(input,output,size) - # Read the image and resize it. The `change_geometry' method - # computes the new image geometry and yields to a block. The - # return value of the block is the return value of the method. - img = Magick::Image.read(input)[0] - img.alpha(Magick::DeactivateAlphaChannel) - img.change_geometry!(size) { |cols, rows| img.thumbnail! cols, rows } - img.write(output) - end - - def encode_hash_from(object) - Digest::SHA1.hexdigest object - end - - def thumbnail_path(filename, size) - thumbnail_name = encode_hash_from filename - thumbnail_path = "#{thumbnails_dir}/#{thumbnail_name}_#{size}.jpg" - end - - def thumbnails_dir - @thumbnails_dir ||= "/thumbnails" - end - - def root_dir - @root_dir ||= Rails.root.join('public') - end - - end -end diff --git a/lib/thumbnail/generate.rb b/lib/thumbnail/generate.rb new file mode 100644 index 0000000000000000000000000000000000000000..8f2f59ca27fd43319d910ac22930d0e949ef9982 --- /dev/null +++ b/lib/thumbnail/generate.rb @@ -0,0 +1,211 @@ +module Thumbnail + module Generate + + include AcceptedFormats + include RepositoriesProxy + + def generate_thumbnail(learning_object_id) + + size = "530x298" + + item = learning_object_repository.find learning_object_id + return false if item.nil? + + puts "[generate_thumbnail (#{item.id})] Starting to generate thumbnail for ID='#{item.id}'" + + item_uniq_hash = encode_hash_from item.id.to_s + item.name + + workdir = tmpdir_path(item_uniq_hash) + thumbnail = thumbnail_path(item_uniq_hash, size) + output = "#{root_dir}#{thumbnail}" + + accepted_files = get_accepted_files(item,workdir) + accepted_files.sort! { |x,y| File.size(y) <=> File.size(x) } + accepted_files.each do |input| + filename = get_file_basename input + file_format = get_file_extname input + puts "[generate_thumbnail (#{item.id})] Trying to generate thumbnail for '#{filename}'" + begin + if accepted_video_formats.include? file_format + generate_video_thumbnail(input, output, size) + else + generate_image_thumbnail(input, output, size) + end + rescue Exception => e + puts "#{e} + \r[generate_thumbnail (#{item.id})] ERROR: input='#{input}' + \r[generate_thumbnail (#{item.id})] RESCUE: Try the next input.." + else + learning_object_repository.update_property(item,"thumbnail",thumbnail) + + clean_tmpfiles(workdir, accepted_files) + puts "[generate_thumbnail (#{item.id})] SUCCESS: input='#{input}'" + return true + end + end + + clean_tmpfiles(workdir, accepted_files) + puts "[generate_thumbnail (#{item.id})] ERROR: End of input files!!!" + return false + + end + + def default_thumbnail + @default_thumbnail ||= nil + end + + private + + def generate_video_thumbnail(input,output,size) + movie = FFMPEG::Movie.new(input) + if movie.valid? and !movie.video_codec.nil? + frame = (movie.duration * 25/100).floor + loop do + begin + movie.screenshot(output, + { seek_time: frame, resolution: size }, + preserve_aspect_ratio: :width) + rescue + if frame > 0 + puts "[generate_video_thumbnail] Decreasing frame and trying again!!! " + frame = (frame * 0.50).floor + else + raise "[generate_video_thumbnail] ERROR: Video's thumbnail was not generated. (FRAME_ERROR)" + end + else + return true, "[generate_video_thumbnail] Video's thumbnail was successfully generated!" + end + end + else + raise "[generate_video_thumbnail] ERROR: Video's thumbnail was not generated. (NULL_CODEC)" + end + end + + def generate_image_thumbnail(input,output,size) + # Read the image and resize it. The `change_geometry' method + # computes the new image geometry and yields to a block. The + # return value of the block is the return value of the method. + img = Magick::Image.read(input)[0] + img.alpha(Magick::DeactivateAlphaChannel) + img.change_geometry!(size) { |cols, rows| img.thumbnail! cols, rows } + img.write(output) + end + + def encode_hash_from(object) + Digest::SHA1.hexdigest object + end + + def create_dir(dirname) + FileUtils.mkdir_p(dirname) unless File.directory?(dirname) + end + + def tmpdir_path(id) + tmpdir = "/tmp/#{id}/" + create_dir tmpdir + return tmpdir + end + + def thumbnail_path(id, size) + thumbnails_dir = "/thumbnails" + create_dir "#{root_dir}#{thumbnails_dir}" + return "#{thumbnails_dir}/#{id}_#{size}.jpg" + end + + def root_dir + @root_dir ||= Rails.root.join('public') + end + + def clean_tmpfiles(workdir,files) + files.each do |f| + delete_file f + end + delete_dir workdir + end + + def delete_dir(dir) + FileUtils.rmdir(dir) if File.directory?(dir) + end + + def delete_file(file) + FileUtils.rm(file) if File.exist?(file) + end + + def get_accepted_files(item,workdir) + + filename = item.get_filename + file_format = get_file_extname filename + + accepted_files = [] + + if accepted_formats.include? file_format or accepted_archive_formats.include? file_format + begin + retrieve_link = item.get_retrievelink + file = workdir + filename + download_bitstream(retrieve_link,file) + rescue Exception => e + puts "#{e}" + else + if accepted_archive_formats.include? file_format + extract_accepted_files(file,workdir).each do |f| + accepted_files << f + end + delete_file file + else + accepted_files << file + end + end + end + + return accepted_files + end + + def extract_accepted_files(archive_file,workdir) + + return [] if archive_file.nil? + + extracted_files = [] + + puts "[extract_accepted_files] Extracting accepted files out from '#{get_file_basename(archive_file)}'" + Archive.read_open_filename(archive_file) do |archive| + while entry = archive.next_header + if accepted_formats.include? get_file_extname(entry.pathname) + filename = get_file_basename(entry.pathname) + file = workdir + filename + begin + File.open(file, 'wb') do |f| + archive.read_data(1024) do |d| + f << d + end + end + rescue Exception => e + puts "#{e} + \r[extract_accepted_files] ERROR: Some error occurred while extracting file '#{filename}'" + else + puts "[extract_accepted_files] SUCCESS: Extracted '#{filename}' to '#{file}'" + extracted_files << file + end + end + end + end + + return extracted_files + end + + def download_bitstream(url,output) + begin + c = Curl::Easy.new(url) + c.ssl_verify_peer = false + c.ssl_verify_host = false + File.open(output, 'wb') do |f| + c.on_body {|data| f << data; data.size } + c.perform + end + rescue + raise "[download_bitstream] ERROR: Some error occurred during file download." + else + return true + end + end + + end +end