carrierwave-video 0.0.1.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.rdoc +58 -0
- data/Rakefile +1 -0
- data/carrierwave-video.gemspec +28 -0
- data/lib/carrierwave-video/version.rb +5 -0
- data/lib/carrierwave/video.rb +59 -0
- data/lib/carrierwave/video/ffmpeg_options.rb +69 -0
- data/spec/lib/carrierwave_video_spec.rb +163 -0
- data/spec/spec_helper.rb +6 -0
- metadata +93 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
= CarrierWave Video Converter
|
2
|
+
|
3
|
+
This gem adds a video converter using ffmpeg and the streamio-ffmpeg rubygem.
|
4
|
+
Note: for watermarking to work, there a fork of the streamio-ffmpeg gem that must be used.
|
5
|
+
|
6
|
+
= Installation
|
7
|
+
|
8
|
+
gem install carrierwave-video
|
9
|
+
|
10
|
+
Using bundler:
|
11
|
+
|
12
|
+
gem 'carrierwave-video'
|
13
|
+
|
14
|
+
= Usage
|
15
|
+
|
16
|
+
class VideoUploader < CarrierWave::Uploader::Base
|
17
|
+
include CarrierWave::VideoConverter
|
18
|
+
|
19
|
+
process encode_video: [:mp4, after_transcode: :set_success ]
|
20
|
+
end
|
21
|
+
|
22
|
+
class Video
|
23
|
+
mount_uploader :file, VideoUploader
|
24
|
+
|
25
|
+
def set_success
|
26
|
+
self.success = true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
= Possible Options
|
31
|
+
|
32
|
+
Pass in options to process:
|
33
|
+
|
34
|
+
process encode_video: [:mp4, resolution: "200x200"]
|
35
|
+
|
36
|
+
Resolution passed to ffmpeg:
|
37
|
+
resolution: "640x360"
|
38
|
+
|
39
|
+
Watermark:
|
40
|
+
watermark: {
|
41
|
+
path: File.join(Rails.root, "directory", "file.png"),
|
42
|
+
position: :bottom_right, # also: :top_right, :bottom_left, :bottom_right
|
43
|
+
pixels_from_edge: 10
|
44
|
+
}
|
45
|
+
|
46
|
+
Callbacks:
|
47
|
+
callbacks: {
|
48
|
+
before_transcode: :method
|
49
|
+
after_transcode: :method
|
50
|
+
ensure: :method
|
51
|
+
}
|
52
|
+
|
53
|
+
Logging:
|
54
|
+
logger: :method #returns object that behaves like Logger
|
55
|
+
|
56
|
+
= Upcoming
|
57
|
+
|
58
|
+
* screengrabs
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "carrierwave-video/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "carrierwave-video"
|
7
|
+
s.version = Carrierwave::Video::VERSION
|
8
|
+
s.authors = ["rheaton"]
|
9
|
+
s.email = ["rachelmheaton@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Carrierwave extension that uses ffmpeg to transcode videos.}
|
12
|
+
s.description = %q{Transcodes to html5-friendly videos.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "carrierwave-video"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
s.add_development_dependency "rspec"
|
23
|
+
|
24
|
+
s.add_runtime_dependency 'streamio-ffmpeg'#, :git => 'git://github.com/stakach/streamio-ffmpeg.git'
|
25
|
+
s.add_runtime_dependency 'carrierwave'
|
26
|
+
s.requirements << 'ruby, version 1.9 or greater'
|
27
|
+
s.requirements << 'ffmpeg, version 0.10 or greater with libx256, libfaac, libtheora, libvorbid, libvpx enabled'
|
28
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'streamio-ffmpeg'
|
2
|
+
require 'carrierwave'
|
3
|
+
require 'carrierwave/video/ffmpeg_options'
|
4
|
+
|
5
|
+
module CarrierWave
|
6
|
+
module Video
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
module ClassMethods
|
9
|
+
def encode_video(target_format, options={})
|
10
|
+
process encode_video: [target_format, options]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def encode_video(format, opts={})
|
15
|
+
# move upload to local cache
|
16
|
+
cache_stored_file! if !cached?
|
17
|
+
|
18
|
+
options = CarrierWave::Video::FfmpegOptions.new(format, opts)
|
19
|
+
tmp_path = File.join( File.dirname(current_path), "tmpfile.#{format}" )
|
20
|
+
|
21
|
+
|
22
|
+
with_callbacks(opts) do
|
23
|
+
file = ::FFMPEG::Movie.new(current_path)
|
24
|
+
file.transcode(tmp_path, options.format_options, options.encoder_options)
|
25
|
+
File.rename tmp_path, current_path
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
def with_callbacks(opts, &block)
|
31
|
+
callbacks = opts[:callbacks] || {}
|
32
|
+
logger_opt = opts[:logger]
|
33
|
+
begin
|
34
|
+
send_callback(callbacks[:before_transcode])
|
35
|
+
setup_logger(logger_opt)
|
36
|
+
block.call
|
37
|
+
send_callback(callbacks[:after_transcode])
|
38
|
+
ensure
|
39
|
+
reset_logger
|
40
|
+
send_callback(callbacks[:ensure])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def send_callback(callback)
|
45
|
+
model.send(callback) if callback.present?
|
46
|
+
end
|
47
|
+
|
48
|
+
def setup_logger(opt)
|
49
|
+
return unless opt.present?
|
50
|
+
@ffmpeg_logger = ::FFMPEG.logger
|
51
|
+
::FFMPEG.logger = model.send(opt)
|
52
|
+
end
|
53
|
+
|
54
|
+
def reset_logger
|
55
|
+
return unless @ffmpeg_logger
|
56
|
+
::FFMPEG.logger = @ffmpeg_logger
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module CarrierWave
|
2
|
+
module Video
|
3
|
+
class FfmpegOptions
|
4
|
+
attr_reader :watermark_path, :watermark_position, :watermark_pixels,
|
5
|
+
:format, :resolution
|
6
|
+
|
7
|
+
def initialize(format, options)
|
8
|
+
@format = format.to_s
|
9
|
+
@watermark = options[:watermark].present?
|
10
|
+
@resolution = options[:resolution] || "640x360"
|
11
|
+
if watermark?
|
12
|
+
@watermark_path = options[:watermark][:path]
|
13
|
+
@watermark_position = options[:watermark][:position].to_s || :bottom_right
|
14
|
+
@watermark_pixels = options[:watermark][:pixels_from_edge] || 10
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def encoder_options
|
19
|
+
{preserve_aspect_ratio: :width}
|
20
|
+
end
|
21
|
+
|
22
|
+
def format_options
|
23
|
+
format_options = case format
|
24
|
+
when "mp4"
|
25
|
+
{
|
26
|
+
video_codec: 'libx264',
|
27
|
+
audio_codec: 'libfaac',
|
28
|
+
custom: "-b 1500k -vpre slow -vpre baseline -g 30 #{watermark_params}"
|
29
|
+
}
|
30
|
+
when "webm"
|
31
|
+
{
|
32
|
+
video_codec: 'libvpx',
|
33
|
+
audio_codec: 'libvorbis',
|
34
|
+
custom: "-b 1500k -ab 160000 -f webm -g 30 #{watermark_params}"
|
35
|
+
}
|
36
|
+
when "ogv"
|
37
|
+
{
|
38
|
+
video_codec: 'libtheora',
|
39
|
+
audio_codec: 'libvorbis',
|
40
|
+
custom: "-b 1500k -ab 160000 -g 30 #{watermark_params}"
|
41
|
+
}
|
42
|
+
else
|
43
|
+
{}
|
44
|
+
end
|
45
|
+
{ resolution: resolution }.merge(format_options)
|
46
|
+
end
|
47
|
+
|
48
|
+
def watermark?
|
49
|
+
@watermark
|
50
|
+
end
|
51
|
+
|
52
|
+
def watermark_params
|
53
|
+
return "" unless watermark?
|
54
|
+
positioning = case watermark_position
|
55
|
+
when 'bottom_left'
|
56
|
+
"#{watermark_pixels}:main_h-overlay_h-#{watermark_pixels}"
|
57
|
+
when 'bottom_right'
|
58
|
+
"main_w-overlay_w-#{watermark_pixels}:main_h-overlay_h-#{watermark_pixels}"
|
59
|
+
when 'top_left'
|
60
|
+
"#{watermark_pixels}:#{watermark_pixels}"
|
61
|
+
when 'top_right'
|
62
|
+
"main_w-overlay_w-#{watermark_pixels}:#{watermark_pixels}"
|
63
|
+
end
|
64
|
+
|
65
|
+
"-vf \"movie=#{watermark_path} [logo]; [in][logo] overlay=#{positioning} [out]\""
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CarrierWave::Video do
|
4
|
+
class Video; end
|
5
|
+
|
6
|
+
class TestVideoUploader
|
7
|
+
include CarrierWave::Video
|
8
|
+
def cached?; end
|
9
|
+
def cache_stored_file!; end
|
10
|
+
def model
|
11
|
+
@video ||= Video.new
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:converter) { TestVideoUploader.new }
|
16
|
+
|
17
|
+
describe ".encode_video" do
|
18
|
+
it "processes the model" do
|
19
|
+
TestVideoUploader.should_receive(:process).with(encode_video: ["format", :opts])
|
20
|
+
TestVideoUploader.encode_video("format", :opts)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "does not require options" do
|
24
|
+
TestVideoUploader.should_receive(:process).with(encode_video: ["format", {}])
|
25
|
+
TestVideoUploader.encode_video("format")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "#encode_video" do
|
30
|
+
let(:format) { 'webm' }
|
31
|
+
let(:movie) { mock }
|
32
|
+
|
33
|
+
before do
|
34
|
+
converter.stub(:current_path).and_return('video/path/file.mov')
|
35
|
+
|
36
|
+
FFMPEG::Movie.should_receive(:new).and_return(movie)
|
37
|
+
end
|
38
|
+
|
39
|
+
context "no options set" do
|
40
|
+
before { File.should_receive(:rename) }
|
41
|
+
it "is calls transcode with correct format options" do
|
42
|
+
movie.should_receive(:transcode) do |path, opts, codec_opts|
|
43
|
+
codec_opts.should == {preserve_aspect_ratio: :width}
|
44
|
+
|
45
|
+
opts[:video_codec].should == 'libvpx'
|
46
|
+
opts[:audio_codec].should == 'libvorbis'
|
47
|
+
opts[:custom].should == '-b 1500k -ab 160000 -f webm -g 30 '
|
48
|
+
|
49
|
+
path.should == "video/path/tmpfile.#{format}"
|
50
|
+
end
|
51
|
+
|
52
|
+
converter.encode_video(format)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context "callbacks set" do
|
57
|
+
before { movie.should_receive(:transcode) }
|
58
|
+
|
59
|
+
context "no exceptions raised" do
|
60
|
+
before { File.should_receive(:rename) }
|
61
|
+
|
62
|
+
it "calls before_transcode, after_transcode, and ensure" do
|
63
|
+
converter.model.should_receive(:method1).ordered
|
64
|
+
converter.model.should_receive(:method2).ordered
|
65
|
+
converter.model.should_receive(:method3).ordered
|
66
|
+
|
67
|
+
converter.encode_video(format, callbacks: {
|
68
|
+
before_transcode: :method1,
|
69
|
+
after_transcode: :method2,
|
70
|
+
ensure: :method3
|
71
|
+
})
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context "exception raised" do
|
76
|
+
let(:e) { StandardError.new("test error") }
|
77
|
+
before { File.should_receive(:rename).and_raise(e) }
|
78
|
+
|
79
|
+
it "calls before_transcode and ensure" do
|
80
|
+
converter.model.should_receive(:method1).ordered
|
81
|
+
converter.model.should_not_receive(:method2)
|
82
|
+
converter.model.should_receive(:method3).ordered
|
83
|
+
|
84
|
+
lambda do
|
85
|
+
converter.encode_video(format, callbacks: {
|
86
|
+
before_transcode: :method1,
|
87
|
+
after_transcode: :method2,
|
88
|
+
ensure: :method3
|
89
|
+
})
|
90
|
+
end.should raise_exception(e)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context "logger set" do
|
96
|
+
let(:logger) { mock }
|
97
|
+
before do
|
98
|
+
converter.model.stub(:logger).and_return(logger)
|
99
|
+
movie.should_receive(:transcode)
|
100
|
+
end
|
101
|
+
|
102
|
+
context "with no exceptions" do
|
103
|
+
before { File.should_receive(:rename) }
|
104
|
+
|
105
|
+
it "sets FFMPEG logger to logger and resets" do
|
106
|
+
old_logger = ::FFMPEG.logger
|
107
|
+
::FFMPEG.should_receive(:logger=).with(logger).ordered
|
108
|
+
::FFMPEG.should_receive(:logger=).with(old_logger).ordered
|
109
|
+
converter.encode_video(format, logger: :logger)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
context "with exceptions" do
|
114
|
+
let(:e) { StandardError.new("test error") }
|
115
|
+
before { File.should_receive(:rename).and_raise(e) }
|
116
|
+
|
117
|
+
it "logs exception" do
|
118
|
+
lambda do
|
119
|
+
converter.encode_video(format, logger: :logger)
|
120
|
+
end.should raise_exception(e)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
context "watermark set" do
|
126
|
+
before { File.should_receive(:rename) }
|
127
|
+
|
128
|
+
it "appends watermark params to custom params for ffmpeg" do
|
129
|
+
movie.should_receive(:transcode) do |path, opts, codec_opts|
|
130
|
+
codec_opts.should == {preserve_aspect_ratio: :width}
|
131
|
+
|
132
|
+
opts[:video_codec].should == 'libvpx'
|
133
|
+
opts[:audio_codec].should == 'libvorbis'
|
134
|
+
opts[:custom].should == "-b 1500k -ab 160000 -f webm -g 30 -vf \"movie=path/to/file.png [logo]; [in][logo] overlay=5:main_h-overlay_h-5 [out]\""
|
135
|
+
|
136
|
+
path.should == "video/path/tmpfile.#{format}"
|
137
|
+
end
|
138
|
+
|
139
|
+
converter.encode_video(format, watermark: {
|
140
|
+
path: 'path/to/file.png',
|
141
|
+
position: :bottom_left,
|
142
|
+
pixels_from_edge: 5
|
143
|
+
})
|
144
|
+
end
|
145
|
+
|
146
|
+
it "only requires path watermark parameter" do
|
147
|
+
movie.should_receive(:transcode) do |path, opts, codec_opts|
|
148
|
+
codec_opts.should == {preserve_aspect_ratio: :width}
|
149
|
+
|
150
|
+
opts[:video_codec].should == 'libvpx'
|
151
|
+
opts[:audio_codec].should == 'libvorbis'
|
152
|
+
opts[:custom].should == "-b 1500k -ab 160000 -f webm -g 30 -vf \"movie=path/to/file.png [logo]; [in][logo] overlay= [out]\""
|
153
|
+
|
154
|
+
path.should == "video/path/tmpfile.#{format}"
|
155
|
+
end
|
156
|
+
|
157
|
+
converter.encode_video(format, watermark: {
|
158
|
+
path: 'path/to/file.png'
|
159
|
+
})
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: carrierwave-video
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1.pre
|
5
|
+
prerelease: 6
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- rheaton
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-11 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &70335404267560 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70335404267560
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: streamio-ffmpeg
|
27
|
+
requirement: &70335404266960 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70335404266960
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: carrierwave
|
38
|
+
requirement: &70335404266520 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70335404266520
|
47
|
+
description: Transcodes to html5-friendly videos.
|
48
|
+
email:
|
49
|
+
- rachelmheaton@gmail.com
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- .gitignore
|
55
|
+
- Gemfile
|
56
|
+
- README.rdoc
|
57
|
+
- Rakefile
|
58
|
+
- carrierwave-video.gemspec
|
59
|
+
- lib/carrierwave-video/version.rb
|
60
|
+
- lib/carrierwave/video.rb
|
61
|
+
- lib/carrierwave/video/ffmpeg_options.rb
|
62
|
+
- spec/lib/carrierwave_video_spec.rb
|
63
|
+
- spec/spec_helper.rb
|
64
|
+
homepage: ''
|
65
|
+
licenses: []
|
66
|
+
post_install_message:
|
67
|
+
rdoc_options: []
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
+
none: false
|
78
|
+
requirements:
|
79
|
+
- - ! '>'
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: 1.3.1
|
82
|
+
requirements:
|
83
|
+
- ruby, version 1.9 or greater
|
84
|
+
- ffmpeg, version 0.10 or greater with libx256, libfaac, libtheora, libvorbid, libvpx
|
85
|
+
enabled
|
86
|
+
rubyforge_project: carrierwave-video
|
87
|
+
rubygems_version: 1.8.17
|
88
|
+
signing_key:
|
89
|
+
specification_version: 3
|
90
|
+
summary: Carrierwave extension that uses ffmpeg to transcode videos.
|
91
|
+
test_files:
|
92
|
+
- spec/lib/carrierwave_video_spec.rb
|
93
|
+
- spec/spec_helper.rb
|