various fixes
This commit is contained in:
parent
3d5510cedb
commit
0395955afc
114
JlSonic/api.jl
114
JlSonic/api.jl
|
@ -61,12 +61,17 @@ function getMusicFolders()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Implement:
|
||||||
|
# getIndexes
|
||||||
|
# getMusicDirectory
|
||||||
|
|
||||||
"Returns all genres."
|
"Returns all genres."
|
||||||
function getGenres()
|
function getGenres()
|
||||||
@subsonic begin
|
(xdoc, xroot) = subsonic()
|
||||||
songs = allsongs();
|
songs = Beets.songs();
|
||||||
res = Dict{String,Int}()
|
res = Dict{String,Int}()
|
||||||
for genre in filter!(!isempty, getfield.(songs, :genre))
|
genrelist = strip.(sort(filter!(!isempty, Beets.genre.(Beets.albums))))
|
||||||
|
for genre in genrelist
|
||||||
t = get(res, genre, 0)
|
t = get(res, genre, 0)
|
||||||
res[genre] = t+1
|
res[genre] = t+1
|
||||||
end
|
end
|
||||||
|
@ -76,39 +81,28 @@ function getGenres()
|
||||||
set_attributes(genre, [
|
set_attributes(genre, [
|
||||||
("songCount", string(res[k])),
|
("songCount", string(res[k])),
|
||||||
# FIXME
|
# FIXME
|
||||||
("albumCount", "0"),
|
("albumCount", string(count(genrelist .== k))),
|
||||||
])
|
])
|
||||||
add_text(genre, k)
|
add_text(genre, k)
|
||||||
end
|
end
|
||||||
end
|
return subsonic_return(xdoc)
|
||||||
end
|
end
|
||||||
|
|
||||||
"Similar to getIndexes, but organizes music according to ID3 tags."
|
"Similar to getIndexes, but organizes music according to ID3 tags."
|
||||||
function getArtists()
|
function getArtists()
|
||||||
# TODO
|
(xdoc, xroot) = subsonic()
|
||||||
(xdoc, xroot) = subsonic(version = "1.12.0")
|
|
||||||
indexes = new_child(xroot, "artists")
|
indexes = new_child(xroot, "artists")
|
||||||
set_attribute(indexes, "ignoredArticles", "")
|
set_attribute(indexes, "ignoredArticles", "")
|
||||||
beetsdb = Beets.getartists()
|
artists = Beets.artists()
|
||||||
artists = unique(beetsdb)
|
firstletters = unique(first.(filter(!isempty, Beets.name.(artists))) .|> uppercase)
|
||||||
# albums = group_albums_as_artists()
|
for index in string.(firstletters)
|
||||||
# .|> does not work in a macro. What to do?
|
|
||||||
for index in unique(first.(filter(!isempty,
|
|
||||||
getfield.(artists, Ref(:name)))) .|> uppercase)
|
|
||||||
indexXML = new_child(indexes, "index")
|
indexXML = new_child(indexes, "index")
|
||||||
set_attribute(indexXML, "name", string(index))
|
set_attribute(indexXML, "name", index)
|
||||||
for artist in filter(x -> startswith(x.name, string(index)), artists)
|
for artist in unique(filter(x -> startswith(x.name, index), artists))
|
||||||
artistXML = new_child(indexXML, "artist")
|
artistXML = push!(indexXML, artist)
|
||||||
set_attributes(artistXML,
|
|
||||||
[("name", artist.name),
|
|
||||||
("id", artist.uuid),
|
|
||||||
("coverArt", ""),
|
|
||||||
("albumCount", "")])
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
doc_str = string(xdoc)
|
return subsonic_return(xdoc)
|
||||||
free(xdoc)
|
|
||||||
return doc_str
|
|
||||||
end
|
end
|
||||||
|
|
||||||
"""Returns details for an artist, including a list of albums.
|
"""Returns details for an artist, including a list of albums.
|
||||||
|
@ -116,22 +110,14 @@ end
|
||||||
This method organizes music according to ID3 tags."""
|
This method organizes music according to ID3 tags."""
|
||||||
function getArtist(id::String)
|
function getArtist(id::String)
|
||||||
artists = Beets.getartists()
|
artists = Beets.getartists()
|
||||||
matching = artists[getfield.(artists, :uuid) .== id]
|
matching = findfirst(a -> a.uuid == id, artists)
|
||||||
name = length(matching) > 0 ? first(matching).name : ""
|
matching === nothing && return not_found("id")
|
||||||
isempty(name) && return not_found()
|
artist = artists[matching]
|
||||||
# Create the response
|
# Create the response
|
||||||
(xdoc, xroot) = subsonic()
|
(xdoc, xroot) = subsonic()
|
||||||
artistXML = new_child(xroot, "artist")
|
artistXML = push!(xroot, artist)
|
||||||
artist = first(matching)
|
for album in Beets.albums(artist)
|
||||||
artist_albums = [i for i in Beets.getalbums() if i.artist == artist]
|
push!(artistXML, album)
|
||||||
set_attributes(artistXML, [
|
|
||||||
("id", artist.uuid),
|
|
||||||
("albumCount", string(length(artist_albums))),
|
|
||||||
("name", artist.name),
|
|
||||||
("coverArt", "false")
|
|
||||||
])
|
|
||||||
for album in artist_albums
|
|
||||||
push!(xroot, album)
|
|
||||||
end
|
end
|
||||||
return subsonic_return(xdoc)
|
return subsonic_return(xdoc)
|
||||||
end
|
end
|
||||||
|
@ -150,9 +136,17 @@ function getAlbum(req::Dict)
|
||||||
return getAlbum(albumid)
|
return getAlbum(albumid)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function getAlbum(albumid)
|
||||||
|
album = Beets.album(string(albumid))
|
||||||
|
album === nothing && return not_found("album")
|
||||||
|
(xdoc, xroot) = subsonic()
|
||||||
|
# push!(albumXML, [(s, album) for s in album.songs])
|
||||||
|
append!(xroot, album)
|
||||||
|
return subsonic_return(xdoc)
|
||||||
|
end
|
||||||
|
|
||||||
function getAlbumList(req::Dict)
|
function getAlbumList(req::Dict)
|
||||||
query = HTTP.URIs.queryparams(req[:query])
|
query = HTTP.URIs.queryparams(req[:query])
|
||||||
@show query
|
|
||||||
albumtype = get(query, "type", "")
|
albumtype = get(query, "type", "")
|
||||||
isempty(albumtype) && return missing_parameter("type")
|
isempty(albumtype) && return missing_parameter("type")
|
||||||
@subsonic begin
|
@subsonic begin
|
||||||
|
@ -161,18 +155,6 @@ function getAlbumList(req::Dict)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function getAlbum(albumid)
|
|
||||||
matching = [album for album in Beets.getalbums() if album.uuid == albumid]
|
|
||||||
if length(matching) < 1
|
|
||||||
return not_found("album")
|
|
||||||
end
|
|
||||||
album = first(matching)
|
|
||||||
(xdoc, xroot) = subsonic()
|
|
||||||
albumXML = push!(xroot, album)
|
|
||||||
push!(albumXML, [(s, album) for s in album.songs])
|
|
||||||
return subsonic_return(xdoc)
|
|
||||||
end
|
|
||||||
|
|
||||||
function getSong(req)
|
function getSong(req)
|
||||||
query = HTTP.URIs.queryparams(req[:query])
|
query = HTTP.URIs.queryparams(req[:query])
|
||||||
id = get(query, "id", "")
|
id = get(query, "id", "")
|
||||||
|
@ -192,7 +174,7 @@ function getRandomSongs(; size = 10,
|
||||||
fromYear::Union{Missing,Int} = missing,
|
fromYear::Union{Missing,Int} = missing,
|
||||||
toYear::Union{Missing,Int} = missing,
|
toYear::Union{Missing,Int} = missing,
|
||||||
musicFolderId::Union{Missing,String} = missing)
|
musicFolderId::Union{Missing,String} = missing)
|
||||||
songs = allsongs();
|
songs = Beets.songs();
|
||||||
# Randomize
|
# Randomize
|
||||||
songs = songs[Random.randperm(length(songs))];
|
songs = songs[Random.randperm(length(songs))];
|
||||||
# Filter
|
# Filter
|
||||||
|
@ -299,7 +281,7 @@ function createPlaylist(req)
|
||||||
songId = get(query, "songId", "")
|
songId = get(query, "songId", "")
|
||||||
# Check required params
|
# Check required params
|
||||||
isempty(songId) && return missing_parameter("songId")
|
isempty(songId) && return missing_parameter("songId")
|
||||||
songs = allsongs();
|
songs = Beets.songs();
|
||||||
songn = findfirst(s -> s.uuid == songId, songs)
|
songn = findfirst(s -> s.uuid == songId, songs)
|
||||||
songn === nothing && return not_found("songId")
|
songn === nothing && return not_found("songId")
|
||||||
song = songs[songn]
|
song = songs[songn]
|
||||||
|
@ -355,7 +337,6 @@ end
|
||||||
function updatePlaylist(req)
|
function updatePlaylist(req)
|
||||||
global user_playlists
|
global user_playlists
|
||||||
query = HTTP.URIs.queryparams(req[:query])
|
query = HTTP.URIs.queryparams(req[:query])
|
||||||
@show query
|
|
||||||
playlistId = get(query, "playlistId", "")
|
playlistId = get(query, "playlistId", "")
|
||||||
isempty(playlistId) && return missing_parameter("playlistId")
|
isempty(playlistId) && return missing_parameter("playlistId")
|
||||||
# FIXME: check ownership
|
# FIXME: check ownership
|
||||||
|
@ -374,7 +355,7 @@ function updatePlaylist(req)
|
||||||
|
|
||||||
# TODO: Support multiple (repeated) parameter
|
# TODO: Support multiple (repeated) parameter
|
||||||
if !isempty(songIdAdd)
|
if !isempty(songIdAdd)
|
||||||
songs = allsongs();
|
songs = Beets.songs();
|
||||||
songn = findfirst(s -> s.uuid == songIdAdd, songs)
|
songn = findfirst(s -> s.uuid == songIdAdd, songs)
|
||||||
songn === nothing && return not_found("songIdToAdd")
|
songn === nothing && return not_found("songIdToAdd")
|
||||||
song = songs[songn]
|
song = songs[songn]
|
||||||
|
@ -405,17 +386,15 @@ function getUser(req)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Media retriveal
|
# Media retriveal
|
||||||
|
|
||||||
"Returns a cover art image."
|
"Returns a cover art image."
|
||||||
function getCoverArt(req::Dict)
|
function getCoverArt(req::Dict)
|
||||||
query = HTTP.URIs.queryparams(req[:query])
|
query = HTTP.URIs.queryparams(req[:query])
|
||||||
id = get(query, "id", "")
|
id = get(query, "id", "")
|
||||||
isempty(id) && return missing_parameter("id")
|
isempty(id) && return missing_parameter("id")
|
||||||
albums = Beets.getalbums()
|
n = findfirst(a -> a.uuid == id, Beets.albums)
|
||||||
n = findfirst(a -> album.uuid == id, albums)
|
|
||||||
n === nothing && return not_found("id")
|
n === nothing && return not_found("id")
|
||||||
# @show matching.cover
|
headers = Dict{String,String}()
|
||||||
return Dict(:body => read(albums))
|
return sendfile(Beets.albums[n].cover)
|
||||||
end
|
end
|
||||||
|
|
||||||
"Streams a given media file."
|
"Streams a given media file."
|
||||||
|
@ -423,8 +402,19 @@ function stream(req::Dict)
|
||||||
query = HTTP.URIs.queryparams(req[:query])
|
query = HTTP.URIs.queryparams(req[:query])
|
||||||
id = get(query, "id", "")
|
id = get(query, "id", "")
|
||||||
isempty(id) && return missing_parameter("id")
|
isempty(id) && return missing_parameter("id")
|
||||||
songs = allsongs()
|
songs = Beets.songs()
|
||||||
m = findfirst(x -> (x.uuid == id), songs)
|
m = findfirst(x -> (x.uuid == id), songs)
|
||||||
m === nothing && return not_found("id")
|
m === nothing && return not_found("id")
|
||||||
return Dict(:body => read(songs[m].path))
|
|
||||||
|
return sendfile(songs[m].path)
|
||||||
|
end
|
||||||
|
|
||||||
|
function sendfile(path; suffix = nothing)
|
||||||
|
isfile(path) || return Dict{String,String}(:body => "Not Found")
|
||||||
|
suffix = suffix == nothing ? lowercase(split(path, '.')[end]) : suffix
|
||||||
|
headers = Dict{String,String}()
|
||||||
|
headers["Content-Type"] = Mux.mimetypes[suffix]
|
||||||
|
headers["Content-Length"] = string(filesize(path))
|
||||||
|
return Dict(:body => read(path),
|
||||||
|
:headers => headers)
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,7 +9,7 @@ function push!(root::XMLElement, p::Playlist)
|
||||||
("owner", p.owner),
|
("owner", p.owner),
|
||||||
("public", string(p.public)),
|
("public", string(p.public)),
|
||||||
("songCount", string(length(p.songs))),
|
("songCount", string(length(p.songs))),
|
||||||
("duration", reduce(+, p.songs, init = 0.0) |> floor |> string),
|
("duration", reduce(+, p.songs, init = 0.0) |> floor |> Int |> string),
|
||||||
("created", "FIXME"),
|
("created", "FIXME"),
|
||||||
("coverArt", p.cover),
|
("coverArt", p.cover),
|
||||||
])
|
])
|
||||||
|
@ -35,38 +35,72 @@ push!(p::Playlist, s::Song) = push!(p.songs, s)
|
||||||
function push!(root::XMLElement, album::Beets.Album)
|
function push!(root::XMLElement, album::Beets.Album)
|
||||||
albumXML = new_child(root, "album")
|
albumXML = new_child(root, "album")
|
||||||
set_attributes(albumXML, [
|
set_attributes(albumXML, [
|
||||||
("name", album.title),
|
|
||||||
("id", album.uuid),
|
("id", album.uuid),
|
||||||
("name", album.artist.name),
|
("name", album.title),
|
||||||
("artistId", album.artist.uuid),
|
|
||||||
("artist", album.artist.name),
|
|
||||||
# FIXME
|
|
||||||
("created", "0"),
|
|
||||||
("coverArt", album.uuid),
|
("coverArt", album.uuid),
|
||||||
("songs", "FIXME"),
|
|
||||||
("songCount", string(length(album.songs))),
|
("songCount", string(length(album.songs))),
|
||||||
("duration", string(sum([t.length for t in album.songs])))
|
("created", "0"), # FIXME
|
||||||
|
("duration", string(sum([t.length for t in album.songs]) |> floor |> Int)),
|
||||||
|
("artist", album.artist.name),
|
||||||
|
("artistId", album.artist.uuid)
|
||||||
])
|
])
|
||||||
albumXML
|
albumXML
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function append!(root::XMLElement, a::Beets.Album)
|
||||||
|
albumXML = push!(root, a)
|
||||||
|
for song in a.songs
|
||||||
|
songXML = push!(albumXML, song)
|
||||||
|
set_attributes(songXML, [
|
||||||
|
("album", a.title),
|
||||||
|
("parent", a.artist.uuid), # Not clear
|
||||||
|
("artist", a.artist.name),
|
||||||
|
("coverArt", a.uuid),
|
||||||
|
("albumId", a.uuid),
|
||||||
|
("artistId", a.artist.uuid),
|
||||||
|
])
|
||||||
|
end
|
||||||
|
albumXML
|
||||||
|
end
|
||||||
|
|
||||||
function push!(root::XMLElement, artist::Beets.Artist)
|
function push!(root::XMLElement, artist::Beets.Artist)
|
||||||
artistXML = new_child(root, "artist")
|
artistXML = new_child(root, "artist")
|
||||||
set_attributes(artistXML, [
|
set_attributes(artistXML, [
|
||||||
("id", artist.uuid),
|
("id", artist.uuid),
|
||||||
("name", artist.name),
|
("name", artist.name),
|
||||||
("coverArt", artist.uuid),
|
("coverArt", artist.uuid),
|
||||||
("albumCount", "0")
|
("albumCount", Beets.albums(artist) |> length |> string)
|
||||||
])
|
])
|
||||||
artistXML
|
artistXML
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function push!(root::XMLElement, song::Beets.Song)
|
||||||
|
songXML = new_child(root, "song")
|
||||||
|
suffix = lowercase(song.format)
|
||||||
|
set_attributes(songXML, [
|
||||||
|
("id", song.uuid),
|
||||||
|
("title", song.title),
|
||||||
|
("isDir", "false"),
|
||||||
|
("created", "FIXME"),
|
||||||
|
("duration", string(floor(song.length) |> Int)),
|
||||||
|
("bitrate", string(song.bitrate)),
|
||||||
|
("size", string(filesize(song.path))),
|
||||||
|
("suffix", suffix),
|
||||||
|
("contentType", Mux.mimetypes[suffix]),
|
||||||
|
("isVideo", "false"),
|
||||||
|
("path", relpath(song.path, Beets.musicdir())),
|
||||||
|
("type", "music")
|
||||||
|
])
|
||||||
|
songXML
|
||||||
|
end
|
||||||
|
|
||||||
function push!(root::XMLElement, song_album::Tuple{Beets.Song,Beets.Album})
|
function push!(root::XMLElement, song_album::Tuple{Beets.Song,Beets.Album})
|
||||||
songXML = new_child(root, "song")
|
songXML = new_child(root, "song")
|
||||||
song, album = song_album
|
song, album = song_album
|
||||||
|
suffix = lowercase(song.format)
|
||||||
set_attributes(songXML, [
|
set_attributes(songXML, [
|
||||||
("id", song.uuid),
|
("id", song.uuid),
|
||||||
# ("parent", album.artist.uuid), # Not clear
|
("parent", album.artist.uuid), # Not clear
|
||||||
("title", song.title),
|
("title", song.title),
|
||||||
("album", album.title),
|
("album", album.title),
|
||||||
("artist", album.artist.name),
|
("artist", album.artist.name),
|
||||||
|
@ -76,9 +110,8 @@ function push!(root::XMLElement, song_album::Tuple{Beets.Song,Beets.Album})
|
||||||
("duration", string(floor(song.length) |> Int)),
|
("duration", string(floor(song.length) |> Int)),
|
||||||
("bitrate", string(song.bitrate)),
|
("bitrate", string(song.bitrate)),
|
||||||
("size", string(filesize(song.path))),
|
("size", string(filesize(song.path))),
|
||||||
("suffix", lowercase(song.format)),
|
("suffix", suffix),
|
||||||
## FIXME
|
("contentType", Mux.mimetypes[suffix]), # mpeg
|
||||||
("contentType", "audio/flac"), # mpeg
|
|
||||||
("isVideo", "false"),
|
("isVideo", "false"),
|
||||||
("path", relpath(song.path, Beets.musicdir())),
|
("path", relpath(song.path, Beets.musicdir())),
|
||||||
("albumId", album.uuid),
|
("albumId", album.uuid),
|
||||||
|
@ -89,6 +122,7 @@ function push!(root::XMLElement, song_album::Tuple{Beets.Song,Beets.Album})
|
||||||
end
|
end
|
||||||
|
|
||||||
function props(song::Song)
|
function props(song::Song)
|
||||||
|
suffix = lowercase(song.format)
|
||||||
[
|
[
|
||||||
("id", song.uuid),
|
("id", song.uuid),
|
||||||
# ("parent", album.artist.uuid), # Not clear
|
# ("parent", album.artist.uuid), # Not clear
|
||||||
|
@ -101,9 +135,8 @@ function props(song::Song)
|
||||||
("duration", string(floor(song.length) |> Int)),
|
("duration", string(floor(song.length) |> Int)),
|
||||||
("bitrate", string(song.bitrate)),
|
("bitrate", string(song.bitrate)),
|
||||||
("size", string(filesize(song.path))),
|
("size", string(filesize(song.path))),
|
||||||
("suffix", lowercase(song.format)),
|
("suffix", suffix),
|
||||||
## FIXME
|
("contentType", Mux.mimetypes[suffix]), # mpeg
|
||||||
("contentType", "audio/flac"), # mpeg
|
|
||||||
("isVideo", "false"),
|
("isVideo", "false"),
|
||||||
("path", relpath(song.path, Beets.musicdir())),
|
("path", relpath(song.path, Beets.musicdir())),
|
||||||
# ("albumId", song.album.uuid),
|
# ("albumId", song.album.uuid),
|
||||||
|
|
|
@ -2,10 +2,13 @@ using Mux
|
||||||
using HTTP
|
using HTTP
|
||||||
using Revise
|
using Revise
|
||||||
|
|
||||||
|
push!(LOAD_PATH, "/home/nixo/memories/projects/2018-2019/musicjl")
|
||||||
import Beets
|
import Beets
|
||||||
|
Beets.update_albums();
|
||||||
push!(LOAD_PATH, realpath("JlSonic"))
|
push!(LOAD_PATH, realpath("JlSonic"))
|
||||||
using JlSonic
|
using JlSonic
|
||||||
|
|
||||||
|
|
||||||
include("router.jl")
|
include("router.jl")
|
||||||
include("login.jl")
|
include("login.jl")
|
||||||
@app sonic = (
|
@app sonic = (
|
||||||
|
@ -21,3 +24,4 @@ if !isdefined(Main, :started)
|
||||||
serve(sonic)
|
serve(sonic)
|
||||||
started = true
|
started = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue