Can I Upload Other Video Files That Mp4 With Carrierwave
Escaping CarrierWave versions
For one of the projects we have been working on , we had to be able to upload videos to the server, convert them to formats supported by browsers with <video>
tag and serve them to the user. Since Scarlet and Ruby on Rails are our tools of choice nosotros used CarrierWave for file uploading and storage. Since Carrier Wave supports processing of input files into various versions, we decided to give it a try.
CarrierWave Gem
Since processing video files takes a lot of time, nosotros had to do it in the groundwork so we used carrierwave-backgrounder. When looking for a way to do video processing, we found the gem carrierwave-video that used streamio-ffmpeg
for transcoding and information technology seemed fine for our task.
Our Gemfile looked like this:
precious stone 'carrierwave', '0.x.0'
precious stone 'carrierwave-backgrounder', '0.four.2'
gem 'carrierwave-video', '0.5.six'
To encode videos to proper formats nosotros used encode_video
method from carrierwave-video
:
version :mp4 do
process encode_video: [:mp4, { progress: :on_progress }]
end version :webm exercise
procedure encode_video: [:webm, { progress: :on_progress }]
cease
Subsequently some time at that place was a demand in the project to be able to cut out small pieces of videos and serve them independently.
- At first nosotros tried to practice information technology the same style as previous processing:
version :mp4 exercise
process encode: [:mp4, { progress: :on_progress }]
finish version :webm do
process encode: [:webm, { progress: :on_progress }]
end def encode(format, opts={})
encode_video(format, opts) practice |movie, params|
params[:custom] = "-ss #{model.playback.start_in_hms} -t #{model.playback.duration_in_hms}"
finish
terminate
Unfortunately this had a very serious drawback — since all of the versions were being candy from one file inputted to the uploader, they had to exist transcoded every fourth dimension. That was far from perfect as it took additional fourth dimension and unnecessarily used resources so we hacked our manner a bit: since the original video was already in the formats we needed, we could just laissez passer those versions instead and re-create the audio and video streams.
The Solution
To brand the code less cluttered, we sprinkled information technology with a flake of DSL:
support_formats custom: proc { |model| "-ss #{model.start_time} -t #{model.duration}" },
source: proc { |model, version| model.parent.file.versions[version].file },
mode: :copy def support_formats(support_opts={})
FORMATS.each practice |version_name, opts|
opts = opts.reverse_merge(support_opts) (conditional = opts.delete(:if)) && (provisional = provisional.to_sym)
uploader = version(version_name, if: conditional) { process encode_format: [opts] }
uploader[:uploader].class_eval <<-RUBY, __FILE__, __LINE__ + 1
def full_filename(file)
file + ".#{version_name}.#{opts[:extension]}" # forcing the extension, otherwise ffmpeg got confused
finish
RUBY
end
stop def encode_format(opts={})
cache_stored_file! if !cached? if opts[:mode] == :copy
opts[:video_codec] = 'copy'
opts[:audio_codec] = 'copy'
terminate opts[:custom] = opts[:custom].call(model) if opts[:custom].respond_to?(:call) source = source.call(model, version_name) if source.respond_to?(:phone call)
source = file if source.nil?
source = source.path if source.respond_to?(:path) # etc
end
This setup kind of worked, merely it posed a lot of problems: we didn't have control over what versions were transcoded and we had to recreate each version if whatsoever of the transcoding failed. More than that, if any transcoding happened during deployment of new version, sidekiq had to be killed and restarted and it didn't have a way of going dorsum from where it started and so the whole processing had to exist either redone or ditched altogether and marked as crashed.
We have tried diverse solutions of mitigating this problem only unfortunately using carrierwave-backgrounder made everything more messy. That gem was great for simpler logic but unfortunately choked a scrap when nosotros tried extending information technology more. This likewise caused the logic of processing to be divided betwixt non-logical parts, like processing code ending upwardly partially in sidekiq worker (because it was easy to set custom worker when mounting an uploader), non-obvious or custom callbacks beingness thrown all over the place or processing starting non-asynchronously if nosotros weren't careful enough. The API got brittle and the whole codebase gradually became a mess.
class StreamUploadWorker < ::CarrierWave::Workers::StoreAsset def perform(*args)
set_args(*args) if args.present?
video = constantized_resource.find id # custom callbacks in model
run_callback video, :before_upload
super(*args)
video.reload run_callback video, :after_upload_success rescue
run_callback video, :after_upload_failure
video.broken! if video.respond_to?(:broken!) # logging
end def run_callback(video, callback)
video.ship(callback) if video.respond_to?(callback)
end
end
The terminal harbinger…
The final straw, notwithstanding, came with a new requirement. The project has matured enough to generate significant traffic and serving multimedia content from a defended server no longer seemed like a feasible solution. We needed to accept files on both a local server (for processing) and on some kind of cloud solution (for hosting). Every bit CarrierWave versions are goose egg more than than differently named files in a directory, using them seemed like a bad idea considering the amount of patchwork needed. It was time to make clean the business firm.
We solved the trouble by ditching versions altogether. We created a divide model for storing files that could either have local or remote (fog) uploader attached. Then we wrote our ain transcoding logic, acme down, piece of cake to understand, using streamio-ffmpeg directly with specified file equally the source and putting it in the path CarrierWave expected.
class FileObject < ActiveRecord::Base
belongs_to :owner, polymorphic: true class Local < FileObject
mount_uploader :file, FileObjectUploader
end class Deject < FileObject
mount_uploader :file, AwsUploader
end
end class Processor
# partial, instance code def recreate_versions!(video, file)
formats_with_options(video, file).each practise |format, opts|
if video.parent.present?
original = video.parent.version(format)
file = original.file if original.respond_to?(:file)
end file_object = FileObject::Local.create!(possessor: video, version: format)
filename = "#{SecureRandom.uuid}.#{opts[:extension]}" FileUtils.mkdir_p file_object.file.store_dir
destination = "#{file_object.file.store_dir}/#{filename}" transcode!(source, destination, format, opts)
file_object.update_column(:file, filename) S3UploadWorker.perform_async(file_object.id) unless Rail.env.evolution?
stop
terminate
end
Summary
CarrierWave is a bang-up solution for file uploading. Information technology'southward too a slap-up solution for uncomplicated processing. The moral of the story is, you accept to employ the appropriate tools. Carrier Wave versions are not enough for circuitous processing or whatsoever processing that doesn't use the original uploaded file every bit the source. It may seem obvious in retrospect just that's what happens when your codebase gradually evolves. When this happens, always try to observe some time to stop, expect dorsum and ask yourself: "Is this code doing what it was originally made for?".
Happy coding!
Source: https://blog.untitledkingdom.com/escaping-carrierwave-versions-6e0015e6e168
0 Response to "Can I Upload Other Video Files That Mp4 With Carrierwave"
Post a Comment