lossfully 0.0.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.
data/README.rdoc ADDED
@@ -0,0 +1,96 @@
1
+ = lossfully
2
+
3
+ Smartly generate transcoded (lossy or otherwise) versions of your main music
4
+ library.
5
+
6
+ == Examples
7
+
8
+ This is not your average, "drag a bunch of folders on me and press go"
9
+ audio converter. You need to write a file that points to your library
10
+ and describes how you want to generate a second library. Let's start
11
+ with:
12
+
13
+ require 'lossfully'
14
+
15
+ Lossfully.generate '~/share/music/' => '~/share/music_lossy' do
16
+ encode '.ogg'
17
+ end
18
+
19
+ That will copy everything from ~/share/music to ~/share/music_lossy,
20
+ encoding all of the non-vorbis audio files to vorbis. So far, this is
21
+ something any of those GUI programs can do. But maybe you don't want
22
+ to convert your lossy formats into oggs, lest the gods of information
23
+ theory smite you for your transcoding wickedness. Well, no problem.
24
+ This converts any wav, flac, or wv files found into ogg, but copies
25
+ the rest without modification:
26
+
27
+ Lossfully.generate '~/share/music/' => '~/share/music_lossy' do
28
+ encode :lossless => '.ogg'
29
+ end
30
+
31
+ But maybe this new library is going on your portable device, so you
32
+ can't take all of your music and you need to shrink some of the lossy
33
+ files, too. How about just taking the songs from a playlist, and
34
+ reencoding the lossy files that have an average bitrate over 192 kbps:
35
+
36
+ Lossfully.generate '~/share/music/awesome.m3u' => '~/share/music_lossy' do
37
+ encode :lossless => ['.ogg', 4] # use quality 4 for the encoding
38
+ encode [:lossy, 192] => ['.ogg', 4]
39
+ end
40
+
41
+ You see, where this is going, right? Maybe something like:
42
+
43
+ Lossfully.generate '~/share/music/awesome.m3u' => '~/share/music_lossy' do
44
+ clobber :rename
45
+ threads 2
46
+ remove_missing true
47
+
48
+ skip /\.cue/
49
+ skip /\.jpg/
50
+
51
+ encode :lossless => 'flac'
52
+ encode [:ogg, 192] => ['.ogg', 4]
53
+ encode [:mp3, 300] => ['.mp3', -192.2]
54
+ encode :lossy => 'ogg'
55
+ end
56
+
57
+ For more details, check out the documentation of Lossfully::Generator.
58
+
59
+ == Requirements
60
+
61
+ * A copy of sox must be found in your path, which is what actually
62
+ does the transcoding. There is currently no check to see if your
63
+ version of sox is compiled with LAME (support for MP3s, etc). When
64
+ confronted with audio files that sox can't handle, Lossfully might
65
+ treat them as nonaudio files, skip them silently, or crash. Who
66
+ knows.
67
+
68
+ * Lots of audio files.
69
+
70
+ == Install
71
+
72
+ * gem install lossfully
73
+
74
+ == Author
75
+
76
+ Original author: Don March <don@ohspite.net>
77
+
78
+ == License
79
+
80
+ Copyright (C) 2011 Don March.
81
+
82
+ Licensed under the GNU General Public License.
83
+
84
+ Lossfully is free software: you can redistribute it and/or modify it
85
+ under the terms of the GNU General Public License as published by the
86
+ Free Software Foundation, either version 3 of the License, or (at your
87
+ option) any later version.
88
+
89
+ Lossfully is distributed in the hope that it will be useful, but
90
+ WITHOUT ANY WARRANTY; without even the implied warranty of
91
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
92
+ General Public License for more details.
93
+
94
+ You should have received a copy of the GNU General Public License
95
+ along with this program. If not, see
96
+ <http://www.gnu.org/licenses/>.
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ begin
2
+ require 'bones'
3
+ rescue LoadError
4
+ abort '### Please install the "bones" gem ###'
5
+ end
6
+
7
+ # ensure_in_path 'lib'
8
+
9
+ task :default => 'test:run'
10
+ task 'gem:release' => 'test:run'
11
+
12
+ Bones do
13
+ name 'lossfully'
14
+ authors 'Don'
15
+ email 'don@ohspite.net'
16
+ url 'https://github.com/ohspite/lossfully'
17
+ history_file 'CHANGELOG'
18
+ readme_file 'README.rdoc'
19
+ rdoc.main 'README.rdoc'
20
+ summary 'Smartly generate transcoded (lossy or not) versions of your main music library.'
21
+ description 'Smartly generate transcoded (lossy or not) versions of your main music library.'
22
+
23
+ ignore_file '.gitignore'
24
+ exclude %w(tmp$ bak$ ~$ CVS \.svn/ \.git/ \.brz/ \.bzrignore ^pkg/ ^coverage/, ^test/data)
25
+ rdoc.include %w(README ^lib/ ^bin/ ^ext/ \.txt$ \.rdoc$)
26
+ end
27
+
@@ -0,0 +1,134 @@
1
+ #--
2
+ # Copyright (C) 2011 Don March
3
+ #
4
+ # This file is part of Lossfully.
5
+ #
6
+ # Lossfully is free software: you can redistribute it and/or modify it
7
+ # under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # Lossfully is distributed in the hope that it will be useful, but
12
+ # WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
+ # General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program. If not, see
18
+ # <http://www.gnu.org/licenses/>.
19
+ #++
20
+
21
+ require 'fileutils'
22
+
23
+ module Lossfully
24
+ class AudioFile
25
+
26
+ private
27
+
28
+ # Raises RuntimeError if +path+ is not a file.
29
+ #
30
+ def self.check_file path
31
+ raise "Does not exist: #{path}" unless File.file? path
32
+ end
33
+
34
+ public
35
+
36
+ def self.soxi_command path, options=''
37
+ check_file path
38
+
39
+ # system("soxi -V0 #{path}")
40
+ return nil if File.extname(path) == '.m3u'
41
+ p = IO.popen("sox --info -V0 #{options} \"#{path}\"")
42
+ return ((Process.wait2 p.pid)[1] == 0) ? p.gets.chomp : nil
43
+ end
44
+
45
+ def self.def_soxi(method, options='')
46
+ class_eval <<-EOM
47
+ def self.#{method} path
48
+ self.soxi_command path, '#{options}'
49
+ end
50
+ EOM
51
+ end
52
+
53
+ # Return the encoding of the file ('soxi -e'). If soxi does not
54
+ # recognize the file as audio, return nil.
55
+ #
56
+ def_soxi :encoding, '-e'
57
+
58
+ # Return the file-type as a symbol ('soxi -t'). If soxi does not
59
+ # recognize the file as audio, return nil.
60
+ #
61
+ def self.type path
62
+ t = soxi_command path, '-t'
63
+ return t ? t.to_sym : nil
64
+ end
65
+ # def_soxi :type, '-t'
66
+
67
+ # Return the bitrate of the file as a string ('soxi -B'). If soxi
68
+ # does not recognize the file as audio, return nil.
69
+ #
70
+ def_soxi :bitrate, '-B'
71
+
72
+ # Return the bitrate of the file as an integer in kbps ('soxi -B'). If soxi
73
+ # does not recognize the file as audio, return nil.
74
+ #
75
+ def self.bitrate_kbps path
76
+ b = bitrate(path)
77
+ return b.to_f * (b[-1..-1] == 'k' ? 1 : 1000)
78
+ end
79
+
80
+ # Return the duration of the file in seconds as a Float
81
+ # ('soxi -D'). If soxi does not recognize the file as audio,
82
+ # return nil.
83
+ #
84
+ def self.duration path
85
+ #(soxi_command path, '-D').to_f
86
+ `soxi -V0 -D \"#{path}\"`.chomp.to_f
87
+ end
88
+
89
+ class << self
90
+ alias :is_audio? :type
91
+ alias :length :duration
92
+ end
93
+
94
+ def self.encode input_path, output_path, options='', effect_options=''
95
+ FileUtils.mkdir_p(File.dirname(output_path)) unless File.directory? output_path
96
+ system("sox \"#{input_path}\" #{options} \"#{output_path}\" #{effect_options}")
97
+ end
98
+
99
+ def initialize path
100
+ @path = path
101
+
102
+ # Actually, let's not do this. This gets checked every time a
103
+ # method is run anyway, so this way we can just use the class as
104
+ # a wrapper around a path string for files that aren't audio.
105
+ #
106
+ # raise "Not recognized as an audio file: #{file}" unless is_audio?
107
+ end
108
+
109
+ attr_reader :path
110
+
111
+ # This could be done with method_missing.
112
+ def self.delegate_and_memoize(method, class_method=nil)
113
+ class_method ||= method
114
+ class_eval <<-EOM
115
+ def #{method}
116
+ @#{method} ||= self.class.#{class_method}(@path)
117
+ end
118
+ EOM
119
+ end
120
+
121
+ def encode output_path, options='', effect_options=''
122
+ self.class.encode @path, output_path, options, effect_options
123
+ end
124
+
125
+ delegate_and_memoize :encoding
126
+ delegate_and_memoize :type
127
+ delegate_and_memoize :bitrate
128
+ delegate_and_memoize :bitrate_kbps
129
+ delegate_and_memoize :duration
130
+
131
+ alias :length :duration
132
+ alias :is_audio? :type
133
+ end
134
+ end