diff --git a/backends/jspf/jspf.go b/backends/jspf/jspf.go new file mode 100644 index 0000000..f23ac28 --- /dev/null +++ b/backends/jspf/jspf.go @@ -0,0 +1,23 @@ +/* +Copyright © 2023 Philipp Wolfer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package jspf diff --git a/backends/jspf/jspf_test.go b/backends/jspf/jspf_test.go new file mode 100644 index 0000000..5522c24 --- /dev/null +++ b/backends/jspf/jspf_test.go @@ -0,0 +1,23 @@ +/* +Copyright © 2023 Philipp Wolfer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package jspf_test diff --git a/backends/jspf/models.go b/backends/jspf/models.go new file mode 100644 index 0000000..bb2b8b0 --- /dev/null +++ b/backends/jspf/models.go @@ -0,0 +1,67 @@ +/* +Copyright © 2023 Philipp Wolfer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package jspf + +import "time" + +type Jspf struct { + Playlist Playlist `json:"playlist"` +} + +type Playlist struct { + Title string `json:"title,omitempty"` + Creator string `json:"creator,omitempty"` + Annotation string `json:"annotation,omitempty"` + Info string `json:"info,omitempty"` + Location string `json:"location,omitempty"` + Identifier string `json:"identifier,omitempty"` + Image string `json:"image,omitempty"` + Date time.Time `json:"date,omitempty"` + License string `json:"license,omitempty"` + Attribution []Attribution `json:"attribution,omitempty"` + Links []Link `json:"link,omitempty"` + Meta []Meta `json:"meta,omitempty"` + Extension map[string]any `json:"extension,omitempty"` + Tracks []Track `json:"track"` +} + +type Track struct { + Location []string `json:"location,omitempty"` + Identifier []string `json:"identifier,omitempty"` + Title string `json:"title,omitempty"` + Creator string `json:"creator,omitempty"` + Annotation string `json:"annotation,omitempty"` + Info string `json:"info,omitempty"` + Album string `json:"album,omitempty"` + TrackNum int `json:"trackNum,omitempty"` + Duration int `json:"duration,omitempty"` + Links []Link `json:"link,omitempty"` + Meta []Meta `json:"meta,omitempty"` + Extension map[string]any `json:"extension,omitempty"` +} + +type Attribution map[string]string + +type Link map[string]string + +type Meta map[string]string diff --git a/backends/jspf/models_test.go b/backends/jspf/models_test.go new file mode 100644 index 0000000..b8b195c --- /dev/null +++ b/backends/jspf/models_test.go @@ -0,0 +1,111 @@ +/* +Copyright © 2023 Philipp Wolfer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package jspf_test + +import ( + "encoding/json" + "fmt" + "io" + "os" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uploadedlobster.com/scotty/backends/jspf" +) + +func TestUnmarshalSimple(t *testing.T) { + data, err := readSampleJson("testdata/simple.jspf") + require.NoError(t, err) + + assert := assert.New(t) + playlist := data.Playlist + assert.Equal("Two Songs From Thriller", playlist.Title) + assert.Equal("MJ Fan", playlist.Creator) + require.Len(t, playlist.Tracks, 2) + track1 := playlist.Tracks[0] + require.Len(t, track1.Location, 1) + assert.Equal("http://example.com/billiejean.mp3", track1.Location[0]) + assert.Equal("Billie Jean", track1.Title) + assert.Equal("Michael Jackson", track1.Creator) + assert.Equal("Thriller", track1.Album) +} + +func TestUnmarshalComprehensive(t *testing.T) { + data, err := readSampleJson("testdata/comprehensive.jspf") + require.NoError(t, err) + + assert := assert.New(t) + playlist := data.Playlist + assert.Equal("http://example.com/", playlist.License) + require.Len(t, playlist.Attribution, 2) + assert.Equal("http://example.com/", playlist.Attribution[0]["identifier"]) + assert.Equal("http://example.com/", playlist.Attribution[1]["location"]) + require.Len(t, playlist.Meta, 2) + assert.Equal("345", playlist.Meta[1]["http://example.com/rel/2/"]) + require.Len(t, playlist.Links, 2) + assert.Equal("http://example.com/body/1/", playlist.Links[0]["http://example.com/rel/1/"]) +} + +func TestUnmarshalListenBrainzPlaylist(t *testing.T) { + data, err := readSampleJson("testdata/lb-playlist.jspf") + require.NoError(t, err) + + assert := assert.New(t) + playlist := data.Playlist + assert.Equal( + "https://listenbrainz.org/playlist/96485e27-967a-492a-9d04-c5a819baa2f3", + playlist.Identifier) + expectedPlaylistDate, err := time.Parse(time.RFC3339, "2023-07-04T21:03:52.317148+00:00") + require.NoError(t, err) + assert.Equal(expectedPlaylistDate, playlist.Date) + assert.NotNil(playlist.Extension["https://musicbrainz.org/doc/jspf#playlist"]) + + require.Len(t, playlist.Tracks, 2) + track1 := playlist.Tracks[0] + assert.Equal( + "https://musicbrainz.org/recording/3f2bdbbd-063e-478c-a394-6da0cb303302", + track1.Identifier[0]) + fmt.Printf("Ext: %v\n", track1.Extension["https://musicbrainz.org/doc/jspf#track"]) + extension := track1.Extension["https://musicbrainz.org/doc/jspf#track"].(map[string]any) + assert.NotNil(extension) + assert.Equal("outsidecontext", extension["added_by"]) +} + +func readSampleJson(path string) (jspf.Jspf, error) { + var result jspf.Jspf + jsonFile, err := os.Open(path) + if err != nil { + return result, err + } + defer jsonFile.Close() + + byteValue, err := io.ReadAll(jsonFile) + if err != nil { + return result, err + } + + err = json.Unmarshal(byteValue, &result) + return result, err +} diff --git a/backends/jspf/testdata/comprehensive.jspf b/backends/jspf/testdata/comprehensive.jspf new file mode 100644 index 0000000..82ec93c --- /dev/null +++ b/backends/jspf/testdata/comprehensive.jspf @@ -0,0 +1,91 @@ +{ + "playlist": { + "title": "JSPF example", + "creator": "Name of playlist author", + "annotation": "Super playlist", + "info": "http://example.com/", + "location": "http://example.com/", + "identifier": "http://example.com/", + "image": "http://example.com/", + "date": "2005-01-08T17:10:47-05:00", + "license": "http://example.com/", + "attribution": [ + { + "identifier": "http://example.com/" + }, + { + "location": "http://example.com/" + } + ], + "link": [ + { + "http://example.com/rel/1/": "http://example.com/body/1/" + }, + { + "http://example.com/rel/2/": "http://example.com/body/2/" + } + ], + "meta": [ + { + "http://example.com/rel/1/": "my meta 14" + }, + { + "http://example.com/rel/2/": "345" + } + ], + "extension": { + "http://example.com/app/1/": [ + "ARBITRARY_EXTENSION_BODY", + {} + ], + "http://example.com/app/2/": [ + "ARBITRARY_EXTENSION_BODY" + ] + }, + "track": [ + { + "location": [ + "http://example.com/1.ogg", + "http://example.com/2.mp3" + ], + "identifier": [ + "http://example.com/1/", + "http://example.com/2/" + ], + "title": "Track title", + "creator": "Artist name", + "annotation": "Some text", + "info": "http://example.com/", + "image": "http://example.com/", + "album": "Album name", + "trackNum": 1, + "duration": 0, + "link": [ + { + "http://example.com/rel/1/": "http://example.com/body/1/" + }, + { + "http://example.com/rel/2/": "http://example.com/body/2/" + } + ], + "meta": [ + { + "http://example.com/rel/1/": "my meta 14" + }, + { + "http://example.com/rel/2/": "345" + } + ], + "extension": { + "http://example.com/app/1/": [ + "ARBITRARY_EXTENSION_BODY", + {} + ], + "http://example.com/app/2/": [ + "ARBITRARY_EXTENSION_BODY" + ] + } + } + ] + } +} diff --git a/backends/jspf/testdata/lb-playlist.jspf b/backends/jspf/testdata/lb-playlist.jspf new file mode 100644 index 0000000..3aa8dd5 --- /dev/null +++ b/backends/jspf/testdata/lb-playlist.jspf @@ -0,0 +1,58 @@ +{ + "playlist": { + "creator": "outsidecontext", + "date": "2023-07-04T21:03:52.317148+00:00", + "extension": { + "https://musicbrainz.org/doc/jspf#playlist": { + "creator": "outsidecontext", + "last_modified_at": "2023-07-10T10:03:48.833282+00:00", + "public": false + } + }, + "identifier": "https://listenbrainz.org/playlist/96485e27-967a-492a-9d04-c5a819baa2f3", + "title": "Fundst\u00fccke", + "track": [ + { + "creator": "Airghoul feat. Priest", + "extension": { + "https://musicbrainz.org/doc/jspf#track": { + "added_at": "2023-07-04T21:05:59.492439+00:00", + "added_by": "outsidecontext", + "additional_metadata": { + "caa_id": 32981136309, + "caa_release_mbid": "fb7d69d6-0b4b-4f99-a77a-c3a0d786b52c" + }, + "artist_identifiers": [ + "https://musicbrainz.org/artist/554a5819-6c3f-4734-ae4c-11eabb7ca2e0", + "https://musicbrainz.org/artist/56ff293f-ec9a-4741-9d14-0537c4fb8f97" + ] + } + }, + "identifier": [ + "https://musicbrainz.org/recording/3f2bdbbd-063e-478c-a394-6da0cb303302" + ], + "title": "Orange Forest" + }, + { + "creator": "Crippled Black Phoenix", + "extension": { + "https://musicbrainz.org/doc/jspf#track": { + "added_at": "2023-07-10T10:03:48.833330+00:00", + "added_by": "outsidecontext", + "additional_metadata": { + "caa_id": 28699084533, + "caa_release_mbid": "92e7cf6c-e626-4409-81b0-0fcb8a0c3699" + }, + "artist_identifiers": [ + "https://musicbrainz.org/artist/055b6082-b9cc-4688-85c4-8153c0ef2d70" + ] + } + }, + "identifier": [ + "https://musicbrainz.org/recording/14d612f0-4022-4adc-8cef-87a569e2d65c" + ], + "title": "Lost" + } + ] + } +} diff --git a/backends/jspf/testdata/simple.jspf b/backends/jspf/testdata/simple.jspf new file mode 100644 index 0000000..5473bee --- /dev/null +++ b/backends/jspf/testdata/simple.jspf @@ -0,0 +1,24 @@ +{ + "playlist": { + "title": "Two Songs From Thriller", + "creator": "MJ Fan", + "track": [ + { + "location": [ + "http://example.com/billiejean.mp3" + ], + "title": "Billie Jean", + "creator": "Michael Jackson", + "album": "Thriller" + }, + { + "location": [ + "http://example.com/thegirlismine.mp3" + ], + "title": "The Girl Is Mine", + "creator": "Michael Jackson", + "album": "Thriller" + } + ] + } +}