albanpeignier-alsa-backup 0.0.1 → 0.0.3
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/.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
|