popcap 0.7.2 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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?