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.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +0 -1
  3. data/Gemfile.lock +23 -32
  4. data/README.md +27 -24
  5. data/lib/pop_cap/audio_file.rb +13 -11
  6. data/lib/pop_cap/class_support.rb +73 -0
  7. data/lib/pop_cap/{commander.rb → ffmpeg/commander.rb} +8 -2
  8. data/lib/pop_cap/ffmpeg/converter.rb +57 -0
  9. data/lib/pop_cap/ffmpeg/ffmpeg.rb +45 -0
  10. data/lib/pop_cap/ffmpeg/tag_reader.rb +45 -0
  11. data/lib/pop_cap/ffmpeg/tag_writer.rb +44 -0
  12. data/lib/pop_cap/formatter.rb +62 -0
  13. data/lib/pop_cap/formatters/bit_rate.rb +5 -7
  14. data/lib/pop_cap/formatters/date.rb +12 -10
  15. data/lib/pop_cap/formatters/duration.rb +10 -12
  16. data/lib/pop_cap/formatters/filesize.rb +6 -8
  17. data/lib/pop_cap/tag/formatted_tag.rb +31 -0
  18. data/lib/pop_cap/tag/tag_hash.rb +55 -0
  19. data/lib/pop_cap/{tag_struct.rb → tag/tag_struct.rb} +2 -0
  20. data/lib/pop_cap/taggable.rb +50 -30
  21. data/lib/pop_cap/version.rb +1 -1
  22. data/popcap.gemspec +1 -2
  23. data/spec/integration/convert_audio_file_spec.rb +2 -2
  24. data/spec/integration/read_metatags_spec.rb +1 -1
  25. data/spec/integration/update_metatags_spec.rb +1 -1
  26. data/spec/lib/pop_cap/audio_file_spec.rb +3 -9
  27. data/spec/lib/pop_cap/class_support_spec.rb +59 -0
  28. data/spec/lib/pop_cap/{commander_spec.rb → ffmpeg/commander_spec.rb} +1 -1
  29. data/spec/lib/pop_cap/ffmpeg/converter_spec.rb +92 -0
  30. data/spec/lib/pop_cap/ffmpeg/ffmpeg_spec.rb +37 -0
  31. data/spec/lib/pop_cap/ffmpeg/tag_reader_spec.rb +67 -0
  32. data/spec/lib/pop_cap/ffmpeg/tag_writer_spec.rb +60 -0
  33. data/spec/lib/pop_cap/fileable_spec.rb +5 -5
  34. data/spec/lib/pop_cap/formatter_spec.rb +71 -0
  35. data/spec/lib/pop_cap/formatters/bit_rate_spec.rb +8 -0
  36. data/spec/lib/pop_cap/formatters/date_spec.rb +8 -0
  37. data/spec/lib/pop_cap/formatters/duration_spec.rb +8 -0
  38. data/spec/lib/pop_cap/formatters/filesize_spec.rb +8 -0
  39. data/spec/lib/pop_cap/tag/formatted_tag_spec.rb +29 -0
  40. data/spec/lib/pop_cap/tag/tag_hash_spec.rb +35 -0
  41. data/spec/lib/pop_cap/{tag_struct_spec.rb → tag/tag_struct_spec.rb} +5 -1
  42. data/spec/lib/pop_cap/taggable_spec.rb +24 -10
  43. data/spec/spec_helper.rb +0 -3
  44. data/spec/support/popcap_spec_helper.rb +50 -33
  45. metadata +40 -56
  46. data/lib/pop_cap/converter.rb +0 -40
  47. data/lib/pop_cap/ffmpeg.rb +0 -130
  48. data/lib/pop_cap/formatters.rb +0 -33
  49. data/lib/pop_cap/helper.rb +0 -53
  50. data/lib/pop_cap/tag_key.rb +0 -32
  51. data/lib/pop_cap/tag_line.rb +0 -36
  52. data/spec/lib/pop_cap/converter_spec.rb +0 -67
  53. data/spec/lib/pop_cap/ffmpeg_spec.rb +0 -96
  54. data/spec/lib/pop_cap/formatters_spec.rb +0 -36
  55. data/spec/lib/pop_cap/helper_spec.rb +0 -42
  56. data/spec/lib/pop_cap/tag_key_spec.rb +0 -48
  57. data/spec/lib/pop_cap/tag_line_spec.rb +0 -26
  58. /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
@@ -9,5 +9,4 @@ group :test do
9
9
  gem 'cane'
10
10
  gem 'reek'
11
11
  gem 'rspec'
12
- gem 'simplecov'
13
12
  end
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.0)
5
+ cane (2.5.2)
6
6
  parallel
7
- coderay (1.0.8)
8
- diff-lcs (1.1.3)
7
+ coderay (1.0.9)
8
+ diff-lcs (1.2.1)
9
9
  method_source (0.8.1)
10
- multi_json (1.5.0)
11
- parallel (0.6.1)
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.2.13)
17
- ripper_ruby_parser (~> 0.0.7)
18
- ruby2ruby (~> 1.2.5)
19
- ruby_parser (~> 2.0)
20
- sexp_processor (~> 3.0)
21
- ripper_ruby_parser (0.0.8)
22
- sexp_processor (~> 3.0)
23
- rspec (2.12.0)
24
- rspec-core (~> 2.12.0)
25
- rspec-expectations (~> 2.12.0)
26
- rspec-mocks (~> 2.12.0)
27
- rspec-core (2.12.2)
28
- rspec-expectations (2.12.1)
29
- diff-lcs (~> 1.1.3)
30
- rspec-mocks (2.12.2)
31
- ruby2ruby (1.2.5)
32
- ruby_parser (~> 2.0)
33
- sexp_processor (~> 3.0)
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 a string of the raw output from running ffprobe -show_format.
26
- [FORMAT]
27
- filename=sample.flac
28
- nb_streams=1
29
- format_name=flac
30
- format_long_name=raw FLAC
31
- start_time=N/A
32
- duration=1.000000
33
- size=18291
34
- bit_rate=146328
35
- TAG:GENRE=Sample Genre
36
- TAG:track=01
37
- TAG:ALBUM=Sample Album
38
- TAG:DATE=2012
39
- TAG:TITLE=Sample Title
40
- TAG:ARTIST=Sample Artist
41
- [/FORMAT]
42
-
43
- audio_file.to_hash => Returns a Ruby hash after sanitizing the raw output of #raw_tags.
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 after applying formatters #to_hash.
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/support/sample.flac'
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({artist: 'David Bowie'})
87
+ audio_file.update_tags(artist: 'David Bowie'})
86
88
 
87
- audio_file.update_tags({fancy_new_tag: 'Custom Tag Input'})
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)
@@ -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/support/sample.flac'
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, tag_util=FFmpeg)
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/support/sample.flac')
42
+ # audio_file = AudioFile.new('spec/fixtures/sample.flac')
41
43
  # audio_file.convert('mp3', 128)
42
- # # => 'spec/support/sample.mp3'
44
+ # # => 'spec/fixtures/sample.mp3'
43
45
  #
44
46
  def convert(format, bitrate=192)
45
- @tag_util.new(@filepath).convert(format, bitrate)
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/support/sample.flac')
54
+ # audio_file = AudioFile.new('spec/fixtures/sample.flac')
53
55
  # audio_file.raw_tags
54
56
  # # =>
55
57
  # [FORMAT]
56
- # filename=spec/support/sample.flac
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 ||= @tag_util.new(@filepath).read_tags
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
- @tag_util.new(@filepath).update_tags(updates)
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
- @command = args.inject(%W{}) { |command, arg| command << arg }
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(*@command)
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 @bitrate.to_i > 0
25
- @bitrate.to_s[0..-4] + ' kb/s'
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 ||= @date.match(/\b\d{4}\b/)
36
+ @match ||= value.to_s.match(/\b\d{4}\b/)
35
37
  end
36
38
 
37
39
  def within_date_range?