lossfully 0.0.0

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