├── .document ├── .rvmrc.sample ├── .yardopts ├── .gitignore ├── readme_content └── image │ └── demo.png ├── Gemfile ├── .travis.yml ├── lib └── carrierwave │ └── video │ ├── thumbnailer │ ├── version.rb │ ├── ffmpegthumbnailer │ │ └── options.rb │ └── ffmpegthumbnailer.rb │ └── thumbnailer.rb ├── spec ├── spec_helper.rb └── lib │ ├── ffmpegthumbnailer_spec.rb │ └── thumbnailer_spec.rb ├── CHANGELOG.md ├── gemspec.yml ├── Rakefile ├── CONTRIBUTING.md ├── LICENSE ├── carrierwave-video-thumbnailer.gemspec ├── CODE-OF-CONDUCT.md └── README.md /.document: -------------------------------------------------------------------------------- 1 | - 2 | ChangeLog.md 3 | LICENSE.txt 4 | -------------------------------------------------------------------------------- /.rvmrc.sample: -------------------------------------------------------------------------------- 1 | rvm --create gemset use cvtn-devel 2 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --markup markdown --title "carrierwave-video-thumbnailer Documentation" --protected 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .yardoc 3 | .rvmrc 4 | Gemfile.lock 5 | doc/ 6 | pkg/ 7 | vendor/cache/*.gem 8 | -------------------------------------------------------------------------------- /readme_content/image/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evrone/carrierwave-video-thumbnailer/HEAD/readme_content/image/demo.png -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | group :development do 6 | gem 'kramdown' 7 | end 8 | 9 | gem 'mime-types', "< 3" 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.1.1 4 | - 2.0.0 5 | - 1.9.3 6 | 7 | branches: 8 | only: 9 | - /^feature\/.*$/ 10 | - develop 11 | - master 12 | - stable 13 | -------------------------------------------------------------------------------- /lib/carrierwave/video/thumbnailer/version.rb: -------------------------------------------------------------------------------- 1 | module CarrierWave 2 | module Video 3 | module Thumbnailer 4 | # carrierwave-video-thumbnailer version 5 | VERSION = "0.1.4" 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rspec' 2 | require 'active_support/all' 3 | require 'carrierwave/video/thumbnailer' 4 | 5 | RSpec.configure do |config| 6 | config.mock_with :rspec 7 | config.formatter = 'progress' 8 | config.color_enabled = true 9 | end 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Released] 8 | 9 | ### 0.1.0 / 2012-10-26 10 | 11 | * Initial release. Mostly harmless. 12 | 13 | ### 0.1.4 / 2013-10-17 14 | 15 | * Docs and tests improvements 16 | -------------------------------------------------------------------------------- /gemspec.yml: -------------------------------------------------------------------------------- 1 | name: carrierwave-video-thumbnailer 2 | summary: "Video thumbnailer plugin for CarrierWave" 3 | description: "Lets you make video thumbnails in carrierwave via ffmpegthumbnailer" 4 | license: MIT 5 | authors: Pavel Argentov 6 | email: argentoff@gmail.com 7 | homepage: https://github.com/evrone/carrierwave-video-thumbnailer#readme 8 | 9 | dependencies: 10 | carrierwave: '>= 0' 11 | 12 | development_dependencies: 13 | pry: '>= 0' 14 | activesupport: ~> 3.2.8 15 | bundler: ~> 1.0 16 | rake: ~> 0.8 17 | rspec: ~> 2.4 18 | rubygems-tasks: ~> 0.2 19 | yard: ~> 0.8 20 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'rubygems' 4 | 5 | begin 6 | require 'bundler' 7 | rescue LoadError => e 8 | warn e.message 9 | warn "Run `gem install bundler` to install Bundler." 10 | exit -1 11 | end 12 | 13 | begin 14 | Bundler.setup(:development) 15 | rescue Bundler::BundlerError => e 16 | warn e.message 17 | warn "Run `bundle install` to install missing gems." 18 | exit e.status_code 19 | end 20 | 21 | require 'rake' 22 | 23 | require 'rubygems/tasks' 24 | Gem::Tasks.new 25 | 26 | require 'rspec/core/rake_task' 27 | RSpec::Core::RakeTask.new 28 | 29 | task :test => :spec 30 | task :default => :spec 31 | 32 | require 'yard' 33 | YARD::Rake::YardocTask.new 34 | task :doc => :yard 35 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thanks for taking the time to contribute! 4 | 5 | The following is a set of guidelines for contributing to this project. These are just guidelines, not rules, so use your best judgement and feel free to propose changes to this document in a pull request. 6 | 7 | ## Reporting issues 8 | 9 | Ensure the bug was not already reported by searching on GitHub under issues. If you're unable to find an open issue addressing the bug, open a new issue. 10 | 11 | Please pay attention to the following points while opening an issue: 12 | * How to reproduce the issue, step-by-step. 13 | * The expected behavior (or what is wrong). 14 | * Screenshots for GUI issues. 15 | * The application version. 16 | * The operating system. 17 | 18 | ## Pull Requests 19 | 20 | Pull Requests are always welcome. 21 | 22 | 1. When you edit the code, please check the formatting of your code before you `git commit`. 23 | 2. Ensure the PR description clearly describes the problem and solution. It should include: 24 | * The operating system on which you tested. 25 | * The relevant issue number, if applicable. 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2012-2019 Pavel Argentov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/carrierwave/video/thumbnailer/ffmpegthumbnailer/options.rb: -------------------------------------------------------------------------------- 1 | module CarrierWave 2 | module Video 3 | module Thumbnailer 4 | 5 | # Options to be be converted to CLI parameters 6 | class Options < Hash 7 | 8 | BOOLEAN = [ 9 | :square, 10 | :strip, 11 | :workaround 12 | ] 13 | 14 | def initialize opts 15 | opts.each { |k, v| self[k] = v} 16 | end 17 | 18 | def to_cli 19 | self.map do |k, v| 20 | if BOOLEAN.include? k 21 | cli_key k if v 22 | else 23 | "#{cli_key k} #{cli_val v}" 24 | end 25 | end.join(' ') 26 | end 27 | 28 | private 29 | 30 | def cli_key k 31 | '-' + ( 32 | case k 33 | when :size then 's' 34 | when :seek then 't' 35 | when :quality then 'q' 36 | when :square then 'a' 37 | when :strip then 'f' 38 | when :workaround then 'w' 39 | else 40 | '-noop' 41 | end 42 | ) 43 | end 44 | 45 | def cli_val v 46 | v.to_s 47 | end 48 | 49 | end 50 | 51 | class FFMpegThumbnailerOptions 52 | 53 | attr_reader :format, :options, :logger, :callbacks, :custom 54 | 55 | def initialize options 56 | @callbacks = options.delete(:callbacks) || {} 57 | @custom = options.delete :custom 58 | @format = options.delete :format 59 | @logger = options.delete :logger 60 | @options = Options.new options 61 | end 62 | 63 | def to_cli 64 | %Q{#{"-c #{format} " if format}#{@options.to_cli}#{" #{custom}" if custom}} 65 | end 66 | 67 | end 68 | end 69 | end 70 | end 71 | 72 | -------------------------------------------------------------------------------- /spec/lib/ffmpegthumbnailer_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'carrierwave/video/thumbnailer' 3 | 4 | describe CarrierWave::Video::Thumbnailer::FFMpegThumbnailer do 5 | 6 | describe "#run" do 7 | let(:input_file_path) { '/tmp/file.mov' } 8 | let(:output_file_path) { '/tmp/file.jpg' } 9 | let(:binary) { 'thumbnailrrr' } 10 | 11 | let(:thumbnailer) { CarrierWave::Video::Thumbnailer::FFMpegThumbnailer.new(input_file_path, output_file_path) } 12 | 13 | before do 14 | CarrierWave::Video::Thumbnailer::FFMpegThumbnailer.binary = binary 15 | end 16 | 17 | it "should run the ffmpegthumbnailer binary" do 18 | @options = CarrierWave::Video::Thumbnailer::FFMpegThumbnailerOptions.new({}) 19 | command = "#{binary} -i \"#{input_file_path}\" -o \"#{output_file_path}\"" 20 | Open3.should_receive(:popen3).with(command) 21 | 22 | thumbnailer.run @options 23 | end 24 | 25 | context "with full set of CLI options" do 26 | 27 | it "runs the thumbnailer with all corresponding CLI keys" do 28 | 29 | opts = { 30 | format: 'png', 31 | size: '512', 32 | seek: '20%', 33 | quality: 10, 34 | square: true, 35 | strip: true, 36 | workaround: true, 37 | custom: '-v' 38 | } 39 | 40 | @options = CarrierWave::Video::Thumbnailer::FFMpegThumbnailerOptions.new opts 41 | 42 | cli = "#{binary} -i \"#{input_file_path}\" -o \"#{output_file_path}\" -c png -s 512 -t 20% -q 10 -a -f -w -v" 43 | Open3.should_receive(:popen3).with(cli) 44 | 45 | thumbnailer.run @options 46 | end 47 | 48 | end 49 | 50 | context "given a logger" do 51 | let(:logger) { double(:logger) } 52 | 53 | it "should run and log results" do 54 | @options = CarrierWave::Video::Thumbnailer::FFMpegThumbnailerOptions.new({logger: logger}) 55 | command = "#{binary} -i \"#{input_file_path}\" -o \"#{output_file_path}\"" 56 | Open3.should_receive(:popen3).with(command) 57 | logger.should_receive(:info).with("Running....#{command}") 58 | logger.should_receive(:error).with("Failure!") 59 | 60 | thumbnailer.run @options 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/carrierwave/video/thumbnailer/ffmpegthumbnailer.rb: -------------------------------------------------------------------------------- 1 | require 'carrierwave/video/thumbnailer/ffmpegthumbnailer/options' 2 | require 'open3' 3 | 4 | module CarrierWave 5 | module Video 6 | module Thumbnailer 7 | class FFMpegThumbnailer 8 | 9 | # Explicit class methods 10 | class << self 11 | 12 | # Sets a required thumbnailer binary 13 | def binary=(bin) 14 | @ffmpegthumbnailer = bin 15 | end 16 | 17 | # Tells the thumbnailer binary name 18 | def binary 19 | @ffmpegthumbnailer.nil? ? 'ffmpegthumbnailer' : @ffmpegthumbnailer 20 | end 21 | 22 | def logger= log 23 | @logger = log 24 | end 25 | 26 | def logger 27 | return @logger if @logger 28 | logger = Logger.new(STDOUT) 29 | logger.level = Logger::INFO 30 | @logger = logger 31 | end 32 | 33 | end 34 | 35 | attr_reader :input_path, :output_path 36 | 37 | def initialize in_path, out_path 38 | @input_path = in_path 39 | @output_path = out_path 40 | end 41 | 42 | def run options 43 | logger = options.logger 44 | cmd = %Q{#{CarrierWave::Video::Thumbnailer::FFMpegThumbnailer.binary} -i "#{input_path.shellescape}" -o "#{output_path.shellescape}" #{options.to_cli}}.rstrip 45 | 46 | logger.info("Running....#{cmd}") if logger 47 | outputs = [] 48 | exit_code = nil 49 | 50 | Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr| 51 | stderr.each("r") do |line| 52 | outputs << line 53 | end 54 | exit_code = wait_thr.value 55 | end 56 | 57 | handle_exit_code(exit_code, outputs, logger) 58 | end 59 | 60 | private 61 | 62 | def handle_exit_code(exit_code, outputs, logger) 63 | return unless logger 64 | if exit_code == 0 65 | logger.info("Success!") 66 | else 67 | outputs.each do |output| 68 | logger.error(output) 69 | end 70 | logger.error("Failure!") 71 | end 72 | exit_code 73 | end 74 | 75 | end 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /carrierwave-video-thumbnailer.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'yaml' 4 | 5 | Gem::Specification.new do |gem| 6 | gemspec = YAML.load_file('gemspec.yml') 7 | 8 | gem.name = gemspec.fetch('name') 9 | gem.version = gemspec.fetch('version') do 10 | lib_dir = File.join(File.dirname(__FILE__),'lib') 11 | $LOAD_PATH << lib_dir unless $LOAD_PATH.include?(lib_dir) 12 | 13 | require 'carrierwave/video/thumbnailer/version' 14 | CarrierWave::Video::Thumbnailer::VERSION 15 | end 16 | 17 | gem.summary = gemspec['summary'] 18 | gem.description = gemspec['description'] 19 | gem.licenses = Array(gemspec['license']) 20 | gem.authors = Array(gemspec['authors']) 21 | gem.email = gemspec['email'] 22 | gem.homepage = gemspec['homepage'] 23 | 24 | glob = lambda { |patterns| gem.files & Dir[*patterns] } 25 | 26 | gem.files = `git ls-files`.split($/) 27 | gem.files = glob[gemspec['files']] if gemspec['files'] 28 | 29 | gem.executables = gemspec.fetch('executables') do 30 | glob['bin/*'].map { |path| File.basename(path) } 31 | end 32 | gem.default_executable = gem.executables.first if Gem::VERSION < '1.7.' 33 | 34 | gem.extensions = glob[gemspec['extensions'] || 'ext/**/extconf.rb'] 35 | gem.test_files = glob[gemspec['test_files'] || '{test/{**/}*_test.rb'] 36 | gem.extra_rdoc_files = glob[gemspec['extra_doc_files'] || '*.{txt,md}'] 37 | 38 | gem.require_paths = Array(gemspec.fetch('require_paths') { 39 | %w[ext lib].select { |dir| File.directory?(dir) } 40 | }) 41 | 42 | gem.requirements = gemspec['requirements'] 43 | gem.required_ruby_version = gemspec['required_ruby_version'] 44 | gem.required_rubygems_version = gemspec['required_rubygems_version'] 45 | gem.post_install_message = gemspec['post_install_message'] 46 | 47 | split = lambda { |string| string.split(/,\s*/) } 48 | 49 | if gemspec['dependencies'] 50 | gemspec['dependencies'].each do |name,versions| 51 | gem.add_dependency(name,split[versions]) 52 | end 53 | end 54 | 55 | if gemspec['development_dependencies'] 56 | gemspec['development_dependencies'].each do |name,versions| 57 | gem.add_development_dependency(name,split[versions]) 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/carrierwave/video/thumbnailer.rb: -------------------------------------------------------------------------------- 1 | require 'carrierwave' 2 | require 'carrierwave/video/thumbnailer/version' 3 | require 'carrierwave/video/thumbnailer/ffmpegthumbnailer' 4 | 5 | module CarrierWave 6 | module Video 7 | module Thumbnailer 8 | extend ActiveSupport::Concern 9 | 10 | module ClassMethods 11 | 12 | def thumbnail options = {} 13 | process thumbnail: options 14 | end 15 | 16 | end 17 | 18 | def thumbnail opts = {} 19 | cache_stored_file! if !cached? 20 | 21 | @options = CarrierWave::Video::Thumbnailer::FFMpegThumbnailerOptions.new(opts) 22 | format = @options.format || 'jpg' 23 | 24 | tmp_path = File.join( File.dirname(current_path), "tmpfile.#{format}" ) 25 | thumbnailer = FFMpegThumbnailer.new(current_path, tmp_path) 26 | 27 | with_thumbnailing_callbacks do 28 | thumbnailer.run(@options) 29 | File.rename tmp_path, current_path 30 | end 31 | end 32 | 33 | private 34 | 35 | def with_thumbnailing_callbacks(&block) 36 | callbacks = @options.callbacks 37 | logger = @options.logger 38 | begin 39 | send_thumbnailing_callback(callbacks[:before_thumbnail]) 40 | setup_thumbnailing_logger 41 | block.call 42 | send_thumbnailing_callback(callbacks[:after_thumbnail]) 43 | rescue => e 44 | send_thumbnailing_callback(callbacks[:rescue]) 45 | 46 | if logger 47 | logger.error "#{e.class}: #{e.message}" 48 | e.backtrace.each do |b| 49 | logger.error b 50 | end 51 | end 52 | 53 | raise CarrierWave::ProcessingError.new("Failed to thumbnail with ffmpegthumbnailer. Check ffmpegthumbnailer install and verify video is not corrupt. Original error: #{e}") 54 | ensure 55 | reset_thumbnailing_logger 56 | send_thumbnailing_callback(callbacks[:ensure]) 57 | end 58 | end 59 | 60 | def send_thumbnailing_callback(callback) 61 | model.send(callback, @options) if callback.present? 62 | end 63 | 64 | def setup_thumbnailing_logger 65 | return unless @options.logger.present? 66 | @ffmpegthumbnailer_logger = FFMpegThumbnailer.logger 67 | FFMpegThumbnailer.logger = @options.logger 68 | end 69 | 70 | def reset_thumbnailing_logger 71 | return unless @ffmpegthumbnailer_logger 72 | FFMpegThumbnailer.logger = @ffmpegthumbnailer_logger 73 | end 74 | 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /CODE-OF-CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | 3 | ### Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ### Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ### Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ### Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ### Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at codeofconduct@evrone.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ### Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ -------------------------------------------------------------------------------- /spec/lib/thumbnailer_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'carrierwave/video/thumbnailer' 3 | 4 | describe CarrierWave::Video::Thumbnailer do 5 | 6 | it "should have a VERSION constant" do 7 | subject.const_get('VERSION').should_not be_empty 8 | end 9 | 10 | #class FFMpegThumbnailer; end 11 | 12 | class TestVideoUploader 13 | include CarrierWave::Video::Thumbnailer 14 | def cached?; end 15 | def cache_stored_file!; end 16 | def model 17 | @thumbnailer ||= FFMpegThumbnailer.new 18 | end 19 | end 20 | 21 | let(:uploader) { TestVideoUploader.new } 22 | 23 | describe ".thumbnail" do 24 | it "processes the model" do 25 | TestVideoUploader.should_receive(:process).with(thumbnail: {option: 'something'}) 26 | TestVideoUploader.thumbnail({option: 'something'}) 27 | end 28 | 29 | it "does not require options" do 30 | TestVideoUploader.should_receive(:process).with(thumbnail: {}) 31 | TestVideoUploader.thumbnail 32 | end 33 | end 34 | 35 | describe "#thumbnail" do 36 | let(:format) { 'jpg' } 37 | let(:thumbnailer) { double } 38 | 39 | before do 40 | uploader.stub(:current_path).and_return('video/path/file.jpg') 41 | 42 | CarrierWave::Video::Thumbnailer::FFMpegThumbnailer.should_receive(:new).at_most(10).times.and_return(thumbnailer) 43 | end 44 | 45 | context "with no options set" do 46 | before { File.should_receive(:rename).with('video/path/tmpfile.jpg', 'video/path/file.jpg') } 47 | 48 | it "runs the thumbnailer with empty options list" do 49 | thumbnailer.should_receive(:run) do |options| 50 | expect(options.options).to be_empty 51 | end 52 | uploader.thumbnail 53 | end 54 | end 55 | 56 | context "with callbacks set" do 57 | before { thumbnailer.should_receive(:run) } 58 | let(:opts) do 59 | { 60 | callbacks: { 61 | before_thumbnail: :method1, 62 | after_thumbnail: :method2, 63 | rescue: :method3, 64 | ensure: :method4 65 | } 66 | } 67 | end 68 | 69 | context "no exceptions raised" do 70 | before { File.should_receive(:rename).with('video/path/tmpfile.jpg', 'video/path/file.jpg') } 71 | 72 | it "calls before_thumbnail, after_thumbnail, and ensure" do 73 | uploader.model.should_receive(:method1).with(an_instance_of CarrierWave::Video::Thumbnailer::FFMpegThumbnailerOptions).ordered 74 | uploader.model.should_receive(:method2).with(an_instance_of CarrierWave::Video::Thumbnailer::FFMpegThumbnailerOptions).ordered 75 | uploader.model.should_not_receive(:method3) 76 | uploader.model.should_receive(:method4).with(an_instance_of CarrierWave::Video::Thumbnailer::FFMpegThumbnailerOptions).ordered 77 | 78 | uploader.thumbnail(opts) 79 | end 80 | end 81 | 82 | context "exception raised" do 83 | let(:e) { StandardError.new("test error") } 84 | before { File.should_receive(:rename).and_raise(e) } 85 | 86 | 87 | it "calls before_thumbnail and ensure" do 88 | uploader.model.should_receive(:method1).with(an_instance_of CarrierWave::Video::Thumbnailer::FFMpegThumbnailerOptions).ordered 89 | uploader.model.should_not_receive(:method2) 90 | uploader.model.should_receive(:method3).with(an_instance_of CarrierWave::Video::Thumbnailer::FFMpegThumbnailerOptions).ordered 91 | uploader.model.should_receive(:method4).with(an_instance_of CarrierWave::Video::Thumbnailer::FFMpegThumbnailerOptions).ordered 92 | 93 | lambda do 94 | uploader.thumbnail(opts) 95 | end.should raise_exception(CarrierWave::ProcessingError) 96 | end 97 | end 98 | end 99 | 100 | context "with logger set" do 101 | let(:logger) { double } 102 | before do 103 | uploader.model.stub(:logger).and_return(logger) 104 | thumbnailer.should_receive(:run) 105 | end 106 | 107 | context "with no exceptions" do 108 | before { File.should_receive(:rename).with('video/path/tmpfile.jpg', 'video/path/file.jpg') } 109 | 110 | it "sets FFMpegThumbnailer logger to logger and resets" do 111 | old_logger = CarrierWave::Video::Thumbnailer::FFMpegThumbnailer.logger 112 | CarrierWave::Video::Thumbnailer::FFMpegThumbnailer.should_receive(:logger=).with(logger).ordered 113 | CarrierWave::Video::Thumbnailer::FFMpegThumbnailer.should_receive(:logger=).with(old_logger).ordered 114 | uploader.thumbnail(logger: logger) 115 | end 116 | end 117 | 118 | context "with exceptions" do 119 | let(:e) { StandardError.new("test error") } 120 | before { File.should_receive(:rename).with('video/path/tmpfile.jpg', 'video/path/file.jpg').and_raise(e) } 121 | 122 | it "logs exception" do 123 | logger.should_receive(:error).with("#{e.class}: #{e.message}") 124 | logger.stub(:error) 125 | 126 | lambda do 127 | uploader.thumbnail(logger: logger) 128 | end.should raise_exception(CarrierWave::ProcessingError) 129 | end 130 | end 131 | end 132 | 133 | context "with custom passed in" do 134 | before { File.should_receive(:rename).with('video/path/tmpfile.jpg', 'video/path/file.jpg') } 135 | 136 | it "takes the provided custom param" do 137 | thumbnailer.should_receive(:run) do |opts| 138 | opts.custom.should eq '-s 256' 139 | end 140 | 141 | uploader.thumbnail(custom: '-s 256') 142 | end 143 | end 144 | 145 | end 146 | end 147 | 148 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # carrierwave-video-thumbnailer 2 | 3 | [![Build Status](https://travis-ci.org/evrone/carrierwave-video-thumbnailer.png)](https://travis-ci.org/evrone/carrierwave-video-thumbnailer) 4 | [![Maintainability](https://api.codeclimate.com/v1/badges/a99a88d28ad37a79dbf6/maintainability)](https://codeclimate.com/github/evrone/carrierwave-video-thumbnailer/maintainability) 5 | [![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com) 6 | 7 | A thumbnailer plugin for Carrierwave. It mixes into your uploader setup and 8 | makes easy thumbnailing of your uploaded videos. This software is quite an 9 | alpha right now so any kind of OpenSource collaboration is welcome. 10 | 11 | 12 | Sponsored by Evrone 14 | 15 | 16 | ### Demo 17 | 18 | ![demo image](readme_content/image/demo.png) 19 | 20 | ## Getting Started 21 | ### Prerequisites 22 | 23 | `ffmpegthumbnailer` binary should be present on the PATH. 24 | 25 | [ffmpegthumbnailer git repository](https://github.com/dirkvdb/ffmpegthumbnailer) 26 | 27 | ### Installation 28 | 29 | gem install carrierwave-video-thumbnailer 30 | 31 | Or add to your Gemfile: 32 | 33 | ```ruby 34 | gem 'carrierwave-video-thumbnailer' 35 | ``` 36 | 37 | If you need resize thumbnail add 38 | [RMagick](https://github.com/rmagick/rmagick) or 39 | [MiniMagic](https://github.com/minimagick/minimagick) 40 | 41 | ### Usage 42 | 43 | 0. Install ffmpegthumbnailer 44 | 45 | linux 46 | 47 | sudo apt install ffmpegthumbnailer 48 | 49 | MacOS 50 | 51 | brew install ffmpegthumbnailer 52 | 53 | 1. Create migration for your model: 54 | 55 | rails g migration add_video_to_your_model video:string 56 | 57 | 2. Generate uploader 58 | 59 | rails generate uploader Video 60 | 61 | 3. Mount uploader in model 62 | 63 | class YourModel < ApplicationRecord 64 | mount_uploader :video, VideoUploader 65 | end 66 | 67 | 4. Update your uploader 68 | 69 | In your Rails `app/uploaders/video_uploader.rb`: 70 | 71 | ```ruby 72 | class VideoUploader < CarrierWave::Uploader::Base 73 | include CarrierWave::Video # for your video processing 74 | include CarrierWave::Video::Thumbnailer 75 | 76 | storage :file 77 | 78 | def store_dir 79 | "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" 80 | end 81 | 82 | version :thumb do 83 | process thumbnail: [{format: 'png', quality: 10, size: 192, strip: true, logger: Rails.logger}] 84 | 85 | def full_filename for_file 86 | png_name for_file, version_name 87 | end 88 | end 89 | 90 | def png_name for_file, version_name 91 | %Q{#{version_name}_#{for_file.chomp(File.extname(for_file))}.png} 92 | end 93 | end 94 | ``` 95 | 96 | For resize thumbnail add version: 97 | 98 | ```ruby 99 | class VideoUploader < CarrierWave::Uploader::Base 100 | include CarrierWave::MiniMagick 101 | 102 | ... 103 | 104 | version :small_thumb, from_version: :thumb do 105 | process resize_to_fill: [20, 200] 106 | end 107 | 108 | end 109 | ``` 110 | 111 | 5. Don't forget add parameter in controller 112 | 113 | ```ruby 114 | def post_params 115 | params.require(:post).permit(:title, :body, :video) 116 | end 117 | ``` 118 | 119 | 6. Add image_tag to your view 120 | 121 | ###### erb example 122 | <%= image_tag(@post.video.thumb.url, alt: 'Video') if @post.video? %> 123 | 124 | Runs `ffmpegthumbnailer` with CLI keys provided by your configuration or just 125 | uses quite a reasonable ffmpegthumbnailer's defaults. 126 | 127 | ##### Thumbnailer Options 128 | 129 | The options are passed as a hash to the `thumbnail` processing callback as 130 | shown in the example. The options may be, according to ffmpegthumbnailer's 131 | manual: 132 | 133 | * format: 'jpg' or 'png' ('jpg' is the default). 134 | * quality: image quality (0 = bad, 10 = best) (default: 8) only applies to jpeg output 135 | * size: size of the generated thumbnail in pixels (use 0 for original size) 136 | (default value: 128 and keep initial aspect ratio). 137 | * strip: movie film strip decoration (defaults to `false`). 138 | * seek: time to seek to (`percentage` or absolute time `hh:mm:ss`) (default: 10) 139 | * square: if set to `true` ignore aspect ratio and generate square thumbnail. 140 | * workaround: if set to `true` runs ffmpegthumbnailer in some safe mode 141 | (read `man ffmpegthumbnailer` for further explanations). 142 | * logger: an object behaving like Rails.logger (may be omitted). 143 | 144 | 145 | ##### film stripes 146 | 147 | For disable film stripes in thumbnail check strip to false 148 | 149 | process thumbnail: [{format: 'png', quality: 10, size: 192, strip: false, logger: Rails.logger}] 150 | 151 | ## Contributing 152 | 153 | Please read [Code of Conduct](CODE-OF-CONDUCT.md) and [Contributing Guidelines](CONTRIBUTING.md) for submitting pull requests to us. 154 | 155 | ## Versioning 156 | 157 | We use [SemVer](http://semver.org/) for versioning. For the versions available, 158 | see the [tags on this repository](https://github.com/evrone/carrierwave-video-thumbnailer/tags). 159 | 160 | ## Changelog 161 | 162 | The changelog is [here](CHANGELOG.md). 163 | 164 | ## Authors 165 | 166 | * [Pavel Argentov](https://github.com/argent-smith) - *Initial work* 167 | 168 | See also the list of [contributors](https://github.com/evrone/carrierwave-video-thumbnailer/contributors) who participated in this project. 169 | 170 | ## License 171 | 172 | This project is licensed under the [MIT License](LICENSE). 173 | 174 | ## Acknowledgments 175 | 176 | Huge Thanks to **Rachel Heaton** () whose 177 | `carrierwave-video` gem has inspired me (and where I've borrowed some code as 178 | well). 179 | --------------------------------------------------------------------------------