popcap 0.7.2 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +0 -1
- data/Gemfile.lock +23 -32
- data/README.md +27 -24
- data/lib/pop_cap/audio_file.rb +13 -11
- data/lib/pop_cap/class_support.rb +73 -0
- data/lib/pop_cap/{commander.rb → ffmpeg/commander.rb} +8 -2
- data/lib/pop_cap/ffmpeg/converter.rb +57 -0
- data/lib/pop_cap/ffmpeg/ffmpeg.rb +45 -0
- data/lib/pop_cap/ffmpeg/tag_reader.rb +45 -0
- data/lib/pop_cap/ffmpeg/tag_writer.rb +44 -0
- data/lib/pop_cap/formatter.rb +62 -0
- data/lib/pop_cap/formatters/bit_rate.rb +5 -7
- data/lib/pop_cap/formatters/date.rb +12 -10
- data/lib/pop_cap/formatters/duration.rb +10 -12
- data/lib/pop_cap/formatters/filesize.rb +6 -8
- data/lib/pop_cap/tag/formatted_tag.rb +31 -0
- data/lib/pop_cap/tag/tag_hash.rb +55 -0
- data/lib/pop_cap/{tag_struct.rb → tag/tag_struct.rb} +2 -0
- data/lib/pop_cap/taggable.rb +50 -30
- data/lib/pop_cap/version.rb +1 -1
- data/popcap.gemspec +1 -2
- data/spec/integration/convert_audio_file_spec.rb +2 -2
- data/spec/integration/read_metatags_spec.rb +1 -1
- data/spec/integration/update_metatags_spec.rb +1 -1
- data/spec/lib/pop_cap/audio_file_spec.rb +3 -9
- data/spec/lib/pop_cap/class_support_spec.rb +59 -0
- data/spec/lib/pop_cap/{commander_spec.rb → ffmpeg/commander_spec.rb} +1 -1
- data/spec/lib/pop_cap/ffmpeg/converter_spec.rb +92 -0
- data/spec/lib/pop_cap/ffmpeg/ffmpeg_spec.rb +37 -0
- data/spec/lib/pop_cap/ffmpeg/tag_reader_spec.rb +67 -0
- data/spec/lib/pop_cap/ffmpeg/tag_writer_spec.rb +60 -0
- data/spec/lib/pop_cap/fileable_spec.rb +5 -5
- data/spec/lib/pop_cap/formatter_spec.rb +71 -0
- data/spec/lib/pop_cap/formatters/bit_rate_spec.rb +8 -0
- data/spec/lib/pop_cap/formatters/date_spec.rb +8 -0
- data/spec/lib/pop_cap/formatters/duration_spec.rb +8 -0
- data/spec/lib/pop_cap/formatters/filesize_spec.rb +8 -0
- data/spec/lib/pop_cap/tag/formatted_tag_spec.rb +29 -0
- data/spec/lib/pop_cap/tag/tag_hash_spec.rb +35 -0
- data/spec/lib/pop_cap/{tag_struct_spec.rb → tag/tag_struct_spec.rb} +5 -1
- data/spec/lib/pop_cap/taggable_spec.rb +24 -10
- data/spec/spec_helper.rb +0 -3
- data/spec/support/popcap_spec_helper.rb +50 -33
- metadata +40 -56
- data/lib/pop_cap/converter.rb +0 -40
- data/lib/pop_cap/ffmpeg.rb +0 -130
- data/lib/pop_cap/formatters.rb +0 -33
- data/lib/pop_cap/helper.rb +0 -53
- data/lib/pop_cap/tag_key.rb +0 -32
- data/lib/pop_cap/tag_line.rb +0 -36
- data/spec/lib/pop_cap/converter_spec.rb +0 -67
- data/spec/lib/pop_cap/ffmpeg_spec.rb +0 -96
- data/spec/lib/pop_cap/formatters_spec.rb +0 -36
- data/spec/lib/pop_cap/helper_spec.rb +0 -42
- data/spec/lib/pop_cap/tag_key_spec.rb +0 -48
- data/spec/lib/pop_cap/tag_line_spec.rb +0 -26
- /data/spec/{support → fixtures}/sample.flac +0 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 06018b8dc59e4e1f5e4a71499b2761a66263fcb3
|
4
|
+
data.tar.gz: 75e7e1d1d0d817d2d53bebdf7be0ffba63f2cd54
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fd92273467cde9d7ebc21c8717034bfa54ed26aeebb56b07374ca77917f248d14c0d76fb3e8e3c6fe70980754f60d06d4149a471e0cb77904c5583e39e2aa77c
|
7
|
+
data.tar.gz: 343bf09d55d5e5f913a5cfc4b0e2d53ee88b039b7229a0c0889ee725396251952c66b4d3fbd5e3307240bc81e51407c18751e2eef7c4eff66f4f074f5a907a33
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -2,42 +2,34 @@ GEM
|
|
2
2
|
remote: https://rubygems.org/
|
3
3
|
specs:
|
4
4
|
awesome_print (1.1.0)
|
5
|
-
cane (2.5.
|
5
|
+
cane (2.5.2)
|
6
6
|
parallel
|
7
|
-
coderay (1.0.
|
8
|
-
diff-lcs (1.1
|
7
|
+
coderay (1.0.9)
|
8
|
+
diff-lcs (1.2.1)
|
9
9
|
method_source (0.8.1)
|
10
|
-
|
11
|
-
|
12
|
-
pry (0.9.11.3)
|
10
|
+
parallel (0.6.2)
|
11
|
+
pry (0.9.12)
|
13
12
|
coderay (~> 1.0.5)
|
14
13
|
method_source (~> 0.8)
|
15
14
|
slop (~> 3.4)
|
16
|
-
reek (1.
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
rspec-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
ruby_parser (2.3.1)
|
35
|
-
sexp_processor (~> 3.0)
|
36
|
-
sexp_processor (3.2.0)
|
37
|
-
simplecov (0.7.1)
|
38
|
-
multi_json (~> 1.0)
|
39
|
-
simplecov-html (~> 0.7.1)
|
40
|
-
simplecov-html (0.7.1)
|
15
|
+
reek (1.3.1)
|
16
|
+
ruby2ruby (~> 2.0.2)
|
17
|
+
ruby_parser (~> 3.1.1)
|
18
|
+
sexp_processor
|
19
|
+
rspec (2.13.0)
|
20
|
+
rspec-core (~> 2.13.0)
|
21
|
+
rspec-expectations (~> 2.13.0)
|
22
|
+
rspec-mocks (~> 2.13.0)
|
23
|
+
rspec-core (2.13.0)
|
24
|
+
rspec-expectations (2.13.0)
|
25
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
26
|
+
rspec-mocks (2.13.0)
|
27
|
+
ruby2ruby (2.0.3)
|
28
|
+
ruby_parser (~> 3.1)
|
29
|
+
sexp_processor (~> 4.0)
|
30
|
+
ruby_parser (3.1.1)
|
31
|
+
sexp_processor (~> 4.1)
|
32
|
+
sexp_processor (4.1.5)
|
41
33
|
slop (3.4.3)
|
42
34
|
|
43
35
|
PLATFORMS
|
@@ -49,4 +41,3 @@ DEPENDENCIES
|
|
49
41
|
pry
|
50
42
|
reek
|
51
43
|
rspec
|
52
|
-
simplecov
|
data/README.md
CHANGED
@@ -22,25 +22,26 @@ Read the metadata tags from an audio file.
|
|
22
22
|
```
|
23
23
|
audio_file = PopCap::AudioFile.new('sample.flac')
|
24
24
|
|
25
|
-
audio_file.raw_tags => Returns
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
audio_file.
|
25
|
+
audio_file.raw_tags => Returns JSON for the raw output from running ffprobe -show_format.
|
26
|
+
|
27
|
+
audio_file.unformatted => Returns a Ruby hash after sanitizing the raw output of #raw_tags.
|
28
|
+
{ filename: 'sample.flac',
|
29
|
+
format_name: 'flac',
|
30
|
+
format_long_name: 'raw FLAC',
|
31
|
+
nb_streams: '1',
|
32
|
+
duration: '1.000000',
|
33
|
+
filesize: '18291',
|
34
|
+
bit_rate: '146328',
|
35
|
+
start_time: 'N/A',
|
36
|
+
genre: 'Sample Genre',
|
37
|
+
track: '01',
|
38
|
+
album: 'Sample Album',
|
39
|
+
date: '2012',
|
40
|
+
title: 'Sample Title',
|
41
|
+
artist: 'Sample Artist' }
|
42
|
+
|
43
|
+
audio_file.formatted => Returns a Ruby hash after sanitizing the raw output of #raw_tags. It also applies internal formatters to make fields such as duration, bit_rate, filesize, & date human readable.
|
44
|
+
|
44
45
|
{ filename: 'sample.flac',
|
45
46
|
format_name: 'flac',
|
46
47
|
format_long_name: 'raw FLAC',
|
@@ -56,13 +57,14 @@ audio_file.to_hash => Returns a Ruby hash after sanitizing the raw output of #ra
|
|
56
57
|
title: 'Sample Title',
|
57
58
|
artist: 'Sample Artist' }
|
58
59
|
|
59
|
-
audio_file.tags => Returns a tag structure
|
60
|
+
audio_file.tags => Returns a tag structure using the #formatted values.
|
61
|
+
|
60
62
|
.album => 'Sample Album'
|
61
63
|
.artist => 'Sample Artist'
|
62
64
|
.bit_rate => '146 kb/s'
|
63
65
|
.date => 2012
|
64
66
|
.duration => '1'
|
65
|
-
.filename => 'spec/
|
67
|
+
.filename => 'spec/fixtures/sample.flac'
|
66
68
|
.filesize => '17.9K'
|
67
69
|
.format_long_name => 'raw FLAC'
|
68
70
|
.format_name => 'flac'
|
@@ -72,7 +74,7 @@ audio_file.tags => Returns a tag structure after applying formatters #to_hash.
|
|
72
74
|
.title => 'Sample Title'
|
73
75
|
.track => '01'
|
74
76
|
|
75
|
-
audio_file.reload! => Reload an instance of itself, useful when updating tags.
|
77
|
+
audio_file.reload! => Reload an instance of itself, useful when updating tags. This behavior is built in, but will need to be called manually in certain situations (such as moving a file on the file system, deleting a file, etc.)
|
76
78
|
```
|
77
79
|
|
78
80
|
Update Tags
|
@@ -82,9 +84,9 @@ This will update the metadata tags for an audio file. It will also dynamically
|
|
82
84
|
|
83
85
|
```
|
84
86
|
audio_file = PopCap::AudioFile.new('sample.flac')
|
85
|
-
audio_file.update_tags(
|
87
|
+
audio_file.update_tags(artist: 'David Bowie'})
|
86
88
|
|
87
|
-
audio_file.update_tags(
|
89
|
+
audio_file.update_tags(fancy_new_tag: 'Custom Tag Input')
|
88
90
|
```
|
89
91
|
|
90
92
|
Convert
|
@@ -133,5 +135,6 @@ audio_file.tmppath # => returns the temporary path, e.g. '/tmp/sample.flac'
|
|
133
135
|
|
134
136
|
Dependencies
|
135
137
|
------------
|
138
|
+
Ruby 2.0+
|
136
139
|
|
137
140
|
[FFmpeg](http://ffmpeg.org)
|
data/lib/pop_cap/audio_file.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
-
require 'pop_cap/ffmpeg'
|
1
|
+
require 'pop_cap/ffmpeg/converter'
|
2
|
+
require 'pop_cap/ffmpeg/tag_reader'
|
3
|
+
require 'pop_cap/ffmpeg/tag_writer'
|
4
|
+
require 'pop_cap/fileable'
|
2
5
|
require 'pop_cap/taggable'
|
3
6
|
|
4
7
|
module PopCap
|
@@ -9,7 +12,7 @@ module PopCap
|
|
9
12
|
#
|
10
13
|
# Examples
|
11
14
|
#
|
12
|
-
# filepath = 'spec/
|
15
|
+
# filepath = 'spec/fixtures/sample.flac'
|
13
16
|
# af = AudioFile.new(filepath)
|
14
17
|
#
|
15
18
|
#
|
@@ -23,10 +26,9 @@ module PopCap
|
|
23
26
|
#
|
24
27
|
# filepath - Requires a valid filepath to a file on the local filesystem.
|
25
28
|
#
|
26
|
-
def initialize(filepath
|
29
|
+
def initialize(filepath)
|
27
30
|
raise(FileNotFound, filepath) unless File.exists?(filepath)
|
28
31
|
@filepath = File.realpath(filepath)
|
29
|
-
@tag_util = tag_util
|
30
32
|
end
|
31
33
|
|
32
34
|
# Public: convert
|
@@ -37,23 +39,23 @@ module PopCap
|
|
37
39
|
# bitrate - An optional bitrate for mp3s.
|
38
40
|
#
|
39
41
|
# Examples
|
40
|
-
# audio_file = AudioFile.new('spec/
|
42
|
+
# audio_file = AudioFile.new('spec/fixtures/sample.flac')
|
41
43
|
# audio_file.convert('mp3', 128)
|
42
|
-
# # => 'spec/
|
44
|
+
# # => 'spec/fixtures/sample.mp3'
|
43
45
|
#
|
44
46
|
def convert(format, bitrate=192)
|
45
|
-
|
47
|
+
Converter.convert(filepath, {format: format, bitrate: bitrate})
|
46
48
|
end
|
47
49
|
|
48
50
|
# Public: raw_tags
|
49
51
|
# This method returns the raw_tags from FFmpeg.
|
50
52
|
#
|
51
53
|
# Examples
|
52
|
-
# audio_file = AudioFile.new('spec/
|
54
|
+
# audio_file = AudioFile.new('spec/fixtures/sample.flac')
|
53
55
|
# audio_file.raw_tags
|
54
56
|
# # =>
|
55
57
|
# [FORMAT]
|
56
|
-
# filename=spec/
|
58
|
+
# filename=spec/fixtures/sample.flac
|
57
59
|
# nb_streams=1
|
58
60
|
# format_name=flac
|
59
61
|
# format_long_name=raw FLAC
|
@@ -70,7 +72,7 @@ module PopCap
|
|
70
72
|
# [/FORMAT]
|
71
73
|
#
|
72
74
|
def raw_tags
|
73
|
-
@raw ||=
|
75
|
+
@raw ||= TagReader.read(filepath)
|
74
76
|
end
|
75
77
|
|
76
78
|
# Public: This method reloads the current instance.
|
@@ -93,7 +95,7 @@ module PopCap
|
|
93
95
|
# audio_file.update_tags({artist: 'New Artist', album: 'New Album'})
|
94
96
|
#
|
95
97
|
def update_tags(updates)
|
96
|
-
|
98
|
+
TagWriter.write(filepath, updates)
|
97
99
|
self.reload!
|
98
100
|
end
|
99
101
|
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module PopCap
|
2
|
+
# Public: This class adds methods to construct a class.
|
3
|
+
#
|
4
|
+
# name - This is the name of the class.
|
5
|
+
#
|
6
|
+
# Examples
|
7
|
+
# ClassSupport.new('array')
|
8
|
+
# ClassSupport.new('active_support')
|
9
|
+
# ClassSupport.new('active_record/base')
|
10
|
+
#
|
11
|
+
class ClassSupport
|
12
|
+
attr_reader :name
|
13
|
+
|
14
|
+
def initialize(name)
|
15
|
+
@name = name.to_s
|
16
|
+
end
|
17
|
+
|
18
|
+
# Public: This method camel cases a string or symbol.
|
19
|
+
#
|
20
|
+
# Examples
|
21
|
+
# support = ClassSupport.new('active_support')
|
22
|
+
# support.camelize
|
23
|
+
# # => 'ActiveSupport'
|
24
|
+
#
|
25
|
+
def camelize
|
26
|
+
name.split('_').map { |word| word.capitalize }.join
|
27
|
+
end
|
28
|
+
|
29
|
+
# Public: This namespaces a string by converting a filepath
|
30
|
+
# to a namespaced constant.
|
31
|
+
#
|
32
|
+
# Examples
|
33
|
+
# support = ClassSupport.new('active_record/base')
|
34
|
+
# support.namespace
|
35
|
+
# # => 'ActiveRecord::Base'
|
36
|
+
#
|
37
|
+
def namespace
|
38
|
+
camelize.split('/').map do |word|
|
39
|
+
_,head,tail = word.partition(%r(^[a-zA-Z]))
|
40
|
+
head.upcase + tail
|
41
|
+
end.join('::')
|
42
|
+
end
|
43
|
+
|
44
|
+
# Public: This converts a string into a constant.
|
45
|
+
#
|
46
|
+
# Examples
|
47
|
+
# support = ClassSupport.new('active_record/base')
|
48
|
+
# support.constantize
|
49
|
+
# # => ActiveSupport::Base
|
50
|
+
#
|
51
|
+
def constantize
|
52
|
+
Object.module_eval(namespace)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Public: This converts a string into a symbol.
|
56
|
+
# It will handle CamelCased strings.
|
57
|
+
#
|
58
|
+
# option - It takes an option to demodulize the string.
|
59
|
+
#
|
60
|
+
# Examples
|
61
|
+
# support = ClassSupport.new('ActiveSupport')
|
62
|
+
# support.symbolize
|
63
|
+
# # => :active_support
|
64
|
+
#
|
65
|
+
# support = ClassSupport.new('ActiveSupport::CamelCase')
|
66
|
+
# support.symbolize(:demodulize)
|
67
|
+
# # => :camel_case
|
68
|
+
#
|
69
|
+
def symbolize
|
70
|
+
name.split('::').last.split(%r((?=[A-Z]))).join('_').downcase.to_sym
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -10,13 +10,15 @@ module PopCap
|
|
10
10
|
# # => <directory contents>
|
11
11
|
#
|
12
12
|
class Commander
|
13
|
+
attr_reader :command
|
13
14
|
# Public: Initialize
|
14
15
|
#
|
15
16
|
# args - Arguments should be escaped with an interpolated literal.
|
16
17
|
#
|
17
18
|
def initialize(*args)
|
18
19
|
raise(ArgumentError, error_message) if args.empty?
|
19
|
-
@
|
20
|
+
@args = args
|
21
|
+
@command = shell_escaped_arguments
|
20
22
|
@executed = []
|
21
23
|
end
|
22
24
|
|
@@ -25,7 +27,7 @@ module PopCap
|
|
25
27
|
# to chain methods.
|
26
28
|
#
|
27
29
|
def execute
|
28
|
-
@executed = Open3.capture3(
|
30
|
+
@executed = Open3.capture3(*command)
|
29
31
|
self
|
30
32
|
end
|
31
33
|
|
@@ -57,6 +59,10 @@ module PopCap
|
|
57
59
|
end
|
58
60
|
|
59
61
|
private
|
62
|
+
def shell_escaped_arguments
|
63
|
+
@args.inject(%W{}) { |command, arg| command << arg }
|
64
|
+
end
|
65
|
+
|
60
66
|
def error_message
|
61
67
|
'Cannot run command with no args.'
|
62
68
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'pop_cap/ffmpeg/ffmpeg'
|
2
|
+
|
3
|
+
module PopCap
|
4
|
+
# Public: This class converts an audio file to the specified
|
5
|
+
# output format.
|
6
|
+
#
|
7
|
+
class Converter < FFmpeg
|
8
|
+
# Public: This method will execute the conversion.
|
9
|
+
#
|
10
|
+
def convert
|
11
|
+
unless commander.new(*command).execute.success?
|
12
|
+
raise(FFmpegError, error_message('converting'))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Public: A convenience class method which wraps the instance
|
17
|
+
# constructor.
|
18
|
+
#
|
19
|
+
def self.convert(filepath, options={})
|
20
|
+
new(filepath, options).convert
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def format
|
25
|
+
options[:format].downcase.to_s
|
26
|
+
end
|
27
|
+
|
28
|
+
def bitrate
|
29
|
+
options[:bitrate] || 192
|
30
|
+
end
|
31
|
+
|
32
|
+
def command
|
33
|
+
input_path + strict_mode + bitrate_options + output_path
|
34
|
+
end
|
35
|
+
|
36
|
+
def bitrate_options
|
37
|
+
%W{-ab #{bitrate}k}
|
38
|
+
end
|
39
|
+
|
40
|
+
def input_path
|
41
|
+
%W{ffmpeg -i #{filepath}}
|
42
|
+
end
|
43
|
+
|
44
|
+
def output_path
|
45
|
+
%W{#{filepath.sub(%r([^.]+\z),format)}}
|
46
|
+
end
|
47
|
+
|
48
|
+
def strict_mode
|
49
|
+
return %W{} unless use_strict_mode?
|
50
|
+
%W{-strict -2}
|
51
|
+
end
|
52
|
+
|
53
|
+
def use_strict_mode?
|
54
|
+
format == 'm4a'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'pop_cap/ffmpeg/commander'
|
2
|
+
|
3
|
+
module PopCap
|
4
|
+
FFmpegError = Class.new(StandardError)
|
5
|
+
MissingDependency = Class.new(Errno::ENOENT)
|
6
|
+
|
7
|
+
# Public: This is a wrapper for the FFmpeg C library.
|
8
|
+
#
|
9
|
+
# Examples
|
10
|
+
#
|
11
|
+
# file = 'spec/fixtures/sample.flac'
|
12
|
+
# ffmpeg = FFmpeg.new(filepath)
|
13
|
+
#
|
14
|
+
class FFmpeg
|
15
|
+
attr_accessor :commander, :filepath, :options
|
16
|
+
|
17
|
+
# Public: initialize
|
18
|
+
#
|
19
|
+
# filepath - Requires a valid filepath to a file on the local filesystem.
|
20
|
+
# commander - Defaults to Commander for executing system commands.
|
21
|
+
#
|
22
|
+
def initialize(filepath, options={})
|
23
|
+
check_for_ffmpeg_install
|
24
|
+
@filepath, @options = filepath, options
|
25
|
+
@commander = options[:commander] || Commander
|
26
|
+
end
|
27
|
+
|
28
|
+
# Public: error_message
|
29
|
+
#
|
30
|
+
# message - Provide the message to return.
|
31
|
+
#
|
32
|
+
def error_message(message)
|
33
|
+
"Error #{message} #{filepath}."
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
def check_for_ffmpeg_install
|
38
|
+
begin
|
39
|
+
Open3.capture3('ffmpeg')
|
40
|
+
rescue Errno::ENOENT
|
41
|
+
raise MissingDependency, 'FFmpeg is not installed.'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'pop_cap/ffmpeg/ffmpeg'
|
3
|
+
|
4
|
+
module PopCap
|
5
|
+
# Public: This class wraps FFprobe to read tags from a specified file.
|
6
|
+
#
|
7
|
+
class TagReader < FFmpeg
|
8
|
+
# Public: This method returns the results of FFprobe -show_format.
|
9
|
+
#
|
10
|
+
def read
|
11
|
+
JSON.load(encode(output)).to_json
|
12
|
+
end
|
13
|
+
|
14
|
+
# Public: A convenience class method which wraps the instance
|
15
|
+
# constructor.
|
16
|
+
#
|
17
|
+
def self.read(filepath, options={})
|
18
|
+
new(filepath, options).read
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def command
|
23
|
+
%W{ffprobe -show_format -print_format json} + %W{#{filepath}}
|
24
|
+
end
|
25
|
+
|
26
|
+
def output
|
27
|
+
executed = commander.new(*command).execute
|
28
|
+
raise(FFmpegError, error_message('reading')) unless executed.success?
|
29
|
+
executed.stdout
|
30
|
+
end
|
31
|
+
|
32
|
+
def encode(string)
|
33
|
+
return string if string.valid_encoding?
|
34
|
+
remove_invalid_bytes(string)
|
35
|
+
end
|
36
|
+
|
37
|
+
def remove_invalid_bytes(string)
|
38
|
+
@string = string
|
39
|
+
original_encoding = @string.encoding.name
|
40
|
+
@string.encode!('UTF-16', original_encoding, undef:
|
41
|
+
:replace, invalid: :replace)
|
42
|
+
@string.encode!('UTF-8')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'pop_cap/ffmpeg/ffmpeg'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module PopCap
|
5
|
+
# Public: This class wraps FFmpeg to write tags for a specified file.
|
6
|
+
#
|
7
|
+
class TagWriter < FFmpeg
|
8
|
+
# Public: This methods writes the tags to a temporary file, if
|
9
|
+
# successful, it moves the temporary file over the original. This
|
10
|
+
# is done to prevent corrupting the original file.
|
11
|
+
#
|
12
|
+
def write
|
13
|
+
unless commander.new(*command).execute.success?
|
14
|
+
raise(FFmpegError, error_message('writing'))
|
15
|
+
end
|
16
|
+
FileUtils.move(tmppath, filepath)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Public: A convenience class method to wrap #new & #write.
|
20
|
+
#
|
21
|
+
def self.write(filepath, options = {})
|
22
|
+
new(filepath, options).write
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def tmppath
|
27
|
+
'/tmp/' + File.basename(filepath)
|
28
|
+
end
|
29
|
+
|
30
|
+
def command
|
31
|
+
%W{ffmpeg -i #{filepath}} + write_options + %W{#{tmppath}}
|
32
|
+
end
|
33
|
+
|
34
|
+
def write_options
|
35
|
+
clean_tags.inject(%W{}) do |tags,tag|
|
36
|
+
tags << '-metadata' << tag.join('=')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def clean_tags
|
41
|
+
@cleaned ||= options.reject { |key,_| key == :commander }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module PopCap
|
2
|
+
module Formatters
|
3
|
+
# Public: This is a super class for all formatter classes.
|
4
|
+
# It establishes the default behavior of formatter classes.
|
5
|
+
#
|
6
|
+
class Formatter
|
7
|
+
attr_reader :value, :options
|
8
|
+
|
9
|
+
# Public: This class is constructed with a value & options hash.
|
10
|
+
#
|
11
|
+
# value - The value to be formatted.
|
12
|
+
# options - An options hash.
|
13
|
+
#
|
14
|
+
def initialize(value, options={})
|
15
|
+
@value, @options = value, options
|
16
|
+
end
|
17
|
+
|
18
|
+
# Public: This method contains the handles the formating.
|
19
|
+
#
|
20
|
+
def format
|
21
|
+
end
|
22
|
+
|
23
|
+
# Public: A class method for convenience calling of #format.
|
24
|
+
#
|
25
|
+
def self.format(value, options={})
|
26
|
+
new(value, options).format
|
27
|
+
end
|
28
|
+
|
29
|
+
# Public: This returns an array of all subclasses.
|
30
|
+
#
|
31
|
+
def self.subclasses
|
32
|
+
require_all_formatters
|
33
|
+
|
34
|
+
ObjectSpace.each_object(Class).select do |klass|
|
35
|
+
klass if is_a_subclass?(klass)
|
36
|
+
end.uniq.compact
|
37
|
+
end
|
38
|
+
|
39
|
+
# Public: This returns an array of all subclasses "demodulized."
|
40
|
+
#
|
41
|
+
def self.subclasses_demodulized
|
42
|
+
subclasses.map { |klass| demodulize(klass) }
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
def self.require_all_formatters
|
47
|
+
Dir["#{File.dirname(__FILE__)}/formatters/*.rb"].each do |file|
|
48
|
+
require File.realpath(file)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.demodulize(klass)
|
53
|
+
klass.to_s.split('::').last
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.is_a_subclass?(klass)
|
57
|
+
superclass = klass.superclass
|
58
|
+
superclass && superclass.name == self.name
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'pop_cap/formatter'
|
2
|
+
|
1
3
|
module PopCap
|
2
4
|
module Formatters
|
3
5
|
# Public: This is a formatter for the bit_rate tag. It is used
|
@@ -5,11 +7,7 @@ module PopCap
|
|
5
7
|
#
|
6
8
|
# bitrate - The bitrate can be sent as a string or integer.
|
7
9
|
#
|
8
|
-
class BitRate
|
9
|
-
def initialize(bitrate)
|
10
|
-
@bitrate = bitrate
|
11
|
-
end
|
12
|
-
|
10
|
+
class BitRate < Formatter
|
13
11
|
# Public: This method returns a bitrate represented in kilobytes.
|
14
12
|
#
|
15
13
|
# It returns nil for anything that is not a number greater than
|
@@ -21,8 +19,8 @@ module PopCap
|
|
21
19
|
# # => '128 kb/s'
|
22
20
|
#
|
23
21
|
def format
|
24
|
-
return unless @
|
25
|
-
@
|
22
|
+
return unless @value.to_i > 0
|
23
|
+
@value.to_s[0..-4] + ' kb/s'
|
26
24
|
end
|
27
25
|
end
|
28
26
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'pop_cap/formatter'
|
2
|
+
|
1
3
|
module PopCap
|
2
4
|
module Formatters
|
3
5
|
# Public: This is a formatter for the date tag. It is used
|
@@ -8,15 +10,7 @@ module PopCap
|
|
8
10
|
# The start_date defaults to 1800, end_date defaults
|
9
11
|
# to 2100.
|
10
12
|
#
|
11
|
-
class Date
|
12
|
-
attr_reader :start_date, :end_date
|
13
|
-
|
14
|
-
def initialize(date, options={})
|
15
|
-
@date = date.to_s
|
16
|
-
@start_date = options[:start_date] || 1800
|
17
|
-
@end_date = options[:end_date] || 2100
|
18
|
-
end
|
19
|
-
|
13
|
+
class Date < Formatter
|
20
14
|
# Public: This method returns a year if it is matched.
|
21
15
|
#
|
22
16
|
# Examples
|
@@ -29,9 +23,17 @@ module PopCap
|
|
29
23
|
@match[0].to_i
|
30
24
|
end
|
31
25
|
|
26
|
+
def start_date
|
27
|
+
options[:start_date] || 1800
|
28
|
+
end
|
29
|
+
|
30
|
+
def end_date
|
31
|
+
options[:end_date] || 2100
|
32
|
+
end
|
33
|
+
|
32
34
|
private
|
33
35
|
def date_match
|
34
|
-
@match ||=
|
36
|
+
@match ||= value.to_s.match(/\b\d{4}\b/)
|
35
37
|
end
|
36
38
|
|
37
39
|
def within_date_range?
|