-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmusic-add.rb
More file actions
executable file
·232 lines (213 loc) · 6.71 KB
/
music-add.rb
File metadata and controls
executable file
·232 lines (213 loc) · 6.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
#!/usr/bin/env ruby
require "fileutils"
require "i18n"
require "pathname"
require "sqlite3"
require "taglib"
require "tty-prompt"
require "walk"
DL = "/Users/hripps/Downloads"
DBT = "/Users/hripps/Music/DJDB/Tracks"
DBS = "/Users/hripps/Music/DJDB/Samples"
DA = "/Users/hripps/Music/Music/Media.localized/Music"
DF = "/Users/hripps/.music-history.sqlite"
VALID = ['mp3','m4a','wav','aiff']
FN_REGEX = /^\d+\w+.+$/
# Configure transliteration
I18n.config.available_locales = :en
def sanitize_filename(filename)
return I18n.transliterate(filename.gsub('/','-'))
end
def sanitize(text)
return text.gsub("'","''")
end
def infer_name(filename)
# Remove the file extension
parts = filename.split('.')
parts.pop(1)
temp = parts.join('')
# Check for a leading track number. If there isn't one, go with the name as-is
if not temp.match(FN_REGEX)
return I18n.transliterate(temp)
end
# Remove the leading track number and return the rest
parts = temp.split(' ')
parts.shift
return I18n.transliterate(parts.join(' '))
end
def infer_apple_music_artist(fpath)
dirs = Pathname(fpath).each_filename.to_a
# Assuming a path ending in .../Artist/Album/Track
artist = dirs[-3]
if artist.downcase == 'compilation'
artist = 'Various Artists'
end
return artist
end
def get_info(fpath,file,suffix,apple_music)
info = {
:ogpath => fpath,
:file => file,
:suffix => suffix,
:title => '',
:genre => '',
:artist => '',
:album => '',
:newname => '',
:complete => false,
:sample => false,
}
found_name = false
begin
if suffix == 'm4a'
TagLib::MP4::File.open(fpath) do |file|
item_map = file.tag.item_map.to_h
artist_key = item_map.has_key?('©ART') ? '©ART' : 'soar'
title_key = item_map.has_key?('©nam') ? '©nam' : 'sonm'
info[:artist] = item_map[artist_key].nil? ? '' : item_map[artist_key].to_string_list[0]
info[:title] = item_map[title_key].nil? ? '' : item_map[title_key].to_string_list[0]
end
if not info[:title] == '' and not info[:title].nil?
found_name = true
end
else
TagLib::FileRef.open(fpath) do |fileref|
if not fileref.null?
tag = fileref.tag
if not tag.title.nil? and not tag.title == ''
found_name = true
info[:title] = tag.title
info[:genre] = tag.genre
info[:artist] = tag.artist
info[:album] = tag.album
end
end
end
end
rescue
puts "RESCUED: #{infer_name(file)} || #{fpath}"
found_name = true
info[:title] = infer_name(file)
end
if not found_name
puts "INFERRED: #{infer_name(file)} || #{fpath}"
info[:title] = infer_name(file)
end
if info[:artist] == '' and apple_music
# We can infer the artist name from the filepath when the track origin is the Apple Music directories
info[:artist] = infer_apple_music_artist(fpath)
end
if info[:title] != '' && info[:artist] != ''
info[:newname] = sanitize_filename(info[:artist]) + ' - ' + sanitize_filename(info[:title]) + '.' + suffix
info[:complete] = true
end
if info[:genre].downcase.include?('tool') || info[:genre].downcase.include?('sample') || info[:genre].downcase.include?('other') || info[:artist] == 'Digital DJ Tips' || suffix.downcase == 'wav'
info[:sample] = true
end
return info
end
puts "Reading tracks folder..."
existing_tracks = {}
Walk.walk(DBT) do |path,dirs,files|
files.each do |file|
ogpath = File.join(path,file)
existing_tracks[ogpath] = 1
end
end
puts " Logged #{existing_tracks.length} existing tracks."
puts "Reading samples folder..."
existing_samples = {}
Walk.walk(DBS) do |path,dirs,files|
files.each do |file|
ogpath = File.join(path,file)
existing_samples[ogpath] = 1
end
end
puts " Logged #{existing_samples.length} existing samples."
puts "Handling Downloads folder..."
found = []
errors = []
Walk.walk(DL) do |path,dirs,files|
files.each do |file|
suffix = file.split('.')[-1].downcase
next unless VALID.include?(suffix)
ogpath = File.join(path,file)
song = get_info(ogpath,file,suffix,false)
new_filename = song[:newname] != '' ? song[:newname] : file
if song[:sample] == true and existing_samples.has_key?(new_filename)
errors << "#{ogpath} - sample name collision '#{new_filename}'"
elsif song [:complete] == true and existing_tracks.has_key?(new_filename)
errors << "#{ogpath} - track name collision '#{new_filename}'"
end
if song[:sample] == false and song[:complete] == false
errors << "#{ogpath} - could not infer track info; fix tags"
end
found << song
end
end
puts " Found #{found.length} new downloaded tracks."
if errors.length > 0
puts "Errors encountered with new files."
errors.each do |error|
puts "- #{error}"
end
exit
end
puts "Adding new files to library directories."
found.each do |song|
new_filename = song[:newname] != '' ? song[:newname] : song[:file]
if song[:sample] == true
FileUtils.mv(song[:ogpath],File.join(DBS,new_filename))
elsif song[:complete] == true
FileUtils.mv(song[:ogpath],File.join(DBT,new_filename))
end
puts " * #{new_filename}"
end
puts "Handling Apple Music folder..."
dbh = SQLite3::Database.new DF
known_apple = {}
dbh.execute( "SELECT PATH FROM ATRACKS" ) do |row|
known_apple[row[0]] = 1
end
new_apple = {}
Walk.walk(DA) do |path,dirs,files|
files.each do |file|
suffix = file.split('.')[-1].downcase
next unless VALID.include?(suffix)
ogpath = File.join(path,file)
next if known_apple.has_key?(ogpath)
new_apple[ogpath] = get_info(ogpath,file,suffix,true)
end
end
puts " Found #{new_apple.keys.length} usable new Apple Music tracks."
prompt = TTY::Prompt.new
new_apple.keys.each do |apath|
song = new_apple[apath]
new_filename = song[:newname] != '' ? song[:newname] : song[:file]
add_to_db = true
if prompt.yes?(" Sync '#{song[:title]}' by '#{song[:artist]}'?")
if song[:sample] == true
if existing_samples.has_key?(new_filename)
puts " Cannot add new sample - naming collision: #{new_filename}"
add_to_db = false
else
FileUtils.cp(apath,File.join(DBS,new_filename))
puts " Added as new sample track."
end
elsif song[:complete] == true
if existing_tracks.has_key?(new_filename)
puts " Cannot add new track - naming collision: #{new_filename}"
add_to_db = false
else
FileUtils.cp(apath,File.join(DBT,new_filename))
puts " Added as new music track."
end
else
puts " Could not determine name / type for this track; skipping it - #{apath}"
add_to_db = false
end
end
if add_to_db == true
dbh.execute( "INSERT INTO ATRACKS ('PATH') VALUES ('#{sanitize(apath)}')" )
end
end