├── .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 | [](https://travis-ci.org/evrone/carrierwave-video-thumbnailer)
4 | [](https://codeclimate.com/github/evrone/carrierwave-video-thumbnailer/maintainability)
5 | [](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 |
14 |
15 |
16 | ### Demo
17 |
18 | 
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 |
--------------------------------------------------------------------------------