seamus 0.2.4 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +1 -1
- data/README.rdoc +35 -19
- data/VERSION +1 -1
- data/lib/mime_table.rb +1 -0
- data/lib/seamus.rb +45 -116
- data/lib/seamus/builder.rb +45 -0
- data/lib/seamus/file/application.rb +13 -0
- data/lib/seamus/file/audio.rb +13 -0
- data/lib/seamus/file/image.rb +13 -0
- data/lib/seamus/file/text.rb +13 -0
- data/lib/seamus/file/video.rb +13 -0
- data/lib/seamus/inspector/application_inspector.rb +1 -12
- data/lib/seamus/inspector/audio_inspector.rb +3 -26
- data/lib/seamus/inspector/image_inspector.rb +3 -17
- data/lib/seamus/inspector/text_inspector.rb +19 -9
- data/lib/seamus/inspector/video_inspector.rb +6 -28
- data/lib/seamus/processor/application_processor.rb +1 -1
- data/lib/seamus/processor/audio_processor.rb +1 -1
- data/lib/seamus/processor/image_processor.rb +12 -8
- data/lib/seamus/processor/text_processor.rb +1 -1
- data/lib/seamus/processor/video_processor.rb +4 -16
- data/lib/seamus/standard_additions.rb +44 -0
- data/seamus.gemspec +19 -5
- data/spec/factories.rb +13 -0
- data/spec/fixtures/test.jpg +0 -0
- data/spec/fixtures/test.mov +0 -0
- data/spec/fixtures/test.mp4 +0 -0
- data/spec/lib/seamus/inspector/image_inspector_spec.rb +5 -35
- data/spec/lib/seamus/inspector/video_inspector_spec.rb +13 -0
- data/spec/lib/seamus/processor/video_processor_spec.rb +66 -0
- data/spec/seamus_spec.rb +46 -9
- data/spec/spec_helper.rb +3 -1
- metadata +18 -4
- data/lib/seamus/inspector.rb +0 -20
- data/lib/seamus/processor.rb +0 -16
data/LICENSE
CHANGED
data/README.rdoc
CHANGED
@@ -4,37 +4,53 @@ Seamus is not an Irish monk. Instead, it inspects a file and returns whatever me
|
|
4
4
|
|
5
5
|
== Usage
|
6
6
|
|
7
|
-
|
8
|
-
|
7
|
+
Seamus inspects your file and discovers useful attributes. It is at its best with rich
|
8
|
+
digital media - video, audio and image files - but is suitable for use with any file type.
|
9
|
+
Just provide a path to Seamus::Builder.new for an enhanced File object that knows about your
|
10
|
+
file.
|
9
11
|
|
10
|
-
|
12
|
+
movie = Seamus::Builder.new("/path/to/my/movie.mov")
|
13
|
+
|
14
|
+
movie.width
|
15
|
+
# => 720
|
16
|
+
|
17
|
+
movie.video_codec
|
18
|
+
# => 'h264'
|
19
|
+
|
20
|
+
Or if you prefer, include Seamus in a class and use the has_file :file_attribute class
|
21
|
+
method for attachments.
|
22
|
+
|
23
|
+
class Upload
|
11
24
|
include Seamus
|
12
|
-
|
13
|
-
|
14
|
-
def initialize(path)
|
15
|
-
@path = File.open(path)
|
16
|
-
end
|
25
|
+
has_file :file
|
17
26
|
end
|
18
27
|
|
19
|
-
|
20
|
-
|
28
|
+
u = Upload.new
|
29
|
+
u.file = "/path/to/image.jpg"
|
30
|
+
u.file.width
|
31
|
+
# => 3284
|
21
32
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
Generate thumbnails automatically:
|
33
|
+
Seamus supports thumbnail creation for visual media types. The #thumbnail method returns
|
34
|
+
an IO instance.
|
26
35
|
|
27
|
-
|
36
|
+
u.file.thumbnail
|
37
|
+
# => #<IO:0x357898>
|
38
|
+
|
39
|
+
== Extending Seamus
|
28
40
|
|
29
|
-
|
41
|
+
Seamus dynamically generates classes for file types as it encounters them. Seamus::Builder
|
42
|
+
will, for example, generate a Seamus::File::Mp4 class and instance when given a path to
|
43
|
+
an MPEG-4 file with .mp4 extension, which subclasses Seamus::File::Video.
|
30
44
|
|
31
|
-
|
45
|
+
You can build custom behavior around a file type by defining a class Seamus::File::<extname>
|
46
|
+
that either subclasses File, subclasses one of Seamus::File's template classes, or implements
|
47
|
+
a path handler of some kind.
|
32
48
|
|
33
49
|
== Note on Patches/Pull Requests
|
34
50
|
|
35
|
-
* I'd be very surprised if anyone wanted to do such a thing.
|
51
|
+
* I'd be very surprised if anyone wanted to do such a thing. But if you really do, contact me and we'll work something out.
|
36
52
|
|
37
53
|
|
38
54
|
== Copyright
|
39
55
|
|
40
|
-
Copyright (c)
|
56
|
+
Copyright (c) 2010 Scott Burton. See LICENSE for details.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
data/lib/mime_table.rb
CHANGED
@@ -85,6 +85,7 @@ MimeTable.register "text/UTF-8", :txt
|
|
85
85
|
MimeTable.register "text/x-java-source", :jav
|
86
86
|
MimeTable.register "text/x-script.python", :py
|
87
87
|
MimeTable.register "text/x-script.sh", :sh
|
88
|
+
MimeTable.register "text/x-ruby", :rb
|
88
89
|
MimeTable.register "video/mp4", :f4v
|
89
90
|
MimeTable.register "video/mp4", :mp4
|
90
91
|
MimeTable.register "video/quicktime", :mov
|
data/lib/seamus.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
|
-
require 'rvideo'
|
2
1
|
require 'digest/md5'
|
3
|
-
|
2
|
+
require 'open3'
|
4
3
|
require 'tempfile'
|
5
4
|
|
6
5
|
$LOAD_PATH << './lib'
|
7
6
|
|
8
7
|
require 'core/numeric'
|
8
|
+
require 'seamus/standard_additions'
|
9
9
|
require 'seamus/inspector'
|
10
10
|
require 'seamus/inspector/video_inspector'
|
11
11
|
require 'seamus/inspector/audio_inspector'
|
@@ -18,138 +18,67 @@ require 'seamus/processor/audio_processor'
|
|
18
18
|
require 'seamus/processor/image_processor'
|
19
19
|
require 'seamus/processor/application_processor'
|
20
20
|
require 'seamus/processor/text_processor'
|
21
|
+
require 'seamus/file/video'
|
22
|
+
require 'seamus/file/audio'
|
23
|
+
require 'seamus/file/text'
|
24
|
+
require 'seamus/file/image'
|
25
|
+
require 'seamus/file/application'
|
26
|
+
require 'seamus/builder'
|
21
27
|
require 'mime_table'
|
22
28
|
|
23
29
|
|
24
30
|
## Seamus - The File Inspector
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
31
|
+
#
|
32
|
+
# Seamus inspects your file and discovers useful attributes. It is at its best with rich
|
33
|
+
# digital media - video, audio and image files - but is suitable for use with any file type.
|
34
|
+
# Just provide a path to Seamus::Builder.new for an enhanced File object that knows about your
|
35
|
+
# file.
|
36
|
+
#
|
37
|
+
# movie = Seamus::Builder.new("/path/to/my/movie.mov")
|
38
|
+
#
|
39
|
+
# movie.width
|
40
|
+
# # => 720
|
41
|
+
#
|
42
|
+
# movie.video_codec
|
43
|
+
# # => 'h264'
|
44
|
+
#
|
45
|
+
# Or if you prefer, include Seamus in a class and use the has_file :file_attribute class
|
46
|
+
# method for attachments.
|
47
|
+
#
|
48
|
+
# class Upload
|
29
49
|
# include Seamus
|
30
|
-
#
|
31
|
-
#
|
32
|
-
# def initialize(path)
|
33
|
-
# @path = File.open(path)
|
34
|
-
# end
|
50
|
+
# has_file :file
|
35
51
|
# end
|
36
52
|
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
# # =>
|
53
|
+
# u = Upload.new
|
54
|
+
# u.file = "/path/to/image.jpg"
|
55
|
+
# u.file.width
|
56
|
+
# # => 3284
|
41
57
|
#
|
42
|
-
#
|
43
|
-
#
|
58
|
+
# Seamus supports thumbnail creation for visual media types. The #thumbnail method returns
|
59
|
+
# an IO instance.
|
44
60
|
#
|
45
|
-
#
|
61
|
+
# u.file.thumbnail
|
62
|
+
# # => #<IO:0x357898>
|
46
63
|
#
|
47
|
-
|
48
64
|
|
49
65
|
module Seamus
|
50
66
|
|
51
67
|
module ClassMethods
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
def file_path
|
58
|
-
file.path
|
59
|
-
end
|
60
|
-
|
61
|
-
def file_name
|
62
|
-
File.basename(file_path)
|
63
|
-
end
|
64
|
-
|
65
|
-
def file_attributes
|
66
|
-
@file_attributes ||= inspect_file
|
67
|
-
end
|
68
|
-
|
69
|
-
def md5
|
70
|
-
@md5 ||= Digest::MD5.file(file_path)
|
71
|
-
end
|
72
|
-
|
73
|
-
def md5_hex_string
|
74
|
-
md5.to_s
|
75
|
-
end
|
76
|
-
|
77
|
-
def md5_base64_encoded
|
78
|
-
Base64.encode64(md5.digest).strip
|
79
|
-
end
|
80
|
-
|
81
|
-
def content_type
|
82
|
-
MimeTable.lookup_by_extension(extension).to_s
|
83
|
-
end
|
84
|
-
|
85
|
-
def thumbnail(&block)
|
86
|
-
@thumbnail ||= generate_thumbnail
|
87
|
-
if block_given?
|
88
|
-
begin
|
89
|
-
@thumbnail.open
|
90
|
-
yield @thumbnail
|
91
|
-
ensure
|
92
|
-
@thumbnail.close
|
68
|
+
def has_file(attribute)
|
69
|
+
self.class_eval(%Q(
|
70
|
+
def #{attribute.to_s}=(file)
|
71
|
+
@#{attribute.to_s} = Seamus::Builder.new(file)
|
93
72
|
end
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
protected
|
99
|
-
|
100
|
-
def inspect_file
|
101
|
-
inspector = Seamus.const_get("#{file_type.classify}Inspector").new(file_path)
|
102
|
-
return inspector.stats
|
103
|
-
end
|
104
|
-
|
105
|
-
def generate_thumbnail
|
106
|
-
processor = Seamus.const_get("#{file_type.classify}Processor").new(file_path, file_attributes)
|
107
|
-
begin
|
108
|
-
processor.thumbnail
|
109
|
-
rescue ThumbnailError => e
|
110
|
-
return nil
|
111
|
-
rescue NameError
|
112
|
-
return nil
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
private
|
117
|
-
|
118
|
-
def file_type
|
119
|
-
# begin
|
120
|
-
# @file_attributes["content-type"].split("/").first
|
121
|
-
# rescue
|
122
|
-
determine_type_from_extension
|
123
|
-
# end
|
124
|
-
end
|
125
|
-
|
126
|
-
def determine_type_from_extension
|
127
|
-
begin
|
128
|
-
Mime::Type.lookup_by_extension(extension).to_s.split("/").first
|
129
|
-
rescue NoMethodError
|
130
|
-
"application"
|
131
|
-
rescue NameError
|
132
|
-
if MimeTable
|
133
|
-
MimeTable.lookup_by_extension(extension).to_s.split("/").first
|
134
|
-
else
|
135
|
-
raise LoadError, "Mime module not loaded"
|
73
|
+
|
74
|
+
def #{attribute.to_s}
|
75
|
+
@#{attribute.to_s}
|
136
76
|
end
|
137
|
-
|
138
|
-
end
|
139
|
-
|
140
|
-
# returns the last segment of the filename, eg "foo.mov".extension => ".mov"
|
141
|
-
def extension
|
142
|
-
file_name.split('.').last.downcase
|
77
|
+
))
|
143
78
|
end
|
79
|
+
end
|
144
80
|
|
145
|
-
|
146
|
-
def method_missing(method, *args, &block)
|
147
|
-
if file_attributes.include?(method.to_s)
|
148
|
-
file_attributes[method.to_s]
|
149
|
-
else
|
150
|
-
super(method, *args, &block)
|
151
|
-
end
|
152
|
-
end
|
81
|
+
module InstanceMethods
|
153
82
|
|
154
83
|
end
|
155
84
|
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Seamus
|
2
|
+
|
3
|
+
module Builder
|
4
|
+
extend self
|
5
|
+
def new(file)
|
6
|
+
path = file.is_a?(File) ? Pathname.new(file.path) : Pathname.new(file)
|
7
|
+
type = file_type(path.extname)
|
8
|
+
extension = path.extname.gsub('.', '')
|
9
|
+
build_class(type, extension).new(path.to_s)
|
10
|
+
end
|
11
|
+
|
12
|
+
def file_type(extension)
|
13
|
+
determine_type_from_extension(extension)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def build_class(type, extension)
|
19
|
+
Seamus::File.module_eval %Q{
|
20
|
+
class #{extension.capitalize} < #{type}; end
|
21
|
+
} unless Seamus::File.const_defined?("#{extension.capitalize}")
|
22
|
+
Seamus::File.const_get("#{extension.capitalize}")
|
23
|
+
end
|
24
|
+
|
25
|
+
def determine_type_from_extension(extension)
|
26
|
+
begin
|
27
|
+
Mime::Type.lookup_by_extension(extension).to_s.split("/").first.capitalize
|
28
|
+
rescue NoMethodError
|
29
|
+
"Application"
|
30
|
+
rescue NameError
|
31
|
+
if MimeTable
|
32
|
+
MimeTable.lookup_by_extension(extension).to_s.split("/").first.capitalize
|
33
|
+
else
|
34
|
+
raise LoadError, "Mime module not loaded"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
#
|
39
|
+
# # returns the last segment of the filename, eg "foo.mov".extension => ".mov"
|
40
|
+
# def extension
|
41
|
+
# file_name.split('.').last.downcase
|
42
|
+
# end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Seamus
|
2
|
+
module File
|
3
|
+
class Application < ::File
|
4
|
+
include Seamus::ApplicationInspector
|
5
|
+
include Seamus::ApplicationProcessor
|
6
|
+
include Seamus::StandardAdditions
|
7
|
+
def initialize(*args, &block)
|
8
|
+
super(*args, &block)
|
9
|
+
# create_methods_for inspector
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Seamus
|
2
|
+
module File
|
3
|
+
class Audio < ::File
|
4
|
+
include Seamus::AudioInspector
|
5
|
+
include Seamus::AudioProcessor
|
6
|
+
include Seamus::StandardAdditions
|
7
|
+
def initialize(*args, &block)
|
8
|
+
super(*args, &block)
|
9
|
+
create_methods_for inspector, :except => "path"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Seamus
|
2
|
+
module File
|
3
|
+
class Image < ::File
|
4
|
+
include Seamus::ImageInspector
|
5
|
+
include Seamus::ImageProcessor
|
6
|
+
include Seamus::StandardAdditions
|
7
|
+
def initialize(*args, &block)
|
8
|
+
super(*args, &block)
|
9
|
+
create_methods_for inspector, :except => ["thumbnail", "thumbnail2"]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Seamus
|
2
|
+
module File
|
3
|
+
class Text < ::File
|
4
|
+
include Seamus::TextInspector
|
5
|
+
include Seamus::TextProcessor
|
6
|
+
include Seamus::StandardAdditions
|
7
|
+
def initialize(*args, &block)
|
8
|
+
super(*args, &block)
|
9
|
+
# create_methods_for inspector
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Seamus
|
2
|
+
module File
|
3
|
+
class Video < ::File
|
4
|
+
include Seamus::VideoInspector
|
5
|
+
include Seamus::VideoProcessor
|
6
|
+
include Seamus::StandardAdditions
|
7
|
+
def initialize(*args, &block)
|
8
|
+
super(*args, &block)
|
9
|
+
create_methods_for inspector, :except => "path"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -1,16 +1,5 @@
|
|
1
1
|
module Seamus
|
2
|
-
|
2
|
+
module ApplicationInspector
|
3
3
|
|
4
|
-
def stats
|
5
|
-
inspection_attributes
|
6
|
-
end
|
7
|
-
|
8
|
-
private
|
9
|
-
|
10
|
-
def inspection_attributes
|
11
|
-
attr_hash = {"size" => file_stat.size}
|
12
|
-
return attr_hash
|
13
|
-
end
|
14
|
-
|
15
4
|
end
|
16
5
|
end
|
@@ -1,32 +1,9 @@
|
|
1
1
|
module Seamus
|
2
|
-
|
2
|
+
module AudioInspector
|
3
3
|
|
4
|
-
def
|
5
|
-
|
4
|
+
def inspector
|
5
|
+
@inspector ||= RVideo::Inspector.new(:file => self.path)
|
6
6
|
end
|
7
|
-
|
8
|
-
private
|
9
|
-
|
10
|
-
def inspection_attributes
|
11
|
-
attr_hash = {}
|
12
|
-
["audio?", "audio_bitrate", "audio_channels", "audio_channels_string", "audio_codec", "audio_sample_rate", "audio_sample_units", "bitrate", "bitrate_units", "container", "duration"].each do |attribute|
|
13
|
-
attr_hash[attribute.to_s] = media_stat.send(attribute) if media_stat.respond_to?(attribute)
|
14
|
-
end
|
15
|
-
attr_hash.merge!("size" => file_stat.size)
|
16
|
-
return attr_hash
|
17
|
-
end
|
18
|
-
|
19
|
-
def media_stat
|
20
|
-
RVideo::Inspector.new(:raw_response => raw_response)
|
21
|
-
end
|
22
|
-
|
23
|
-
def raw_response
|
24
|
-
process = IO.popen("ffmpeg -i '#{file.path}' 2>&1", "r")
|
25
|
-
response = process.read
|
26
|
-
process.close
|
27
|
-
return response
|
28
|
-
end
|
29
|
-
|
30
7
|
|
31
8
|
end
|
32
9
|
end
|
@@ -1,24 +1,10 @@
|
|
1
1
|
require 'devil'
|
2
2
|
|
3
3
|
module Seamus
|
4
|
-
|
5
|
-
def stats
|
6
|
-
inspection_attributes
|
7
|
-
end
|
8
|
-
|
9
|
-
private
|
10
|
-
|
11
|
-
def inspection_attributes
|
12
|
-
attr_hash = {}
|
13
|
-
["width", "height"].each do |attribute|
|
14
|
-
attr_hash[attribute] ||= image.send(attribute.to_sym) if image.respond_to?(attribute)
|
15
|
-
end
|
16
|
-
attr_hash.merge!("size" => file_stat.size)
|
17
|
-
return attr_hash
|
18
|
-
end
|
4
|
+
module ImageInspector
|
19
5
|
|
20
|
-
def
|
21
|
-
@
|
6
|
+
def inspector
|
7
|
+
@inspector ||= Devil.load_image(self.path)
|
22
8
|
end
|
23
9
|
|
24
10
|
end
|
@@ -1,16 +1,26 @@
|
|
1
1
|
module Seamus
|
2
|
-
|
2
|
+
module TextInspector
|
3
3
|
|
4
|
-
def
|
5
|
-
|
4
|
+
def words
|
5
|
+
characters.scan(/\S+/)
|
6
6
|
end
|
7
|
-
|
7
|
+
|
8
|
+
def word_count
|
9
|
+
words.size
|
10
|
+
end
|
11
|
+
|
12
|
+
def lines
|
13
|
+
characters.scan(/.*\n/)
|
14
|
+
end
|
15
|
+
|
16
|
+
def lines_count
|
17
|
+
lines.size
|
18
|
+
end
|
19
|
+
|
8
20
|
private
|
9
|
-
|
10
|
-
def
|
11
|
-
|
12
|
-
return attr_hash
|
21
|
+
|
22
|
+
def characters
|
23
|
+
@characters ||= ::File.read(self.path)
|
13
24
|
end
|
14
|
-
|
15
25
|
end
|
16
26
|
end
|
@@ -1,33 +1,11 @@
|
|
1
|
+
require 'rvideo'
|
2
|
+
require 'forwardable'
|
3
|
+
|
1
4
|
module Seamus
|
2
|
-
|
5
|
+
module VideoInspector
|
3
6
|
|
4
|
-
def
|
5
|
-
|
6
|
-
end
|
7
|
-
|
8
|
-
private
|
9
|
-
|
10
|
-
def inspection_attributes
|
11
|
-
attr_hash = {}
|
12
|
-
["audio?", "audio_bitrate", "audio_channels", "audio_channels_string", "audio_codec", "audio_sample_rate", "audio_sample_units", "bitrate", "bitrate_units", "container", "duration", "fps", "height", "video?", "video_codec", "video_colorspace", "width"].each do |attribute|
|
13
|
-
attr_hash[attribute.to_s] = media_stat.send(attribute) if media_stat.respond_to?(attribute)
|
14
|
-
end
|
15
|
-
attr_hash.merge!("size" => file_stat.size)
|
16
|
-
return attr_hash
|
17
|
-
end
|
18
|
-
|
19
|
-
def media_stat
|
20
|
-
RVideo::Inspector.new(:raw_response => raw_response)
|
21
|
-
end
|
22
|
-
|
23
|
-
def raw_response
|
24
|
-
response = ffmpeg_response_process.read
|
25
|
-
ffmpeg_response_process.close
|
26
|
-
return response
|
27
|
-
end
|
28
|
-
|
29
|
-
def ffmpeg_response_process
|
30
|
-
IO.popen("ffmpeg -i '#{@file.path}' 2>&1", "r")
|
7
|
+
def inspector
|
8
|
+
@inspector ||= RVideo::Inspector.new(:file => self.path)
|
31
9
|
end
|
32
10
|
|
33
11
|
end
|
@@ -1,21 +1,25 @@
|
|
1
1
|
module Seamus
|
2
|
-
|
2
|
+
module ImageProcessor
|
3
3
|
require 'devil'
|
4
4
|
|
5
5
|
def thumbnail
|
6
|
-
tmp =
|
7
|
-
|
6
|
+
tmp = Tempfile.new(::File.basename(path))
|
7
|
+
# io = IO.new
|
8
|
+
Devil.load_image(path) do |img|
|
8
9
|
img.thumbnail(320)
|
9
|
-
img.save("#{
|
10
|
+
img.save("#{path}_#{::File.basename(path)}_thumb.jpg")
|
10
11
|
end
|
11
|
-
File.open("#{
|
12
|
+
::File.open("#{path}_#{::File.basename(path)}_thumb.jpg", "r") do |thumb|
|
12
13
|
tmp.write thumb.read
|
13
14
|
end
|
14
|
-
|
15
|
-
|
15
|
+
# tmp.close
|
16
|
+
tmp.rewind
|
17
|
+
# io.close
|
18
|
+
::File.unlink("#{path}_#{::File.basename(path)}_thumb.jpg")
|
19
|
+
# return io
|
16
20
|
return tmp
|
17
21
|
end
|
18
|
-
|
22
|
+
|
19
23
|
|
20
24
|
end
|
21
25
|
end
|
@@ -1,30 +1,18 @@
|
|
1
1
|
module Seamus
|
2
|
-
|
2
|
+
module VideoProcessor
|
3
3
|
|
4
4
|
def thumbnail
|
5
|
-
|
6
|
-
IO.popen("ffmpeg -y -ss #{start_time} -i '#{@file.path}' -t 00:00:01 -vcodec mjpeg -vframes 1 -an -f rawvideo -s #{dimensions_string} - 2> /dev/null") do |io|
|
7
|
-
thumb.write io.read
|
8
|
-
end
|
9
|
-
thumb.close
|
10
|
-
return thumb
|
5
|
+
Open3.popen3("ffmpeg -y -ss #{start_time} -i '#{self.path}' -t 00:00:01 -vcodec mjpeg -vframes 1 -an -f rawvideo -s #{dimensions_string} - ")[1]
|
11
6
|
end
|
12
7
|
|
13
8
|
private
|
14
9
|
|
15
10
|
def start_time
|
16
|
-
(
|
17
|
-
end
|
18
|
-
|
19
|
-
# nodoc
|
20
|
-
def thumb_tempfile
|
21
|
-
Tempfile.new("#{File.basename(@file.path, File.extname(@file.path))}_thumb.jpg")
|
11
|
+
(duration || 10000)/3333
|
22
12
|
end
|
23
13
|
|
24
14
|
def aspect_ratio
|
25
|
-
width
|
26
|
-
height = (file_attributes["height"]).to_f
|
27
|
-
return (width/height)
|
15
|
+
width.to_r/height.to_r
|
28
16
|
end
|
29
17
|
|
30
18
|
# For ffmpeg dimensions to work, the dimension components have to be even numbers,
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Seamus
|
2
|
+
module StandardAdditions
|
3
|
+
def md5
|
4
|
+
@md5 ||= Digest::MD5.file(self.path)
|
5
|
+
end
|
6
|
+
|
7
|
+
def md5_digest
|
8
|
+
md5.digest
|
9
|
+
end
|
10
|
+
|
11
|
+
def md5_base64_encoded
|
12
|
+
Base64.encode64(md5_digest).strip
|
13
|
+
end
|
14
|
+
|
15
|
+
def size
|
16
|
+
stat.size
|
17
|
+
end
|
18
|
+
|
19
|
+
def content_type
|
20
|
+
MimeTable.lookup_by_extension(extension).to_s
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def create_method(method, method_binding)
|
26
|
+
self.class.send(:define_method, method.to_sym, method_binding)
|
27
|
+
end
|
28
|
+
|
29
|
+
def create_methods_for(object, options={})
|
30
|
+
options[:except] = options[:except].to_a unless options[:except].is_a?(Array)
|
31
|
+
object.public_methods(false).each do |method|
|
32
|
+
create_method(method, lambda {object.method(method.to_s).call}) unless options[:except].include?(method)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# def method_missing(method, *args, &block)
|
37
|
+
# if file_attributes.has_key?(method.to_s)
|
38
|
+
# file_attributes[method.to_s]
|
39
|
+
# else
|
40
|
+
# super(method, *args, &block)
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
end
|
44
|
+
end
|
data/seamus.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{seamus}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.3.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Scott Burton"]
|
12
|
-
s.date = %q{2010-
|
12
|
+
s.date = %q{2010-02-10}
|
13
13
|
s.description = %q{Seamus is not an Irish monk. Instead, it inspects a file and returns whatever metadata it can determine.}
|
14
14
|
s.email = %q{scottburton11@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -29,22 +29,33 @@ Gem::Specification.new do |s|
|
|
29
29
|
"lib/core/numeric.rb",
|
30
30
|
"lib/mime_table.rb",
|
31
31
|
"lib/seamus.rb",
|
32
|
-
"lib/seamus/
|
32
|
+
"lib/seamus/builder.rb",
|
33
|
+
"lib/seamus/file/application.rb",
|
34
|
+
"lib/seamus/file/audio.rb",
|
35
|
+
"lib/seamus/file/image.rb",
|
36
|
+
"lib/seamus/file/text.rb",
|
37
|
+
"lib/seamus/file/video.rb",
|
33
38
|
"lib/seamus/inspector/application_inspector.rb",
|
34
39
|
"lib/seamus/inspector/audio_inspector.rb",
|
35
40
|
"lib/seamus/inspector/image_inspector.rb",
|
36
41
|
"lib/seamus/inspector/text_inspector.rb",
|
37
42
|
"lib/seamus/inspector/video_inspector.rb",
|
38
|
-
"lib/seamus/processor.rb",
|
39
43
|
"lib/seamus/processor/application_processor.rb",
|
40
44
|
"lib/seamus/processor/audio_processor.rb",
|
41
45
|
"lib/seamus/processor/image_processor.rb",
|
42
46
|
"lib/seamus/processor/text_processor.rb",
|
43
47
|
"lib/seamus/processor/video_processor.rb",
|
48
|
+
"lib/seamus/standard_additions.rb",
|
44
49
|
"seamus.gemspec",
|
50
|
+
"spec/factories.rb",
|
51
|
+
"spec/fixtures/test.jpg",
|
52
|
+
"spec/fixtures/test.mov",
|
53
|
+
"spec/fixtures/test.mp4",
|
45
54
|
"spec/lib/core/numeric_spec.rb",
|
46
55
|
"spec/lib/seamus/inspector/image_inspector_spec.rb",
|
56
|
+
"spec/lib/seamus/inspector/video_inspector_spec.rb",
|
47
57
|
"spec/lib/seamus/processor/image_processor_spec.rb",
|
58
|
+
"spec/lib/seamus/processor/video_processor_spec.rb",
|
48
59
|
"spec/seamus_spec.rb",
|
49
60
|
"spec/spec_helper.rb"
|
50
61
|
]
|
@@ -54,9 +65,12 @@ Gem::Specification.new do |s|
|
|
54
65
|
s.rubygems_version = %q{1.3.5}
|
55
66
|
s.summary = %q{Seamus is not an Irish monk}
|
56
67
|
s.test_files = [
|
57
|
-
"spec/
|
68
|
+
"spec/factories.rb",
|
69
|
+
"spec/lib/core/numeric_spec.rb",
|
58
70
|
"spec/lib/seamus/inspector/image_inspector_spec.rb",
|
71
|
+
"spec/lib/seamus/inspector/video_inspector_spec.rb",
|
59
72
|
"spec/lib/seamus/processor/image_processor_spec.rb",
|
73
|
+
"spec/lib/seamus/processor/video_processor_spec.rb",
|
60
74
|
"spec/seamus_spec.rb",
|
61
75
|
"spec/spec_helper.rb"
|
62
76
|
]
|
data/spec/factories.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
module Factories
|
2
|
+
def mov
|
3
|
+
File.open(File.expand_path(File.dirname(__FILE__) + "/fixtures/test.mov"))
|
4
|
+
end
|
5
|
+
|
6
|
+
def mp4
|
7
|
+
File.open(File.expand_path(File.dirname(__FILE__) + "/fixtures/test.mp4"))
|
8
|
+
end
|
9
|
+
|
10
|
+
def jpg
|
11
|
+
File.open(File.expand_path(File.dirname(__FILE__) + "/fixtures/test.jpg"))
|
12
|
+
end
|
13
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
@@ -1,43 +1,13 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__)) + "/../../../spec_helper.rb"
|
2
2
|
|
3
3
|
describe Seamus::ImageInspector do
|
4
|
-
|
5
|
-
include FakeFS::SpecHelpers
|
6
|
-
|
7
4
|
before(:each) do
|
8
|
-
|
9
|
-
@
|
5
|
+
@image = mock(Seamus::File::Image, :path => "/path/to/image.jpg")
|
6
|
+
@image.extend Seamus::ImageInspector
|
10
7
|
end
|
11
8
|
|
12
|
-
it "
|
13
|
-
|
9
|
+
it "loads an image inspector using Devil.load_image" do
|
10
|
+
Devil.should_receive(:load_image).with("/path/to/image.jpg")
|
11
|
+
@image.inspector
|
14
12
|
end
|
15
|
-
|
16
|
-
describe "stats" do
|
17
|
-
|
18
|
-
before(:each) do
|
19
|
-
@image = mock(Devil::Image, :width => 100, :height => 100)
|
20
|
-
@file_stat = mock(File::Stat, :size => 100)
|
21
|
-
@inspector.should_receive(:image).any_number_of_times.and_return(@image)
|
22
|
-
@inspector.should_receive(:file_stat).and_return(@file_stat)
|
23
|
-
end
|
24
|
-
|
25
|
-
it "returns a hash" do
|
26
|
-
@inspector.stats.should be_an_instance_of(Hash)
|
27
|
-
end
|
28
|
-
|
29
|
-
it "assigns :width" do
|
30
|
-
@inspector.stats["width"].should eql(100)
|
31
|
-
end
|
32
|
-
|
33
|
-
it "assigns :height" do
|
34
|
-
@inspector.stats["height"].should eql(100)
|
35
|
-
end
|
36
|
-
|
37
|
-
it "assigns :size" do
|
38
|
-
@inspector.stats["size"].should eql(100)
|
39
|
-
end
|
40
|
-
|
41
|
-
end
|
42
|
-
|
43
13
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__)) + "/../../../spec_helper.rb"
|
2
|
+
|
3
|
+
describe Seamus::VideoInspector do
|
4
|
+
before(:each) do
|
5
|
+
@video = mock(Seamus::File::Video, :path => "/path/to/video.jpg")
|
6
|
+
@video.extend Seamus::VideoInspector
|
7
|
+
end
|
8
|
+
|
9
|
+
it "loads an video inspector using RVideo::Inspector" do
|
10
|
+
RVideo::Inspector.should_receive(:new).with(:file => "/path/to/video.jpg")
|
11
|
+
@video.inspector
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__)) + "/../../../spec_helper.rb"
|
2
|
+
|
3
|
+
describe Seamus::VideoProcessor do
|
4
|
+
# include FakeFS::SpecHelpers
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
# File.new("/path/to/movie.mov", "w+") {|file| file << "MovieData"}
|
8
|
+
# file = File.open("/path/to/movie.mov", "r")
|
9
|
+
# @video = file.dup.extend Seamus::VideoProcessor
|
10
|
+
@video = mock(Seamus::File::Video, :path => "/path/to/movie.mov")
|
11
|
+
@video.extend Seamus::VideoProcessor
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "thumbnail creation" do
|
15
|
+
before(:each) do
|
16
|
+
@video.stub!(:start_time => 3333, :dimensions_string => "320x180")
|
17
|
+
end
|
18
|
+
|
19
|
+
it "uses Open3 to capture the stdout of ffmpeg in an IO instance" do
|
20
|
+
@stdin, @stdout, @stderr = mock(IO), mock(IO), mock(IO)
|
21
|
+
Open3.should_receive(:popen3).
|
22
|
+
with("ffmpeg -y -ss 3333 -i '/path/to/movie.mov' -t 00:00:01 -vcodec mjpeg -vframes 1 -an -f rawvideo -s 320x180 - ").
|
23
|
+
and_return([@stdin, @stdout, @stderr])
|
24
|
+
@video.thumbnail.should eql(@stdout)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "thumbnail capture start time" do
|
30
|
+
it "defaults to 10000/3333 - roughly 3 seconds in - when the #duration attribute is nil" do
|
31
|
+
@video.stub!(:duration => nil)
|
32
|
+
@video.send(:start_time).should eql(10000/3333)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "equals one third of the duration (in milliseconds)" do
|
36
|
+
@video.stub!(:duration => 20000)
|
37
|
+
@video.send(:start_time).should eql(20000/3333)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "aspect ratio" do
|
42
|
+
before(:each) do
|
43
|
+
@video.stub!(:width => 640, :height => 480)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "is a rational number calculated from #width divided by #height" do
|
47
|
+
@video.send(:aspect_ratio).should == Rational(4,3)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "dimensions string" do
|
52
|
+
before(:each) do
|
53
|
+
@video.stub!(:aspect_ratio => Rational(4,3))
|
54
|
+
end
|
55
|
+
|
56
|
+
it "is a formatted 'WxH' string, using the aspect ratio to calculate the height constant width" do
|
57
|
+
@video.send(:dimensions_string).should eql("320x240")
|
58
|
+
end
|
59
|
+
|
60
|
+
it "can be provided with an alternate width" do
|
61
|
+
@video.send(:dimensions_string, 240).should eql("240x180")
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
data/spec/seamus_spec.rb
CHANGED
@@ -1,16 +1,53 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
2
|
|
3
|
-
describe Seamus do
|
3
|
+
describe Seamus::Builder do
|
4
4
|
|
5
|
-
describe "
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
5
|
+
describe "md5 hashcode calculation" do
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
@movie = Seamus::Builder.new(Factories::mov.path)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "creates a md5 instance" do
|
12
|
+
Digest::MD5.should_receive(:file).
|
13
|
+
with(@movie.path).
|
14
|
+
and_return("Digest_MD5_instance")
|
15
|
+
@movie.md5.should eql("Digest_MD5_instance")
|
16
|
+
end
|
12
17
|
|
18
|
+
it "Base64 encodes a md5 hex string" do
|
19
|
+
@movie.stub!(:md5_digest).
|
20
|
+
and_return("md5-hexdigest-string")
|
21
|
+
|
22
|
+
Base64.should_receive(:encode64).
|
23
|
+
with("md5-hexdigest-string").
|
24
|
+
and_return("base64-encoded")
|
25
|
+
@movie.md5_base64_encoded.should eql("base64-encoded")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "on-demand classes" do
|
30
|
+
it "creates a ::Mov class for .mov files" do
|
31
|
+
@movie = Seamus::Builder.new(Factories::mov.path)
|
32
|
+
@movie.should be_an_instance_of(Seamus::File::Mov)
|
13
33
|
end
|
14
34
|
|
35
|
+
it "creates a ::Mp4 class for .mp4 files" do
|
36
|
+
@mpeg4 = Seamus::Builder.new(Factories::mp4.path)
|
37
|
+
@mpeg4.should be_an_instance_of(Seamus::File::Mp4)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "creates a ::Jpg class for .jpg files" do
|
41
|
+
@image = Seamus::Builder.new(Factories::jpg.path)
|
42
|
+
@image.should be_an_instance_of(Seamus::File::Jpg)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "includes Seamus::File::Video into Mov" do
|
46
|
+
Seamus::File::Mov.superclass.should be(Seamus::File::Video)
|
47
|
+
end
|
48
|
+
|
49
|
+
#etc
|
50
|
+
|
15
51
|
end
|
16
|
-
|
52
|
+
|
53
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
2
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
require 'rubygems'
|
3
4
|
require 'Seamus'
|
4
5
|
require 'spec'
|
5
6
|
require 'spec/autorun'
|
6
7
|
require 'fakefs/spec_helpers'
|
8
|
+
require 'factories'
|
7
9
|
|
8
10
|
Spec::Runner.configure do |config|
|
9
|
-
|
11
|
+
include Factories
|
10
12
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: seamus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Scott Burton
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-
|
12
|
+
date: 2010-02-10 00:00:00 -08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -35,22 +35,33 @@ files:
|
|
35
35
|
- lib/core/numeric.rb
|
36
36
|
- lib/mime_table.rb
|
37
37
|
- lib/seamus.rb
|
38
|
-
- lib/seamus/
|
38
|
+
- lib/seamus/builder.rb
|
39
|
+
- lib/seamus/file/application.rb
|
40
|
+
- lib/seamus/file/audio.rb
|
41
|
+
- lib/seamus/file/image.rb
|
42
|
+
- lib/seamus/file/text.rb
|
43
|
+
- lib/seamus/file/video.rb
|
39
44
|
- lib/seamus/inspector/application_inspector.rb
|
40
45
|
- lib/seamus/inspector/audio_inspector.rb
|
41
46
|
- lib/seamus/inspector/image_inspector.rb
|
42
47
|
- lib/seamus/inspector/text_inspector.rb
|
43
48
|
- lib/seamus/inspector/video_inspector.rb
|
44
|
-
- lib/seamus/processor.rb
|
45
49
|
- lib/seamus/processor/application_processor.rb
|
46
50
|
- lib/seamus/processor/audio_processor.rb
|
47
51
|
- lib/seamus/processor/image_processor.rb
|
48
52
|
- lib/seamus/processor/text_processor.rb
|
49
53
|
- lib/seamus/processor/video_processor.rb
|
54
|
+
- lib/seamus/standard_additions.rb
|
50
55
|
- seamus.gemspec
|
56
|
+
- spec/factories.rb
|
57
|
+
- spec/fixtures/test.jpg
|
58
|
+
- spec/fixtures/test.mov
|
59
|
+
- spec/fixtures/test.mp4
|
51
60
|
- spec/lib/core/numeric_spec.rb
|
52
61
|
- spec/lib/seamus/inspector/image_inspector_spec.rb
|
62
|
+
- spec/lib/seamus/inspector/video_inspector_spec.rb
|
53
63
|
- spec/lib/seamus/processor/image_processor_spec.rb
|
64
|
+
- spec/lib/seamus/processor/video_processor_spec.rb
|
54
65
|
- spec/seamus_spec.rb
|
55
66
|
- spec/spec_helper.rb
|
56
67
|
has_rdoc: true
|
@@ -82,8 +93,11 @@ signing_key:
|
|
82
93
|
specification_version: 3
|
83
94
|
summary: Seamus is not an Irish monk
|
84
95
|
test_files:
|
96
|
+
- spec/factories.rb
|
85
97
|
- spec/lib/core/numeric_spec.rb
|
86
98
|
- spec/lib/seamus/inspector/image_inspector_spec.rb
|
99
|
+
- spec/lib/seamus/inspector/video_inspector_spec.rb
|
87
100
|
- spec/lib/seamus/processor/image_processor_spec.rb
|
101
|
+
- spec/lib/seamus/processor/video_processor_spec.rb
|
88
102
|
- spec/seamus_spec.rb
|
89
103
|
- spec/spec_helper.rb
|
data/lib/seamus/inspector.rb
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
module Seamus
|
2
|
-
class Inspector
|
3
|
-
attr_reader :file
|
4
|
-
|
5
|
-
def initialize(file_path)
|
6
|
-
@file = File.open(file_path, "r")
|
7
|
-
end
|
8
|
-
|
9
|
-
def stats
|
10
|
-
raise RuntimeError, "method called on base class"
|
11
|
-
end
|
12
|
-
|
13
|
-
private
|
14
|
-
|
15
|
-
def file_stat
|
16
|
-
File::Stat.new(file.path)
|
17
|
-
end
|
18
|
-
|
19
|
-
end
|
20
|
-
end
|
data/lib/seamus/processor.rb
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
module Seamus
|
2
|
-
class Processor
|
3
|
-
attr_reader :file, :file_attributes
|
4
|
-
|
5
|
-
def initialize(file_path, file_attributes)
|
6
|
-
@file = File.open(file_path, "r")
|
7
|
-
@file_attributes = file_attributes
|
8
|
-
end
|
9
|
-
|
10
|
-
# nodoc
|
11
|
-
def thumb_tempfile
|
12
|
-
Tempfile.new("#{File.basename(@file.path, File.extname(@file.path))}_thumb.jpg")
|
13
|
-
end
|
14
|
-
|
15
|
-
end
|
16
|
-
end
|