I've been trying to develop a site that allows for the uploading of various types of documents including pdfs. Below is my uploader:
class PictureUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
version :jpg do
process :convert_to_images
process :set_content_type_jpg
def convert_to_images(*args)
image = MiniMagick::Image.open(current_path)
image.pages.each_with_index do |page, index|
MiniMagick::Tool::Convert.new do |convert|
convert.background 'white'
convert.flatten
convert.density 300
convert.quality 95
convert << page.path
convert << "#{CarrierWave.root}/#{store_dir}/image-#{index}.jpg"
end
end
end
end
def set_content_type_jpg(*args)
self.file.instance_variable_set(:#content_type, "image/jpg")
end
def extension_whitelist
%w(jpg jpeg gif png pdf)
end
end
So far the site will allow uploads to the server of all of the whitelisted file types, and everything but pdfs will actually display. Pdfs, however, will only show a broken image rather than an image of that pdf. What can I do to fix this?
You should first convert the pdf into a jpg or other image as described here Then try to run the converted file through your above code
Related
Expect the following Uploader, when I upload .png with no background via remote_XXX_url the :thumb version using a resize_to_fit is converted with a black background.
The original file uploaded is as the same as the original with the transparent background.
If I upload the image via a file_input the issue is not occurring.
Therefore I deduce that the issue is only occurring when doing a resize_to_XX transformation on remote_XXX_url file. Do you know what is happening ?
carrierwave (2.2.2) -- mini_magick (4.11.0) - The original image has a transparent background.
Image used : https://whirlpool-cdn.thron.com/delivery/public/thumbnail/whirlpool/pi-81b54ac7-7614-4a32-bdd1-a3f79db530d5/sckne7/std/320x320/859791401010.jpg?fill=zoom&fillcolor=rgba:255,255,255&scalemode=product
Thumb Image resized_to_fit
Image without resizing
class ImageUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
def store_dir
"#{ENV['AWS_S3_BUCKET_NAME']}/#{model.class.to_s.underscore}/#{model.id}"
end
def extension_allowlist
%w(jpg jpeg gif png svg webp)
end
version :thumb do
process resize_to_fit: [150, 150]
end
def filename
#name ||= "#{mounted_as}_#{timestamp}.#{file.extension}" if original_filename.present?
end
def timestamp
var = :"##{mounted_as}_timestamp"
model.instance_variable_get(var) or model.instance_variable_set(var, Time.now.to_i)
end
end
Your original file appears to have the extension .jpg but actually contains a PNG image.
ImageMagick itself is smart enough to spot that and it won't cause you any trouble. I don't know or use carrierwave but it appears to be carrying forward the original .jpg extension and forcing ImageMagick to write a JPEG which will make transparent regions black. I think it's this line:
#name ||= "#{mounted_as}_#{timestamp}.#{file.extension}" if original_filename.present?
I want to create the ability for the user to upload an image into a pdf which will be generated inside of the app. To do so I'm using gem prawn to pdf creation and CarrierWave::MiniMagic to create thumbnail versions of images for a Rails 6 app, and I've noticed loss of quality when the images are downsized.
original image:
the same image inside of pdf:
Such a bad quality is not acceptable for me. I don't know if it's a common problem but I've found a few posts without any resolution, is there any alternative solution/tool to handle it? I have been struggling with this problem for weeks.
I don't have nothing special in my uploader file:
class LogoUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
EXTENSION_WHITELIST = %w[jpg jpeg png].freeze
storage :file
process convert: 'png'
version :thumb do
process resize_to_fit: [300, 300]
end
def extension_whitelist
EXTENSION_WHITELIST
end
end
I have a problem with the validation "extension_whitelist" at CarrierWave Uploader. The model.is_special_category? return nil, any other way to validate it?
I want to allow only image if the category is "special category"
uploader:
class FileUploader < CarrierWave::Uploader::Base
def extension_whitelist
return %w(jpg jpeg gif png bmp svg ) if model.is_special_category?
%w(jpg jpeg gif png bmp svg pdf docx doc xlsx xls odt ods csv )
end
end
model:
class AttachedFile < ApplicationRecord
def is_special_category?
category == 'Special Category'
end
end
so I have a site already in production using the carrierwave gem with images stored on amazon s3. my uploader uses the store_dir method to specify a particular structure to put in processed images.
well, now in my dev environment I've added the carrierwave_direct gem to start uploading directly to S3. The problem is this gem completely overrides the store_dir and filename defaults in my uploader. I can't push my fully working uploader live because all my old image links will be broken.
it's my understanding that the CWdirect gem would upload a raw image file to a "temp" directory on S3, then S3 responds and gives you a key variable so you can grab this file and process it as you see fit. so, should i be using a completely separate image uploader class in carrierwave to process the images and place them in the correct folders? meaning I'll have one uploader dedicated to carrierwave_direct that uploads wherever this gem seems to want to upload to; and I'll use another uploader.rb class linked to my real model that keeps my current store_dir and filename structure?
In any case, my basic question is, how can I use CarrierWave_Direct gem if I already have CW running in production with images in a specific folder structure?
Ok, so I did figure out how to do this and I'll explain below. My hunch was correct to use two different CarrierWave uploader classes--one class dedicated to uploading to S3 (using CarrierWave_Direct gem), and a second class used only for image processing (the class I was already using in production). I'll try and post relevant code below but if anyone has questions let me know. I'm unsure why I haven't seen others using separate classes like this but it seems to work for me.
My image Uploader class app\uploaders\image_uploader.rb utilizing carrierwave_direct gem:
class ImageUploader < CarrierWave::Uploader::Base
include CarrierWaveDirect::Uploader
include ActiveModel::Conversion
extend ActiveModel::Naming
include CarrierWave::MimeTypes
process :set_content_type
# Add a white list of extensions which are allowed to be uploaded.
# For images you might use something like this:
def extension_white_list
%w(jpg jpeg gif png)
end
# Include the Sprockets helpers for Rails 3.1+ asset pipeline compatibility:
include Sprockets::Helpers::RailsHelper
include Sprockets::Helpers::IsolatedHelper
# Override the directory where uploaded files will be stored.
# CarrierWaveDirect::Uploader puts raw uploaded files in this directory on S3 as a first step
def store_dir
"unprocessed_uploads"
end
end
**notice there's no processing being done in this class
My image PROCESSING class app\uploaders\image_processor.rb (what was already in place in production):
class ImageProcessor < CarrierWave::Uploader::Base
# Include RMagick or MiniMagick support:
# include CarrierWave::RMagick
include CarrierWave::MiniMagick
include CarrierWave::MimeTypes
process :set_content_type
# Include the Sprockets helpers for Rails 3.1+ asset pipeline compatibility:
include Sprockets::Helpers::RailsHelper
include Sprockets::Helpers::IsolatedHelper
# Choose what kind of storage to use for this uploader:
storage :fog
# Override the directory where uploaded files will be stored.
# This is a sensible default for uploaders that are meant to be mounted:
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
# "uploads/#{model.class.to_s.underscore}/path/#{model.id}"
end
# Provide a default URL as a default if there hasn't been a file uploaded:
def default_url
"logos/" + [version_name, "default.png"].compact.join('_')
end
# Process files fetched from S3 after they are uploaded:
def make_thumbnail(width, height)
# uses MiniMagick classes to get a square, centered thumbnail image
manipulate! do |img|
if img[:width] < img[:height]
remove = ((img[:height] - img[:width])/2).round
img.shave("0x#{remove}")
elsif img[:width] > img[:height]
remove = ((img[:width] - img[:height])/2).round
img.shave("#{remove}x0")
end
img.resize("#{width}x#{height}")
img
end
end
# Create different versions of your uploaded files:
# the process statement below isn't defined within a version block on purpose--this means the ORIGINAL uploaded photo is constrained to 1050 pics
process :resize_to_limit => [1050, 1050]
process :quality => 85 # this reduces filesize greatly and saves space
version :thumb do
process :make_thumbnail => [100, 100]
process :quality => 85 # this reduces filesize greatly and saves space
end
version :big_thumb do
process :make_thumbnail => [350, 350]
process :quality => 85 # this reduces filesize greatly and saves space
end
# Add a white list of extensions which are allowed to be uploaded.
# For images you might use something like this:
def extension_white_list
%w(jpg jpeg gif png)
end
# Override the filename of the uploaded files:
# Avoid using model.id or version_name here, see uploader/store.rb for details.
def filename
if original_filename
if model && model.read_attribute(:image).present?
model.read_attribute(:image)
else
"#{secure_token}.#{file.extension}"
end
end
end
protected
def secure_token
var = :"##{mounted_as}_secure_token"
model.instance_variable_get(var) or model.instance_variable_set(var, SecureRandom.uuid)
end
end
My Photo model (summarized):
class Photo < ActiveRecord::Base
mount_uploader :image, ImageProcessor
def save_and_process_image(options = {})
s3_unprocessed_image_url = self.image.asset_host + '/' + self.key
# this next line downloads the image from S3
# and this save line below will process the image and reupload to S3 according to ImageProcessor settings
self.remote_image_url = s3_unprocessed_image_url
save
end
end
I also have Photo controller and view code available, if ya'll want it let me know. Basically I use the ImageUploader class to do the initial upload to S3 to a folder called unprocessed_uploads. Then S3 responds with a key field in the URL which I pass on to ImageProcessor class--this is attached to a Photo and processes the thumbnail and other images then re-uploads them to my uploads folder on S3.
This separation meant i didn't need to change my current folder structure on S3 when adding the carrierwave_direct gem. Hope this helps others. Let me know if you need more code I'm kind of tired of typing :)
UPDATE--Adding more code:
Photos Controller:
class PhotosController < ApplicationController
def index
#photos = #photos.sort_by(&:created_at)
#uploader = ImageUploader.new
#uploader.success_action_redirect = new_tank_photo_url(#tank)
respond_to do |format|
format.html # index.html.erb
format.json { render json: #photos }
end
end
def create
respond_to do |format|
if #photo.save_and_process_image
format.html { redirect_to tank_photos_path(#tank), notice: 'Photo uploaded successfully and is being processed...' }
format.json { render json: #photo, status: :created, location: #photo }
else
format.html { render :new }
format.json { render json: #photo.errors, status: :unprocessable_entity }
end
end
end
Photos Index view, the form with the Upload button:
<%= direct_upload_form_for #uploader, :html => {:class => "form-inline"} do |f| %>
<%= f.file_field :image %>
<%= f.submit "Upload", :class => "btn btn-primary btn-medium" %>
<% end %>
So with the above view/controller code added, here is a summary of the steps taken. Please note the difference between the ImageUploader class and the ImageProcessor class:
In my Photos Controller I create the #uploader variable which is of class ImageUploader--this means that images submitted in my index/view form which uses #uploader will upload the image to a "temp" folder--Since this uses the ImageUploader class then no processing is done when this image is uploaded (yet).
When the user clicks "Upload" button on the form, the Photo>>create action is called. This action calls #photo.save_and_process_image -- look at the model to see that this method actually grabs the recently uploaded image from the S3 'temp' folder, processes it, then re-uploads the processed images to their final destination. This is all possible because my Photo model is linked to the ImageProcessor class, which does the processing/uploading. It's not linked to the separate ImageUploader class.
Hope this helps explain what i have going on
I have model:
class RealtyPhoto < ActiveRecord::Base
attr_accessible :name, :photo
mount_uploader :photo, RealtyPhotoUploader
belongs_to :realty_object
end
And uploader:
class RealtyPhotoUploader < CarrierWave::Uploader::Base
include CarrierWave::RMagick
storage :file
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
def default_url
[version_name, "/assets/default-flat.jpg"].compact.join('_')
end
def extension_white_list
%w(jpg jpeg gif png)
end
end
I write in my code something like this:
require 'open-uri'
object = RealtyObject.last
photo = RealtyPhoto.new
photo.photo = open('http://milushov.ru/bg.jpg').read
object.realty_photos << photo
And i get an error on that line:
photo.photo = open('http://milushov.ru/bg.jpg').read
ArgumentError: string contains null byte
from /home/roma/.rvm/gems/ruby-1.9.3-p327#miel/gems/carrierwave-0.7.1/lib/carrierwave/sanitized_file.rb:113:in `expand_path'
Does anybody know how to download and save file from outside? (when i save file from form on the page - all ok). Maybe using CarrierWave Uploader?
Carrierwave upload image from remote url using remote_column_name_url in your case
photo.remote_photo_url = "url"
You seemed to have forgotten to close the file handle. Closing the file will likely make your application work correctly.
you could also do it in another way..
more efficient is
model[:name] = filename
model.save
surl = model.photo.url
FileUtils.copy(filesrc,surl,derefrence=true)
this approach basically saves you from copying the file to one location and ten copying it to another location when saved