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
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?
|