lossfully 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.bnsignore +19 -0
- data/.gitmodules +3 -0
- data/CHANGELOG +4 -0
- data/COPYING +674 -0
- data/README.rdoc +96 -0
- data/Rakefile +27 -0
- data/lib/lossfully/audio_file.rb +134 -0
- data/lib/lossfully/generator.rb +614 -0
- data/lib/lossfully/input_rules.rb +176 -0
- data/lib/lossfully/thread_pool.rb +162 -0
- data/lib/lossfully.rb +79 -0
- data/test/test_audio_file.rb +47 -0
- data/test/test_input_rules.rb +125 -0
- data/test/test_thread_pool.rb +77 -0
- data/version.txt +1 -0
- metadata +100 -0
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
|