seamus 0.2.4 → 0.3.0
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/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
|