2021-03-02 09:14:24 +00:00
|
|
|
-- require this and use it in a REPL to modify the JSON database
|
|
|
|
|
|
|
|
local cjson = require("cjson")
|
|
|
|
|
|
|
|
local music = {}
|
|
|
|
|
|
|
|
function music.normalize(str)
|
|
|
|
return str:gsub("%W", ""):lower()
|
|
|
|
end
|
|
|
|
|
2021-09-28 23:08:39 +00:00
|
|
|
function music.name(name)
|
|
|
|
local entry = music.data[music.normalize(name)]
|
2021-09-30 05:17:08 +00:00
|
|
|
if not entry then entry = music.data[name] end -- some normalized names have extra characters somehow
|
2021-09-28 23:08:39 +00:00
|
|
|
if entry then
|
|
|
|
return entry.names[1]
|
|
|
|
else
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-09-28 21:22:34 +00:00
|
|
|
function music.load(force, file_name)
|
2021-03-02 09:14:24 +00:00
|
|
|
if music.data and not force then
|
|
|
|
print("Music library was already loaded, use 'music.load(true)' to force load.")
|
2021-09-28 21:22:34 +00:00
|
|
|
return false
|
2021-03-02 09:14:24 +00:00
|
|
|
end
|
2021-09-28 21:22:34 +00:00
|
|
|
local file, err = io.open(file_name or "music.json", "r")
|
|
|
|
if not file then error(err) end
|
2021-03-02 09:14:24 +00:00
|
|
|
music.data = cjson.decode(file:read("*a"))
|
|
|
|
file:close()
|
|
|
|
print("Music library loaded.")
|
2021-09-28 21:22:34 +00:00
|
|
|
return true
|
2021-03-02 09:14:24 +00:00
|
|
|
end
|
|
|
|
|
2021-09-28 21:22:34 +00:00
|
|
|
function music.save(file_name)
|
2021-03-02 09:14:24 +00:00
|
|
|
local str = cjson.encode(music.data)
|
2021-09-28 21:22:34 +00:00
|
|
|
local file, err = io.open(file_name or "music.json", "w")
|
|
|
|
if not file then error(err) end
|
2021-03-02 09:14:24 +00:00
|
|
|
file:write(str)
|
|
|
|
file:close()
|
|
|
|
print("Music library saved.")
|
2021-09-28 21:22:34 +00:00
|
|
|
return true
|
2021-03-02 09:14:24 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function music.find(str)
|
|
|
|
str = music.normalize(str)
|
|
|
|
local matches = {}
|
|
|
|
for key in pairs(music.data) do
|
|
|
|
if key:find(str) then
|
|
|
|
table.insert(matches, key)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
print(#matches .. " matches returned.")
|
|
|
|
return matches
|
|
|
|
end
|
|
|
|
|
2021-09-28 22:40:05 +00:00
|
|
|
-- count is maximum number of returned names, defaults to 1
|
|
|
|
-- match can be nil, a name, or a list of names
|
|
|
|
-- include is a table of key-value pairs that must exist
|
|
|
|
-- exclude is a table of key-value pairs that must not exist
|
|
|
|
-- 'true' values mean non-false keys, any other value must match exactly
|
|
|
|
-- example: music.random(5, nil, {url = true}, {downloaded = true}) will return
|
|
|
|
-- 5 random tracks that have a url, but have not been downloaded
|
|
|
|
function music.random(count, match, include, exclude)
|
|
|
|
local matches, results = {}, {}
|
|
|
|
if not music.seed then music.seed = os.time() math.randomseed(music.seed) end
|
|
|
|
count = math.floor(tonumber(count) or 1)
|
|
|
|
local function filter(match, include, exclude)
|
|
|
|
local matches = {}
|
|
|
|
for _, name in ipairs(match) do
|
|
|
|
local valid = true
|
|
|
|
local compare = music.data[name]
|
|
|
|
for k,v in pairs(include) do
|
|
|
|
if v == true then
|
|
|
|
if not compare[k] then
|
|
|
|
valid = false
|
|
|
|
break
|
|
|
|
end
|
|
|
|
else
|
|
|
|
if compare[k] ~= v then
|
|
|
|
valid = false
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
for k,v in pairs(exclude) do
|
|
|
|
if v == true then
|
|
|
|
if compare[k] then
|
|
|
|
valid = false
|
|
|
|
break
|
|
|
|
end
|
|
|
|
else
|
|
|
|
if compare[k] == v then
|
|
|
|
valid = false
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if valid then
|
|
|
|
table.insert(matches, name)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return matches
|
|
|
|
end
|
|
|
|
if type(match) == "table" then
|
|
|
|
for _, v in ipairs(match) do
|
2021-09-30 05:17:08 +00:00
|
|
|
table.insert(matches, v) -- should be normalized already
|
2021-09-28 22:40:05 +00:00
|
|
|
end
|
|
|
|
elseif type(match) == "string" then
|
2021-09-30 05:17:08 +00:00
|
|
|
-- compensating for db bug...
|
|
|
|
if music.data[music.normalize(match)] then
|
|
|
|
matches[1] = music.normalize(match)
|
|
|
|
else
|
|
|
|
matches[1] = match
|
|
|
|
end
|
2021-09-28 22:40:05 +00:00
|
|
|
else
|
|
|
|
for k in pairs(music.data) do
|
|
|
|
table.insert(matches, k)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
matches = filter(matches, include or {}, exclude or {})
|
|
|
|
while count > 0 and #matches > 0 do
|
|
|
|
for i = #matches, 1, -1 do
|
2021-10-06 22:50:44 +00:00
|
|
|
if math.random() < 1 / #matches then
|
2021-09-28 22:40:05 +00:00
|
|
|
table.insert(results, table.remove(matches, i))
|
|
|
|
count = count - 1
|
|
|
|
end
|
|
|
|
if count < 1 then
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return results
|
|
|
|
end
|
|
|
|
|
2021-09-30 05:17:08 +00:00
|
|
|
function music.add(name) -- input should never be normalized
|
2021-03-02 09:14:24 +00:00
|
|
|
local normalized = music.normalize(name)
|
2021-09-28 21:22:34 +00:00
|
|
|
local entry = music.data[normalized]
|
|
|
|
if entry then
|
|
|
|
for _, existing_name in ipairs(entry.names) do
|
2021-03-02 09:14:24 +00:00
|
|
|
if existing_name == name then
|
|
|
|
print("Already in library.")
|
2021-09-28 21:22:34 +00:00
|
|
|
return false
|
2021-03-02 09:14:24 +00:00
|
|
|
end
|
|
|
|
end
|
2021-09-28 21:22:34 +00:00
|
|
|
table.insert(entry.names, name)
|
|
|
|
print("Already in library (normalized to: '" .. normalized .. "'). Added as alternate name of '" .. entry.names[1] .. "'")
|
|
|
|
return true
|
2021-03-02 09:14:24 +00:00
|
|
|
else
|
|
|
|
music.data[normalized] = { names = { name } }
|
|
|
|
print("Track added (normalized to: '" .. normalized .. "')")
|
2021-09-28 21:22:34 +00:00
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function music.add_file(file_name)
|
|
|
|
local file, err = io.open(file_name, "r")
|
|
|
|
if not file then error(err) end
|
|
|
|
for track in file:lines() do
|
|
|
|
if #track > 0 then
|
|
|
|
music.add(track)
|
|
|
|
end
|
2021-03-02 09:14:24 +00:00
|
|
|
end
|
2021-09-28 21:22:34 +00:00
|
|
|
file:close()
|
|
|
|
return true
|
2021-03-02 09:14:24 +00:00
|
|
|
end
|
|
|
|
|
2021-09-28 23:08:39 +00:00
|
|
|
function music.remove(name)
|
|
|
|
local normalized = music.normalize(name)
|
2021-09-30 05:17:08 +00:00
|
|
|
if not music.data[normalized] then normalized = name end -- handling db bug
|
2021-09-28 23:08:39 +00:00
|
|
|
if music.data[normalized] then
|
|
|
|
music.data[normalized] = nil
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
2021-09-28 22:40:05 +00:00
|
|
|
-- match is a name or a list of names, info is a table of key-value pairs to be set
|
2021-03-02 09:14:24 +00:00
|
|
|
function music.set(match, info)
|
|
|
|
if type(match) == "table" then
|
|
|
|
for _, value in ipairs(match) do
|
|
|
|
music.set(value, info)
|
|
|
|
end
|
2021-09-28 21:22:34 +00:00
|
|
|
return true
|
2021-03-02 09:14:24 +00:00
|
|
|
end
|
|
|
|
|
2021-09-28 21:22:34 +00:00
|
|
|
tab = music.data[music.normalize(match)]
|
2021-09-30 05:17:08 +00:00
|
|
|
if not tab then tab = music.data[match] end -- compensating for database bug
|
2021-03-02 09:14:24 +00:00
|
|
|
if not tab then
|
2021-09-30 05:17:08 +00:00
|
|
|
print("'" .. match .. "' does not exist!")
|
2021-09-28 21:22:34 +00:00
|
|
|
return false
|
2021-03-02 09:14:24 +00:00
|
|
|
end
|
|
|
|
for key, value in pairs(info) do
|
|
|
|
tab[key] = value
|
|
|
|
end
|
2021-09-28 21:22:34 +00:00
|
|
|
return true
|
2021-03-02 09:14:24 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
-- returns a list of all tracks with a `url` key but no `downloaded` key.
|
|
|
|
-- function music.generate_needed_downloads()
|
|
|
|
-- print("To be written.")
|
|
|
|
-- end
|
|
|
|
|
|
|
|
music.load()
|
|
|
|
|
|
|
|
return music
|