albanpeignier-alsa-backup 0.0.1 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +1 -0
- data/History.txt +11 -2
- data/Manifest.txt +6 -0
- data/README.rdoc +40 -6
- data/Rakefile +8 -1
- data/TODO +4 -0
- data/alsa-backup.gemspec +6 -3
- data/config.sample +19 -0
- data/lib/alsa.rb +31 -12
- data/lib/alsa_backup.rb +4 -10
- data/lib/alsa_backup/cli.rb +12 -0
- data/lib/alsa_backup/core_ext.rb +42 -0
- data/lib/alsa_backup/length_controller.rb +19 -0
- data/lib/alsa_backup/recorder.rb +34 -40
- data/lib/alsa_backup/writer.rb +92 -0
- data/spec/alsa_backup/cli_spec.rb +18 -1
- data/spec/alsa_backup/core_ext_spec.rb +58 -0
- data/spec/alsa_backup/recorder_spec.rb +62 -9
- data/spec/alsa_backup/writer_spec.rb +53 -0
- data/spec/spec_helper.rb +9 -2
- metadata +18 -2
data/.autotest
CHANGED
data/History.txt
CHANGED
@@ -1,4 +1,13 @@
|
|
1
|
+
== 0.0.3
|
2
|
+
|
3
|
+
* Support --background to daemonize process
|
4
|
+
* Support --pidfile option
|
5
|
+
* Rename existing file
|
6
|
+
|
7
|
+
== 0.0.2 2009-05-19
|
8
|
+
|
9
|
+
* Improve capture by support partial reads and recovering errors
|
10
|
+
|
1
11
|
== 0.0.1 2009-05-14
|
2
12
|
|
3
|
-
*
|
4
|
-
* Initial release
|
13
|
+
* Initial release
|
data/Manifest.txt
CHANGED
@@ -4,20 +4,26 @@ Manifest.txt
|
|
4
4
|
PostInstall.txt
|
5
5
|
README.rdoc
|
6
6
|
Rakefile
|
7
|
+
TODO
|
7
8
|
alsa-backup.gemspec
|
8
9
|
bin/alsa.backup
|
9
10
|
config.sample
|
10
11
|
lib/alsa.rb
|
11
12
|
lib/alsa_backup.rb
|
12
13
|
lib/alsa_backup/cli.rb
|
14
|
+
lib/alsa_backup/core_ext.rb
|
15
|
+
lib/alsa_backup/length_controller.rb
|
13
16
|
lib/alsa_backup/recorder.rb
|
17
|
+
lib/alsa_backup/writer.rb
|
14
18
|
lib/sndfile.rb
|
15
19
|
script/console
|
16
20
|
script/destroy
|
17
21
|
script/generate
|
18
22
|
spec/alsa/pcm_spec.rb
|
19
23
|
spec/alsa_backup/cli_spec.rb
|
24
|
+
spec/alsa_backup/core_ext_spec.rb
|
20
25
|
spec/alsa_backup/recorder_spec.rb
|
26
|
+
spec/alsa_backup/writer_spec.rb
|
21
27
|
spec/alsa_backup_spec.rb
|
22
28
|
spec/fixtures/config_test.rb
|
23
29
|
spec/sndfile/info_spec.rb
|
data/README.rdoc
CHANGED
@@ -32,7 +32,43 @@ will load the specified configuration
|
|
32
32
|
|
33
33
|
== CONFIGURATION:
|
34
34
|
|
35
|
-
|
35
|
+
The configuration file is a Ruby file. This piece of code can
|
36
|
+
configurate the AlsaBacup recorder :
|
37
|
+
|
38
|
+
AlsaBackup.config do |recorder|
|
39
|
+
# configure recorder here :
|
40
|
+
recorder.file = "record.wav"
|
41
|
+
end
|
42
|
+
|
43
|
+
=== Recorder File
|
44
|
+
|
45
|
+
The recorder file can be specified as a simple string :
|
46
|
+
|
47
|
+
recorder.file = "record.wav"
|
48
|
+
|
49
|
+
The recorder file can be specified by a Proc which returns the string :
|
50
|
+
|
51
|
+
recorder.file = Proc.new {
|
52
|
+
Time.now.strftime("%Y/%m-%b/%d-%a/%Hh.wav")
|
53
|
+
}
|
54
|
+
|
55
|
+
will use the current time to create file names like these :
|
56
|
+
|
57
|
+
2009/05-May/17-Sun/19h.wav
|
58
|
+
2009/05-May/17-Sun/20h.wav
|
59
|
+
|
60
|
+
To use different files every 15 minutes :
|
61
|
+
|
62
|
+
recorder.file = Proc.new {
|
63
|
+
Time.now.floor(:min, 15).strftime("%Y/%m-%b/%d-%a/%Hh%M.wav")
|
64
|
+
}
|
65
|
+
|
66
|
+
will create files like these :
|
67
|
+
|
68
|
+
2009/05-May/17-Sun/19h00.wav
|
69
|
+
2009/05-May/17-Sun/19h15.wav
|
70
|
+
|
71
|
+
See config.sample.
|
36
72
|
|
37
73
|
== REQUIREMENTS:
|
38
74
|
|
@@ -41,16 +77,14 @@ See config.sample for the moment.
|
|
41
77
|
|
42
78
|
== INSTALL:
|
43
79
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
* sudo gem install --source http://gems.github.com albanpeignier-alsa-backup
|
80
|
+
sudo apt-get install libasound2 libsndfile1
|
81
|
+
sudo gem install --source http://gems.github.com albanpeignier-alsa-backup
|
48
82
|
|
49
83
|
== LICENSE:
|
50
84
|
|
51
85
|
(The MIT License)
|
52
86
|
|
53
|
-
Copyright (c) 2009
|
87
|
+
Copyright (c) 2009 Alban Peignier
|
54
88
|
|
55
89
|
Permission is hereby granted, free of charge, to any person obtaining
|
56
90
|
a copy of this software and associated documentation files (the
|
data/Rakefile
CHANGED
@@ -9,7 +9,7 @@ $hoe = Hoe.new('alsa-backup', AlsaBackup::VERSION) do |p|
|
|
9
9
|
p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
|
10
10
|
p.rubyforge_name = p.name # TODO this is default value
|
11
11
|
p.extra_deps = [
|
12
|
-
['ffi','>= 0.3.5'],
|
12
|
+
['ffi','>= 0.3.5'], ['newgem', ">= #{::Newgem::VERSION}"]
|
13
13
|
]
|
14
14
|
p.extra_dev_deps = [
|
15
15
|
['newgem', ">= #{::Newgem::VERSION}"]
|
@@ -25,3 +25,10 @@ require 'newgem/tasks' # load /tasks/*.rake
|
|
25
25
|
Dir['tasks/**/*.rake'].each { |t| load t }
|
26
26
|
|
27
27
|
task :default => :spec
|
28
|
+
|
29
|
+
namespace :gems do
|
30
|
+
task :install do
|
31
|
+
gems = %w{activesupport ffi rspec}
|
32
|
+
sh "sudo gem install #{gems.join(' ')}"
|
33
|
+
end
|
34
|
+
end
|
data/TODO
ADDED
data/alsa-backup.gemspec
CHANGED
@@ -2,17 +2,17 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{alsa-backup}
|
5
|
-
s.version = "0.0.
|
5
|
+
s.version = "0.0.3"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Alban Peignier"]
|
9
|
-
s.date = %q{2009-05-
|
9
|
+
s.date = %q{2009-05-21}
|
10
10
|
s.default_executable = %q{alsa.backup}
|
11
11
|
s.description = %q{ALSA client to perform continuous recording}
|
12
12
|
s.email = ["alban.peignier@free.fr"]
|
13
13
|
s.executables = ["alsa.backup"]
|
14
14
|
s.extra_rdoc_files = ["History.txt", "Manifest.txt", "PostInstall.txt", "README.rdoc"]
|
15
|
-
s.files = [".autotest", "History.txt", "Manifest.txt", "PostInstall.txt", "README.rdoc", "Rakefile", "alsa-backup.gemspec", "bin/alsa.backup", "config.sample", "lib/alsa.rb", "lib/alsa_backup.rb", "lib/alsa_backup/cli.rb", "lib/alsa_backup/recorder.rb", "lib/sndfile.rb", "script/console", "script/destroy", "script/generate", "spec/alsa/pcm_spec.rb", "spec/alsa_backup/cli_spec.rb", "spec/alsa_backup/recorder_spec.rb", "spec/alsa_backup_spec.rb", "spec/fixtures/config_test.rb", "spec/sndfile/info_spec.rb", "spec/spec.opts", "spec/spec_helper.rb", "tasks/rspec.rake"]
|
15
|
+
s.files = [".autotest", "History.txt", "Manifest.txt", "PostInstall.txt", "README.rdoc", "Rakefile", "TODO", "alsa-backup.gemspec", "bin/alsa.backup", "config.sample", "lib/alsa.rb", "lib/alsa_backup.rb", "lib/alsa_backup/cli.rb", "lib/alsa_backup/core_ext.rb", "lib/alsa_backup/length_controller.rb", "lib/alsa_backup/recorder.rb", "lib/alsa_backup/writer.rb", "lib/sndfile.rb", "script/console", "script/destroy", "script/generate", "spec/alsa/pcm_spec.rb", "spec/alsa_backup/cli_spec.rb", "spec/alsa_backup/core_ext_spec.rb", "spec/alsa_backup/recorder_spec.rb", "spec/alsa_backup/writer_spec.rb", "spec/alsa_backup_spec.rb", "spec/fixtures/config_test.rb", "spec/sndfile/info_spec.rb", "spec/spec.opts", "spec/spec_helper.rb", "tasks/rspec.rake"]
|
16
16
|
s.has_rdoc = true
|
17
17
|
s.homepage = %q{http://github.com/alban.peignier/alsa-backup}
|
18
18
|
s.rdoc_options = ["--main", "README.rdoc"]
|
@@ -27,16 +27,19 @@ Gem::Specification.new do |s|
|
|
27
27
|
|
28
28
|
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
29
29
|
s.add_runtime_dependency(%q<ffi>, [">= 0.3.5"])
|
30
|
+
s.add_runtime_dependency(%q<newgem>, [">= 1.4.1"])
|
30
31
|
s.add_development_dependency(%q<newgem>, [">= 1.4.1"])
|
31
32
|
s.add_development_dependency(%q<hoe>, [">= 1.8.0"])
|
32
33
|
else
|
33
34
|
s.add_dependency(%q<ffi>, [">= 0.3.5"])
|
34
35
|
s.add_dependency(%q<newgem>, [">= 1.4.1"])
|
36
|
+
s.add_dependency(%q<newgem>, [">= 1.4.1"])
|
35
37
|
s.add_dependency(%q<hoe>, [">= 1.8.0"])
|
36
38
|
end
|
37
39
|
else
|
38
40
|
s.add_dependency(%q<ffi>, [">= 0.3.5"])
|
39
41
|
s.add_dependency(%q<newgem>, [">= 1.4.1"])
|
42
|
+
s.add_dependency(%q<newgem>, [">= 1.4.1"])
|
40
43
|
s.add_dependency(%q<hoe>, [">= 1.8.0"])
|
41
44
|
end
|
42
45
|
end
|
data/config.sample
CHANGED
@@ -38,6 +38,25 @@ AlsaBackup.config do |recorder|
|
|
38
38
|
# Define where the files are created
|
39
39
|
#
|
40
40
|
#recorder.directory = "/var/lib/alsa.backup"
|
41
|
+
|
42
|
+
#
|
43
|
+
# Customize error handler during record
|
44
|
+
#
|
45
|
+
|
46
|
+
# To sleep 30 seconds on error
|
47
|
+
#recorder.error_handler = Proc.new do |exception|
|
48
|
+
# 30
|
49
|
+
#end
|
50
|
+
|
51
|
+
# To fail on any error
|
52
|
+
#recorder.error_handler = Proc.new do |exception|
|
53
|
+
# false
|
54
|
+
#end
|
55
|
+
|
56
|
+
# To sleep the default time
|
57
|
+
#recorder.error_handler = Proc.new do |exception|
|
58
|
+
# true
|
59
|
+
#end
|
41
60
|
end
|
42
61
|
|
43
62
|
|
data/lib/alsa.rb
CHANGED
@@ -18,7 +18,7 @@ module ALSA
|
|
18
18
|
|
19
19
|
def self.try_to(message, &block)
|
20
20
|
logger.debug('alsa') { message }
|
21
|
-
if (response = yield)
|
21
|
+
if ALSA::Native::error_code?(response = yield)
|
22
22
|
raise "cannot #{message} (#{ALSA::Native::strerror(response)})"
|
23
23
|
else
|
24
24
|
response
|
@@ -30,6 +30,10 @@ module ALSA
|
|
30
30
|
ffi_lib "libasound.so"
|
31
31
|
|
32
32
|
attach_function :strerror, :snd_strerror, [:int], :string
|
33
|
+
|
34
|
+
def self.error_code?(response)
|
35
|
+
response < 0
|
36
|
+
end
|
33
37
|
end
|
34
38
|
|
35
39
|
module PCM
|
@@ -77,6 +81,7 @@ module ALSA
|
|
77
81
|
def hardware_parameters
|
78
82
|
HwParameters.new(self).current_for_device
|
79
83
|
end
|
84
|
+
alias_method :hw_params, :hardware_parameters
|
80
85
|
|
81
86
|
def hardware_parameters=(attributes= {})
|
82
87
|
attributes = {:access => :rw_interleaved}.update(attributes)
|
@@ -86,21 +91,33 @@ module ALSA
|
|
86
91
|
end
|
87
92
|
|
88
93
|
def read
|
89
|
-
frame_count =
|
90
|
-
|
94
|
+
frame_count = hw_params.sample_rate / 2
|
95
|
+
|
96
|
+
FFI::MemoryPointer.new(:char, hw_params.buffer_size_for(frame_count)) do |buffer|
|
97
|
+
begin
|
98
|
+
read_buffer buffer, frame_count
|
99
|
+
end while yield buffer, frame_count
|
100
|
+
end
|
101
|
+
end
|
91
102
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
103
|
+
def read_buffer(buffer, frame_count)
|
104
|
+
read_count = ALSA::try_to "read from audio interface" do
|
105
|
+
response = ALSA::PCM::Native::readi(self.handle, buffer, frame_count)
|
106
|
+
if ALSA::Native::error_code?(response)
|
107
|
+
ALSA.logger.debug('alsa') { "try to recover '#{ALSA::Native::strerror(response)}' on read"}
|
108
|
+
ALSA::PCM::Native::pcm_recover(self.handle, response, 1)
|
109
|
+
else
|
110
|
+
response
|
96
111
|
end
|
97
|
-
|
98
|
-
raise "can't read expected frame count (#{read_count}/#{frame_count})" unless read_count == frame_count
|
99
|
-
|
100
|
-
continue = yield buffer, read_count
|
101
112
|
end
|
102
113
|
|
103
|
-
|
114
|
+
missing_frame_count = frame_count - read_count
|
115
|
+
if missing_frame_count > 0
|
116
|
+
ALSA.logger.debug('alsa') { "re-read missing frame count: #{missing_frame_count}"}
|
117
|
+
read_buffer_size = hw_params.buffer_size_for(read_count)
|
118
|
+
# buffer[read_buffer_size] doesn't return a MemoryPointer
|
119
|
+
read_buffer(buffer + read_buffer_size, missing_frame_count)
|
120
|
+
end
|
104
121
|
end
|
105
122
|
|
106
123
|
def close
|
@@ -242,6 +259,8 @@ module ALSA
|
|
242
259
|
|
243
260
|
attach_function :readi, :snd_pcm_readi, [ :pointer, :pointer, :ulong ], :long
|
244
261
|
|
262
|
+
attach_function :pcm_recover, :snd_pcm_recover, [ :pointer, :int, :int ], :int
|
263
|
+
|
245
264
|
attach_function :hw_params_malloc, :snd_pcm_hw_params_malloc, [:pointer], :int
|
246
265
|
attach_function :hw_params_free, :snd_pcm_hw_params_free, [:pointer], :int
|
247
266
|
|
data/lib/alsa_backup.rb
CHANGED
@@ -10,7 +10,7 @@ require 'activesupport'
|
|
10
10
|
require 'logger'
|
11
11
|
|
12
12
|
module AlsaBackup
|
13
|
-
VERSION = '0.0.
|
13
|
+
VERSION = '0.0.3'
|
14
14
|
|
15
15
|
def self.recorder
|
16
16
|
@recorder ||= AlsaBackup::Recorder.new
|
@@ -33,15 +33,9 @@ module AlsaBackup
|
|
33
33
|
|
34
34
|
end
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
actual = self.send(attribute)
|
40
|
-
self.change(attribute => actual - actual%modulo)
|
41
|
-
end
|
42
|
-
|
43
|
-
end
|
44
|
-
|
36
|
+
require 'alsa_backup/core_ext'
|
37
|
+
require 'alsa_backup/length_controller'
|
38
|
+
require 'alsa_backup/writer'
|
45
39
|
require 'alsa_backup/recorder'
|
46
40
|
|
47
41
|
|
data/lib/alsa_backup/cli.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'optparse'
|
2
|
+
require 'daemons'
|
2
3
|
|
3
4
|
module AlsaBackup
|
4
5
|
class CLI
|
@@ -23,6 +24,10 @@ module AlsaBackup
|
|
23
24
|
"Base directory") { |arg| options[:directory] = arg }
|
24
25
|
opts.on("-c", "--config=CONFIG", String,
|
25
26
|
"Configuration file") { |arg| options[:config] = arg }
|
27
|
+
opts.on("-p", "--pid=PID_FILE", String,
|
28
|
+
"File to write the process pid") { |arg| options[:pid] = arg }
|
29
|
+
opts.on("-b", "--background", nil,
|
30
|
+
"Daemonize the process") { |arg| options[:daemonize] = true }
|
26
31
|
opts.on("-h", "--help",
|
27
32
|
"Show this help message.") { stdout.puts opts; exit }
|
28
33
|
opts.parse!(arguments)
|
@@ -36,9 +41,16 @@ module AlsaBackup
|
|
36
41
|
|
37
42
|
AlsaBackup.recorder.file = options[:file] if options[:file]
|
38
43
|
AlsaBackup.recorder.directory = options[:directory] if options[:directory]
|
44
|
+
|
45
|
+
pid_file = File.expand_path(options[:pid]) if options[:pid]
|
46
|
+
|
47
|
+
Daemonize.daemonize(nil, "alsa.backup") if options[:daemonize]
|
48
|
+
File.write(pid_file, $$) if pid_file
|
39
49
|
|
40
50
|
length = options[:length].to_i if options[:length]
|
41
51
|
AlsaBackup.recorder.start(length)
|
52
|
+
rescue Exception => e
|
53
|
+
AlsaBackup.logger.fatal(e)
|
42
54
|
end
|
43
55
|
end
|
44
56
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class Time
|
2
|
+
|
3
|
+
def floor(attribute, modulo)
|
4
|
+
actual = self.send(attribute)
|
5
|
+
self.change(attribute => actual - actual%modulo)
|
6
|
+
end
|
7
|
+
|
8
|
+
end
|
9
|
+
|
10
|
+
class File
|
11
|
+
|
12
|
+
def self.extension(file)
|
13
|
+
if file =~ /(\.[^.]*)$/
|
14
|
+
$1
|
15
|
+
else
|
16
|
+
""
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.suffix_basename(file, suffix)
|
21
|
+
dirname = File.dirname(file)
|
22
|
+
|
23
|
+
dirname =
|
24
|
+
case dirname
|
25
|
+
when "/": "/"
|
26
|
+
when ".": ""
|
27
|
+
else
|
28
|
+
dirname + "/"
|
29
|
+
end
|
30
|
+
|
31
|
+
extension = File.extension(file)
|
32
|
+
dirname +
|
33
|
+
File.basename(file, extension) +
|
34
|
+
suffix +
|
35
|
+
extension
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.write(file, content)
|
39
|
+
File.open(file, "w") { |f| f.puts content }
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module AlsaBackup
|
2
|
+
module LengthController
|
3
|
+
class Loop
|
4
|
+
def continue_after?(frame_count)
|
5
|
+
true
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class FrameCount
|
10
|
+
def initialize(frame_count)
|
11
|
+
@frame_count = frame_count
|
12
|
+
end
|
13
|
+
|
14
|
+
def continue_after?(frame_count)
|
15
|
+
(@frame_count -= frame_count) > 0
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/alsa_backup/recorder.rb
CHANGED
@@ -1,71 +1,65 @@
|
|
1
1
|
require 'alsa'
|
2
|
-
require 'sndfile'
|
3
|
-
|
4
|
-
require 'fileutils'
|
5
2
|
|
6
3
|
module AlsaBackup
|
7
4
|
class Recorder
|
8
5
|
|
9
6
|
def initialize(file = "record.wav")
|
10
|
-
@file = file
|
11
|
-
@directory =
|
7
|
+
@file = File.basename(file)
|
8
|
+
@directory = File.dirname(file)
|
9
|
+
|
10
|
+
@error_handler = Proc.new { |e| true }
|
12
11
|
end
|
13
12
|
|
14
|
-
attr_accessor :file, :directory
|
13
|
+
attr_accessor :file, :directory, :error_handler
|
15
14
|
|
16
15
|
def start(seconds_to_record = nil)
|
17
|
-
|
18
|
-
|
19
|
-
# prepare sndfile
|
20
|
-
self.sndfile
|
16
|
+
length_controller = self.length_controller(seconds_to_record)
|
21
17
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
else
|
28
|
-
true
|
18
|
+
Writer.open(directory, file, format(:format => "wav pcm_16")) do |writer|
|
19
|
+
ALSA::PCM::Capture.open("hw:0", self.format(:sample_format => :s16_le)) do |capture|
|
20
|
+
capture.read do |buffer, frame_count|
|
21
|
+
writer.write buffer, frame_count
|
22
|
+
length_controller.continue_after? frame_count
|
29
23
|
end
|
30
24
|
end
|
31
25
|
end
|
26
|
+
rescue Interrupt
|
27
|
+
AlsaBackup.logger.debug('recorder interrupted')
|
32
28
|
rescue Exception => e
|
33
29
|
AlsaBackup.logger.error(e)
|
34
|
-
|
35
|
-
ensure
|
36
|
-
@sndfile.close if @sndfile
|
37
|
-
end
|
30
|
+
AlsaBackup.logger.debug { e.backtrace.join("\n") }
|
38
31
|
|
39
|
-
|
40
|
-
|
41
|
-
when Proc
|
42
|
-
@file.call
|
32
|
+
if seconds_to_record.nil? and continue_on_error?(e)
|
33
|
+
retry
|
43
34
|
else
|
44
|
-
|
35
|
+
raise e
|
45
36
|
end
|
46
37
|
end
|
47
38
|
|
48
|
-
def
|
49
|
-
|
39
|
+
def continue_on_error?(e)
|
40
|
+
error_handler_response = @error_handler.call(e) if @error_handler
|
41
|
+
|
42
|
+
if error_handler_response
|
43
|
+
sleep_time = Numeric === error_handler_response ? error_handler_response : 5
|
44
|
+
AlsaBackup.logger.warn("sleep #{sleep_time}s before retrying")
|
45
|
+
sleep sleep_time
|
46
|
+
end
|
47
|
+
|
48
|
+
error_handler_response
|
50
49
|
end
|
51
50
|
|
52
51
|
def format(additional_parameters = {})
|
53
52
|
{:sample_rate => 44100, :channels => 2}.merge(additional_parameters)
|
54
53
|
end
|
55
54
|
|
56
|
-
def
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
@sndfile.close if @sndfile
|
62
|
-
AlsaBackup.logger.info "new file #{target_file}"
|
63
|
-
|
64
|
-
FileUtils.mkdir_p File.dirname(target_file)
|
65
|
-
@sndfile = Sndfile::File.new(target_file, "w", self.format(:format => "wav pcm_16"))
|
55
|
+
def length_controller(seconds_to_record)
|
56
|
+
if seconds_to_record
|
57
|
+
AlsaBackup::LengthController::FrameCount.new format[:sample_rate] * seconds_to_record
|
58
|
+
else
|
59
|
+
AlsaBackup::LengthController::Loop.new
|
66
60
|
end
|
67
|
-
@sndfile
|
68
61
|
end
|
69
|
-
|
62
|
+
|
70
63
|
end
|
64
|
+
|
71
65
|
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'sndfile'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module AlsaBackup
|
5
|
+
class Writer
|
6
|
+
|
7
|
+
attr_accessor :directory, :file, :format
|
8
|
+
|
9
|
+
def self.default_format
|
10
|
+
{:sample_rate => 44100, :channels => 2, :format => "wav pcm_16"}
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(directory, file, format = Writer.default_format)
|
14
|
+
@directory = directory
|
15
|
+
@file = file
|
16
|
+
@format = format
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.open(directory, file, format, &block)
|
20
|
+
writer = Writer.new(directory, file, format).prepare
|
21
|
+
|
22
|
+
begin
|
23
|
+
yield writer
|
24
|
+
ensure
|
25
|
+
writer.close
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def prepare
|
30
|
+
# prepare sndfile
|
31
|
+
self.sndfile
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def write(*arguments)
|
36
|
+
self.sndfile.write *arguments
|
37
|
+
end
|
38
|
+
|
39
|
+
def close
|
40
|
+
if @sndfile
|
41
|
+
AlsaBackup.logger.info('close current file')
|
42
|
+
@sndfile.close
|
43
|
+
end
|
44
|
+
@sndfile = nil
|
45
|
+
end
|
46
|
+
|
47
|
+
def file
|
48
|
+
case @file
|
49
|
+
when Proc
|
50
|
+
@file.call
|
51
|
+
else
|
52
|
+
@file
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def target_file
|
57
|
+
File.join self.directory, self.file
|
58
|
+
end
|
59
|
+
|
60
|
+
def sndfile
|
61
|
+
target_file = self.target_file
|
62
|
+
raise "no recording file" unless target_file
|
63
|
+
|
64
|
+
unless @sndfile and @sndfile.path == target_file
|
65
|
+
@sndfile.close if @sndfile
|
66
|
+
|
67
|
+
Writer.rename_existing_file(target_file)
|
68
|
+
AlsaBackup.logger.info{"new file #{File.expand_path target_file}"}
|
69
|
+
|
70
|
+
FileUtils.mkdir_p File.dirname(target_file)
|
71
|
+
@sndfile = Sndfile::File.new(target_file, "w", self.format)
|
72
|
+
end
|
73
|
+
@sndfile
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.rename_existing_file(file)
|
77
|
+
if File.exists?(file)
|
78
|
+
index = 1
|
79
|
+
|
80
|
+
while File.exists?(new_file = File.suffix_basename(file, "-#{index}"))
|
81
|
+
index += 1
|
82
|
+
|
83
|
+
raise "can't find a free file for #{file}" if index > 1000
|
84
|
+
end
|
85
|
+
|
86
|
+
AlsaBackup.logger.warn "rename existing file #{File.basename(file)} into #{new_file}"
|
87
|
+
File.rename(file, new_file)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
@@ -15,7 +15,12 @@ describe AlsaBackup::CLI, "execute" do
|
|
15
15
|
def execute_cli(options = {})
|
16
16
|
options = { :file => @file, :length => 2 }.update(options)
|
17
17
|
arguments = options.collect do |key,value|
|
18
|
-
|
18
|
+
case value
|
19
|
+
when true: "--#{key}"
|
20
|
+
when nil: nil
|
21
|
+
else
|
22
|
+
"--#{key}=#{value}"
|
23
|
+
end
|
19
24
|
end.compact
|
20
25
|
|
21
26
|
AlsaBackup::CLI.execute(@stdout_io, *arguments)
|
@@ -59,4 +64,16 @@ describe AlsaBackup::CLI, "execute" do
|
|
59
64
|
AlsaBackup.recorder.file.should == argument_file
|
60
65
|
end
|
61
66
|
|
67
|
+
it "should write pid in specified file" do
|
68
|
+
pid_file = test_file("pid")
|
69
|
+
execute_cli :pid => pid_file
|
70
|
+
|
71
|
+
IO.read(pid_file).strip.should == $$.to_s
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should write pid in specified file" do
|
75
|
+
Daemonize.should_receive(:daemonize)
|
76
|
+
execute_cli :background => true
|
77
|
+
end
|
78
|
+
|
62
79
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper.rb'
|
2
|
+
|
3
|
+
describe Time do
|
4
|
+
|
5
|
+
describe "floor" do
|
6
|
+
|
7
|
+
it "should change xh41 on xh49 for min with a modulo of 10" do
|
8
|
+
time = Time.now.change(:min => 49)
|
9
|
+
time.floor(:min, 10).should == time.change(:min => 40)
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
describe File do
|
17
|
+
|
18
|
+
describe "extension" do
|
19
|
+
|
20
|
+
it "should be .rb for test.rb" do
|
21
|
+
File.extension('test.rb').should == '.rb'
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should be '.' for 'test.'" do
|
25
|
+
File.extension('test.').should == '.'
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should be blank for 'test'" do
|
29
|
+
File.extension('test').should be_blank
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "suffix_basename" do
|
35
|
+
|
36
|
+
def take_this_form(expected_file)
|
37
|
+
simple_matcher("have this #{expected_file}") do |actual|
|
38
|
+
File.suffix_basename(actual,'<prefix>') == expected_file
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
it { 'test.rb'.should take_this_form('test<prefix>.rb') }
|
43
|
+
|
44
|
+
it "should return test<prefix> for test" do
|
45
|
+
File.suffix_basename('test','<prefix>').should == 'test<prefix>'
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should return /path/test<prefix>.rb for /patest" do
|
49
|
+
File.suffix_basename('/path/test.rb','<prefix>').should == '/path/test<prefix>.rb'
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should return /test<prefix>.rb for /test.rb" do
|
53
|
+
File.suffix_basename('/path/test.rb','<prefix>').should == '/path/test<prefix>.rb'
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
@@ -13,19 +13,72 @@ describe AlsaBackup::Recorder do
|
|
13
13
|
lambda { @recorder.start(2) }.should_not raise_error
|
14
14
|
end
|
15
15
|
|
16
|
-
describe "
|
16
|
+
describe "error handler" do
|
17
|
+
|
18
|
+
class TestErrorHandler
|
19
|
+
|
20
|
+
def initialize(proc)
|
21
|
+
proc = Proc.new { |e| proc } unless Proc == proc
|
22
|
+
@proc = proc
|
23
|
+
end
|
24
|
+
|
25
|
+
def call(e)
|
26
|
+
if @proc
|
27
|
+
response = @proc.call(e)
|
28
|
+
@proc = nil
|
29
|
+
response
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
17
34
|
|
18
|
-
|
19
|
-
|
20
|
-
@recorder.
|
35
|
+
before(:each) do
|
36
|
+
AlsaBackup::Writer.stub!(:open).and_raise("dummy")
|
37
|
+
@recorder.stub!(:sleep)
|
21
38
|
end
|
22
39
|
|
23
|
-
it "should
|
24
|
-
|
25
|
-
@recorder.
|
26
|
-
@recorder.file.should == file_name
|
40
|
+
it "should raise error when error handler is nil" do
|
41
|
+
@recorder.error_handler = nil
|
42
|
+
lambda { @recorder.start }.should raise_error
|
27
43
|
end
|
28
|
-
|
44
|
+
|
45
|
+
it "should raise error when error handler returns nil or false" do
|
46
|
+
@recorder.error_handler = TestErrorHandler.new(nil)
|
47
|
+
lambda { @recorder.start }.should raise_error
|
48
|
+
end
|
49
|
+
|
50
|
+
def start_recorder(limit = nil)
|
51
|
+
@recorder.start(limit)
|
52
|
+
rescue RuntimeError
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should retry when error handler returns something (not false or nil)" do
|
57
|
+
@recorder.error_handler = TestErrorHandler.new(true)
|
58
|
+
AlsaBackup::Writer.should_receive(:open).twice().and_raise("dummy")
|
59
|
+
|
60
|
+
start_recorder
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should use the error handler response as sleep time if numerical" do
|
64
|
+
@recorder.error_handler = TestErrorHandler.new(error_handler_response = 5)
|
65
|
+
@recorder.should_receive(:sleep).with(error_handler_response)
|
66
|
+
start_recorder
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should sleep 5 seconds when the error handler response is a number" do
|
70
|
+
@recorder.error_handler = TestErrorHandler.new(true)
|
71
|
+
@recorder.should_receive(:sleep).with(5)
|
72
|
+
start_recorder
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should not use error handler when recorder is started with a time length" do
|
76
|
+
@recorder.error_handler = mock("error_handler")
|
77
|
+
@recorder.error_handler.should_not_receive(:call)
|
78
|
+
|
79
|
+
start_recorder(2)
|
80
|
+
end
|
81
|
+
|
29
82
|
end
|
30
83
|
|
31
84
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper.rb'
|
2
|
+
|
3
|
+
describe AlsaBackup::Writer do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@file = "test.wav"
|
7
|
+
@directory = test_directory
|
8
|
+
@recorder = AlsaBackup::Writer.new(@file, @directory)
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "file" do
|
12
|
+
|
13
|
+
it "should accept file as string" do
|
14
|
+
@recorder.file = file_name = "dummy"
|
15
|
+
@recorder.file.should == file_name
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should accept file as Proc" do
|
19
|
+
file_name = "dummy"
|
20
|
+
@recorder.file = Proc.new { file_name }
|
21
|
+
@recorder.file.should == file_name
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "rename_existing_file" do
|
27
|
+
|
28
|
+
it "should keep file if not exists" do
|
29
|
+
File.should_receive(:exists?).with(@file).and_return(false)
|
30
|
+
File.should_not_receive(:rename)
|
31
|
+
AlsaBackup::Writer.rename_existing_file(@file)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should try to suffix with '-n' to find a free name" do
|
35
|
+
File.stub!(:exists?).and_return(true)
|
36
|
+
|
37
|
+
free_file = File.suffix_basename(@file, "-99")
|
38
|
+
File.should_receive(:exists?).with(free_file).and_return(false)
|
39
|
+
|
40
|
+
File.should_receive(:rename).with(@file, free_file)
|
41
|
+
AlsaBackup::Writer.rename_existing_file(@file)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should raise an error when no free file is found" do
|
45
|
+
File.stub!(:exists?).and_return(true)
|
46
|
+
lambda do
|
47
|
+
AlsaBackup::Writer.rename_existing_file(@file)
|
48
|
+
end.should raise_error
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -9,13 +9,20 @@ end
|
|
9
9
|
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
10
10
|
require 'alsa_backup'
|
11
11
|
|
12
|
+
def test_directory
|
13
|
+
File.dirname(__FILE__) + '/../tmp'
|
14
|
+
end
|
15
|
+
|
12
16
|
def test_file(name = 'test.wav')
|
13
|
-
File.
|
17
|
+
File.join(test_directory, name)
|
14
18
|
end
|
15
19
|
|
16
20
|
def fixture_file(name)
|
17
21
|
File.dirname(__FILE__) + '/fixtures/' + name
|
18
22
|
end
|
19
23
|
|
24
|
+
log_dir = File.dirname(__FILE__) + '/../log/'
|
25
|
+
Dir.mkdir(log_dir) unless File.exists?(log_dir)
|
26
|
+
|
20
27
|
ALSA::logger = AlsaBackup.logger =
|
21
|
-
Logger.new(File.
|
28
|
+
Logger.new(File.join(log_dir,'test.log'))
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: albanpeignier-alsa-backup
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alban Peignier
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-05-
|
12
|
+
date: 2009-05-21 00:00:00 -07:00
|
13
13
|
default_executable: alsa.backup
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -22,6 +22,16 @@ dependencies:
|
|
22
22
|
- !ruby/object:Gem::Version
|
23
23
|
version: 0.3.5
|
24
24
|
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: newgem
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.4.1
|
34
|
+
version:
|
25
35
|
- !ruby/object:Gem::Dependency
|
26
36
|
name: newgem
|
27
37
|
type: :development
|
@@ -61,20 +71,26 @@ files:
|
|
61
71
|
- PostInstall.txt
|
62
72
|
- README.rdoc
|
63
73
|
- Rakefile
|
74
|
+
- TODO
|
64
75
|
- alsa-backup.gemspec
|
65
76
|
- bin/alsa.backup
|
66
77
|
- config.sample
|
67
78
|
- lib/alsa.rb
|
68
79
|
- lib/alsa_backup.rb
|
69
80
|
- lib/alsa_backup/cli.rb
|
81
|
+
- lib/alsa_backup/core_ext.rb
|
82
|
+
- lib/alsa_backup/length_controller.rb
|
70
83
|
- lib/alsa_backup/recorder.rb
|
84
|
+
- lib/alsa_backup/writer.rb
|
71
85
|
- lib/sndfile.rb
|
72
86
|
- script/console
|
73
87
|
- script/destroy
|
74
88
|
- script/generate
|
75
89
|
- spec/alsa/pcm_spec.rb
|
76
90
|
- spec/alsa_backup/cli_spec.rb
|
91
|
+
- spec/alsa_backup/core_ext_spec.rb
|
77
92
|
- spec/alsa_backup/recorder_spec.rb
|
93
|
+
- spec/alsa_backup/writer_spec.rb
|
78
94
|
- spec/alsa_backup_spec.rb
|
79
95
|
- spec/fixtures/config_test.rb
|
80
96
|
- spec/sndfile/info_spec.rb
|