sapis 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/lib/sapis/bash_helper.rb +17 -20
- data/lib/sapis/computations_helper.rb +15 -18
- data/lib/sapis/concurrency_helper.rb +17 -21
- data/lib/sapis/configuration_helper.rb +30 -32
- data/lib/sapis/desktop_helper.rb +6 -8
- data/lib/sapis/generic_helper.rb +7 -7
- data/lib/sapis/gnome_helper.rb +1 -4
- data/lib/sapis/graphing_helper.rb +48 -51
- data/lib/sapis/interactions_helper.rb +27 -30
- data/lib/sapis/multimedia_helper.rb +71 -74
- data/lib/sapis/sqlite_layer.rb +38 -39
- data/lib/sapis/system_helper.rb +27 -30
- data/lib/sapis/version.rb +1 -1
- metadata +19 -5
|
@@ -19,7 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
19
19
|
=end
|
|
20
20
|
|
|
21
21
|
module GraphingHelper
|
|
22
|
-
|
|
23
22
|
require 'gruff'
|
|
24
23
|
require 'tempfile'
|
|
25
24
|
|
|
@@ -33,41 +32,41 @@ module GraphingHelper
|
|
|
33
32
|
# Labels must be unique.
|
|
34
33
|
# Days can be either a string or a Date.
|
|
35
34
|
#
|
|
36
|
-
def self.transpose_data_top_headers(
|
|
37
|
-
fill_missing_days = !
|
|
35
|
+
def self.transpose_data_top_headers(source_data, options={})
|
|
36
|
+
fill_missing_days = !options.has_key?(:fill_missing_days) || !!options[:fill_missing_days]
|
|
38
37
|
|
|
39
|
-
labels = source_data.shift[
|
|
38
|
+
labels = source_data.shift[0...-1]
|
|
40
39
|
|
|
41
|
-
source_data.each { |
|
|
40
|
+
source_data.each { |row| row[row.size - 1] = Date.strptime(row.last) unless row.last.is_a?(Date) }
|
|
42
41
|
|
|
43
|
-
min_day = source_data.map { |
|
|
44
|
-
max_day = source_data.map { |
|
|
42
|
+
min_day = source_data.map { |row| row.last }.min
|
|
43
|
+
max_day = source_data.map { |row| row.last }.max
|
|
45
44
|
|
|
46
45
|
# Pre-fill the output matrix (hash)
|
|
47
46
|
|
|
48
|
-
output_data = labels.inject(
|
|
49
|
-
current_output_data[
|
|
47
|
+
output_data = labels.inject({}) do |current_output_data, label|
|
|
48
|
+
current_output_data[label] = [nil] * (max_day - min_day)
|
|
50
49
|
current_output_data
|
|
51
50
|
end
|
|
52
51
|
|
|
53
52
|
# Fill the values
|
|
54
53
|
|
|
55
|
-
source_data.each do |
|
|
54
|
+
source_data.each do |row|
|
|
56
55
|
day = row.last
|
|
57
56
|
|
|
58
|
-
labels.each_with_index do |
|
|
59
|
-
output_data[
|
|
57
|
+
labels.each_with_index do |label, i|
|
|
58
|
+
output_data[label][ day - min_day ] = row[i]
|
|
60
59
|
end
|
|
61
60
|
end
|
|
62
61
|
|
|
63
62
|
# Apply final manipulations and convert output data to array
|
|
64
63
|
|
|
65
|
-
output_data.each { |
|
|
64
|
+
output_data.each { |label, values| values.compact! } if !fill_missing_days
|
|
66
65
|
|
|
67
|
-
output_data = output_data.map { |
|
|
68
|
-
days = (
|
|
66
|
+
output_data = output_data.map { |label, values| [label, values] }
|
|
67
|
+
days = (min_day..max_day).to_a
|
|
69
68
|
|
|
70
|
-
[
|
|
69
|
+
[output_data, days]
|
|
71
70
|
end
|
|
72
71
|
|
|
73
72
|
# Transpose from format:
|
|
@@ -77,45 +76,45 @@ module GraphingHelper
|
|
|
77
76
|
# label1, value, day3
|
|
78
77
|
# labelN, value, day3
|
|
79
78
|
#
|
|
80
|
-
def self.transpose_data_left_headers(
|
|
81
|
-
source_data.each { |
|
|
79
|
+
def self.transpose_data_left_headers(source_data)
|
|
80
|
+
source_data.each { |row| row[row.size - 1] = Date.strptime(row.last) unless row.last.is_a?(Date) }
|
|
82
81
|
|
|
83
82
|
# Fill a map day => { label => value, ... }
|
|
84
83
|
|
|
85
84
|
data_by_day_by_label = {}
|
|
86
85
|
|
|
87
|
-
source_data.each do |
|
|
88
|
-
day = Date.strptime(
|
|
86
|
+
source_data.each do |label, value, day|
|
|
87
|
+
day = Date.strptime(day) unless day.is_a?(Date)
|
|
89
88
|
|
|
90
|
-
data_by_day_by_label[
|
|
91
|
-
data_by_day_by_label[
|
|
89
|
+
data_by_day_by_label[day] ||= {}
|
|
90
|
+
data_by_day_by_label[day][ label ] = value
|
|
92
91
|
end
|
|
93
92
|
|
|
94
93
|
# Convert to tabular form by label
|
|
95
94
|
|
|
96
|
-
labels = source_data.map { |
|
|
95
|
+
labels = source_data.map { |row| row.first }.uniq
|
|
97
96
|
|
|
98
|
-
output_data = labels.inject(
|
|
99
|
-
current_output_data[
|
|
97
|
+
output_data = labels.inject({}) do |current_output_data, label|
|
|
98
|
+
current_output_data[label] = []
|
|
100
99
|
current_output_data
|
|
101
100
|
end
|
|
102
101
|
|
|
103
102
|
days = data_by_day_by_label.keys.sort
|
|
104
103
|
|
|
105
|
-
days.each do |
|
|
106
|
-
labels_values = data_by_day_by_label[
|
|
104
|
+
days.each do |day|
|
|
105
|
+
labels_values = data_by_day_by_label[day]
|
|
107
106
|
|
|
108
|
-
labels.each do |
|
|
109
|
-
value = labels_values[
|
|
110
|
-
output_data[
|
|
107
|
+
labels.each do |label|
|
|
108
|
+
value = labels_values[label]
|
|
109
|
+
output_data[label] << value
|
|
111
110
|
end
|
|
112
111
|
end
|
|
113
112
|
|
|
114
113
|
# Apply final manipulations and convert output data to array
|
|
115
114
|
|
|
116
|
-
output_data = output_data.map { |
|
|
115
|
+
output_data = output_data.map { |label, values| [label, values] }
|
|
117
116
|
|
|
118
|
-
[
|
|
117
|
+
[output_data, days]
|
|
119
118
|
end
|
|
120
119
|
|
|
121
120
|
# Ouput a line graph, optionally to a file.
|
|
@@ -125,12 +124,12 @@ module GraphingHelper
|
|
|
125
124
|
# options:
|
|
126
125
|
# :out_file output file. if not passed, the graph is displayed live
|
|
127
126
|
#
|
|
128
|
-
def self.format_as_line_graph(
|
|
129
|
-
out_file = options[
|
|
127
|
+
def self.format_as_line_graph(data, days, options={})
|
|
128
|
+
out_file = options[:out_file]
|
|
130
129
|
|
|
131
130
|
graph = Gruff::Line.new
|
|
132
131
|
|
|
133
|
-
data.each { |
|
|
132
|
+
data.each { |label_data| graph.data(*label_data) }
|
|
134
133
|
|
|
135
134
|
graph.labels = {
|
|
136
135
|
0 => days.first.to_s,
|
|
@@ -138,16 +137,16 @@ module GraphingHelper
|
|
|
138
137
|
}
|
|
139
138
|
|
|
140
139
|
if out_file
|
|
141
|
-
graph.write(
|
|
140
|
+
graph.write(out_file)
|
|
142
141
|
else
|
|
143
|
-
Tempfile.open(
|
|
142
|
+
Tempfile.open('tracking_graph') do |f|
|
|
144
143
|
# Base#write doesn't work because the tempfile doesn't have any extension
|
|
145
144
|
#
|
|
146
145
|
rendered_data = graph.to_blob
|
|
147
146
|
f << rendered_data
|
|
148
147
|
|
|
149
148
|
images_display_app = get_images_display_app
|
|
150
|
-
`#{
|
|
149
|
+
`#{images_display_app} #{f.path}`
|
|
151
150
|
end
|
|
152
151
|
end
|
|
153
152
|
end
|
|
@@ -158,29 +157,29 @@ module GraphingHelper
|
|
|
158
157
|
# :separator default: '|'
|
|
159
158
|
# :align hash { <field> => :left }. makes sense only if the first row is the headers.
|
|
160
159
|
#
|
|
161
|
-
def self.format_as_table(
|
|
160
|
+
def self.format_as_table(rows, options={})
|
|
162
161
|
return "" if rows.empty?
|
|
163
162
|
|
|
164
|
-
separator = options[
|
|
165
|
-
align = options[
|
|
163
|
+
separator = options[:separator] || '|'
|
|
164
|
+
align = options[:align] || {}
|
|
166
165
|
|
|
167
166
|
max_field_sizes = nil
|
|
168
167
|
alignment_symbols = nil
|
|
169
168
|
|
|
170
|
-
rows.each_with_index do |
|
|
169
|
+
rows.each_with_index do |row, row_num|
|
|
171
170
|
if row_num == 0
|
|
172
|
-
max_field_sizes = row.map { |
|
|
173
|
-
alignment_symbols = row.map { |
|
|
171
|
+
max_field_sizes = row.map { |value| value.to_s.size }
|
|
172
|
+
alignment_symbols = row.map { |value| '-' if align[value] == :left }
|
|
174
173
|
else
|
|
175
|
-
row.each_with_index do |
|
|
176
|
-
max_field_sizes[
|
|
174
|
+
row.each_with_index do |value, value_pos|
|
|
175
|
+
max_field_sizes[value_pos] = value.to_s.size if value.to_s.size > max_field_sizes[value_pos]
|
|
177
176
|
end
|
|
178
177
|
end
|
|
179
178
|
end
|
|
180
179
|
|
|
181
|
-
template = separator + max_field_sizes.zip(
|
|
180
|
+
template = separator + max_field_sizes.zip(alignment_symbols).map { |size, alignment_symbol| " %#{alignment_symbol}#{size}s #{separator}" }.join
|
|
182
181
|
|
|
183
|
-
rows.inject(
|
|
182
|
+
rows.inject("") do |buffer, row|
|
|
184
183
|
buffer << template % row << "\n"
|
|
185
184
|
end
|
|
186
185
|
end
|
|
@@ -194,9 +193,7 @@ module GraphingHelper
|
|
|
194
193
|
when /darwin/
|
|
195
194
|
'open -W'
|
|
196
195
|
else
|
|
197
|
-
raise "Unsupported platform: #{
|
|
196
|
+
raise "Unsupported platform: #{RUBY_PLATFORM}"
|
|
198
197
|
end
|
|
199
198
|
end
|
|
200
|
-
|
|
201
199
|
end
|
|
202
|
-
|
|
@@ -19,11 +19,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
19
19
|
=end
|
|
20
20
|
|
|
21
21
|
module InteractionsHelper
|
|
22
|
-
|
|
23
22
|
require 'highline/import'
|
|
24
23
|
|
|
25
|
-
def self.secure_ask(
|
|
26
|
-
HighLine.new.ask(
|
|
24
|
+
def self.secure_ask(question='Insert password: ')
|
|
25
|
+
HighLine.new.ask(question) { |q| q.echo = '*' }
|
|
27
26
|
end
|
|
28
27
|
|
|
29
28
|
# Asks a question, optionally using a default.
|
|
@@ -32,10 +31,10 @@ module InteractionsHelper
|
|
|
32
31
|
#
|
|
33
32
|
# <header> [default]?
|
|
34
33
|
#
|
|
35
|
-
def self.ask_entry(
|
|
34
|
+
def self.ask_entry(header, default=nil)
|
|
36
35
|
while true
|
|
37
|
-
print "#{
|
|
38
|
-
print " [#{
|
|
36
|
+
print "#{header}"
|
|
37
|
+
print " [#{default}]" if default
|
|
39
38
|
print "? "
|
|
40
39
|
|
|
41
40
|
answer = STDIN.gets.chomp
|
|
@@ -67,32 +66,32 @@ module InteractionsHelper
|
|
|
67
66
|
# If there are more matches and the pattern matches exactly one of
|
|
68
67
|
# them, it's automatically chosen.
|
|
69
68
|
#
|
|
70
|
-
def self.ask_entries_with_points(
|
|
69
|
+
def self.ask_entries_with_points(header, entries, options={})
|
|
71
70
|
raise ArgumentError.new("No entries passed! [#{header}]") if entries.empty?
|
|
72
71
|
|
|
73
|
-
default = options[
|
|
74
|
-
autochoose_if_one = options[
|
|
75
|
-
filtering_pattern = options[
|
|
72
|
+
default = options[:default]
|
|
73
|
+
autochoose_if_one = options[:autochoose_if_one]
|
|
74
|
+
filtering_pattern = options[:filter_by]
|
|
76
75
|
|
|
77
|
-
raise "Pattern must be a String, Regexp is not supported" if filtering_pattern.is_a?(
|
|
76
|
+
raise "Pattern must be a String, Regexp is not supported" if filtering_pattern.is_a?(Regexp)
|
|
78
77
|
|
|
79
78
|
# Convert to Hash if it's an array
|
|
80
79
|
#
|
|
81
|
-
if entries.is_a?(
|
|
82
|
-
entries = (
|
|
80
|
+
if entries.is_a?(Array)
|
|
81
|
+
entries = (0 ... entries.size).zip(entries)
|
|
83
82
|
|
|
84
|
-
entries = entries.inject(
|
|
85
|
-
current_entries[
|
|
83
|
+
entries = entries.inject({}) do |current_entries, (i, entry)|
|
|
84
|
+
current_entries[i.to_s] = entry
|
|
86
85
|
current_entries
|
|
87
86
|
end
|
|
88
87
|
end
|
|
89
88
|
|
|
90
89
|
if filtering_pattern
|
|
91
|
-
exact_matches = entries.select { |
|
|
90
|
+
exact_matches = entries.select { |_, entry_value| entry_value.downcase == filtering_pattern.downcase }
|
|
92
91
|
|
|
93
92
|
return exact_matches.values.first if exact_matches.size == 1
|
|
94
93
|
|
|
95
|
-
entries = entries.select { |
|
|
94
|
+
entries = entries.select { |_, entry_value| entry_value.downcase.include?(filtering_pattern.downcase) }
|
|
96
95
|
|
|
97
96
|
raise ArgumentError.new("No entries after filtering! [#{header}, #{filtering_pattern}]") if entries.empty?
|
|
98
97
|
end
|
|
@@ -102,20 +101,20 @@ module InteractionsHelper
|
|
|
102
101
|
end
|
|
103
102
|
|
|
104
103
|
while true
|
|
105
|
-
puts "#{
|
|
104
|
+
puts "#{header}:"
|
|
106
105
|
|
|
107
|
-
entries.each do |
|
|
108
|
-
print " #{
|
|
106
|
+
entries.each do |point, entry|
|
|
107
|
+
print " #{point}"
|
|
109
108
|
print default.to_s == entry ? '*' : ')'
|
|
110
|
-
puts " #{
|
|
109
|
+
puts " #{entry}"
|
|
111
110
|
end
|
|
112
111
|
|
|
113
112
|
answer = STDIN.gets.chomp
|
|
114
113
|
|
|
115
114
|
if answer == '' && default
|
|
116
115
|
break default
|
|
117
|
-
elsif entries.has_key?(
|
|
118
|
-
break entries[
|
|
116
|
+
elsif entries.has_key?(answer)
|
|
117
|
+
break entries[answer]
|
|
119
118
|
end
|
|
120
119
|
end
|
|
121
120
|
end
|
|
@@ -124,13 +123,13 @@ module InteractionsHelper
|
|
|
124
123
|
#
|
|
125
124
|
# header: entry_a,entry_b [default]?
|
|
126
125
|
#
|
|
127
|
-
def self.ask_entries_in_line(
|
|
126
|
+
def self.ask_entries_in_line(header, entries, default=nil)
|
|
128
127
|
while true
|
|
129
|
-
print "#{
|
|
128
|
+
print "#{header}: "
|
|
130
129
|
|
|
131
|
-
print entries.join(
|
|
130
|
+
print entries.join(',')
|
|
132
131
|
|
|
133
|
-
print " [#{
|
|
132
|
+
print " [#{default}]" if default
|
|
134
133
|
|
|
135
134
|
print "? "
|
|
136
135
|
|
|
@@ -138,11 +137,9 @@ module InteractionsHelper
|
|
|
138
137
|
|
|
139
138
|
if answer == '' && default
|
|
140
139
|
break default
|
|
141
|
-
elsif entries.include?(
|
|
140
|
+
elsif entries.include?(answer)
|
|
142
141
|
break answer
|
|
143
142
|
end
|
|
144
143
|
end
|
|
145
144
|
end
|
|
146
|
-
|
|
147
145
|
end
|
|
148
|
-
|
|
@@ -22,38 +22,37 @@ require_relative 'system_helper'
|
|
|
22
22
|
require_relative 'bash_helper'
|
|
23
23
|
|
|
24
24
|
module MultimediaHelper
|
|
25
|
-
|
|
26
25
|
include SystemHelper, BashHelper
|
|
27
26
|
|
|
28
|
-
RHYTHMBOX_PLAYLISTS_FILE = File.expand_path(
|
|
29
|
-
BANSHEE_DATA_FILE = File.expand_path(
|
|
30
|
-
AUDIO_FILES_EXTENSIONS = [
|
|
27
|
+
RHYTHMBOX_PLAYLISTS_FILE = File.expand_path('.local/share/rhythmbox/playlists.xml', '~')
|
|
28
|
+
BANSHEE_DATA_FILE = File.expand_path('.config/banshee-1/banshee.db', '~')
|
|
29
|
+
AUDIO_FILES_EXTENSIONS = ['m4a', 'mp3']
|
|
31
30
|
|
|
32
|
-
def image_format(
|
|
33
|
-
if image_data.start_with?(
|
|
31
|
+
def image_format(image_data)
|
|
32
|
+
if image_data.start_with?('GIF8')
|
|
34
33
|
'gif'
|
|
35
|
-
elsif image_data.start_with?(
|
|
34
|
+
elsif image_data.start_with?("\xFF\xD8\xFF\xE0")
|
|
36
35
|
'jpg'
|
|
37
36
|
else
|
|
38
37
|
raise "Unrecognized picture format."
|
|
39
38
|
end
|
|
40
39
|
end
|
|
41
40
|
|
|
42
|
-
def play_audio_file(
|
|
43
|
-
filename = File.expand_path(
|
|
41
|
+
def play_audio_file(filename)
|
|
42
|
+
filename = File.expand_path(filename)
|
|
44
43
|
|
|
45
44
|
if SystemHelper.mac?
|
|
46
45
|
simple_bash_execute 'afplay', filename
|
|
47
46
|
else
|
|
48
|
-
simple_bash_execute "gst-launch-1.0 playbin -q", "uri=file://#{
|
|
47
|
+
simple_bash_execute "gst-launch-1.0 playbin -q", "uri=file://#{filename}"
|
|
49
48
|
end
|
|
50
49
|
end
|
|
51
50
|
|
|
52
|
-
def normalize_songs(
|
|
51
|
+
def normalize_songs(*files)
|
|
53
52
|
files.flatten!
|
|
54
53
|
|
|
55
|
-
mp3_files = files.select { |
|
|
56
|
-
mp4_files = files.select { |
|
|
54
|
+
mp3_files = files.select { |file| file =~ /\.mp3$/i }
|
|
55
|
+
mp4_files = files.select { |file| file =~ /\.m4a$/i }
|
|
57
56
|
|
|
58
57
|
raise "xxx!" if mp3_files.size + mp4_files.size != files.size
|
|
59
58
|
|
|
@@ -68,80 +67,80 @@ module MultimediaHelper
|
|
|
68
67
|
|
|
69
68
|
# Works on a single folder - no recursion.
|
|
70
69
|
#
|
|
71
|
-
def normalize_album(
|
|
70
|
+
def normalize_album(directory, extension)
|
|
72
71
|
case extension
|
|
73
72
|
when 'm4a'
|
|
74
73
|
program = 'aacgain'
|
|
75
74
|
when 'mp3'
|
|
76
75
|
program = 'mp3gain'
|
|
77
76
|
else
|
|
78
|
-
raise "Unsupported extension: #{
|
|
77
|
+
raise "Unsupported extension: #{extension}"
|
|
79
78
|
end
|
|
80
79
|
|
|
81
|
-
files = Dir.glob(
|
|
82
|
-
encoded_files = encode_bash_filenames(
|
|
80
|
+
files = Dir.glob(File.join(directory, "*.#{extension}"))
|
|
81
|
+
encoded_files = encode_bash_filenames(*files)
|
|
83
82
|
|
|
84
83
|
# The m.f. popen3 hangs when normalizing, possibly because aacgain rewrites to screen because of the counter
|
|
85
84
|
# F**K F**K
|
|
86
85
|
#
|
|
87
86
|
# Regardless, aacgain appears to be broken, as if an error happens, it still exits successfully.
|
|
88
87
|
#
|
|
89
|
-
safe_execute "#{
|
|
88
|
+
safe_execute "#{program} -a -k " + encoded_files + " 2> /dev/null"
|
|
90
89
|
end
|
|
91
90
|
|
|
92
|
-
def encode_alac_to_m4a(
|
|
91
|
+
def encode_alac_to_m4a(file)
|
|
93
92
|
temp_file = file + ".wav"
|
|
94
93
|
|
|
95
94
|
simple_bash_execute "ffmpeg -i", file, temp_file
|
|
96
95
|
|
|
97
|
-
File.delete(
|
|
96
|
+
File.delete(file)
|
|
98
97
|
|
|
99
98
|
simple_bash_execute "neroAacEnc -q 0.5", "-if", temp_file, "-of", file
|
|
100
99
|
|
|
101
|
-
File.delete(
|
|
100
|
+
File.delete(temp_file)
|
|
102
101
|
end
|
|
103
102
|
|
|
104
103
|
# Sorts files by name
|
|
105
104
|
#
|
|
106
|
-
def create_m3u_playlist(
|
|
105
|
+
def create_m3u_playlist(files_or_pattern, basedir, output)
|
|
107
106
|
case files_or_pattern
|
|
108
107
|
when Array
|
|
109
108
|
files = files_or_pattern
|
|
110
109
|
# do nothing
|
|
111
110
|
when String
|
|
112
|
-
files = Dir.glob(
|
|
111
|
+
files = Dir.glob(files_or_pattern)
|
|
113
112
|
else
|
|
114
|
-
raise "ziokann!! #{
|
|
113
|
+
raise "ziokann!! #{files_or_pattern}"
|
|
115
114
|
end
|
|
116
115
|
|
|
117
116
|
buffer = "#EXTM3U" << "\n"
|
|
118
117
|
|
|
119
|
-
files.sort.each do |
|
|
120
|
-
duration = get_audio_file_duration(
|
|
121
|
-
song_name = File.basename(
|
|
118
|
+
files.sort.each do |file|
|
|
119
|
+
duration = get_audio_file_duration(file)
|
|
120
|
+
song_name = File.basename(file).sub(/\.\w+$/, '')
|
|
122
121
|
|
|
123
|
-
buffer << "#EXTINF:#{
|
|
122
|
+
buffer << "#EXTINF:#{duration},#{song_name}" << "\n"
|
|
124
123
|
|
|
125
|
-
file_relative_path = File.join(
|
|
124
|
+
file_relative_path = File.join(basedir, File.basename(file))
|
|
126
125
|
|
|
127
126
|
buffer << file_relative_path << "\n"
|
|
128
127
|
end
|
|
129
128
|
|
|
130
|
-
IO.write(
|
|
129
|
+
IO.write(output, buffer)
|
|
131
130
|
end
|
|
132
131
|
|
|
133
|
-
def get_audio_file_duration(
|
|
132
|
+
def get_audio_file_duration(file)
|
|
134
133
|
case file
|
|
135
134
|
when /.mp3$/
|
|
136
|
-
duration = safe_execute(
|
|
135
|
+
duration = safe_execute("mp3info -p '%S' " + encode_bash_filenames(file))
|
|
137
136
|
when /.m4a$/
|
|
138
137
|
# f#!$ing faad writes only to stderr
|
|
139
138
|
#
|
|
140
|
-
raw_result = safe_execute(
|
|
139
|
+
raw_result = safe_execute('faad -i ' + encode_bash_filenames(file) + " 2>&1")
|
|
141
140
|
|
|
142
|
-
duration = raw_result[
|
|
141
|
+
duration = raw_result[/(\d+)\.\d+ secs/, 1] || raise("z.k.!!")
|
|
143
142
|
else
|
|
144
|
-
raise "ziokann!!! #{
|
|
143
|
+
raise "ziokann!!! #{file}"
|
|
145
144
|
end
|
|
146
145
|
|
|
147
146
|
duration.to_i
|
|
@@ -149,79 +148,79 @@ module MultimediaHelper
|
|
|
149
148
|
|
|
150
149
|
# :directories single entry or array
|
|
151
150
|
#
|
|
152
|
-
def add_playlists_to_rhythmbox(
|
|
151
|
+
def add_playlists_to_rhythmbox(directories, options={})
|
|
153
152
|
require 'rexml/document'
|
|
154
153
|
require 'uri'
|
|
155
154
|
|
|
156
|
-
directories = [
|
|
155
|
+
directories = [directories] if !directories.is_a?(Array)
|
|
157
156
|
|
|
158
|
-
raw_xml = IO.read(
|
|
159
|
-
xml_doc = REXML::Document.new(
|
|
157
|
+
raw_xml = IO.read(RHYTHMBOX_PLAYLISTS_FILE)
|
|
158
|
+
xml_doc = REXML::Document.new(raw_xml)
|
|
160
159
|
xml_root = xml_doc.elements.first
|
|
161
160
|
|
|
162
|
-
directories.each do |
|
|
163
|
-
puts "Adding #{
|
|
161
|
+
directories.each do |directory|
|
|
162
|
+
puts "Adding #{directory}..."
|
|
164
163
|
|
|
165
|
-
playlist_name = File.basename(
|
|
166
|
-
filenames = AUDIO_FILES_EXTENSIONS.map { |
|
|
164
|
+
playlist_name = File.basename(directory)
|
|
165
|
+
filenames = AUDIO_FILES_EXTENSIONS.map { |extension| Dir.glob(File.join(directory, "*.#{extension}")) }.flatten.sort
|
|
167
166
|
|
|
168
|
-
if xml_root.elements.any? { |
|
|
167
|
+
if xml_root.elements.any? { |xml_element| xml_element.attributes['name'] == playlist_name }
|
|
169
168
|
puts ">>> playlist already existent!"
|
|
170
169
|
else
|
|
171
|
-
playlist_node = xml_root.add_element(
|
|
170
|
+
playlist_node = xml_root.add_element('playlist', 'name' => playlist_name, 'type' => 'static')
|
|
172
171
|
|
|
173
172
|
filenames.sort!
|
|
174
173
|
|
|
175
|
-
filenames.each do |
|
|
176
|
-
entry_node = playlist_node.add_element(
|
|
177
|
-
encoded_filename = URI.encode(
|
|
174
|
+
filenames.each do |filename|
|
|
175
|
+
entry_node = playlist_node.add_element('location')
|
|
176
|
+
encoded_filename = URI.encode(File.expand_path(filename))
|
|
178
177
|
entry_node.text = "file://" + encoded_filename
|
|
179
178
|
end
|
|
180
179
|
end
|
|
181
180
|
end
|
|
182
181
|
|
|
183
|
-
buffer = format_xml_playlist_for_rhythmbox(
|
|
182
|
+
buffer = format_xml_playlist_for_rhythmbox(xml_doc)
|
|
184
183
|
|
|
185
|
-
IO.write(
|
|
184
|
+
IO.write(RHYTHMBOX_PLAYLISTS_FILE, buffer)
|
|
186
185
|
end
|
|
187
186
|
|
|
188
187
|
# :directories single entry or array
|
|
189
188
|
#
|
|
190
|
-
def add_playlists_to_banshee(
|
|
189
|
+
def add_playlists_to_banshee(directories, options={})
|
|
191
190
|
require 'uri'
|
|
192
191
|
|
|
193
|
-
directories = [
|
|
192
|
+
directories = [directories] if !directories.is_a?(Array)
|
|
194
193
|
|
|
195
|
-
db_layer = SQLiteLayer.new(
|
|
194
|
+
db_layer = SQLiteLayer.new(BANSHEE_DATA_FILE)
|
|
196
195
|
|
|
197
196
|
primary_source_id_music = 1
|
|
198
197
|
|
|
199
198
|
db_layer.transaction do
|
|
200
|
-
directories.each do |
|
|
201
|
-
puts "Adding #{
|
|
199
|
+
directories.each do |directory|
|
|
200
|
+
puts "Adding #{directory}..."
|
|
202
201
|
|
|
203
|
-
playlist_name = File.basename(
|
|
202
|
+
playlist_name = File.basename(directory)
|
|
204
203
|
|
|
205
204
|
insertion_values = {
|
|
206
205
|
:PrimarySourceID => primary_source_id_music,
|
|
207
206
|
:Name => playlist_name,
|
|
208
207
|
}
|
|
209
208
|
|
|
210
|
-
playlist_id = db_layer.insert_values(
|
|
209
|
+
playlist_id = db_layer.insert_values('CorePlaylists', insertion_values)
|
|
211
210
|
|
|
212
|
-
filenames = AUDIO_FILES_EXTENSIONS.map { |
|
|
211
|
+
filenames = AUDIO_FILES_EXTENSIONS.map { |extension| Dir.glob(File.join(directory, "*.#{extension}")) }.flatten.sort
|
|
213
212
|
|
|
214
|
-
filenames.each do |
|
|
215
|
-
puts " - #{
|
|
213
|
+
filenames.each do |filename|
|
|
214
|
+
puts " - #{filename}"
|
|
216
215
|
|
|
217
|
-
track_id = banshee_find_track_id(
|
|
216
|
+
track_id = banshee_find_track_id(filename, db_layer) || banshee_add_playlist_entry(filename, db_layer, primary_source_id_music)
|
|
218
217
|
|
|
219
218
|
insertion_values = {
|
|
220
219
|
:PlaylistID => playlist_id,
|
|
221
220
|
:TrackID => track_id,
|
|
222
221
|
}
|
|
223
222
|
|
|
224
|
-
db_layer.insert_values(
|
|
223
|
+
db_layer.insert_values('CorePlaylistEntries', insertion_values)
|
|
225
224
|
end
|
|
226
225
|
end
|
|
227
226
|
end
|
|
@@ -229,16 +228,16 @@ module MultimediaHelper
|
|
|
229
228
|
|
|
230
229
|
private
|
|
231
230
|
|
|
232
|
-
def banshee_find_track_id(
|
|
233
|
-
uri_filename = "file://" + URI.encode(
|
|
231
|
+
def banshee_find_track_id(filename, db_layer)
|
|
232
|
+
uri_filename = "file://" + URI.encode(File.expand_path(filename))
|
|
234
233
|
|
|
235
|
-
db_layer.select_value(
|
|
234
|
+
db_layer.select_value("SELECT TrackID FROM CoreTracks WHERE Uri = ?", uri_filename)
|
|
236
235
|
end
|
|
237
236
|
|
|
238
|
-
def banshee_add_playlist_entry(
|
|
239
|
-
uri_filename = "file://" + URI.encode(
|
|
240
|
-
title = File.basename(
|
|
241
|
-
straight_title = "'" + title.gsub(
|
|
237
|
+
def banshee_add_playlist_entry(filename, db_layer, primary_source_id_music)
|
|
238
|
+
uri_filename = "file://" + URI.encode(File.expand_path(filename))
|
|
239
|
+
title = File.basename(filename).sub(/\.\w+$/, '')
|
|
240
|
+
straight_title = "'" + title.gsub("'", "''") + "'"
|
|
242
241
|
timestamp = Time.now.to_i
|
|
243
242
|
|
|
244
243
|
insertion_values = {
|
|
@@ -262,20 +261,18 @@ module MultimediaHelper
|
|
|
262
261
|
:TitleLowered => straight_title.downcase,
|
|
263
262
|
}
|
|
264
263
|
|
|
265
|
-
db_layer.insert_values(
|
|
264
|
+
db_layer.insert_values('CoreTracks', insertion_values, :straight_insert => straight_insert_values)
|
|
266
265
|
end
|
|
267
266
|
|
|
268
|
-
def format_xml_playlist_for_rhythmbox(
|
|
267
|
+
def format_xml_playlist_for_rhythmbox(xml_doc)
|
|
269
268
|
buffer = ""
|
|
270
269
|
xml_formatter = REXML::Formatters::Pretty.new
|
|
271
270
|
|
|
272
271
|
xml_formatter.compact = true
|
|
273
272
|
xml_formatter.width = 16384 # avoid introducing f*ing spaces inside <location> elements, which are not compatible with RhythmBox
|
|
274
273
|
|
|
275
|
-
xml_formatter.write(
|
|
274
|
+
xml_formatter.write(xml_doc, buffer)
|
|
276
275
|
|
|
277
276
|
buffer
|
|
278
277
|
end
|
|
279
|
-
|
|
280
278
|
end
|
|
281
|
-
|