kelredd-pruview 0.1.1
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.
Potentially problematic release.
This version of kelredd-pruview might be problematic. Click here for more details.
- data/README.rdoc +62 -0
- data/Rakefile +41 -0
- data/features/document.feature +21 -0
- data/features/step_definitions/common_steps.rb +32 -0
- data/features/step_definitions/document_steps.rb +16 -0
- data/features/step_definitions/support/env.rb +20 -0
- data/features/step_definitions/support/helpers.rb +5 -0
- data/features/step_definitions/video_image_steps.rb +16 -0
- data/features/step_definitions/video_steps.rb +16 -0
- data/features/video.feature +26 -0
- data/features/video_image.feature +20 -0
- data/lib/pruview.rb +8 -0
- data/lib/pruview/USWebCoatedSWOP.icc +0 -0
- data/lib/pruview/document.rb +138 -0
- data/lib/pruview/exceptions.rb +8 -0
- data/lib/pruview/sRGB.icm +0 -0
- data/lib/pruview/version.rb +13 -0
- data/lib/pruview/video.rb +130 -0
- data/lib/pruview/video_image.rb +40 -0
- data/test/files/basic.jpg +0 -0
- data/test/files/basic.mpg +0 -0
- data/test/files/error.jpg +0 -0
- data/test/files/error.mov +0 -0
- data/test/files/invalid_format.poop +0 -0
- data/test/test_helper.rb +10 -0
- data/test/unit/pruview_test.rb +13 -0
- metadata +103 -0
data/README.rdoc
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
= Pruview
|
2
|
+
|
3
|
+
== Description
|
4
|
+
|
5
|
+
A gem to ease generating image previews (thumbnails) of various files
|
6
|
+
|
7
|
+
== Installation
|
8
|
+
|
9
|
+
# install image magick with whatever build dependencies you need for image handling, etc...)
|
10
|
+
# if you want to preview movie files
|
11
|
+
# => install ffmpeg with whatever build dependencies you need for video/audio handling, etc...)
|
12
|
+
# if you want to preview postscript files (PDFs, Adobe Illustrator, etc...)
|
13
|
+
# => install any postscript handling libs or dependencies for image magick
|
14
|
+
|
15
|
+
sudo gem install kelredd-pruview --source http://gems.github.com
|
16
|
+
|
17
|
+
== Dependencies
|
18
|
+
|
19
|
+
* image magick + any special file type handling dependencies
|
20
|
+
* ffmpeg + h264 encoder + any special file type handling dependencies (for video previewing)
|
21
|
+
# something like 'sudo port install ffmpeg +gpl +lame +x264 +xvid' on Mac OSX Leopard
|
22
|
+
* gawk
|
23
|
+
* Rubygems: mini_magick
|
24
|
+
|
25
|
+
== Usage
|
26
|
+
|
27
|
+
require 'pruview'
|
28
|
+
|
29
|
+
# Preview a document (using image magick)
|
30
|
+
document = Pruview::Document.new(<document_file_path>, <pruview_output_path>)
|
31
|
+
doc_image_thumb_path = document.to_jpg(<thumb_file_name>, <thumb_pixel_width>, <thumb_pixel_height)
|
32
|
+
|
33
|
+
# Preview a video (ffmpeg to get an image from movie, then image magick to resize that image)
|
34
|
+
video_image_path = Pruview::VideoImage.to_jpg(<video_file_path>, <pruview_output_path>, <video_image_file_name>)
|
35
|
+
document = Pruview::Document.new(video_image_path, <pruview_output_path>)
|
36
|
+
video_image_thumb_path = document.to_jpg(<thumb_file_name>, <thumb_pixel_width>, <thumb_pixel_height)
|
37
|
+
|
38
|
+
|
39
|
+
== License
|
40
|
+
|
41
|
+
Copyright (c) 2007-2009 Kelly Redding
|
42
|
+
|
43
|
+
Permission is hereby granted, free of charge, to any person
|
44
|
+
obtaining a copy of this software and associated documentation
|
45
|
+
files (the "Software"), to deal in the Software without
|
46
|
+
restriction, including without limitation the rights to use,
|
47
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
48
|
+
copies of the Software, and to permit persons to whom the
|
49
|
+
Software is furnished to do so, subject to the following
|
50
|
+
conditions:
|
51
|
+
|
52
|
+
The above copyright notice and this permission notice shall be
|
53
|
+
included in all copies or substantial portions of the Software.
|
54
|
+
|
55
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
56
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
57
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
58
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
59
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
60
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
61
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
62
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
require 'rake/testtask'
|
4
|
+
|
5
|
+
require 'lib/pruview/version'
|
6
|
+
|
7
|
+
task :default => :test
|
8
|
+
|
9
|
+
spec = Gem::Specification.new do |s|
|
10
|
+
s.name = 'pruview'
|
11
|
+
s.version = Pruview::Version.to_s
|
12
|
+
s.has_rdoc = true
|
13
|
+
s.extra_rdoc_files = %w(README.rdoc)
|
14
|
+
s.rdoc_options = %w(--main README.rdoc)
|
15
|
+
s.summary = "A gem to ease generating image previews (thumbnails) of various files"
|
16
|
+
s.author = 'Kelly Redding'
|
17
|
+
s.email = 'kelly@kelredd.com'
|
18
|
+
s.homepage = ''
|
19
|
+
s.files = %w(README.rdoc Rakefile) + Dir.glob("{features,lib,test}/**/*")
|
20
|
+
# s.executables = ['pruview']
|
21
|
+
|
22
|
+
s.add_dependency('mini_magick')
|
23
|
+
s.add_dependency('flvtool2')
|
24
|
+
end
|
25
|
+
|
26
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
27
|
+
pkg.gem_spec = spec
|
28
|
+
end
|
29
|
+
|
30
|
+
Rake::TestTask.new do |t|
|
31
|
+
t.libs << 'test'
|
32
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
33
|
+
t.verbose = true
|
34
|
+
end
|
35
|
+
|
36
|
+
desc 'Generate the gemspec to serve this Gem from Github'
|
37
|
+
task :github do
|
38
|
+
file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
|
39
|
+
File.open(file, 'w') {|f| f << spec.to_ruby }
|
40
|
+
puts "Created gemspec: #{file}"
|
41
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
Feature: Document
|
2
|
+
In order have file thumbnails programmatically
|
3
|
+
As a user
|
4
|
+
I want create document file thumbnails
|
5
|
+
|
6
|
+
Scenario: Basic Image thumbnail
|
7
|
+
Given I have a basic image document
|
8
|
+
When I create a jpg version
|
9
|
+
Then I should have a jpg file
|
10
|
+
|
11
|
+
Scenario: Invalid source image
|
12
|
+
Given I have an invalid image document
|
13
|
+
Then Pruview should complain about an invalid source file
|
14
|
+
|
15
|
+
Scenario: Invalid output path
|
16
|
+
Given I have an invalid output path for documents
|
17
|
+
Then Pruview should complain about an invalid output path
|
18
|
+
|
19
|
+
Scenario: Invalid source format
|
20
|
+
Given I have an invalid format document
|
21
|
+
Then Pruview should complain about an invalid source file format
|
@@ -0,0 +1,32 @@
|
|
1
|
+
When /^I create a[n]* (.+) version$/ do |format|
|
2
|
+
@output = @file.send("to_#{format}", 'file', 50, 50)
|
3
|
+
end
|
4
|
+
|
5
|
+
Then /^I should have a[n]* (.+) file$/ do |format|
|
6
|
+
assert File.exists?(@output)
|
7
|
+
assert File.extname(@output), format
|
8
|
+
end
|
9
|
+
|
10
|
+
Then /^Pruview should complain about an invalid source file$/ do
|
11
|
+
assert @complaint
|
12
|
+
assert_kind_of Pruview::Exceptions::InvalidError, @complaint
|
13
|
+
assert_match /^Invalid source file/, @complaint.message
|
14
|
+
end
|
15
|
+
|
16
|
+
Then /^Pruview should complain about an invalid output path$/ do
|
17
|
+
assert @complaint
|
18
|
+
assert_kind_of Pruview::Exceptions::InvalidError, @complaint
|
19
|
+
assert_match /^Invalid target directory/, @complaint.message
|
20
|
+
end
|
21
|
+
|
22
|
+
Then /^Pruview should complain about an invalid source file format$/ do
|
23
|
+
assert @complaint
|
24
|
+
assert_kind_of Pruview::Exceptions::InvalidError, @complaint
|
25
|
+
assert_match /not supported - file extension: .poop/, @complaint.message
|
26
|
+
end
|
27
|
+
|
28
|
+
Then /^Pruview should complain about an error$/ do
|
29
|
+
assert @complaint
|
30
|
+
assert_kind_of Pruview::Exceptions::InvalidError, @complaint
|
31
|
+
assert_match /not supported - file extension: .poop/, @complaint.message
|
32
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
Given /^I have a[n]* (.+) document$/ do |file_type|
|
2
|
+
begin
|
3
|
+
@file = Pruview::Document.new(FILES[file_type], OUTPUT_PATH)
|
4
|
+
rescue Exception => err
|
5
|
+
@complaint = err
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
Given /^I have an invalid output path for documents$/ do
|
10
|
+
begin
|
11
|
+
@file = Pruview::Document.new(FILES['basic image'], INVALID_OUTPUT_PATH)
|
12
|
+
rescue Exception => err
|
13
|
+
@complaint = err
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'test/unit/assertions'
|
2
|
+
World(Test::Unit::Assertions)
|
3
|
+
|
4
|
+
require File.dirname(__FILE__) + '/../../../lib/pruview'
|
5
|
+
|
6
|
+
FILES_PATH = "./test/files"
|
7
|
+
OUTPUT_PATH = "./test_output"
|
8
|
+
INVALID_OUTPUT_PATH = "./test_output/invalid"
|
9
|
+
FILES = {
|
10
|
+
'basic image' => "./test/files/basic.jpg",
|
11
|
+
'invalid image' => "./test/files/invalid.jpg",
|
12
|
+
'error image' => "./test/files/error.jpg",
|
13
|
+
'basic video' => "./test/files/basic.mpg",
|
14
|
+
'invalid video' => "./test/files/invalid.mov",
|
15
|
+
'error video' => "./test/files/error.mov",
|
16
|
+
'invalid format' => "./test/files/invalid_format.poop"
|
17
|
+
}
|
18
|
+
|
19
|
+
FileUtils.mkdir_p File.expand_path(FILES_PATH) unless File.exists? File.expand_path(FILES_PATH)
|
20
|
+
FileUtils.mkdir_p File.expand_path(OUTPUT_PATH) unless File.exists? File.expand_path(OUTPUT_PATH)
|
@@ -0,0 +1,16 @@
|
|
1
|
+
Given /^I have a[n]* (.+) video image (.+)$/ do |file_type, format|
|
2
|
+
begin
|
3
|
+
@output = Pruview::VideoImage.send("to_#{format}", FILES[file_type], OUTPUT_PATH, 'file')
|
4
|
+
rescue Exception => err
|
5
|
+
@complaint = err
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
Given /^I have an invalid output path for video images$/ do
|
10
|
+
begin
|
11
|
+
@output = Pruview::VideoImage.to_jpg(FILES['basic video'], INVALID_OUTPUT_PATH, 'file')
|
12
|
+
rescue Exception => err
|
13
|
+
@complaint = err
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
Given /^I have a[n]* (.+) file$/ do |file_type|
|
2
|
+
begin
|
3
|
+
@file = Pruview::Video.new(FILES[file_type], OUTPUT_PATH)
|
4
|
+
rescue Exception => err
|
5
|
+
@complaint = err
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
Given /^I have an invalid output path for videos$/ do
|
10
|
+
begin
|
11
|
+
@file = Pruview::Video.new(FILES['basic video'], INVALID_OUTPUT_PATH)
|
12
|
+
rescue Exception => err
|
13
|
+
@complaint = err
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
Feature: Video
|
2
|
+
In order to use video in more universal ways
|
3
|
+
As a user
|
4
|
+
I want convert video to other formats for viewing
|
5
|
+
|
6
|
+
Scenario: Basic video to FLV
|
7
|
+
Given I have a basic video file
|
8
|
+
When I create an flv version
|
9
|
+
Then I should have an flv file
|
10
|
+
|
11
|
+
Scenario: Basic video to MOV
|
12
|
+
Given I have a basic video file
|
13
|
+
When I create an mov version
|
14
|
+
Then I should have an mov file
|
15
|
+
|
16
|
+
Scenario: Invalid source video
|
17
|
+
Given I have an invalid video file
|
18
|
+
Then Pruview should complain about an invalid source file
|
19
|
+
|
20
|
+
Scenario: Invalid output path
|
21
|
+
Given I have an invalid output path for videos
|
22
|
+
Then Pruview should complain about an invalid output path
|
23
|
+
|
24
|
+
Scenario: Invalid source format
|
25
|
+
Given I have an invalid format file
|
26
|
+
Then Pruview should complain about an invalid source file format
|
@@ -0,0 +1,20 @@
|
|
1
|
+
Feature: Video Image
|
2
|
+
In order have video image thumbnails programmatically
|
3
|
+
As a user
|
4
|
+
I want create video image file thumbnails
|
5
|
+
|
6
|
+
Scenario: Basic Image thumbnail
|
7
|
+
Given I have a basic video video image jpg
|
8
|
+
Then I should have a jpg file
|
9
|
+
|
10
|
+
Scenario: Invalid source image
|
11
|
+
Given I have an invalid video video image jpg
|
12
|
+
Then Pruview should complain about an invalid source file
|
13
|
+
|
14
|
+
Scenario: Invalid output path
|
15
|
+
Given I have an invalid output path for video images
|
16
|
+
Then Pruview should complain about an invalid output path
|
17
|
+
|
18
|
+
Scenario: Invalid source format
|
19
|
+
Given I have an invalid format video image jpg
|
20
|
+
Then Pruview should complain about an invalid source file format
|
data/lib/pruview.rb
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'mini_magick'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
require File.join(File.dirname(__FILE__), 'pruview', 'exceptions.rb')
|
6
|
+
require File.join(File.dirname(__FILE__), 'pruview', 'document.rb')
|
7
|
+
require File.join(File.dirname(__FILE__), 'pruview', 'video.rb')
|
8
|
+
require File.join(File.dirname(__FILE__), 'pruview', 'video_image.rb')
|
Binary file
|
@@ -0,0 +1,138 @@
|
|
1
|
+
module Pruview
|
2
|
+
|
3
|
+
class Document
|
4
|
+
|
5
|
+
def initialize(source, target_dir)
|
6
|
+
raise Pruview::Exceptions::InvalidError, "Invalid source file: #{source.to_s}" if !File.file?(source)
|
7
|
+
raise Pruview::Exceptions::InvalidError, "Invalid target directory: #{target_dir.to_s}" if !File.directory?(target_dir)
|
8
|
+
raise Pruview::Exceptions::InvalidError, "Document not supported - file extension: " + file_extension(source) if !format_supported?(source)
|
9
|
+
@source = source
|
10
|
+
@target_dir = target_dir
|
11
|
+
@image = process_image(get_image(source))
|
12
|
+
@tempfile = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_jpg(name, width, height, crop = false)
|
16
|
+
scale_img = scale_image(width, height, crop)
|
17
|
+
scale_img.combine_options do |img|
|
18
|
+
img.format 'jpg'
|
19
|
+
img.quality '90'
|
20
|
+
img.interlace 'plane'
|
21
|
+
end
|
22
|
+
target = File.join(@target_dir, name.to_s + '.jpg')
|
23
|
+
scale_img.write(target)
|
24
|
+
return target
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
29
|
+
def format_supported?(source)
|
30
|
+
file_ext = file_extension(source)
|
31
|
+
#return true if file_ext == PSD_EXT # don't support photoshop for now
|
32
|
+
POSTSCRIPT_EXT.each { |extension| return true if file_ext == extension }
|
33
|
+
IMAGE_EXT.each { |extension| return true if file_ext == extension }
|
34
|
+
return false
|
35
|
+
end
|
36
|
+
|
37
|
+
def format_postscript?(source)
|
38
|
+
file_ext = file_extension(source)
|
39
|
+
POSTSCRIPT_EXT.each { |extension| return true if file_ext == extension }
|
40
|
+
return false
|
41
|
+
end
|
42
|
+
|
43
|
+
def file_extension(source_file)
|
44
|
+
File.extname(source_file).downcase.chomp
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_image(source)
|
48
|
+
source = get_postscript_source(source) if format_postscript?(source)
|
49
|
+
begin
|
50
|
+
return MiniMagick::Image.from_file(source)
|
51
|
+
rescue Exception => err
|
52
|
+
raise "Error reading source image: #{err.message}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def get_postscript_source(source)
|
57
|
+
begin
|
58
|
+
@tempfile = MiniMagick::ImageTempFile.new("pruview.jpg")
|
59
|
+
@tempfile.binmode
|
60
|
+
ensure
|
61
|
+
@tempfile.close
|
62
|
+
end
|
63
|
+
run_system_command("convert -format jpg \"#{source}[0]\" \"#{@tempfile.path}\"", "Error processing postscript document")
|
64
|
+
return @tempfile.path
|
65
|
+
end
|
66
|
+
|
67
|
+
def process_image(image)
|
68
|
+
image.format PROCESS_FORMAT
|
69
|
+
set_RGB_colorspace(image)
|
70
|
+
image.strip
|
71
|
+
return image
|
72
|
+
end
|
73
|
+
|
74
|
+
def set_RGB_colorspace(image)
|
75
|
+
colorspace = run_system_command("identify -format \"%r\" #{image.path}", "Error reading document colorspace")
|
76
|
+
puts "Colorspace: #{colorspace}"
|
77
|
+
if colorspace =~ /CMYK/
|
78
|
+
image.combine_options do |img|
|
79
|
+
img.profile File.join(File.dirname(__FILE__), 'USWebCoatedSWOP.icc')
|
80
|
+
img.profile File.join(File.dirname(__FILE__), 'sRGB.icm')
|
81
|
+
img.colorspace 'sRGB'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def scale_image(width, height, crop = false)
|
87
|
+
begin
|
88
|
+
image = MiniMagick::Image.from_file(@image.path)
|
89
|
+
crop_image(image, crop)
|
90
|
+
image.resize "#{width}x#{height}" if crop || @image[:width].to_i > width || @image[:height] > height
|
91
|
+
return image
|
92
|
+
rescue Exception => err
|
93
|
+
raise "Error scaling image: #{err.message}"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def crop_image(image, ratio)
|
98
|
+
if ratio.kind_of?(Array) && ratio.length == 2
|
99
|
+
ratio_width, ratio_height = ratio
|
100
|
+
puts "image orig size: #{image[:width].to_i}x#{image[:height].to_i}"
|
101
|
+
puts "ratio_width: #{ratio_width}, ratio_height: #{ratio_height}"
|
102
|
+
if ratio_width > ratio_height || (ratio_width == ratio_height && image[:height].to_i > image[:width].to_i)
|
103
|
+
# calc ratio height from width
|
104
|
+
rheight = (image[:width].to_i*(ratio_height.to_f / ratio_width.to_f)).round
|
105
|
+
puts "rheight: #{rheight}"
|
106
|
+
# shave off height
|
107
|
+
shave_off = ((image[:height].to_i - rheight)/2).round
|
108
|
+
puts "shave off height: #{image[:height].to_i - rheight}"
|
109
|
+
image.shave("0x#{shave_off}")
|
110
|
+
puts "image crop size: #{image[:width].to_i}x#{image[:height].to_i}"
|
111
|
+
elsif ratio_height > ratio_width || (ratio_width == ratio_height && image[:width].to_i > image[:height].to_i)
|
112
|
+
# calc ratio width from height
|
113
|
+
rwidth = (image[:height].to_i*(ratio_width.to_f / ratio_height.to_f)).round
|
114
|
+
# shave off width
|
115
|
+
shave_off = ((image[:width].to_i - rwidth).to_f / 2.to_f).round
|
116
|
+
image.shave("#{shave_off}x0")
|
117
|
+
end
|
118
|
+
end
|
119
|
+
return image
|
120
|
+
end
|
121
|
+
|
122
|
+
def run_system_command(command, error_message)
|
123
|
+
output = `#{command}`
|
124
|
+
raise "#{error_message}: error given #{$?}\n#{output}" if $? != 0
|
125
|
+
return output
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
# Configurations
|
131
|
+
Document::PROCESS_FORMAT = 'jpg'
|
132
|
+
|
133
|
+
Document::PSD_EXT = '.psd'
|
134
|
+
Document::POSTSCRIPT_EXT = ['.pdf', '.eps', '.ai']
|
135
|
+
Document::IMAGE_EXT = ['.bmp', '.gif', '.jpg', '.jpeg', '.png', '.tga']
|
136
|
+
|
137
|
+
end
|
138
|
+
|
Binary file
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module Pruview
|
2
|
+
|
3
|
+
class Video
|
4
|
+
|
5
|
+
# this class assumes you have 'ffmpeg' and 'flvtool2' installed and in your path
|
6
|
+
|
7
|
+
def initialize(source, target_dir, bitrate_mult = 1)
|
8
|
+
raise Pruview::Exceptions::InvalidError, "Invalid source file:: #{source.to_s}" if !File.file?(source)
|
9
|
+
raise Pruview::Exceptions::InvalidError, "Invalid target directory: #{target_dir.to_s}" if !File.directory?(target_dir)
|
10
|
+
raise Pruview::Exceptions::InvalidError, "Video not supported - file extension: " + file_extension(source) if !format_supported?(source)
|
11
|
+
@source = source
|
12
|
+
@target_dir = target_dir
|
13
|
+
@bitrate_multiplier = bitrate_mult
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_flv(name, width, height, scale_static = false)
|
17
|
+
target = to_base(name, width, height, '.flv', scale_static)
|
18
|
+
run("#{FLVTOOL} -U #{target}", "Unable to add meta-data for #{target}.")
|
19
|
+
return target
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_mov(name, width, height, scale_static = false)
|
23
|
+
target = to_base(name, width, height, '.mov', scale_static)
|
24
|
+
# TODO: run qt-faststart
|
25
|
+
return target
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
|
30
|
+
def to_base(name, width, height, extension, scale_static)
|
31
|
+
target = File.join(@target_dir, name.to_s + extension)
|
32
|
+
info_yml = File.join(@target_dir, name.to_s + '_info.yml')
|
33
|
+
run(build_command(@source, target, width, height, get_info(info_yml), scale_static), "Unable to convert #{@source} to #{target}.")
|
34
|
+
return target
|
35
|
+
end
|
36
|
+
|
37
|
+
def format_supported?(source)
|
38
|
+
file_ext = file_extension(source)
|
39
|
+
EXT.each { |extension| return true if file_ext == extension }
|
40
|
+
return false
|
41
|
+
end
|
42
|
+
|
43
|
+
def file_extension(source_file)
|
44
|
+
File.extname(source_file).downcase.chomp
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_info(yml)
|
48
|
+
run("#{FFYML} #{@source} #{yml}", "Unable to get video info")
|
49
|
+
YAML.load_file(yml)
|
50
|
+
end
|
51
|
+
|
52
|
+
def build_command(source, target, width, height, info, scale_static)
|
53
|
+
command = "#{FFMPEG} -i #{source}"
|
54
|
+
command += get_scale_command(info['width'], info['height'], width, height, scale_static)
|
55
|
+
scale_factor = get_scale_factor(info['width'], info['height'], width, height)
|
56
|
+
if file_extension(target) != '.flv' # use h264 codec with lower bitrate scaling factor
|
57
|
+
command += " -vcodec libx264"
|
58
|
+
scale_factor /= 2.0
|
59
|
+
end
|
60
|
+
puts "scale factor: #{scale_factor.to_s}"
|
61
|
+
if !info['bitrate'].zero?
|
62
|
+
command += " -b #{(info['bitrate']*@bitrate_multiplier*scale_factor).to_s}"
|
63
|
+
else
|
64
|
+
command += " -sameq"
|
65
|
+
end
|
66
|
+
command += " -ab #{AUDIO_BITRATE}"
|
67
|
+
command += " -ar #{AUDIO_SAMPLING}"
|
68
|
+
command += " -y #{target}"
|
69
|
+
end
|
70
|
+
|
71
|
+
def get_scale_factor(source_width, source_height, scale_width, scale_height)
|
72
|
+
if source_width > source_height
|
73
|
+
return (scale_width.to_f / source_width.to_f)
|
74
|
+
else
|
75
|
+
return (scale_height.to_f / source_height.to_f)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def get_scale_command(source_width, source_height, scale_width, scale_height, static)
|
80
|
+
# this type of scaling assumes a static overall resolution with black padding added appropriately
|
81
|
+
# to keep the meaningful video area at the source aspect ratio
|
82
|
+
scale_params = get_scaling_params(source_width, source_height, scale_width, scale_height)
|
83
|
+
scale_command = " -s #{scale_params[:width].to_s}x#{scale_params[:height].to_s}"
|
84
|
+
if static == true
|
85
|
+
scale_command += " -padcolor #{PAD_COLOR}"
|
86
|
+
scale_command += " -padleft #{scale_params[:left]}" if scale_params[:left] > 0
|
87
|
+
scale_command += " -padtop #{scale_params[:top]}" if scale_params[:top] > 0
|
88
|
+
scale_command += " -padright #{scale_params[:right]}" if scale_params[:right] > 0
|
89
|
+
scale_command += " -padbottom #{scale_params[:bottom]}" if scale_params[:bottom] > 0
|
90
|
+
end
|
91
|
+
scale_command
|
92
|
+
end
|
93
|
+
|
94
|
+
def get_scaling_params(source_width, source_height, scale_width, scale_height)
|
95
|
+
params = {}
|
96
|
+
params[:left],params[:top],params[:right],params[:bottom] = 0,0,0,0
|
97
|
+
params[:width],params[:height] = scale_width, scale_height
|
98
|
+
scale_aspect = scale_width.to_f / scale_height.to_f
|
99
|
+
source_aspect = source_width.to_f / source_height.to_f
|
100
|
+
if(source_aspect > scale_aspect)
|
101
|
+
params[:height] = (scale_width.to_f / source_aspect).to_i
|
102
|
+
params[:height] += 1 if params[:height] % 2 != 0
|
103
|
+
params[:top] = params[:bottom] = ((scale_height - params[:height]).to_f / 2).to_i
|
104
|
+
params[:top] = params[:bottom] += 1 if params[:top] % 2 != 0
|
105
|
+
elsif(source_aspect < scale_aspect)
|
106
|
+
params[:width] = (scale_height.to_f * source_aspect).to_i
|
107
|
+
params[:width] += 1 if params[:width] % 2 != 0
|
108
|
+
params[:left] = params[:right] = ((scale_width - params[:width]).to_f / 2).to_i
|
109
|
+
params[:left] = params[:right] += 1 if params[:left] % 2 != 0
|
110
|
+
end
|
111
|
+
return params
|
112
|
+
end
|
113
|
+
|
114
|
+
def run(command, error_message = "Unknown error.")
|
115
|
+
raise "Ffmpeg error: " + error_message + " - command: '#{command}'" if !system(command)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Configurations
|
119
|
+
Video::FFYML = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'bin', 'ffyml'))
|
120
|
+
Video::FFMPEG = 'ffmpeg'
|
121
|
+
Video::FLVTOOL = 'flvtool2'
|
122
|
+
Video::PAD_COLOR = "000000"
|
123
|
+
Video::AUDIO_BITRATE = '128' # kbps
|
124
|
+
Video::AUDIO_SAMPLING = '44100'
|
125
|
+
|
126
|
+
Video::EXT = ['.avi', '.flv', '.mov', '.mpg', '.mp4']
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Pruview
|
2
|
+
class VideoImage
|
3
|
+
|
4
|
+
# this class assumes you have 'ffmpeg' installed and in your path
|
5
|
+
|
6
|
+
def self.to_jpg(source, target_dir, name)
|
7
|
+
raise Pruview::Exceptions::InvalidError, "Invalid source file:: #{source.to_s}" if !File.file?(source)
|
8
|
+
raise Pruview::Exceptions::InvalidError, "Invalid target directory: #{target_dir.to_s}" if !File.directory?(target_dir)
|
9
|
+
raise Pruview::Exceptions::InvalidError, "Video not supported - file extension: " + file_extension(source) if !format_supported?(source)
|
10
|
+
target = File.join(target_dir, name.to_s + '.jpg')
|
11
|
+
run(build_command(source, '-ss 00:00:00.2', 'mjpeg', target), "Unable to get preview image for #{target}")
|
12
|
+
# TODO: analyze image - create better
|
13
|
+
return target
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
def self.format_supported?(source)
|
19
|
+
file_ext = file_extension(source)
|
20
|
+
Video::EXT.each { |extension| return true if file_ext == extension }
|
21
|
+
return false
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.file_extension(source_file)
|
25
|
+
File.extname(source_file).downcase.chomp
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.build_command(source, time_str, format, target)
|
29
|
+
command = "#{Video::FFMPEG} -i #{source}"
|
30
|
+
command += " #{time_str}"
|
31
|
+
command += " -f #{format}" if !format.empty?
|
32
|
+
command += " -an -y #{target}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.run(command, error_message = "Unknown error.")
|
36
|
+
raise "Ffmpeg error: " + error_message + " - command: '#{command}'" if !system(command)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
Binary file
|
Binary file
|
File without changes
|
File without changes
|
File without changes
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: kelredd-pruview
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kelly Redding
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-07-22 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: mini_magick
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: flvtool2
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
version:
|
35
|
+
description:
|
36
|
+
email: kelly@kelredd.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- README.rdoc
|
43
|
+
files:
|
44
|
+
- README.rdoc
|
45
|
+
- Rakefile
|
46
|
+
- features/document.feature
|
47
|
+
- features/step_definitions
|
48
|
+
- features/step_definitions/common_steps.rb
|
49
|
+
- features/step_definitions/document_steps.rb
|
50
|
+
- features/step_definitions/support
|
51
|
+
- features/step_definitions/support/env.rb
|
52
|
+
- features/step_definitions/support/helpers.rb
|
53
|
+
- features/step_definitions/video_image_steps.rb
|
54
|
+
- features/step_definitions/video_steps.rb
|
55
|
+
- features/video.feature
|
56
|
+
- features/video_image.feature
|
57
|
+
- lib/pruview
|
58
|
+
- lib/pruview/document.rb
|
59
|
+
- lib/pruview/exceptions.rb
|
60
|
+
- lib/pruview/sRGB.icm
|
61
|
+
- lib/pruview/USWebCoatedSWOP.icc
|
62
|
+
- lib/pruview/version.rb
|
63
|
+
- lib/pruview/video.rb
|
64
|
+
- lib/pruview/video_image.rb
|
65
|
+
- lib/pruview.rb
|
66
|
+
- test/files
|
67
|
+
- test/files/basic.jpg
|
68
|
+
- test/files/basic.mpg
|
69
|
+
- test/files/error.jpg
|
70
|
+
- test/files/error.mov
|
71
|
+
- test/files/invalid_format.poop
|
72
|
+
- test/test_helper.rb
|
73
|
+
- test/unit
|
74
|
+
- test/unit/pruview_test.rb
|
75
|
+
has_rdoc: true
|
76
|
+
homepage: ""
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options:
|
79
|
+
- --main
|
80
|
+
- README.rdoc
|
81
|
+
require_paths:
|
82
|
+
- lib
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: "0"
|
88
|
+
version:
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: "0"
|
94
|
+
version:
|
95
|
+
requirements: []
|
96
|
+
|
97
|
+
rubyforge_project:
|
98
|
+
rubygems_version: 1.2.0
|
99
|
+
signing_key:
|
100
|
+
specification_version: 2
|
101
|
+
summary: A gem to ease generating image previews (thumbnails) of various files
|
102
|
+
test_files: []
|
103
|
+
|