JSPF: Implemented export as loves and listens

This commit is contained in:
Philipp Wolfer 2025-05-01 15:10:00 +02:00
parent cfc3cd522d
commit a645ec5c78
No known key found for this signature in database
GPG key ID: 8FDF744D4919943B
6 changed files with 225 additions and 42 deletions

View file

@ -1,5 +1,5 @@
/*
Copyright © 2023 Philipp Wolfer <phw@uploadedlobster.com>
Copyright © 2023-2025 Philipp Wolfer <phw@uploadedlobster.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -22,7 +22,28 @@ THE SOFTWARE.
package jspf
import "time"
import (
"encoding/json"
"fmt"
"time"
)
// Represents a JSPF extension
type Extension any
// A map of JSPF extensions
type ExtensionMap map[string]Extension
// Parses the extension with the given ID and unmarshals it into "v".
// If the extensions is not found or the data cannot be unmarshalled,
// an error is returned.
func (e ExtensionMap) Get(id string, v any) error {
ext, ok := e[id]
if !ok {
return fmt.Errorf("extension %q not found", id)
}
return unmarshalExtension(ext, v)
}
const (
// The identifier for the MusicBrainz / ListenBrainz JSPF playlist extension
@ -83,3 +104,11 @@ type MusicBrainzTrackExtension struct {
// this document.
AdditionalMetadata map[string]any `json:"additional_metadata,omitempty"`
}
func unmarshalExtension(ext Extension, v any) error {
asJson, err := json.Marshal(ext)
if err != nil {
return err
}
return json.Unmarshal(asJson, v)
}

View file

@ -1,5 +1,5 @@
/*
Copyright © 2023 Philipp Wolfer <phw@uploadedlobster.com>
Copyright © 2023-2025 Philipp Wolfer <phw@uploadedlobster.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -26,6 +26,7 @@ import (
"bytes"
"fmt"
"log"
"testing"
"time"
"go.uploadedlobster.com/scotty/pkg/jspf"
@ -38,7 +39,7 @@ func ExampleMusicBrainzTrackExtension() {
Tracks: []jspf.Track{
{
Title: "Oweynagat",
Extension: map[string]any{
Extension: jspf.ExtensionMap{
jspf.MusicBrainzTrackExtensionID: jspf.MusicBrainzTrackExtension{
AddedAt: time.Date(2023, 11, 24, 07, 47, 50, 0, time.UTC),
AddedBy: "scotty",
@ -72,3 +73,29 @@ func ExampleMusicBrainzTrackExtension() {
// }
// }
}
func TestExtensionMapGet(t *testing.T) {
ext := jspf.ExtensionMap{
jspf.MusicBrainzTrackExtensionID: jspf.MusicBrainzTrackExtension{
AddedAt: time.Date(2023, 11, 24, 07, 47, 50, 0, time.UTC),
AddedBy: "scotty",
},
}
var trackExt jspf.MusicBrainzTrackExtension
err := ext.Get(jspf.MusicBrainzTrackExtensionID, &trackExt)
if err != nil {
t.Fatal(err)
}
if trackExt.AddedBy != "scotty" {
t.Fatalf("expected 'scotty', got '%s'", trackExt.AddedBy)
}
}
func TestExtensionMapGetNotFound(t *testing.T) {
ext := jspf.ExtensionMap{}
var trackExt jspf.MusicBrainzTrackExtension
err := ext.Get(jspf.MusicBrainzTrackExtensionID, &trackExt)
if err == nil {
t.Fatal("expected ExtensionMap.Get to return an error")
}
}

View file

@ -1,5 +1,5 @@
/*
Copyright © 2023 Philipp Wolfer <phw@uploadedlobster.com>
Copyright © 2023-2025 Philipp Wolfer <phw@uploadedlobster.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -32,35 +32,35 @@ type JSPF struct {
}
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"`
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 ExtensionMap `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 int64 `json:"duration,omitempty"`
Links []Link `json:"link,omitempty"`
Meta []Meta `json:"meta,omitempty"`
Extension map[string]any `json:"extension,omitempty"`
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 int64 `json:"duration,omitempty"`
Links []Link `json:"link,omitempty"`
Meta []Meta `json:"meta,omitempty"`
Extension ExtensionMap `json:"extension,omitempty"`
}
type Attribution map[string]string