popcap 0.7.2 → 0.8.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.
- 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
|