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
@@ -1,13 +1,11 @@
|
|
1
|
+
require 'pop_cap/formatter'
|
2
|
+
|
1
3
|
module PopCap
|
2
4
|
module Formatters
|
3
5
|
# Public: This will format a duration tag as strftime.
|
4
6
|
#
|
5
7
|
# time - Provide a string, float, or integer.
|
6
|
-
class Duration
|
7
|
-
def initialize(time)
|
8
|
-
@time = time
|
9
|
-
end
|
10
|
-
|
8
|
+
class Duration < Formatter
|
11
9
|
# Public: This will format a duration tag as strftime.
|
12
10
|
# It will raise a warning if the time is greater than 24 hours.
|
13
11
|
# Leading zeroes & colons are removed.
|
@@ -18,22 +16,22 @@ module PopCap
|
|
18
16
|
# # => '7:00'
|
19
17
|
#
|
20
18
|
def format
|
21
|
-
return unless
|
19
|
+
return unless value.to_i > 0
|
22
20
|
return warning_message if over_twenty_four_hours?
|
23
|
-
|
21
|
+
human_readable_time
|
24
22
|
end
|
25
23
|
|
26
24
|
private
|
27
|
-
def
|
28
|
-
|
25
|
+
def human_readable_time
|
26
|
+
to_strftime.sub(/^(0+|:)+/,'')
|
29
27
|
end
|
30
28
|
|
31
|
-
def
|
32
|
-
|
29
|
+
def to_strftime
|
30
|
+
Time.at(value.to_f).gmtime.strftime('%H:%M:%S')
|
33
31
|
end
|
34
32
|
|
35
33
|
def over_twenty_four_hours?
|
36
|
-
|
34
|
+
value.to_i > 86399
|
37
35
|
end
|
38
36
|
|
39
37
|
def warning_message
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'pop_cap/formatter'
|
2
|
+
|
1
3
|
module PopCap
|
2
4
|
module Formatters
|
3
5
|
# Public: This class formats a filesize as human readable,
|
@@ -5,14 +7,10 @@ module PopCap
|
|
5
7
|
#
|
6
8
|
# filesize - Provide a filesize as string, integer, or float.
|
7
9
|
#
|
8
|
-
class Filesize
|
10
|
+
class Filesize < Formatter
|
9
11
|
::BASE = 1024
|
10
12
|
::UNITS = %W{B K M G T}
|
11
13
|
|
12
|
-
def initialize(filesize)
|
13
|
-
@filesize = filesize
|
14
|
-
end
|
15
|
-
|
16
14
|
# Public: This method will format the filesize.
|
17
15
|
# It raises a warning message if size is greater than 999 terabytes.
|
18
16
|
#
|
@@ -22,7 +20,7 @@ module PopCap
|
|
22
20
|
# # => '11.8M'
|
23
21
|
#
|
24
22
|
def format
|
25
|
-
return if
|
23
|
+
return if value.to_i == 0
|
26
24
|
return warning_message if too_large?
|
27
25
|
converted_filesize.to_s + measurement_character
|
28
26
|
end
|
@@ -42,7 +40,7 @@ module PopCap
|
|
42
40
|
end
|
43
41
|
|
44
42
|
def float
|
45
|
-
Float(
|
43
|
+
Float(value)
|
46
44
|
end
|
47
45
|
|
48
46
|
def is_zero_decimal?
|
@@ -58,7 +56,7 @@ module PopCap
|
|
58
56
|
end
|
59
57
|
|
60
58
|
def too_large?
|
61
|
-
|
59
|
+
value.to_i > 1099400000000000
|
62
60
|
end
|
63
61
|
|
64
62
|
def warning_message
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module PopCap
|
2
|
+
# Public: This class will apply a provided Formatter to
|
3
|
+
# the supplied value.
|
4
|
+
#
|
5
|
+
class FormattedTag
|
6
|
+
attr_reader :formatter, :value
|
7
|
+
|
8
|
+
# Public: To construct an instance, provide a Formatter
|
9
|
+
# and the value to format.
|
10
|
+
#
|
11
|
+
# formatter - A class of type Formatters::Formatter.
|
12
|
+
# value - The value to format.
|
13
|
+
#
|
14
|
+
def initialize(formatter, value)
|
15
|
+
@formatter, @value = formatter, value
|
16
|
+
end
|
17
|
+
|
18
|
+
# Public: This returns the formatted value.
|
19
|
+
#
|
20
|
+
def format
|
21
|
+
formatter.format(value)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Public: A class level method that initializes the class
|
25
|
+
# and formats the value.
|
26
|
+
#
|
27
|
+
def self.format(formatter, value)
|
28
|
+
new(formatter, value).format
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module PopCap
|
4
|
+
# Public: This class formats the JSON output of TagReader.
|
5
|
+
#
|
6
|
+
class TagHash
|
7
|
+
attr_reader :json
|
8
|
+
|
9
|
+
def initialize(json)
|
10
|
+
@json = json
|
11
|
+
end
|
12
|
+
|
13
|
+
# Public: This method returns a Ruby hash.
|
14
|
+
# The keys have been converted to symbols.
|
15
|
+
#
|
16
|
+
def hash
|
17
|
+
renamed_hash.reject { |key,_| key == :size }
|
18
|
+
end
|
19
|
+
|
20
|
+
# Public: This method wraps #new & #hash.
|
21
|
+
#
|
22
|
+
def self.hash(json)
|
23
|
+
new(json).hash
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
def renamed_hash
|
28
|
+
symbolized_hash.merge({filesize: size_element})
|
29
|
+
end
|
30
|
+
|
31
|
+
def symbolized_hash
|
32
|
+
Hash[merged_hashes.map { |key,val| [key.downcase.to_sym, val] }]
|
33
|
+
end
|
34
|
+
|
35
|
+
def merged_hashes
|
36
|
+
format_hash.merge(tags_hash)
|
37
|
+
end
|
38
|
+
|
39
|
+
def size_element
|
40
|
+
format_hash['size']
|
41
|
+
end
|
42
|
+
|
43
|
+
def format_hash
|
44
|
+
@format ||= parsed_json.reject { |key,_| key == 'tags' }
|
45
|
+
end
|
46
|
+
|
47
|
+
def tags_hash
|
48
|
+
parsed_json['tags']
|
49
|
+
end
|
50
|
+
|
51
|
+
def parsed_json
|
52
|
+
@parsed ||= JSON.parse(json)['format']
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/pop_cap/taggable.rb
CHANGED
@@ -1,21 +1,48 @@
|
|
1
|
-
require 'pop_cap/
|
2
|
-
require 'pop_cap/
|
3
|
-
require 'pop_cap/
|
4
|
-
require 'pop_cap/
|
1
|
+
require 'pop_cap/class_support'
|
2
|
+
require 'pop_cap/formatter'
|
3
|
+
require 'pop_cap/tag/formatted_tag'
|
4
|
+
require 'pop_cap/tag/tag_hash'
|
5
|
+
require 'pop_cap/tag/tag_struct'
|
5
6
|
|
6
7
|
module PopCap
|
7
|
-
#
|
8
|
+
# Public: This module is included in anything with a #raw_tags
|
8
9
|
# attribute. It is used to parse and build tags from FFmpeg raw output.
|
9
10
|
#
|
10
11
|
module Taggable
|
11
|
-
|
12
|
-
|
13
|
-
# Internal: This method reloads memoized tags.
|
12
|
+
# Public: This method reloads memoized tags.
|
13
|
+
#
|
14
14
|
def reload!
|
15
|
-
@
|
15
|
+
@unformatted_hash, @formatted_hash, @tag_struct = nil, nil, nil
|
16
|
+
end
|
17
|
+
|
18
|
+
# Public: This method builds an unformatted hash of tags.
|
19
|
+
#
|
20
|
+
# Examples
|
21
|
+
# class SomeClass
|
22
|
+
# def raw_tags
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# klass = SomeClass.new
|
27
|
+
# klass.unformatted
|
28
|
+
# # =>
|
29
|
+
# { filename: 'spec/fixtures/sample.flac',
|
30
|
+
# format_name: 'flac',
|
31
|
+
# duration: '1.000000',
|
32
|
+
# filesize: '18291',
|
33
|
+
# bit_rate: '146328',
|
34
|
+
# genre: 'Sample Genre',
|
35
|
+
# track: '01',
|
36
|
+
# album: 'Sample Album',
|
37
|
+
# date: '2012',
|
38
|
+
# title: 'Sample Title',
|
39
|
+
# artist: 'Sample Artist' }
|
40
|
+
#
|
41
|
+
def unformatted(tag_hash: TagHash)
|
42
|
+
@unformatted_hash ||= tag_hash.hash(self.raw_tags)
|
16
43
|
end
|
17
44
|
|
18
|
-
#
|
45
|
+
# Public: This method builds an unformatted hash of tags.
|
19
46
|
#
|
20
47
|
# Examples
|
21
48
|
# class SomeClass
|
@@ -24,9 +51,9 @@ module PopCap
|
|
24
51
|
# end
|
25
52
|
#
|
26
53
|
# klass = SomeClass.new
|
27
|
-
# klass.
|
54
|
+
# klass.unformatted
|
28
55
|
# # =>
|
29
|
-
# { filename: 'spec/
|
56
|
+
# { filename: 'spec/fixtures/sample.flac',
|
30
57
|
# format_name: 'flac',
|
31
58
|
# duration: '1.000000',
|
32
59
|
# filesize: '18291',
|
@@ -38,8 +65,8 @@ module PopCap
|
|
38
65
|
# title: 'Sample Title',
|
39
66
|
# artist: 'Sample Artist' }
|
40
67
|
#
|
41
|
-
def
|
42
|
-
@
|
68
|
+
def formatted
|
69
|
+
@formatted_hash ||= unformatted.merge(formatted_tags)
|
43
70
|
end
|
44
71
|
|
45
72
|
# Public: This method builds an tag structure from #to_hash. Also,
|
@@ -60,7 +87,7 @@ module PopCap
|
|
60
87
|
# .bit_rate => '146 kb/s'
|
61
88
|
# .date => 2012
|
62
89
|
# .duration => '1'
|
63
|
-
# .filename => 'spec/
|
90
|
+
# .filename => 'spec/fixtures/sample.flac'
|
64
91
|
# .filesize => '17.9K'
|
65
92
|
# .format_long_name => 'raw FLAC'
|
66
93
|
# .format_name => 'flac'
|
@@ -71,30 +98,23 @@ module PopCap
|
|
71
98
|
# .track => '01'
|
72
99
|
#
|
73
100
|
def tags
|
74
|
-
@
|
101
|
+
@tag_struct ||= build_tag_struct(formatted)
|
75
102
|
end
|
76
103
|
|
77
104
|
private
|
78
|
-
def lines
|
79
|
-
self.raw_tags.split("\n")
|
80
|
-
end
|
81
|
-
|
82
105
|
def build_tag_struct(hash)
|
83
106
|
TagStruct.new(hash)
|
84
107
|
end
|
85
108
|
|
86
|
-
def
|
87
|
-
|
88
|
-
|
109
|
+
def formatted_tags(support: ClassSupport)
|
110
|
+
Formatters::Formatter.subclasses.inject({}) do |formatted, formatter|
|
111
|
+
attribute = support.new(formatter).symbolize
|
112
|
+
formatted.merge({attribute => format(formatter, attribute)})
|
113
|
+
end
|
89
114
|
end
|
90
115
|
|
91
|
-
def
|
92
|
-
|
93
|
-
key, value = formatter
|
94
|
-
helper = Helper.new(value)
|
95
|
-
klass = helper.constantize
|
96
|
-
formatted.merge({key => klass.new(lined_hash[key]).format})
|
97
|
-
end
|
116
|
+
def format(formatter, attribute, tag: FormattedTag)
|
117
|
+
tag.format(formatter, unformatted[attribute])
|
98
118
|
end
|
99
119
|
end
|
100
120
|
end
|
data/lib/pop_cap/version.rb
CHANGED
data/popcap.gemspec
CHANGED
@@ -13,12 +13,11 @@ Gem::Specification.new do |s|
|
|
13
13
|
s.summary = %q{A library work with audio files on the filesystem .}
|
14
14
|
s.description = %q{Read & write metadata tags, convert audio files to alternate formats, manage files on the filesystem.}
|
15
15
|
|
16
|
-
s.required_ruby_version = '>=
|
16
|
+
s.required_ruby_version = '>= 2.0.0'
|
17
17
|
s.required_rubygems_version = '>= 1.3.6'
|
18
18
|
|
19
19
|
s.add_development_dependency 'reek', '~> 1.2'
|
20
20
|
s.add_development_dependency 'rspec', '~> 2.11'
|
21
|
-
s.add_development_dependency 'simplecov', '~> 0.7'
|
22
21
|
|
23
22
|
git_files = `git ls-files -z`.split("\0") rescue ''
|
24
23
|
s.files = git_files
|
@@ -7,9 +7,9 @@ module PopCap
|
|
7
7
|
after { PopCapSpecHelper.remove_converted }
|
8
8
|
|
9
9
|
it 'creates a new audio file with the specified format & bitrate' do
|
10
|
-
audio_file = AudioFile.new(
|
10
|
+
audio_file = AudioFile.new(PopCapSpecHelper::SAMPLE_FILE)
|
11
11
|
audio_file.convert(:mp3, 128)
|
12
|
-
expect(File.exists?('spec/
|
12
|
+
expect(File.exists?('spec/fixtures/sample.mp3')).to be_true
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
@@ -4,7 +4,7 @@ require 'support/popcap_spec_helper'
|
|
4
4
|
|
5
5
|
describe PopCap do
|
6
6
|
let(:audio_file) { PopCap::AudioFile.new(filepath) }
|
7
|
-
let(:filepath) {
|
7
|
+
let(:filepath) { PopCapSpecHelper::SAMPLE_FILE }
|
8
8
|
|
9
9
|
it '#raw_tags' do
|
10
10
|
expect(audio_file.raw_tags).to eq PopCapSpecHelper.raw_tags
|
@@ -4,7 +4,7 @@ require 'support/popcap_spec_helper'
|
|
4
4
|
|
5
5
|
describe PopCap do
|
6
6
|
let(:audio_file) { PopCap::AudioFile.new(filepath) }
|
7
|
-
let(:filepath) {
|
7
|
+
let(:filepath) { PopCapSpecHelper::SAMPLE_FILE }
|
8
8
|
|
9
9
|
context '#update_tags' do
|
10
10
|
before { PopCapSpecHelper.setup }
|
@@ -5,7 +5,7 @@ require 'support/popcap_spec_helper'
|
|
5
5
|
module PopCap
|
6
6
|
describe AudioFile do
|
7
7
|
let(:audio_file) { AudioFile.new(filepath) }
|
8
|
-
let(:filepath) {
|
8
|
+
let(:filepath) { PopCapSpecHelper::SAMPLE_FILE }
|
9
9
|
let(:included_modules) { AudioFile.included_modules }
|
10
10
|
|
11
11
|
before { PopCapSpecHelper.setup }
|
@@ -50,22 +50,16 @@ module PopCap
|
|
50
50
|
context '#reload!' do
|
51
51
|
it 'reloads raw tags' do
|
52
52
|
audio_file.raw_tags
|
53
|
+
expect(audio_file.instance_variable_get('@raw')).not_to be_nil
|
53
54
|
audio_file.reload!
|
54
55
|
expect(audio_file.instance_variable_get('@raw')).to be_nil
|
55
56
|
end
|
56
|
-
|
57
|
-
it 'calls up to Taggable#reload!' do
|
58
|
-
audio_file.raw_tags
|
59
|
-
audio_file.reload!
|
60
|
-
expect(audio_file.instance_variable_get('@tags')).to be_nil
|
61
|
-
expect(audio_file.instance_variable_get('@to_hash')).to be_nil
|
62
|
-
end
|
63
57
|
end
|
64
58
|
|
65
59
|
context '#update_tags' do
|
66
60
|
it 'reloads after tags updated' do
|
67
61
|
audio_file.should_receive(:reload!)
|
68
|
-
|
62
|
+
audio_file.update_tags({foo: 'foo'})
|
69
63
|
end
|
70
64
|
end
|
71
65
|
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'pop_cap/class_support'
|
3
|
+
|
4
|
+
module PopCap
|
5
|
+
describe ClassSupport do
|
6
|
+
context '#camelize' do
|
7
|
+
it 'converts to CamelCase' do
|
8
|
+
maker = ClassSupport.new('one_two')
|
9
|
+
expect(maker.camelize).to eq('OneTwo')
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'handles symbols' do
|
13
|
+
maker = ClassSupport.new(:one_two_three)
|
14
|
+
expect(maker.camelize).to eq('OneTwoThree')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context '#symbolize' do
|
19
|
+
it 'returns a symbol' do
|
20
|
+
maker = ClassSupport.new('One')
|
21
|
+
expect(maker.symbolize).to eq(:one)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'handles CamelCase' do
|
25
|
+
maker = ClassSupport.new('OneTwo')
|
26
|
+
expect(maker.symbolize).to eq(:one_two)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'demodulizes the symbol' do
|
30
|
+
maker = ClassSupport.new('One::Two::ThreeFour')
|
31
|
+
expect(maker.symbolize).to eq(:three_four)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context '#namespace' do
|
36
|
+
it 'accounts for namespacing with a forward slash' do
|
37
|
+
maker = ClassSupport.new('one/two/three_four')
|
38
|
+
expect(maker.namespace).to eq('One::Two::ThreeFour')
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'accounts for mixed case in namespacing' do
|
42
|
+
maker = ClassSupport.new('One/Two/Three_Four')
|
43
|
+
expect(maker.namespace).to eq('One::Two::ThreeFour')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context '#constantize' do
|
48
|
+
it 'converts to a constant' do
|
49
|
+
maker = ClassSupport.new('array')
|
50
|
+
expect(maker.constantize).to eq(Array)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'handles module namespacing' do
|
54
|
+
maker = ClassSupport.new('file/stat')
|
55
|
+
expect(maker.constantize).to eq(File::Stat)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'pop_cap/ffmpeg/converter'
|
3
|
+
|
4
|
+
module PopCap
|
5
|
+
describe Converter do
|
6
|
+
let(:bitrate) { %W{-ab 192k}}
|
7
|
+
let(:command) { input + bitrate + output_path }
|
8
|
+
let(:commander) { double('commander') }
|
9
|
+
let(:filepath) { 'path/to/file.flac' }
|
10
|
+
let(:input) { %W{ffmpeg -i #{filepath}} }
|
11
|
+
let(:instance) { double('converter_instance') }
|
12
|
+
|
13
|
+
def shared_expectation(command=command)
|
14
|
+
commander.should_receive(:new).with(*command) { instance }
|
15
|
+
instance.stub_chain(:execute, :success?) { true }
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '.convert' do
|
19
|
+
let(:output_path) { %W{path/to/file.ogg} }
|
20
|
+
|
21
|
+
it 'has a class method for #convert' do
|
22
|
+
shared_expectation
|
23
|
+
Converter.convert(filepath, {format: 'ogg', commander: commander})
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#convert' do
|
28
|
+
let(:output_path) { %W{path/to/file.ogg} }
|
29
|
+
|
30
|
+
context 'format' do
|
31
|
+
it 'handles a string' do
|
32
|
+
shared_expectation
|
33
|
+
Converter.new(filepath, {format: 'ogg', commander: commander}).convert
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'handles a symbol' do
|
37
|
+
shared_expectation
|
38
|
+
Converter.new(filepath, {format: :ogg, commander: commander}).convert
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'is case insenstive' do
|
42
|
+
shared_expectation
|
43
|
+
Converter.new(filepath, {format: 'OGG', commander: commander}).convert
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'bitrate' do
|
48
|
+
let(:output_path) { %W{path/to/file.mp3} }
|
49
|
+
|
50
|
+
it 'defaults to 192 kb/s' do
|
51
|
+
shared_expectation
|
52
|
+
Converter.new(filepath, {format: :mp3, commander: commander}).convert
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'takes an optional bitrate' do
|
56
|
+
options = {format: :mp3, bitrate: 64, commander: commander}
|
57
|
+
bitrate_command = input + %W{-ab 64k} + output_path
|
58
|
+
shared_expectation(bitrate_command)
|
59
|
+
Converter.new(filepath, options).convert
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'handles bitrate as string' do
|
63
|
+
options = {format: :mp3, bitrate: '128', commander: commander}
|
64
|
+
bitrate_command = input + %W{-ab 128k} + output_path
|
65
|
+
shared_expectation(bitrate_command)
|
66
|
+
Converter.new(filepath, options).convert
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'm4a' do
|
71
|
+
let(:output_path) { %W{path/to/file.m4a} }
|
72
|
+
|
73
|
+
it 'uses strict mode' do
|
74
|
+
strict = input + %W{-strict -2} + bitrate + output_path
|
75
|
+
shared_expectation(strict)
|
76
|
+
Converter.new(filepath, {format: :m4a, commander: commander}).convert
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context 'errors' do
|
81
|
+
it 'raises error if could not convert file' do
|
82
|
+
expect do
|
83
|
+
commander.should_receive(:new).with(*command) { instance }
|
84
|
+
instance.stub_chain(:execute, :success?) { false }
|
85
|
+
Converter.new(filepath, {format: :ogg,
|
86
|
+
commander: commander}).convert
|
87
|
+
end.to raise_error(FFmpegError, "Error converting #{filepath}.")
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'support/popcap_spec_helper'
|
3
|
+
require 'pop_cap/ffmpeg/ffmpeg'
|
4
|
+
|
5
|
+
module PopCap
|
6
|
+
describe FFmpeg do
|
7
|
+
|
8
|
+
let(:filepath) { File.realpath(PopCapSpecHelper::SAMPLE_FILE) }
|
9
|
+
let(:ffmpeg) { FFmpeg.new(filepath) }
|
10
|
+
|
11
|
+
it 'returns its filepath' do
|
12
|
+
expect(ffmpeg.filepath).to eq filepath
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'sets default commander to Commander' do
|
16
|
+
expect(ffmpeg.commander).to eq Commander
|
17
|
+
end
|
18
|
+
|
19
|
+
it '#new takes an options hash' do
|
20
|
+
opts = {foo: 'foo'}
|
21
|
+
ffmpeg = FFmpeg.new(filepath, opts)
|
22
|
+
expect(ffmpeg.options).to eq({foo: 'foo'})
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'has a custom error message' do
|
26
|
+
expect(ffmpeg.error_message('loving')).to eq "Error loving #{filepath}."
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'raises error if FFmpeg not installed' do
|
30
|
+
error_message = 'No such file or directory - FFmpeg is not installed.'
|
31
|
+
expect do
|
32
|
+
Open3.stub(:capture3).with('ffmpeg').and_raise(Errno::ENOENT)
|
33
|
+
FFmpeg.new('filepath')
|
34
|
+
end.to raise_error(MissingDependency, error_message)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'pop_cap/ffmpeg/tag_reader'
|
3
|
+
|
4
|
+
module PopCap
|
5
|
+
describe TagReader do
|
6
|
+
let(:commander) { double('commander') }
|
7
|
+
let(:instance) { double('commander_instance') }
|
8
|
+
let(:file) { 'path/to/file.flac' }
|
9
|
+
let(:reader) { TagReader.new(file, {commander: commander}) }
|
10
|
+
let(:command) { %W{ffprobe -show_format -print_format json #{file}} }
|
11
|
+
|
12
|
+
let(:shared_example) do
|
13
|
+
commander.should_receive(:new).with(*command) { instance }
|
14
|
+
instance.stub_chain(:execute, :success?) { true }
|
15
|
+
instance.stub_chain(:execute, :stdout) { instance }
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'test' do
|
19
|
+
puts JSON.parse(TagReader.read('spec/fixtures/sample.flac'))
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '.read' do
|
23
|
+
before { shared_example }
|
24
|
+
|
25
|
+
it 'has a class method for #read' do
|
26
|
+
instance.stub(:valid_encoding?) { true }
|
27
|
+
JSON.stub(:load)
|
28
|
+
TagReader.read(file, {commander: commander})
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe '#read' do
|
33
|
+
before { shared_example }
|
34
|
+
after { reader.read }
|
35
|
+
|
36
|
+
it 'returns the output as JSON' do
|
37
|
+
instance.stub(:valid_encoding?) { true }
|
38
|
+
JSON.should_receive(:load).with(instance) { instance }
|
39
|
+
instance.should_receive(:to_json)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'does not encode valid byte strings' do
|
43
|
+
instance.stub(:valid_encoding?) { true }
|
44
|
+
JSON.stub(:load)
|
45
|
+
instance.should_not_receive(:encode!)
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'encodes invalid byte strings as UTF-8' do
|
49
|
+
instance.stub(:valid_encoding?) { false }
|
50
|
+
instance.stub_chain(:encoding, :name) { 'UTF-8' }
|
51
|
+
instance.should_receive(:encode!).
|
52
|
+
with('UTF-16', 'UTF-8', undef: :replace, invalid: :replace)
|
53
|
+
instance.should_receive(:encode!).with('UTF-8')
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'errors' do
|
58
|
+
it 'raises error if could not read tags' do
|
59
|
+
expect do
|
60
|
+
commander.should_receive(:new).with(*command) { instance }
|
61
|
+
instance.stub_chain(:execute, :success?) { false }
|
62
|
+
reader.read
|
63
|
+
end.to raise_error(FFmpegError, "Error reading #{file}.")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|