popcap 0.7.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.rspec +4 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +52 -0
- data/LICENSE +9 -0
- data/README.md +137 -0
- data/lib/pop_cap/audio_file.rb +100 -0
- data/lib/pop_cap/commander.rb +64 -0
- data/lib/pop_cap/converter.rb +40 -0
- data/lib/pop_cap/ffmpeg.rb +130 -0
- data/lib/pop_cap/fileable.rb +134 -0
- data/lib/pop_cap/formatters/bit_rate.rb +29 -0
- data/lib/pop_cap/formatters/date.rb +42 -0
- data/lib/pop_cap/formatters/duration.rb +44 -0
- data/lib/pop_cap/formatters/filesize.rb +69 -0
- data/lib/pop_cap/formatters.rb +33 -0
- data/lib/pop_cap/helper.rb +53 -0
- data/lib/pop_cap/tag_key.rb +32 -0
- data/lib/pop_cap/tag_line.rb +36 -0
- data/lib/pop_cap/tag_struct.rb +45 -0
- data/lib/pop_cap/taggable.rb +100 -0
- data/lib/pop_cap/version.rb +3 -0
- data/lib/popcap.rb +5 -0
- data/popcap.gemspec +27 -0
- data/spec/integration/convert_audio_file_spec.rb +15 -0
- data/spec/integration/read_metatags_spec.rb +12 -0
- data/spec/integration/update_metatags_spec.rb +20 -0
- data/spec/lib/pop_cap/audio_file_spec.rb +72 -0
- data/spec/lib/pop_cap/commander_spec.rb +64 -0
- data/spec/lib/pop_cap/converter_spec.rb +67 -0
- data/spec/lib/pop_cap/ffmpeg_spec.rb +96 -0
- data/spec/lib/pop_cap/fileable_spec.rb +118 -0
- data/spec/lib/pop_cap/formatters/bit_rate_spec.rb +53 -0
- data/spec/lib/pop_cap/formatters/date_spec.rb +74 -0
- data/spec/lib/pop_cap/formatters/duration_spec.rb +64 -0
- data/spec/lib/pop_cap/formatters/filesize_spec.rb +89 -0
- data/spec/lib/pop_cap/formatters_spec.rb +36 -0
- data/spec/lib/pop_cap/helper_spec.rb +42 -0
- data/spec/lib/pop_cap/tag_key_spec.rb +48 -0
- data/spec/lib/pop_cap/tag_line_spec.rb +26 -0
- data/spec/lib/pop_cap/tag_struct_spec.rb +50 -0
- data/spec/lib/pop_cap/taggable_spec.rb +62 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/support/popcap_spec_helper.rb +86 -0
- data/spec/support/reek_spec.rb +8 -0
- data/spec/support/sample.flac +0 -0
- metadata +163 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
awesome_print (1.1.0)
|
5
|
+
cane (2.5.0)
|
6
|
+
parallel
|
7
|
+
coderay (1.0.8)
|
8
|
+
diff-lcs (1.1.3)
|
9
|
+
method_source (0.8.1)
|
10
|
+
multi_json (1.5.0)
|
11
|
+
parallel (0.6.1)
|
12
|
+
pry (0.9.11.3)
|
13
|
+
coderay (~> 1.0.5)
|
14
|
+
method_source (~> 0.8)
|
15
|
+
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)
|
41
|
+
slop (3.4.3)
|
42
|
+
|
43
|
+
PLATFORMS
|
44
|
+
ruby
|
45
|
+
|
46
|
+
DEPENDENCIES
|
47
|
+
awesome_print
|
48
|
+
cane
|
49
|
+
pry
|
50
|
+
reek
|
51
|
+
rspec
|
52
|
+
simplecov
|
data/LICENSE
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
Copyright (C) 2012 Culley Smith
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
8
|
+
|
9
|
+
Released under MIT License.
|
data/README.md
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
PopCap
|
2
|
+
======
|
3
|
+
|
4
|
+
PopCap is an audio file management library. It wraps some functionality from FFmpeg for reading & writing metadata tags, converting between audio file formats, and managing an audio file on the file system.
|
5
|
+
|
6
|
+
Getting Started
|
7
|
+
---------------
|
8
|
+
|
9
|
+
```
|
10
|
+
gem install popcap
|
11
|
+
require 'popcap'
|
12
|
+
|
13
|
+
Song = Class.new(PopCap::AudioFile)
|
14
|
+
song = Song.new('path/to/sample.flac')
|
15
|
+
```
|
16
|
+
|
17
|
+
Read Tags
|
18
|
+
---------
|
19
|
+
|
20
|
+
Read the metadata tags from an audio file.
|
21
|
+
|
22
|
+
```
|
23
|
+
audio_file = PopCap::AudioFile.new('sample.flac')
|
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.
|
44
|
+
{ filename: 'sample.flac',
|
45
|
+
format_name: 'flac',
|
46
|
+
format_long_name: 'raw FLAC',
|
47
|
+
nb_streams: '1',
|
48
|
+
duration: '1.000000',
|
49
|
+
filesize: '18291',
|
50
|
+
bit_rate: '146328',
|
51
|
+
start_time: 'N/A',
|
52
|
+
genre: 'Sample Genre',
|
53
|
+
track: '01',
|
54
|
+
album: 'Sample Album',
|
55
|
+
date: '2012',
|
56
|
+
title: 'Sample Title',
|
57
|
+
artist: 'Sample Artist' }
|
58
|
+
|
59
|
+
audio_file.tags => Returns a tag structure after applying formatters #to_hash.
|
60
|
+
.album => 'Sample Album'
|
61
|
+
.artist => 'Sample Artist'
|
62
|
+
.bit_rate => '146 kb/s'
|
63
|
+
.date => 2012
|
64
|
+
.duration => '1'
|
65
|
+
.filename => 'spec/support/sample.flac'
|
66
|
+
.filesize => '17.9K'
|
67
|
+
.format_long_name => 'raw FLAC'
|
68
|
+
.format_name => 'flac'
|
69
|
+
.genre => 'Sample Genre'
|
70
|
+
.nb_streams => '1'
|
71
|
+
.start_time => 'N/A'
|
72
|
+
.title => 'Sample Title'
|
73
|
+
.track => '01'
|
74
|
+
|
75
|
+
audio_file.reload! => Reload an instance of itself, useful when updating tags.
|
76
|
+
```
|
77
|
+
|
78
|
+
Update Tags
|
79
|
+
-----------
|
80
|
+
|
81
|
+
This will update the metadata tags for an audio file. It will also dynamically add any newly provided tags. It takes a hash of attributes.
|
82
|
+
|
83
|
+
```
|
84
|
+
audio_file = PopCap::AudioFile.new('sample.flac')
|
85
|
+
audio_file.update_tags({artist: 'David Bowie'})
|
86
|
+
|
87
|
+
audio_file.update_tags({fancy_new_tag: 'Custom Tag Input'})
|
88
|
+
```
|
89
|
+
|
90
|
+
Convert
|
91
|
+
-------
|
92
|
+
|
93
|
+
This will convert between audio file formats. It is restricted to basic audio formats. It also takes an optional bitrate for mp3 formats. The original file is preserved during the conversion.
|
94
|
+
|
95
|
+
Supported formats: aac, flac, m4a, mp3, ogg, wav
|
96
|
+
Supported mp3 bitrates: 64, 128, 160, 192, 256, 320
|
97
|
+
|
98
|
+
```
|
99
|
+
audio_file = PopCap::AudioFile.new('sample.flac')
|
100
|
+
|
101
|
+
audio_file.convert(:ogg)
|
102
|
+
audio_file.convert(:mp3) # => default bitrate is 192k
|
103
|
+
audio_file.convert(:mp3, 256)
|
104
|
+
```
|
105
|
+
|
106
|
+
File Management Options
|
107
|
+
-----------------------
|
108
|
+
|
109
|
+
Various Ruby File & FileUtils methods are wrapped for convenience.
|
110
|
+
|
111
|
+
```
|
112
|
+
audio_file = PopCap::AudioFile.new('sample.flac')
|
113
|
+
|
114
|
+
audio_file.backup # => default directory is '/tmp'
|
115
|
+
audio_file.backup('some/path')
|
116
|
+
|
117
|
+
audio_file.backup_path # => returns backup path
|
118
|
+
|
119
|
+
audio_file.destroy # => removes file from filesystem
|
120
|
+
|
121
|
+
audio_file.directory # => returns directory, excluding filename
|
122
|
+
|
123
|
+
audio_file.filename # => returns filename, excluding directory
|
124
|
+
|
125
|
+
audio_file.move('destination') # = > moves file to destination
|
126
|
+
|
127
|
+
audio_file.rename('new_name.flac') # => renames file
|
128
|
+
|
129
|
+
audio_file.restore # => restores file from backup_path, takes an optional path as well
|
130
|
+
|
131
|
+
audio_file.tmppath # => returns the temporary path, e.g. '/tmp/sample.flac'
|
132
|
+
```
|
133
|
+
|
134
|
+
Dependencies
|
135
|
+
------------
|
136
|
+
|
137
|
+
[FFmpeg](http://ffmpeg.org)
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'pop_cap/ffmpeg'
|
2
|
+
require 'pop_cap/taggable'
|
3
|
+
|
4
|
+
module PopCap
|
5
|
+
FileNotFound = Class.new(StandardError)
|
6
|
+
# Public: This is a class for managing audio files on the filesystem.
|
7
|
+
# It is used to read & write metadata tags, convert between audio formats,
|
8
|
+
# and manage a file on the filesystem using standard UNIX file commands.
|
9
|
+
#
|
10
|
+
# Examples
|
11
|
+
#
|
12
|
+
# filepath = 'spec/support/sample.flac'
|
13
|
+
# af = AudioFile.new(filepath)
|
14
|
+
#
|
15
|
+
#
|
16
|
+
class AudioFile
|
17
|
+
attr_accessor :filepath
|
18
|
+
|
19
|
+
include Fileable
|
20
|
+
include Taggable
|
21
|
+
|
22
|
+
# Public: Initialize
|
23
|
+
#
|
24
|
+
# filepath - Requires a valid filepath to a file on the local filesystem.
|
25
|
+
#
|
26
|
+
def initialize(filepath, tag_util=FFmpeg)
|
27
|
+
raise(FileNotFound, filepath) unless File.exists?(filepath)
|
28
|
+
@filepath = File.realpath(filepath)
|
29
|
+
@tag_util = tag_util
|
30
|
+
end
|
31
|
+
|
32
|
+
# Public: convert
|
33
|
+
# This method converts an audio file between formats.
|
34
|
+
# It takes an optional bitrate for mp3 formats.
|
35
|
+
#
|
36
|
+
# format - A valid audio file format as string or symbol.
|
37
|
+
# bitrate - An optional bitrate for mp3s.
|
38
|
+
#
|
39
|
+
# Examples
|
40
|
+
# audio_file = AudioFile.new('spec/support/sample.flac')
|
41
|
+
# audio_file.convert('mp3', 128)
|
42
|
+
# # => 'spec/support/sample.mp3'
|
43
|
+
#
|
44
|
+
def convert(format, bitrate=192)
|
45
|
+
@tag_util.new(@filepath).convert(format, bitrate)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Public: raw_tags
|
49
|
+
# This method returns the raw_tags from FFmpeg.
|
50
|
+
#
|
51
|
+
# Examples
|
52
|
+
# audio_file = AudioFile.new('spec/support/sample.flac')
|
53
|
+
# audio_file.raw_tags
|
54
|
+
# # =>
|
55
|
+
# [FORMAT]
|
56
|
+
# filename=spec/support/sample.flac
|
57
|
+
# nb_streams=1
|
58
|
+
# format_name=flac
|
59
|
+
# format_long_name=raw FLAC
|
60
|
+
# start_time=N/A
|
61
|
+
# duration=1.000000
|
62
|
+
# size=18291
|
63
|
+
# bit_rate=146328
|
64
|
+
# TAG:GENRE=Sample Genre
|
65
|
+
# TAG:track=01
|
66
|
+
# TAG:ALBUM=Sample Album
|
67
|
+
# TAG:DATE=2012
|
68
|
+
# TAG:TITLE=Sample Title
|
69
|
+
# TAG:ARTIST=Sample Artist
|
70
|
+
# [/FORMAT]
|
71
|
+
#
|
72
|
+
def raw_tags
|
73
|
+
@raw ||= @tag_util.new(@filepath).read_tags
|
74
|
+
end
|
75
|
+
|
76
|
+
# Public: This method reloads the current instance.
|
77
|
+
#
|
78
|
+
# Examples
|
79
|
+
# audio_file.reload!
|
80
|
+
#
|
81
|
+
def reload!
|
82
|
+
@raw = nil
|
83
|
+
super
|
84
|
+
end
|
85
|
+
|
86
|
+
# Public: update_tags(updates)
|
87
|
+
# Updates existing tags, adds a tag if it does not exist.
|
88
|
+
#
|
89
|
+
# updates - This takes a hash of keys matching a tag name with a value.
|
90
|
+
#
|
91
|
+
# Examples
|
92
|
+
#
|
93
|
+
# audio_file.update_tags({artist: 'New Artist', album: 'New Album'})
|
94
|
+
#
|
95
|
+
def update_tags(updates)
|
96
|
+
@tag_util.new(@filepath).update_tags(updates)
|
97
|
+
self.reload!
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'open3'
|
2
|
+
|
3
|
+
module PopCap
|
4
|
+
# Public: This is a wrapper for the Open3 Ruby Standard Library.
|
5
|
+
#
|
6
|
+
# Examples
|
7
|
+
#
|
8
|
+
# options = %W{ls} + %W{-lh}
|
9
|
+
# Commander.new(options).execute.output
|
10
|
+
# # => <directory contents>
|
11
|
+
#
|
12
|
+
class Commander
|
13
|
+
# Public: Initialize
|
14
|
+
#
|
15
|
+
# args - Arguments should be escaped with an interpolated literal.
|
16
|
+
#
|
17
|
+
def initialize(*args)
|
18
|
+
raise(ArgumentError, error_message) if args.empty?
|
19
|
+
@command = args.inject(%W{}) { |command, arg| command << arg }
|
20
|
+
@executed = []
|
21
|
+
end
|
22
|
+
|
23
|
+
# Public: Execute the command using Open3.capture3.
|
24
|
+
# It will return an instance of Commander, in order
|
25
|
+
# to chain methods.
|
26
|
+
#
|
27
|
+
def execute
|
28
|
+
@executed = Open3.capture3(*@command)
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
# Public: Open3.capture3 returns an array of three elements.
|
33
|
+
# The first element returned is stdout.
|
34
|
+
#
|
35
|
+
def stdout
|
36
|
+
@executed[0]
|
37
|
+
end
|
38
|
+
|
39
|
+
# Public: Open3.capture3 returns an array of three elements.
|
40
|
+
# The second element returned is stderr.
|
41
|
+
#
|
42
|
+
def stderr
|
43
|
+
@executed[1]
|
44
|
+
end
|
45
|
+
|
46
|
+
# Public: Open3.capture3 returns an array of three elements.
|
47
|
+
# The third element returned is status. Status can have a
|
48
|
+
# 'success?' of true or false.
|
49
|
+
#
|
50
|
+
# Examples
|
51
|
+
#
|
52
|
+
# self.success?
|
53
|
+
# # => true
|
54
|
+
#
|
55
|
+
def success?
|
56
|
+
@executed[2].success?
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
def error_message
|
61
|
+
'Cannot run command with no args.'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module PopCap
|
2
|
+
# Internal: This module builds a command to convert an audio file
|
3
|
+
# to the specified output format. The module is included in FFmpeg.
|
4
|
+
#
|
5
|
+
module Converter
|
6
|
+
# Internal: This method takes an input format & optional bitrate
|
7
|
+
# in order to supply the ffmpeg command used to convert.
|
8
|
+
#
|
9
|
+
# format - Provide a valid format as a string or symbol.
|
10
|
+
# bitrate - Provide a valid bitrate as a string or integer.
|
11
|
+
#
|
12
|
+
def convert(format, bitrate=192)
|
13
|
+
@bitrate = bitrate
|
14
|
+
@format = format.downcase.to_s
|
15
|
+
input_path + strict_mode + bitrate_options + output_path
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
def bitrate_options
|
20
|
+
%W{-ab #{@bitrate}k}
|
21
|
+
end
|
22
|
+
|
23
|
+
def input_path
|
24
|
+
%W{ffmpeg -i #{self.filepath}}
|
25
|
+
end
|
26
|
+
|
27
|
+
def output_path
|
28
|
+
%W{#{self.filepath.sub(%r([^.]+\z),@format)}}
|
29
|
+
end
|
30
|
+
|
31
|
+
def strict_mode
|
32
|
+
return %W{} unless use_strict_mode?
|
33
|
+
%W{-strict -2}
|
34
|
+
end
|
35
|
+
|
36
|
+
def use_strict_mode?
|
37
|
+
@format == 'm4a'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'pop_cap/commander'
|
2
|
+
require 'pop_cap/converter'
|
3
|
+
require 'pop_cap/fileable'
|
4
|
+
|
5
|
+
module PopCap
|
6
|
+
MissingDependency = Class.new(Errno::ENOENT)
|
7
|
+
# Internal: This is a wrapper for the FFmpeg C library.
|
8
|
+
#
|
9
|
+
# Examples
|
10
|
+
#
|
11
|
+
# filepath = 'spec/support/sample.flac'
|
12
|
+
# ffmpeg = FFmpeg.new(filepath)
|
13
|
+
#
|
14
|
+
class FFmpeg
|
15
|
+
include Converter
|
16
|
+
include Fileable
|
17
|
+
|
18
|
+
attr_accessor :filepath
|
19
|
+
|
20
|
+
# Internal: initialize
|
21
|
+
#
|
22
|
+
# filepath - Requires a valid filepath to a file on the local filesystem.
|
23
|
+
#
|
24
|
+
def initialize(filepath)
|
25
|
+
check_for_ffmpeg_install
|
26
|
+
@filepath = filepath
|
27
|
+
end
|
28
|
+
|
29
|
+
# Internal: convert
|
30
|
+
# This command calls up to Converter@convert.
|
31
|
+
#
|
32
|
+
# format - A valid audio file format as string or symbol.
|
33
|
+
# bitrate = A valid bit rate as string or symbol.
|
34
|
+
#
|
35
|
+
def convert(format, bitrate=192)
|
36
|
+
conversion = super(format,bitrate)
|
37
|
+
Commander.new(*conversion).execute
|
38
|
+
end
|
39
|
+
|
40
|
+
# Internal: read_tags
|
41
|
+
# Returns the raw output of FFProbe's show_format option.
|
42
|
+
#
|
43
|
+
# Examples
|
44
|
+
#
|
45
|
+
# [FORMAT]
|
46
|
+
# filename=spec/support/sample.flac
|
47
|
+
# nb_streams=1
|
48
|
+
# format_name=flac
|
49
|
+
# format_long_name=raw FLAC
|
50
|
+
# start_time=N/A
|
51
|
+
# duration=1.000000
|
52
|
+
# size=18291
|
53
|
+
# bit_rate=146328
|
54
|
+
# TAG:GENRE=Sample Genre
|
55
|
+
# TAG:track=01
|
56
|
+
# TAG:ALBUM=Sample Album
|
57
|
+
# TAG:DATE=2012
|
58
|
+
# TAG:TITLE=Sample Title
|
59
|
+
# TAG:ARTIST=Sample Artist
|
60
|
+
# [/FORMAT]
|
61
|
+
#
|
62
|
+
def read_tags
|
63
|
+
@stdout ||= encode(read_output)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Internal: update_tags(updates)
|
67
|
+
# This wraps FFmpeg's -metadata command.
|
68
|
+
#
|
69
|
+
# Examples
|
70
|
+
# filepath = 'spec/support/sample.flac'
|
71
|
+
# ffmpeg = FFmpeg.new(filepath)
|
72
|
+
# ffmpeg.update_tags({artist: 'New Artist'})
|
73
|
+
#
|
74
|
+
def update_tags(updates)
|
75
|
+
@updates = updates
|
76
|
+
unless Commander.new(*write_command).execute.success?
|
77
|
+
raise(FFmpegError, write_error_message)
|
78
|
+
end
|
79
|
+
self.restore('/tmp')
|
80
|
+
@stdout = nil
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
def check_for_ffmpeg_install
|
85
|
+
begin
|
86
|
+
Open3.capture3('ffmpeg')
|
87
|
+
rescue Errno::ENOENT
|
88
|
+
raise MissingDependency, 'FFmpeg is not installed.'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def encode(string)
|
93
|
+
return string if string.valid_encoding?
|
94
|
+
original_encoding = string.encoding.name
|
95
|
+
string.encode!('UTF-16', original_encoding, undef:
|
96
|
+
:replace, invalid: :replace)
|
97
|
+
string.encode!('UTF-8')
|
98
|
+
end
|
99
|
+
|
100
|
+
def read_command
|
101
|
+
%W{ffprobe -show_format} + %W{#{filepath}}
|
102
|
+
end
|
103
|
+
|
104
|
+
def read_output
|
105
|
+
commander = Commander.new(*read_command).execute
|
106
|
+
raise(FFmpegError, read_error_message) unless commander.success?
|
107
|
+
commander.stdout
|
108
|
+
end
|
109
|
+
|
110
|
+
def read_error_message
|
111
|
+
"Error reading #{self.filepath}"
|
112
|
+
end
|
113
|
+
|
114
|
+
def write_command
|
115
|
+
%W{ffmpeg -i #{filepath}} + write_options + %W{#{self.tmppath}}
|
116
|
+
end
|
117
|
+
|
118
|
+
def write_options
|
119
|
+
@updates.inject(%W{}) do |options,update|
|
120
|
+
options << '-metadata' << update.join('=')
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def write_error_message
|
125
|
+
"Error updating #{self.filepath}"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
FFmpegError = Class.new(StandardError)
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module PopCap
|
4
|
+
PathError = Class.new(StandardError)
|
5
|
+
|
6
|
+
# Public: This is a wrapper for the File & FileUtils Ruby
|
7
|
+
# Standard Libraries. The module requires it be included in
|
8
|
+
# a class which has a #filepath method that returns a filepath.
|
9
|
+
#
|
10
|
+
module Fileable
|
11
|
+
|
12
|
+
# Public: This will backup a file to a specified directory.
|
13
|
+
#
|
14
|
+
# backup_dir - path to a directory on the filesystem.
|
15
|
+
# It defaults to '/tmp'.
|
16
|
+
#
|
17
|
+
# Examples
|
18
|
+
# audio_file.backup('/usr')
|
19
|
+
# # => file is copied to '/usr'
|
20
|
+
#
|
21
|
+
def backup(backup_dir='/tmp')
|
22
|
+
@backup_dir = backup_dir
|
23
|
+
FileUtils.cp(self.filepath, backup_path)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Public: This will return the backup path.
|
27
|
+
# It will raise an error if file was not backed up previously.
|
28
|
+
#
|
29
|
+
# Examples
|
30
|
+
# klass = SomeClass.new('path/to/file.txt').backup('/tmp')
|
31
|
+
# klass.backup_path
|
32
|
+
# # => '/tmp/file.txt'
|
33
|
+
#
|
34
|
+
def backup_path
|
35
|
+
raise(PathError, backup_path_error_message) unless @backup_dir
|
36
|
+
"#{@backup_dir}/" + filename
|
37
|
+
end
|
38
|
+
|
39
|
+
# Public: This will remove the file from the system.
|
40
|
+
#
|
41
|
+
# Examples
|
42
|
+
# klass = SomeClass.new('path/to/file.txt')
|
43
|
+
# klass.destroy
|
44
|
+
#
|
45
|
+
def destroy
|
46
|
+
FileUtils.rm_f(self.filepath)
|
47
|
+
self.filepath = nil
|
48
|
+
end
|
49
|
+
|
50
|
+
# Public: This will return the directory, excluding the filename.
|
51
|
+
#
|
52
|
+
# Examples
|
53
|
+
# SomeClass.new('path/to/file.txt').directory
|
54
|
+
# # => 'path/to/'
|
55
|
+
#
|
56
|
+
def directory
|
57
|
+
File.dirname(self.filepath)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Public: This will return the filename, excluding directory.
|
61
|
+
#
|
62
|
+
# Examples
|
63
|
+
# SomeClass.new('path/to/file.txt').filename
|
64
|
+
# # => 'file.txt'
|
65
|
+
#
|
66
|
+
def filename
|
67
|
+
File.basename(self.filepath)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Public: This method moves a file to destination.
|
71
|
+
#
|
72
|
+
# destination - The folder/directory to move the file.
|
73
|
+
#
|
74
|
+
# Examples
|
75
|
+
# klass = SomeClass.new('path/to/file.txt')
|
76
|
+
# klass.move('/tmp')
|
77
|
+
# # => '/tmp/file.txt'
|
78
|
+
#
|
79
|
+
def move(destination)
|
80
|
+
FileUtils.mv(self.filepath, destination)
|
81
|
+
self.filepath = destination + '/' + filename
|
82
|
+
end
|
83
|
+
|
84
|
+
# Public: This method renames a file.
|
85
|
+
#
|
86
|
+
# new_name - The new name of the file.
|
87
|
+
#
|
88
|
+
# Examples
|
89
|
+
# klass = SomeClass.new('path/to/file.txt')
|
90
|
+
# klass.rename('rename.txt')
|
91
|
+
# # => 'path/to/rename.txt'
|
92
|
+
#
|
93
|
+
def rename(new_name)
|
94
|
+
new_path = self.directory + '/' + new_name
|
95
|
+
FileUtils.mv(self.filepath, new_path)
|
96
|
+
self.filepath = new_path
|
97
|
+
end
|
98
|
+
|
99
|
+
# Public: This will restore a file from the backup path.
|
100
|
+
# It will raise an error if file has no backup path.
|
101
|
+
#
|
102
|
+
# from_path - The path from which to restore. It defaults
|
103
|
+
# to the #backup_path.
|
104
|
+
#
|
105
|
+
# Examples
|
106
|
+
# klass = SomeClass.new('path/to/file.txt').backup('/tmp')
|
107
|
+
# klass.restore
|
108
|
+
#
|
109
|
+
def restore(from_path=nil)
|
110
|
+
@from_path = from_path
|
111
|
+
FileUtils.mv(restore_path, self.filepath)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Public: This returns a temporary path for the file.
|
115
|
+
#
|
116
|
+
# Examples
|
117
|
+
# klass = SomeClass.new('path/to/file.txt').tmppath
|
118
|
+
# # => '/tmp/file.txt'
|
119
|
+
#
|
120
|
+
def tmppath
|
121
|
+
'/tmp/' + filename
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
def backup_path_error_message
|
126
|
+
'Cannot determine backup path.'
|
127
|
+
end
|
128
|
+
|
129
|
+
def restore_path
|
130
|
+
return backup_path unless @from_path
|
131
|
+
@from_path + '/' + filename
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|