diff --git a/.DS_Store b/.DS_Store index 7c893150..a726a307 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.gitignore b/.gitignore index 7a8449a9..9cf3c556 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,7 @@ xxx /dcai/ ***/.pmomusic.yml +***/.pmomusic_covers/*** +***/.DS_Strore/*** +.DS_Strore/*** +.pmomusic_covers/*** \ No newline at end of file diff --git a/.pmomusic_covers/bd89518e78a247d3.128.webp b/.pmomusic_covers/bd89518e78a247d3.128.webp new file mode 100644 index 00000000..7e2e15f0 Binary files /dev/null and b/.pmomusic_covers/bd89518e78a247d3.128.webp differ diff --git a/.pmomusic_covers/bd89518e78a247d3.256.webp b/.pmomusic_covers/bd89518e78a247d3.256.webp new file mode 100644 index 00000000..151378e5 Binary files /dev/null and b/.pmomusic_covers/bd89518e78a247d3.256.webp differ diff --git a/.pmomusic_covers/bd89518e78a247d3.64.webp b/.pmomusic_covers/bd89518e78a247d3.64.webp new file mode 100644 index 00000000..d44f791d Binary files /dev/null and b/.pmomusic_covers/bd89518e78a247d3.64.webp differ diff --git a/.pmomusic_covers/bd89518e78a247d3.orig.webp b/.pmomusic_covers/bd89518e78a247d3.orig.webp new file mode 100644 index 00000000..cb8d366d Binary files /dev/null and b/.pmomusic_covers/bd89518e78a247d3.orig.webp differ diff --git a/.pmomusic_covers/cache.db b/.pmomusic_covers/cache.db new file mode 100644 index 00000000..f9341960 Binary files /dev/null and b/.pmomusic_covers/cache.db differ diff --git a/all.txt b/all.txt new file mode 100644 index 00000000..352b2c4c --- /dev/null +++ b/all.txt @@ -0,0 +1,415 @@ +------- cache.go ------ +package pmocover + +import ( + "bytes" + "crypto/sha1" + "encoding/hex" + "errors" + "image" + "io" + "net/http" + "os" + "path/filepath" + "sync" +) + +// CacheEntry représente une image stockée (original + dérivés) +type CacheEntry struct { + PK string `json:"pk"` + SourceURL string `json:"source_url"` + Hits int `json:"hits"` + LastUsed string `json:"last_used"` // ISO8601 +} + +// Cache persistant avec SQLite +type Cache struct { + dir string + limit int + db *DB + + mu sync.Mutex +} + +// NewCache ouvre ou crée un cache persistant avec SQLite +func NewCache(dir string, limit int) (*Cache, error) { + if err := os.MkdirAll(dir, 0o755); err != nil { + return nil, err + } + + db, err := InitDB(dir) + if err != nil { + return nil, err + } + + return &Cache{ + dir: dir, + limit: limit, + db: db, + }, nil +} + +// AddFromURL télécharge une image et la met en cache +func (c *Cache) AddFromURL(url string) (string, error) { + resp, err := http.Get(url) + if err != nil { + return "", err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return "", errors.New("bad status") + } + data, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + return c.Add(url, data) +} + +// Add ajoute une image déjà téléchargée +func (c *Cache) Add(url string, data []byte) (string, error) { + pk := pkFromURL(url) + origPath := filepath.Join(c.dir, pk+".orig.webp") + + c.mu.Lock() + defer c.mu.Unlock() + + if _, err := os.Stat(origPath); errors.Is(err, os.ErrNotExist) { + img, _, err := image.Decode(bytes.NewReader(data)) + if err != nil { + return "", err + } + buf, err := encodeWebP(img) + if err != nil { + return "", err + } + if err := os.WriteFile(origPath, buf, 0o644); err != nil { + return "", err + } + } + + _ = c.db.Add(pk, url) // ajoute ou met à jour la base + return pk, nil +} + +// Get renvoie le chemin du fichier original et met à jour hits/last_used +func (c *Cache) Get(pk string) (string, error) { + c.mu.Lock() + defer c.mu.Unlock() + + _, err := c.db.Get(pk) + if err != nil { + return "", err + } + + _ = c.db.UpdateHit(pk) + + origPath := filepath.Join(c.dir, pk+".orig.webp") + if _, err := os.Stat(origPath); err != nil { + return "", err + } + return origPath, nil +} + +// Purge vide complètement le cache +func (c *Cache) Purge() error { + c.mu.Lock() + defer c.mu.Unlock() + + files, _ := filepath.Glob(filepath.Join(c.dir, "*")) + for _, f := range files { + os.Remove(f) + } + return c.db.Purge() +} + +// pkFromURL → hash stable basé sur l’URL +func pkFromURL(url string) string { + h := sha1.Sum([]byte(url)) + return hex.EncodeToString(h[:8]) +} +----------------- +------- db.go ------ +package pmocover + +import ( + "database/sql" + "path/filepath" + "time" + + _ "modernc.org/sqlite" +) + +// DB représente la base SQLite du cache +type DB struct { + conn *sql.DB +} + +// InitDB ouvre ou crée la base SQLite dans le répertoire dir +func InitDB(dir string) (*DB, error) { + path := filepath.Join(dir, "cache.db") + conn, err := sql.Open("sqlite", path) + if err != nil { + return nil, err + } + + db := &DB{conn: conn} + if err := db.initTables(); err != nil { + conn.Close() + return nil, err + } + return db, nil +} + +// initTables crée la table covers si elle n’existe pas +func (db *DB) initTables() error { + _, err := db.conn.Exec(` + CREATE TABLE IF NOT EXISTS covers ( + pk TEXT PRIMARY KEY, + source_url TEXT, + hits INTEGER DEFAULT 0, + last_used TEXT + ); + `) + return err +} + +// Add ajoute ou met à jour une entrée cover +func (db *DB) Add(pk, url string) error { + _, err := db.conn.Exec(` + INSERT INTO covers(pk, source_url, hits, last_used) + VALUES(?, ?, 0, ?) + ON CONFLICT(pk) DO UPDATE SET + source_url=excluded.source_url, + last_used=excluded.last_used; + `, pk, url, time.Now().UTC().Format(time.RFC3339)) + return err +} + +// Get récupère une entrée +func (db *DB) Get(pk string) (*CacheEntry, error) { + row := db.conn.QueryRow(` + SELECT pk, source_url, hits, last_used + FROM covers + WHERE pk = ? + `, pk) + entry := &CacheEntry{} + var lastUsed sql.NullString + err := row.Scan(&entry.PK, &entry.SourceURL, &entry.Hits, &lastUsed) + if err != nil { + return nil, err + } + if lastUsed.Valid { + entry.LastUsed = lastUsed.String + } + return entry, nil +} + +// UpdateHit incrémente hits et met à jour last_used +func (db *DB) UpdateHit(pk string) error { + _, err := db.conn.Exec(` + UPDATE covers + SET hits = hits + 1, + last_used = ? + WHERE pk = ? + `, time.Now().UTC().Format(time.RFC3339), pk) + return err +} + +// Purge supprime toutes les entrées de la base +func (db *DB) Purge() error { + _, err := db.conn.Exec(`DELETE FROM covers`) + return err +} + +// GetAll récupère toutes les entrées triées par hits décroissant +func (db *DB) GetAll() ([]*CacheEntry, error) { + rows, err := db.conn.Query(` + SELECT pk, source_url, hits, last_used + FROM covers + ORDER BY hits DESC + `) + if err != nil { + return nil, err + } + defer rows.Close() + + var entries []*CacheEntry + for rows.Next() { + entry := &CacheEntry{} + var lastUsed sql.NullString + if err := rows.Scan(&entry.PK, &entry.SourceURL, &entry.Hits, &lastUsed); err != nil { + continue + } + if lastUsed.Valid { + entry.LastUsed = lastUsed.String + } + entries = append(entries, entry) + } + return entries, nil +} +----------------- +------- handler.go ------ +package pmocover + +import ( + "encoding/json" + "net/http" + "os" + "path/filepath" + "strconv" + "strings" +) + +// ServeMux branche les routes /covers +func (c *Cache) ServeMux(mux *http.ServeMux) { + // Images + mux.HandleFunc("/covers/images/", func(w http.ResponseWriter, r *http.Request) { + parts := strings.Split(strings.TrimPrefix(r.URL.Path, "/covers/images/"), "/") + if len(parts) == 0 || parts[0] == "" { + http.Error(w, "missing pk", 400) + return + } + pk := parts[0] + if len(parts) == 1 { + path, err := c.Get(pk) + if err != nil { + http.NotFound(w, r) + return + } + w.Header().Set("Content-Type", "image/webp") + http.ServeFile(w, r, path) + return + } + size, err := strconv.Atoi(parts[1]) + if err != nil { + http.Error(w, "invalid size", 400) + return + } + variantPath := filepath.Join(c.dir, pk+"."+parts[1]+".webp") + if _, err := os.Stat(variantPath); os.IsNotExist(err) { + data, err := c.generateVariant(pk, size) + if err != nil { + http.Error(w, "cannot generate", 500) + return + } + w.Header().Set("Content-Type", "image/webp") + w.Write(data) + return + } + w.Header().Set("Content-Type", "image/webp") + http.ServeFile(w, r, variantPath) + }) + + // Stats + mux.HandleFunc("/covers/stats", func(w http.ResponseWriter, r *http.Request) { + entries, _ := c.db.GetAll() + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(entries) + }) +} +----------------- +------- pmocover.go ------ +package pmocover + +import "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoconfig" + +// GetCoverCache récupère le cache, crée le dossier et la base si nécessaire +func GetCoverCache() (*Cache, error) { + dir := pmoconfig.GetConfig().GetCoverCacheDir() + cache, err := NewCache(dir, 2000) + if err != nil { + return nil, err + } + return cache, nil +} +----------------- +------- webp.go ------ +package pmocover + +import ( + "bytes" + "image" + "image/draw" + "os" + "path/filepath" + "strconv" + + "github.com/chai2010/webp" + xdraw "golang.org/x/image/draw" +) + +// encodeWebP convertit une image.Image en WebP avec compression sans perte. +func encodeWebP(img image.Image) ([]byte, error) { + var buf bytes.Buffer + if err := webp.Encode(&buf, img, &webp.Options{Lossless: true}); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +// ensureSquare génère une image carrée paddée transparente +func ensureSquare(img image.Image, size int) image.Image { + dst := image.NewRGBA(image.Rect(0, 0, size, size)) + draw.Draw(dst, dst.Bounds(), &image.Uniform{C: image.Transparent}, image.Point{}, draw.Src) + + srcBounds := img.Bounds() + srcW, srcH := srcBounds.Dx(), srcBounds.Dy() + + var scale float64 + if srcW > srcH { + scale = float64(size) / float64(srcW) + } else { + scale = float64(size) / float64(srcH) + } + newW, newH := int(float64(srcW)*scale), int(float64(srcH)*scale) + + // redimensionnement avec Catmull-Rom + tmp := image.NewRGBA(image.Rect(0, 0, newW, newH)) + xdraw.CatmullRom.Scale(tmp, tmp.Bounds(), img, srcBounds, xdraw.Over, nil) + + // centrer l'image redimensionnée dans le carré + offset := image.Pt((size-newW)/2, (size-newH)/2) + draw.Draw(dst, tmp.Bounds().Add(offset), tmp, image.Point{}, draw.Over) + + return dst +} + +// generateVariant génère une version carrée en WebP pour une taille donnée. +// Si le fichier existe déjà, il est renvoyé directement. +func (c *Cache) generateVariant(pk string, size int) ([]byte, error) { + variantPath := filepath.Join(c.dir, pk+"."+strconv.Itoa(size)+".webp") + + // si le variant existe déjà, le renvoyer + if data, err := os.ReadFile(variantPath); err == nil { + return data, nil + } + + // lire l'image originale + origPath := filepath.Join(c.dir, pk+".orig.webp") + data, err := os.ReadFile(origPath) + if err != nil { + return nil, err + } + img, _, err := image.Decode(bytes.NewReader(data)) + if err != nil { + return nil, err + } + + // générer une image carrée + sq := ensureSquare(img, size) + + // encoder en WebP + buf, err := encodeWebP(sq) + if err != nil { + return nil, err + } + + // sauvegarder le variant + if err := os.WriteFile(variantPath, buf, 0o644); err != nil { + return nil, err + } + + return buf, nil +} +----------------- diff --git a/cmd/pmomusic/main.go b/cmd/pmomusic/main.go index 723eb538..a7a3861a 100644 --- a/cmd/pmomusic/main.go +++ b/cmd/pmomusic/main.go @@ -8,8 +8,8 @@ import ( log "github.com/sirupsen/logrus" - "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp" - "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/mediarenderer" + "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp" + "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/mediarenderer" ) func main() { @@ -23,7 +23,7 @@ func main() { defer stop() // Crée le serveur avec baseURL auto-déduite depuis l’IP locale - server := upnp.NewServer("pmomusic") + server := pmoupnp.NewServer("pmomusic") server.RegisterDevice("", mediarenderer.FakeRenderer) diff --git a/go.mod b/go.mod index 6dad701b..2e2ee421 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,19 @@ require ( ) require ( - golang.org/x/sys v0.32.0 // indirect + github.com/chai2010/webp v1.4.0 // indirect + github.com/disintegration/imaging v1.6.2 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-sqlite3 v1.14.32 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect + golang.org/x/image v0.0.0-20211028202545-6944b10bf410 // indirect + golang.org/x/sys v0.34.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + modernc.org/libc v1.66.3 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect + modernc.org/sqlite v1.38.2 // indirect ) diff --git a/go.sum b/go.sum index 85214551..2df94bec 100644 --- a/go.sum +++ b/go.sum @@ -1,24 +1,58 @@ github.com/beevik/etree v1.5.1 h1:TC3zyxYp+81wAmbsi8SWUpZCurbxa6S8RITYRSkNRwo= github.com/beevik/etree v1.5.1/go.mod h1:gPNJNaBGVZ9AwsidazFZyygnd+0pAU38N4D+WemwKNs= +github.com/chai2010/webp v1.4.0 h1:6DA2pkkRUPnbOHvvsmGI3He1hBKf/bkRlniAiSGuEko= +github.com/chai2010/webp v1.4.0/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= +github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/globusdigital/soap v1.4.0 h1:nQDpWelZr3zKg6Oe1e5SqWd4PfiUvsl5/44oWUXy3II= github.com/globusdigital/soap v1.4.0/go.mod h1:p8hjOZ4FmK0jXBTcIZ6e5M2QBfcsxBUKWBYsHM2eZRw= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= +github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= +golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U= +golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ= +modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek= +modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E= diff --git a/pmoapp/web/src/App.css b/pmoapp/web/src/App.css index 57bc2d52..2b1071b2 100644 --- a/pmoapp/web/src/App.css +++ b/pmoapp/web/src/App.css @@ -1,123 +1,207 @@ /* App.css ou index.css */ @import "tailwindcss"; -@tailwind base; -@tailwind components; -@tailwind utilities; +/* Styles de base améliorés */ +:root { + font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; + line-height: 1.6; + font-weight: 400; + color-scheme: light dark; + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} -/* Styles custom */ +/* Styles pour le conteneur principal */ .log-container { - @apply p-4 space-y-2 min-h-screen; + @apply p-4 space-y-3 min-h-screen; + max-width: 100%; + overflow-x: hidden; } +/* Styles généraux pour les entrées de log améliorées */ .log-entry { - @apply rounded-lg p-2 font-mono shadow-md; -} - -.log-entry.info { @apply bg-gray-800 text-green-400; } -.log-entry.warning { @apply bg-gray-800 text-yellow-400; } -.log-entry.error { @apply bg-gray-800 text-red-400; } -.log-entry.debug { @apply bg-gray-800 text-purple-400; } - - -/* Styles pour le thème sombre */ -.dark .log-entry { - background: linear-gradient(90deg, rgba(30, 30, 30, 0.5), transparent); + @apply rounded-xl p-4 font-mono shadow-lg transition-all duration-300 ease-out; border-left: 4px solid; + backdrop-filter: blur(10px); + transform-origin: center top; } -.dark .log-entry.error { - border-color: #f87171; - background: linear-gradient(90deg, rgba(248, 113, 113, 0.1), transparent); -} - -.dark .log-entry.warning { - border-color: #fbbf24; - background: linear-gradient(90deg, rgba(251, 191, 36, 0.1), transparent); -} - -.dark .log-entry.info { - border-color: #60a5fa; - background: linear-gradient(90deg, rgba(96, 165, 250, 0.1), transparent); -} - -.dark .log-entry.debug { - border-color: #a78bfa; - background: linear-gradient(90deg, rgba(167, 139, 250, 0.1), transparent); -} - -/* Styles pour le thème clair */ -.light .log-entry { - background: linear-gradient(90deg, rgba(240, 240, 240, 0.5), transparent); - border-left: 4px solid; -} - -.light .log-entry.error { - border-color: #dc2626; - background: linear-gradient(90deg, rgba(220, 38, 38, 0.1), transparent); -} - -.light .log-entry.warning { - border-color: #d97706; - background: linear-gradient(90deg, rgba(217, 119, 6, 0.1), transparent); -} - -.light .log-entry.info { - border-color: #2563eb; - background: linear-gradient(90deg, rgba(37, 99, 235, 0.1), transparent); -} - -.light .log-entry.debug { - border-color: #7c3aed; - background: linear-gradient(90deg, rgba(124, 58, 237, 0.1), transparent); -} - -/* Animation pour les nouveaux logs */ -@keyframes highlight { - 0% { opacity: 0.5; } - 100% { opacity: 1; } +/* Animation d'entrée pour les nouveaux logs */ +@keyframes slideIn { + 0% { + opacity: 0; + transform: translateY(-10px); + } + 100% { + opacity: 1; + transform: translateY(0); + } } .log-entry:first-child { - animation: highlight 0.5s ease; + animation: slideIn 0.4s ease-out; } -/* Styles pour la barre de défilement */ -.log-container::-webkit-scrollbar { - width: 8px; +/* Styles pour le thème sombre - améliorés */ +.dark .log-entry { + background: linear-gradient(90deg, rgba(30, 30, 30, 0.7), rgba(30, 30, 30, 0.4)); + border-top: 1px solid rgba(255, 255, 255, 0.05); } -.log-container::-webkit-scrollbar-track { - background: transparent; +.dark .log-entry.info { + border-color: #3b82f6; + box-shadow: 0 4px 12px rgba(59, 130, 246, 0.15); } -.log-container::-webkit-scrollbar-thumb { - background: rgba(156, 163, 175, 0.5); - border-radius: 4px; +.dark .log-entry.warning { + border-color: #f59e0b; + box-shadow: 0 4px 12px rgba(245, 158, 11, 0.15); } -.log-container::-webkit-scrollbar-thumb:hover { - background: rgba(156, 163, 175, 0.7); +.dark .log-entry.error { + border-color: #ef4444; + box-shadow: 0 4px 12px rgba(239, 68, 68, 0.15); } -.dark .log-container::-webkit-scrollbar-thumb { - background: rgba(75, 85, 99, 0.5); +.dark .log-entry.debug { + border-color: #8b5cf6; + box-shadow: 0 4px 12px rgba(139, 92, 246, 0.15); } -.dark .log-container::-webkit-scrollbar-thumb:hover { - background: rgba(75, 85, 99, 0.7); +.dark .log-entry:hover { + transform: translateY(-2px); + box-shadow: 0 6px 16px rgba(0, 0, 0, 0.3); } -/* Amélioration de l'apparence des détails */ -details summary::-webkit-details-marker { - display: none; +/* Styles pour les éléments de détail et code */ +.dark details { + @apply mt-3 rounded-lg overflow-hidden; + background: rgba(20, 20, 20, 0.6); } -details summary { - list-style: none; - transition: color 0.2s ease; +.dark summary { + @apply p-2 cursor-pointer font-semibold flex items-center; + background: rgba(0, 0, 0, 0.2); + user-select: none; } -details[open] summary { - margin-bottom: 0.5rem; +.dark summary::before { + content: "►"; + @apply mr-2 text-xs transition-transform; +} + +.dark details[open] summary::before { + transform: rotate(90deg); +} + +.dark pre { + @apply p-4 overflow-x-auto; + background: rgba(10, 10, 10, 0.8) !important; + border-left: 3px solid rgba(255, 255, 255, 0.1); +} + +.dark code { + @apply text-sm; + font-family: 'Fira Code', 'Monaco', 'Cascadia Code', monospace; +} + +/* Styles pour les éléments spécifiques dans les logs */ +.dark .bg-black { + background: rgba(0, 0, 0, 0.3) !important; +} + +.dark ul { + @apply mt-2 space-y-1; +} + +.dark li { + @apply flex; +} + +.dark li strong { + @apply min-w-[100px] text-neutral-300; +} + +.dark img { + @apply rounded-lg mt-2 max-h-24 border border-neutral-700; +} + +/* Amélioration du header */ +.dark header { + @apply sticky top-0 z-10 shadow-md border-b border-neutral-700; + background: rgba(23, 23, 23, 0.95); + backdrop-filter: blur(10px); +} + +.dark button { + @apply transition-all duration-200; +} + +.dark button:hover { + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); +} + +/* Barre de défilement personnalisée */ +.dark ::-webkit-scrollbar { + width: 10px; +} + +.dark ::-webkit-scrollbar-track { + background: rgba(30, 30, 30, 0.5); + border-radius: 5px; +} + +.dark ::-webkit-scrollbar-thumb { + background: rgba(90, 90, 90, 0.5); + border-radius: 5px; +} + +.dark ::-webkit-scrollbar-thumb:hover { + background: rgba(120, 120, 120, 0.6); +} + +/* Styles pour les messages spécifiques */ +.log-entry .emoji { + @apply mr-2; +} + +.log-entry .ip-address { + @apply font-bold text-amber-400; +} + +.log-entry .success { + @apply text-green-400; +} + +/* Responsive design */ +@media (max-width: 768px) { + .log-entry { + @apply p-3; + } + + .dark header { + @apply flex-col space-y-2 items-start; + } + + .dark header h1 { + @apply text-lg; + } + + .dark pre { + @apply text-xs; + } +} + +/* Animation de pulse pour les logs actifs */ +@keyframes pulseSubtle { + 0% { opacity: 0.9; } + 50% { opacity: 1; } + 100% { opacity: 0.9; } +} + +.log-entry:first-child { + animation: slideIn 0.4s ease-out, pulseSubtle 3s infinite; } \ No newline at end of file diff --git a/pmoapp/web/src/App.tsx b/pmoapp/web/src/App.tsx index 83b53bdd..3cdafcfd 100644 --- a/pmoapp/web/src/App.tsx +++ b/pmoapp/web/src/App.tsx @@ -1,16 +1,17 @@ // App.tsx import { useEffect, useState } from "react"; import { Logs, type LogEntry } from "./Logs"; +import { Covers } from "./Covers"; import "./App.css"; export default function App() { const [theme, setTheme] = useState<"light" | "dark">("dark"); const [logs, setLogs] = useState([]); + const [view, setView] = useState<"logs" | "covers">("logs"); const toggleTheme = () => setTheme((prev) => (prev === "dark" ? "light" : "dark")); - // Fonction pour transformer le SSE en LogEntry const mapSSEToLogEntry = (sseData: any, index: number): LogEntry => ({ id: sseData.time || `log-${index}`, level: sseData.level || "info", @@ -37,17 +38,36 @@ export default function App() { }, []); return ( -
-
-

Mon Logger

- +
+
+

PMO-Music

+
+ + + +
- + {view === "logs" && } + {view === "covers" && }
); } diff --git a/pmoapp/web/src/Covers.tsx b/pmoapp/web/src/Covers.tsx new file mode 100644 index 00000000..25f78202 --- /dev/null +++ b/pmoapp/web/src/Covers.tsx @@ -0,0 +1,46 @@ +// Covers.tsx +import { useEffect, useState } from "react"; + +type CoverEntry = { + pk: string; + source_url: string; + hits: number; +}; + +export function Covers() { + const [covers, setCovers] = useState([]); + + useEffect(() => { + fetch("/covers/stats") + .then((res) => res.json()) + .then((data) => { + setCovers(data.top_hits || []); + }) + .catch((err) => console.error("Erreur stats covers:", err)); + }, []); + + return ( +
+

+ Top Covers +

+
+ {covers.map((cover) => ( +
+ {cover.source_url} +

+ {cover.hits} hits +

+
+ ))} +
+
+ ); +} diff --git a/pmoapp/web/src/Logs.tsx b/pmoapp/web/src/Logs.tsx index cc3f3a58..586df015 100644 --- a/pmoapp/web/src/Logs.tsx +++ b/pmoapp/web/src/Logs.tsx @@ -13,19 +13,19 @@ type LogsProps = { theme: "light" | "dark"; }; -export function Logs({ logs, theme }: LogsProps) { +export function Logs({ logs }: LogsProps) { const levelClass = (level: LogEntry["level"]) => { switch (level) { case "info": - return "info"; + return "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200"; case "warn": - return "warning"; + return "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200"; case "error": - return "error"; + return "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200"; case "debug": - return "debug"; + return "bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200"; default: - return "info"; + return "bg-gray-100 dark:bg-gray-800"; } }; @@ -36,19 +36,20 @@ export function Logs({ logs, theme }: LogsProps) { }; return ( -
+
{logs.map((log) => ( -
+
{children} diff --git a/pmoapp/web/src/index.css b/pmoapp/web/src/index.css index 08a3ac9e..51178550 100644 --- a/pmoapp/web/src/index.css +++ b/pmoapp/web/src/index.css @@ -54,7 +54,7 @@ button:focus-visible { outline: 4px auto -webkit-focus-ring-color; } -@media (prefers-color-scheme: light) { +/* @media (prefers-color-scheme: light) { :root { color: #213547; background-color: #ffffff; @@ -65,4 +65,4 @@ button:focus-visible { button { background-color: #f9f9f9; } -} +} */ diff --git a/upnp/config.go b/pmoconfig/config.go similarity index 93% rename from upnp/config.go rename to pmoconfig/config.go index f25050d0..37a973b4 100644 --- a/upnp/config.go +++ b/pmoconfig/config.go @@ -1,4 +1,4 @@ -package upnp +package pmoconfig import ( _ "embed" @@ -366,7 +366,7 @@ func (conf *Config) GetHTTPPort() int { return 1900 } -func (conf *Config) GetDeviceUDN(devtype DeviceType, name string) string { +func (conf *Config) GetDeviceUDN(devtype, name string) string { udn, error := conf.GetValue([]string{"devices", string(devtype), name, "udn"}) if error != nil { @@ -377,3 +377,34 @@ func (conf *Config) GetDeviceUDN(devtype DeviceType, name string) string { return udn.(string) } + +func (conf *Config) GetCoverCacheDir() string { + cachedir, _ := conf.GetValue([]string{"host", "cover_cache", "directory"}) + if cachedir == nil { + cachedir = "./.pmomusic_covers" + } + + sdir, ok := cachedir.(string) + + if ok && sdir != "" { + return sdir + } + + return "./.pmomusic_covers" +} + +func (conf *Config) GetCoverCacheSize() int { + size, _ := conf.GetValue([]string{"host", "cover_cache", "size"}) + if size == nil { + return 2000 + } + + switch v := size.(type) { + case float64: + return int(v) + case int: + return v + default: + return 2000 + } +} diff --git a/upnp/pmomusic.yaml b/pmoconfig/pmomusic.yaml similarity index 58% rename from upnp/pmomusic.yaml rename to pmoconfig/pmomusic.yaml index 23a49537..c2acfad7 100644 --- a/upnp/pmomusic.yaml +++ b/pmoconfig/pmomusic.yaml @@ -1,5 +1,8 @@ host: - http_port: "1900" + http_port: "8080" + cover_cache: + directory: "./.pmomusic_covers" + size: 2000 devices: mediarenderer: mpd_renderer: diff --git a/pmocover/cache.go b/pmocover/cache.go new file mode 100644 index 00000000..2dea9cba --- /dev/null +++ b/pmocover/cache.go @@ -0,0 +1,209 @@ +package pmocover + +import ( + "bytes" + "crypto/sha1" + "encoding/hex" + "errors" + "fmt" + "image" + _ "image/jpeg" // support JPEG + _ "image/png" // support PNG + "io" + "net/http" + "os" + "path/filepath" + "sync" + + _ "golang.org/x/image/webp" // support WebP +) + +// CacheEntry représente une image stockée (original + dérivés) +type CacheEntry struct { + PK string `json:"pk"` + SourceURL string `json:"source_url"` + Hits int `json:"hits"` + LastUsed string `json:"last_used"` // ISO8601 +} + +// Cache persistant avec SQLite +type Cache struct { + dir string + limit int + db *DB + + mu sync.Mutex +} + +// NewCache ouvre ou crée un cache persistant avec SQLite +func NewCache(dir string, limit int) (*Cache, error) { + if err := os.MkdirAll(dir, 0o755); err != nil { + return nil, err + } + + db, err := InitDB(dir) + if err != nil { + return nil, err + } + + return &Cache{ + dir: dir, + limit: limit, + db: db, + }, nil +} + +// AddFromURL télécharge une image et la met en cache +func (c *Cache) AddFromURL(url string) (string, error) { + resp, err := http.Get(url) + if err != nil { + return "", err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return "", errors.New("bad status") + } + data, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + return c.Add(url, data) +} + +// EnsureFromURL vérifie si l'URL est déjà dans le cache et que le fichier existe. +func (c *Cache) EnsureFromURL(url string) (string, error) { + pk := pkFromURL(url) + + // Vérifie si une entrée existe déjà en base + _, err := c.db.Get(pk) + if err == nil { + // Vérifie aussi que le fichier original existe + origPath := filepath.Join(c.dir, pk+".orig.webp") + if _, statErr := os.Stat(origPath); statErr == nil { + return pk, nil + } + // Si le fichier a disparu → on retélécharge + } + + // Sinon, on télécharge et on ajoute + return c.AddFromURL(url) +} + +// decodeImage essaie de décoder une image depuis un []byte en utilisant les formats connus +func decodeImage(data []byte) (image.Image, string, error) { + img, format, err := image.Decode(bytes.NewReader(data)) + if err != nil { + return nil, "", fmt.Errorf("échec de décodage image: %w", err) + } + return img, format, nil +} + +// Add ajoute une image déjà téléchargée +func (c *Cache) Add(url string, data []byte) (string, error) { + pk := pkFromURL(url) + origPath := filepath.Join(c.dir, pk+".orig.webp") + + c.mu.Lock() + defer c.mu.Unlock() + + if _, err := os.Stat(origPath); errors.Is(err, os.ErrNotExist) { + img, format, err := decodeImage(data) + if err != nil { + return "", err + } + fmt.Printf("Décodage réussi : format %s depuis %s\n", format, url) + + buf, err := encodeWebP(img) + if err != nil { + return "", err + } + if err := os.WriteFile(origPath, buf, 0o644); err != nil { + return "", err + } + } + + _ = c.db.Add(pk, url) // ajoute ou met à jour la base + return pk, nil +} + +// Get renvoie le chemin du fichier original et met à jour hits/last_used +func (c *Cache) Get(pk string) (string, error) { + c.mu.Lock() + defer c.mu.Unlock() + + _, err := c.db.Get(pk) + if err != nil { + return "", err + } + + _ = c.db.UpdateHit(pk) + + origPath := filepath.Join(c.dir, pk+".orig.webp") + if _, err := os.Stat(origPath); err != nil { + return "", err + } + return origPath, nil +} + +// Purge vide complètement le cache +func (c *Cache) Purge() error { + c.mu.Lock() + defer c.mu.Unlock() + + files, _ := filepath.Glob(filepath.Join(c.dir, "*")) + for _, f := range files { + os.Remove(f) + } + return c.db.Purge() +} + +// pkFromURL → hash stable basé sur l’URL +func pkFromURL(url string) string { + h := sha1.Sum([]byte(url)) + return hex.EncodeToString(h[:8]) +} + +// Consolidate parcourt la base et corrige les incohérences +func (c *Cache) Consolidate() error { + c.mu.Lock() + defer c.mu.Unlock() + + entries, err := c.db.GetAll() + if err != nil { + return err + } + + // Vérifie chaque entrée de la base + for _, e := range entries { + origPath := filepath.Join(c.dir, e.PK+".orig.webp") + if _, err := os.Stat(origPath); os.IsNotExist(err) { + // Fichier absent → on retente un download + resp, err := http.Get(e.SourceURL) + if err != nil || resp.StatusCode != http.StatusOK { + // Impossible de retélécharger → suppression de l’entrée + _ = c.db.Delete(e.PK) + continue + } + defer resp.Body.Close() + data, _ := io.ReadAll(resp.Body) + if _, err := c.Add(e.SourceURL, data); err != nil { + // Si on échoue quand même → suppression + _ = c.db.Delete(e.PK) + } + } + } + + // Vérifie les fichiers orphelins (qui n’ont pas d’entrée en DB) + files, _ := filepath.Glob(filepath.Join(c.dir, "*.orig.webp")) + for _, f := range files { + pk := filepath.Base(f) + pk = pk[:len(pk)-len(".orig.webp")] + _, err := c.db.Get(pk) + if err != nil { + // Pas d’entrée en DB → supprimer le fichier + _ = os.Remove(f) + } + } + + return nil +} diff --git a/pmocover/db.go b/pmocover/db.go new file mode 100644 index 00000000..2fba85ac --- /dev/null +++ b/pmocover/db.go @@ -0,0 +1,124 @@ +package pmocover + +import ( + "database/sql" + "path/filepath" + "time" + + _ "modernc.org/sqlite" +) + +// DB représente la base SQLite du cache +type DB struct { + conn *sql.DB +} + +// InitDB ouvre ou crée la base SQLite dans le répertoire dir +func InitDB(dir string) (*DB, error) { + path := filepath.Join(dir, "cache.db") + conn, err := sql.Open("sqlite", path) + if err != nil { + return nil, err + } + + db := &DB{conn: conn} + if err := db.initTables(); err != nil { + conn.Close() + return nil, err + } + return db, nil +} + +// initTables crée la table covers si elle n’existe pas +func (db *DB) initTables() error { + _, err := db.conn.Exec(` + CREATE TABLE IF NOT EXISTS covers ( + pk TEXT PRIMARY KEY, + source_url TEXT, + hits INTEGER DEFAULT 0, + last_used TEXT + ); + `) + return err +} + +// Add ajoute ou met à jour une entrée cover +func (db *DB) Add(pk, url string) error { + _, err := db.conn.Exec(` + INSERT INTO covers(pk, source_url, hits, last_used) + VALUES(?, ?, 0, ?) + ON CONFLICT(pk) DO UPDATE SET + source_url=excluded.source_url, + last_used=excluded.last_used; + `, pk, url, time.Now().UTC().Format(time.RFC3339)) + return err +} + +// Get récupère une entrée +func (db *DB) Get(pk string) (*CacheEntry, error) { + row := db.conn.QueryRow(` + SELECT pk, source_url, hits, last_used + FROM covers + WHERE pk = ? + `, pk) + entry := &CacheEntry{} + var lastUsed sql.NullString + err := row.Scan(&entry.PK, &entry.SourceURL, &entry.Hits, &lastUsed) + if err != nil { + return nil, err + } + if lastUsed.Valid { + entry.LastUsed = lastUsed.String + } + return entry, nil +} + +// UpdateHit incrémente hits et met à jour last_used +func (db *DB) UpdateHit(pk string) error { + _, err := db.conn.Exec(` + UPDATE covers + SET hits = hits + 1, + last_used = ? + WHERE pk = ? + `, time.Now().UTC().Format(time.RFC3339), pk) + return err +} + +// Purge supprime toutes les entrées de la base +func (db *DB) Purge() error { + _, err := db.conn.Exec(`DELETE FROM covers`) + return err +} + +// GetAll récupère toutes les entrées triées par hits décroissant +func (db *DB) GetAll() ([]*CacheEntry, error) { + rows, err := db.conn.Query(` + SELECT pk, source_url, hits, last_used + FROM covers + ORDER BY hits DESC + `) + if err != nil { + return nil, err + } + defer rows.Close() + + var entries []*CacheEntry + for rows.Next() { + entry := &CacheEntry{} + var lastUsed sql.NullString + if err := rows.Scan(&entry.PK, &entry.SourceURL, &entry.Hits, &lastUsed); err != nil { + continue + } + if lastUsed.Valid { + entry.LastUsed = lastUsed.String + } + entries = append(entries, entry) + } + return entries, nil +} + +// Delete supprime une entrée spécifique +func (db *DB) Delete(pk string) error { + _, err := db.conn.Exec(`DELETE FROM covers WHERE pk = ?`, pk) + return err +} diff --git a/pmocover/handler.go b/pmocover/handler.go new file mode 100644 index 00000000..2b807399 --- /dev/null +++ b/pmocover/handler.go @@ -0,0 +1,62 @@ +package pmocover + +import ( + "encoding/json" + "net/http" + "os" + "path/filepath" + "strconv" + "strings" +) + +// ServeMux branche les routes /covers +func (c *Cache) ServeMux(mux *http.ServeMux) { + // Images + mux.HandleFunc("/covers/images/", func(w http.ResponseWriter, r *http.Request) { + parts := strings.Split(strings.TrimPrefix(r.URL.Path, "/covers/images/"), "/") + if len(parts) == 0 || parts[0] == "" { + http.Error(w, "missing pk", 400) + return + } + pk := parts[0] + if len(parts) == 1 { + path, err := c.Get(pk) + if err != nil { + http.NotFound(w, r) + return + } + w.Header().Set("Content-Type", "image/webp") + http.ServeFile(w, r, path) + return + } + size, err := strconv.Atoi(parts[1]) + if err != nil { + http.Error(w, "invalid size", 400) + return + } + variantPath := filepath.Join(c.dir, pk+"."+parts[1]+".webp") + if _, err := os.Stat(variantPath); os.IsNotExist(err) { + data, err := c.generateVariant(pk, size) + if err != nil { + http.Error(w, "cannot generate", 500) + return + } + w.Header().Set("Content-Type", "image/webp") + w.Write(data) + return + } + w.Header().Set("Content-Type", "image/webp") + http.ServeFile(w, r, variantPath) + }) + + // Stats + mux.HandleFunc("/covers/stats", func(w http.ResponseWriter, r *http.Request) { + entries, err := c.db.GetAll() + if err != nil { + http.Error(w, "cannot retrieve stats", 500) + return + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(entries) + }) +} diff --git a/pmocover/pmocover.go b/pmocover/pmocover.go new file mode 100644 index 00000000..166b5a77 --- /dev/null +++ b/pmocover/pmocover.go @@ -0,0 +1,16 @@ +package pmocover + +import "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoconfig" + +// GetCoverCache récupère le cache, crée le dossier et la base si nécessaire +func GetCoverCache() (*Cache, error) { + config := pmoconfig.GetConfig() + + dir := config.GetCoverCacheDir() + size := config.GetCoverCacheSize() + cache, err := NewCache(dir, size) + if err != nil { + return nil, err + } + return cache, nil +} diff --git a/pmocover/webp.go b/pmocover/webp.go new file mode 100644 index 00000000..c02e04ee --- /dev/null +++ b/pmocover/webp.go @@ -0,0 +1,87 @@ +package pmocover + +import ( + "bytes" + "image" + "image/draw" + "os" + "path/filepath" + "strconv" + + "github.com/chai2010/webp" + xdraw "golang.org/x/image/draw" +) + +// encodeWebP convertit une image.Image en WebP avec compression sans perte. +func encodeWebP(img image.Image) ([]byte, error) { + var buf bytes.Buffer + if err := webp.Encode(&buf, img, &webp.Options{Lossless: true}); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +// ensureSquare génère une image carrée paddée transparente +func ensureSquare(img image.Image, size int) image.Image { + dst := image.NewRGBA(image.Rect(0, 0, size, size)) + draw.Draw(dst, dst.Bounds(), &image.Uniform{C: image.Transparent}, image.Point{}, draw.Src) + + srcBounds := img.Bounds() + srcW, srcH := srcBounds.Dx(), srcBounds.Dy() + + var scale float64 + if srcW > srcH { + scale = float64(size) / float64(srcW) + } else { + scale = float64(size) / float64(srcH) + } + newW, newH := int(float64(srcW)*scale), int(float64(srcH)*scale) + + // redimensionnement avec Catmull-Rom + tmp := image.NewRGBA(image.Rect(0, 0, newW, newH)) + xdraw.CatmullRom.Scale(tmp, tmp.Bounds(), img, srcBounds, xdraw.Over, nil) + + // centrer l'image redimensionnée dans le carré + offset := image.Pt((size-newW)/2, (size-newH)/2) + draw.Draw(dst, tmp.Bounds().Add(offset), tmp, image.Point{}, draw.Over) + + return dst +} + +// generateVariant génère une version carrée en WebP pour une taille donnée. +// Si le fichier existe déjà, il est renvoyé directement. +func (c *Cache) generateVariant(pk string, size int) ([]byte, error) { + variantPath := filepath.Join(c.dir, pk+"."+strconv.Itoa(size)+".webp") + + // si le variant existe déjà, le renvoyer + if data, err := os.ReadFile(variantPath); err == nil { + return data, nil + } + + // lire l'image originale + origPath := filepath.Join(c.dir, pk+".orig.webp") + data, err := os.ReadFile(origPath) + if err != nil { + return nil, err + } + img, _, err := image.Decode(bytes.NewReader(data)) + if err != nil { + return nil, err + } + + // générer une image carrée + sq := ensureSquare(img, size) + + // encoder en WebP + buf, err := encodeWebP(sq) + if err != nil { + return nil, err + } + + // sauvegarder le variant + if err := os.WriteFile(variantPath, buf, 0o644); err != nil { + return nil, err + } + + return buf, nil +} diff --git a/pmodidl/didilite.go b/pmodidl/didilite.go new file mode 100644 index 00000000..978a7969 --- /dev/null +++ b/pmodidl/didilite.go @@ -0,0 +1,281 @@ +package pmodidl + +import ( + "iter" + + "slices" + "strings" + + "gargoton.petite-maison-orange.fr/eric/pmomusic/pmocover" + log "github.com/sirupsen/logrus" +) + +// Méthodes pour DIDLLite + +// CacheAllCoverArts met en cache toutes les images de couverture et stocke les clés primaires +func (d *DIDLLite) CacheAllCoverArts(cache *pmocover.Cache) error { + itemsWithCovers := Filter(d.AllItems(), func(item *Item) bool { + return item.AlbumArt != "" + }) + + for item := range itemsWithCovers { + pk, err := cache.EnsureFromURL(item.AlbumArt) + if err != nil { + log.Errorf("❌ Erreur lors de la mise en cache de %s: %v", item.AlbumArt, err) + continue + } + log.Infof("✅ Mise en cache de %s : pk[%s]", item.AlbumArt, pk) + item.AlbumArtPk = pk + } + + return nil +} + +// AllContainers retourne un itérateur sur tous les containers de manière récursive +func (d *DIDLLite) AllContainers() iter.Seq[*Container] { + return func(yield func(*Container) bool) { + for _, container := range d.Containers { + if !yield(&container) { + return + } + for child := range container.AllContainers() { + if !yield(child) { + return + } + } + } + } +} + +// AllItems retourne un itérateur sur tous les items de manière récursive +func (d *DIDLLite) AllItems() iter.Seq[*Item] { + return func(yield func(*Item) bool) { + for _, item := range d.Items { + if !yield(&item) { + return + } + } + for container := range d.AllContainers() { + for _, item := range container.Items { + if !yield(&item) { + return + } + } + } + } +} + +// GetContainerByID retourne un itérateur sur les containers avec l'ID spécifié +func (d *DIDLLite) GetContainerByID(id string) iter.Seq[*Container] { + return func(yield func(*Container) bool) { + for container := range d.AllContainers() { + if container.ID == id { + if !yield(container) { + return + } + } + } + } +} + +// GetItemByID retourne un itérateur sur les items avec l'ID spécifié +func (d *DIDLLite) GetItemByID(id string) iter.Seq[*Item] { + return func(yield func(*Item) bool) { + for item := range d.AllItems() { + if item.ID == id { + if !yield(item) { + return + } + } + } + } +} + +// FilterContainers filtre les containers selon un prédicat +func (d *DIDLLite) FilterContainers(predicate func(*Container) bool) iter.Seq[*Container] { + return func(yield func(*Container) bool) { + for container := range d.AllContainers() { + if predicate(container) && !yield(container) { + return + } + } + } +} + +// FilterItems filtre les items selon un prédicat +func (d *DIDLLite) FilterItems(predicate func(*Item) bool) iter.Seq[*Item] { + return func(yield func(*Item) bool) { + for item := range d.AllItems() { + if predicate(item) && !yield(item) { + return + } + } + } +} + +// Méthodes pour Container + +// AllContainers retourne un itérateur sur tous les containers enfants de manière récursive +func (c *Container) AllContainers() iter.Seq[*Container] { + return func(yield func(*Container) bool) { + if !yield(c) { + return + } + for _, child := range c.Containers { + for container := range child.AllContainers() { + if !yield(container) { + return + } + } + } + } +} + +// AllItems retourne un itérateur sur tous les items du container et de ses enfants +func (c *Container) AllItems() iter.Seq[*Item] { + return func(yield func(*Item) bool) { + for _, item := range c.Items { + if !yield(&item) { + return + } + } + for _, child := range c.Containers { + for item := range child.AllItems() { + if !yield(item) { + return + } + } + } + } +} + +// GetChildContainers retourne un itérateur sur les containers enfants directs +func (c *Container) GetChildContainers() iter.Seq[*Container] { + return func(yield func(*Container) bool) { + for _, container := range c.Containers { + if !yield(&container) { + return + } + } + } +} + +// GetChildItems retourne un itérateur sur les items directs du container +func (c *Container) GetChildItems() iter.Seq[*Item] { + return func(yield func(*Item) bool) { + for _, item := range c.Items { + if !yield(&item) { + return + } + } + } +} + +// Méthodes pour Item + +// GetAudioResources retourne un itérateur sur les ressources audio +func (i *Item) GetAudioResources() iter.Seq[Res] { + return func(yield func(Res) bool) { + for _, res := range i.Ress { + if strings.HasPrefix(res.ProtocolInfo, "http-get:*:audio/") { + if !yield(res) { + return + } + } + } + } +} + +// GetPrimaryResource retourne la ressource principale (première disponible) +func (i *Item) GetPrimaryResource() iter.Seq[Res] { + return func(yield func(Res) bool) { + if len(i.Ress) > 0 { + if !yield(i.Ress[0]) { + return + } + } + } +} + +// GetMetadata retourne un itérateur sur les métadonnées sous forme de paires clé-valeur +func (i *Item) GetMetadata() iter.Seq2[string, string] { + return func(yield func(string, string) bool) { + if i.Title != "" && !yield("title", i.Title) { + return + } + if i.Artist != "" && !yield("artist", i.Artist) { + return + } + if i.Album != "" && !yield("album", i.Album) { + return + } + if i.Genre != "" && !yield("genre", i.Genre) { + return + } + if i.Date != "" && !yield("date", i.Date) { + return + } + if i.OriginalTrackNumber != "" && !yield("trackNumber", i.OriginalTrackNumber) { + return + } + for _, desc := range i.Descs { + if desc.TrackGain != "" && !yield("replayGain", desc.TrackGain) { + return + } + if desc.TrackPeak != "" && !yield("replayPeak", desc.TrackPeak) { + return + } + } + } +} + +// Fonctions utilitaires + +// Filter filtre une séquence selon un prédicat +func Filter[T any](seq iter.Seq[T], predicate func(T) bool) iter.Seq[T] { + return func(yield func(T) bool) { + for value := range seq { + if predicate(value) && !yield(value) { + return + } + } + } +} + +// Map transforme une séquence en appliquant une fonction +func Map[T, U any](seq iter.Seq[T], f func(T) U) iter.Seq[U] { + return func(yield func(U) bool) { + for value := range seq { + if !yield(f(value)) { + return + } + } + } +} + +// Collect collecte tous les éléments d'une séquence dans une slice +func Collect[T any](seq iter.Seq[T]) []T { + return slices.Collect(seq) +} + +// First retourne le premier élément d'une séquence +func First[T any](seq iter.Seq[T]) (T, bool) { + var zero T + next, stop := iter.Pull(seq) + defer stop() + + value, ok := next() + if !ok { + return zero, false + } + return value, true +} + +// Count compte le nombre d'éléments dans une séquence +func Count[T any](seq iter.Seq[T]) int { + count := 0 + for range seq { + count++ + } + return count +} diff --git a/didl/markdown.go b/pmodidl/markdown.go similarity index 99% rename from didl/markdown.go rename to pmodidl/markdown.go index 589f943a..3ee568c2 100644 --- a/didl/markdown.go +++ b/pmodidl/markdown.go @@ -1,4 +1,4 @@ -package didl +package pmodidl import ( "fmt" diff --git a/didl/model.go b/pmodidl/model.go similarity index 98% rename from didl/model.go rename to pmodidl/model.go index 5dad7d49..faaaef4c 100644 --- a/didl/model.go +++ b/pmodidl/model.go @@ -1,4 +1,4 @@ -package didl +package pmodidl import "encoding/xml" @@ -40,6 +40,7 @@ type Item struct { Album string `xml:"urn:schemas-upnp-org:metadata-1-0/upnp/ album,omitempty"` Genre string `xml:"urn:schemas-upnp-org:metadata-1-0/upnp/ genre,omitempty"` AlbumArt string `xml:"urn:schemas-upnp-org:metadata-1-0/upnp/ albumArtURI,omitempty"` + AlbumArtPk string Date string `xml:"http://purl.org/dc/elements/1.1/ date,omitempty"` OriginalTrackNumber string `xml:"urn:schemas-upnp-org:metadata-1-0/upnp/ originalTrackNumber,omitempty"` diff --git a/didl/parser.go b/pmodidl/parser.go similarity index 52% rename from didl/parser.go rename to pmodidl/parser.go index 08297f87..a8a7ebc4 100644 --- a/didl/parser.go +++ b/pmodidl/parser.go @@ -1,8 +1,10 @@ -package didl +package pmodidl import ( "encoding/xml" "fmt" + + "gargoton.petite-maison-orange.fr/eric/pmomusic/pmocover" ) func Parse(metadata string) (*DIDLLite, error) { @@ -13,5 +15,12 @@ func Parse(metadata string) (*DIDLLite, error) { } + cache, err := pmocover.GetCoverCache() + if err != nil { + return &didl, fmt.Errorf("failed to get cover cache: %v", err) + } + + didl.CacheAllCoverArts(cache) + return &didl, nil } diff --git a/upnp/debug_index.go b/pmoupnp/debug_index.go similarity index 97% rename from upnp/debug_index.go rename to pmoupnp/debug_index.go index a2959a16..8e7e0337 100644 --- a/upnp/debug_index.go +++ b/pmoupnp/debug_index.go @@ -1,4 +1,4 @@ -package upnp +package pmoupnp import ( "fmt" diff --git a/upnp/dev_set.go b/pmoupnp/dev_set.go similarity index 90% rename from upnp/dev_set.go rename to pmoupnp/dev_set.go index b878a739..98fd875c 100644 --- a/upnp/dev_set.go +++ b/pmoupnp/dev_set.go @@ -1,9 +1,9 @@ -package upnp +package pmoupnp import ( "iter" - "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/objectstore" + "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/objectstore" "github.com/beevik/etree" ) diff --git a/upnp/device.go b/pmoupnp/device.go similarity index 99% rename from upnp/device.go rename to pmoupnp/device.go index 54fd577c..89f93ada 100644 --- a/upnp/device.go +++ b/pmoupnp/device.go @@ -1,4 +1,4 @@ -package upnp +package pmoupnp import ( "fmt" diff --git a/upnp/deviceinstance.go b/pmoupnp/deviceinstance.go similarity index 99% rename from upnp/deviceinstance.go rename to pmoupnp/deviceinstance.go index a9399d61..e936ae62 100644 --- a/upnp/deviceinstance.go +++ b/pmoupnp/deviceinstance.go @@ -1,4 +1,4 @@ -package upnp +package pmoupnp import ( "fmt" diff --git a/upnp/devices/mediarenderer/AVTransport/ac_Play.go b/pmoupnp/devices/mediarenderer/AVTransport/ac_Play.go similarity index 76% rename from upnp/devices/mediarenderer/AVTransport/ac_Play.go rename to pmoupnp/devices/mediarenderer/AVTransport/ac_Play.go index c60e0d8f..f21ed3f2 100644 --- a/upnp/devices/mediarenderer/AVTransport/ac_Play.go +++ b/pmoupnp/devices/mediarenderer/AVTransport/ac_Play.go @@ -1,6 +1,6 @@ package avtransport -import "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/actions" +import "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/actions" var Play = func() *actions.Action { diff --git a/upnp/devices/mediarenderer/AVTransport/ac_SetAVTransportURI.go b/pmoupnp/devices/mediarenderer/AVTransport/ac_SetAVTransportURI.go similarity index 82% rename from upnp/devices/mediarenderer/AVTransport/ac_SetAVTransportURI.go rename to pmoupnp/devices/mediarenderer/AVTransport/ac_SetAVTransportURI.go index e3d8bded..9095ad46 100644 --- a/upnp/devices/mediarenderer/AVTransport/ac_SetAVTransportURI.go +++ b/pmoupnp/devices/mediarenderer/AVTransport/ac_SetAVTransportURI.go @@ -1,6 +1,6 @@ package avtransport -import "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/actions" +import "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/actions" var SetAVTransportURI = func() *actions.Action { diff --git a/upnp/devices/mediarenderer/AVTransport/ac_Stop.go b/pmoupnp/devices/mediarenderer/AVTransport/ac_Stop.go similarity index 69% rename from upnp/devices/mediarenderer/AVTransport/ac_Stop.go rename to pmoupnp/devices/mediarenderer/AVTransport/ac_Stop.go index e7180a6e..9543a9d0 100644 --- a/upnp/devices/mediarenderer/AVTransport/ac_Stop.go +++ b/pmoupnp/devices/mediarenderer/AVTransport/ac_Stop.go @@ -1,6 +1,6 @@ package avtransport -import "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/actions" +import "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/actions" var Stop = func() *actions.Action { diff --git a/upnp/devices/mediarenderer/AVTransport/sv_AVTransportURI.go b/pmoupnp/devices/mediarenderer/AVTransport/sv_AVTransportURI.go similarity index 59% rename from upnp/devices/mediarenderer/AVTransport/sv_AVTransportURI.go rename to pmoupnp/devices/mediarenderer/AVTransport/sv_AVTransportURI.go index 1bf59aee..d92c3b02 100644 --- a/upnp/devices/mediarenderer/AVTransport/sv_AVTransportURI.go +++ b/pmoupnp/devices/mediarenderer/AVTransport/sv_AVTransportURI.go @@ -1,6 +1,6 @@ package avtransport -import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables" +import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/statevariables" var AVTransportURI = func() *sv.StateVariable { diff --git a/upnp/devices/mediarenderer/AVTransport/sv_AVTransportURIMetaData.go b/pmoupnp/devices/mediarenderer/AVTransport/sv_AVTransportURIMetaData.go similarity index 70% rename from upnp/devices/mediarenderer/AVTransport/sv_AVTransportURIMetaData.go rename to pmoupnp/devices/mediarenderer/AVTransport/sv_AVTransportURIMetaData.go index 8b24fa4a..258464c7 100644 --- a/upnp/devices/mediarenderer/AVTransport/sv_AVTransportURIMetaData.go +++ b/pmoupnp/devices/mediarenderer/AVTransport/sv_AVTransportURIMetaData.go @@ -1,14 +1,14 @@ package avtransport import ( - "gargoton.petite-maison-orange.fr/eric/pmomusic/didl" - sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables" + "gargoton.petite-maison-orange.fr/eric/pmomusic/pmodidl" + sv "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/statevariables" log "github.com/sirupsen/logrus" ) func _AVTransportURIMetaDataParser(value string) (interface{}, error) { log.Debug("[avtransport] Parsing AVTransport)") - didl, err := didl.Parse(value) + didl, err := pmodidl.Parse(value) if err != nil { return value, err } diff --git a/upnp/devices/mediarenderer/AVTransport/sv_A_ARG_TYPE_InstanceID.go b/pmoupnp/devices/mediarenderer/AVTransport/sv_A_ARG_TYPE_InstanceID.go similarity index 61% rename from upnp/devices/mediarenderer/AVTransport/sv_A_ARG_TYPE_InstanceID.go rename to pmoupnp/devices/mediarenderer/AVTransport/sv_A_ARG_TYPE_InstanceID.go index 735aa11d..12351e4f 100644 --- a/upnp/devices/mediarenderer/AVTransport/sv_A_ARG_TYPE_InstanceID.go +++ b/pmoupnp/devices/mediarenderer/AVTransport/sv_A_ARG_TYPE_InstanceID.go @@ -1,6 +1,6 @@ package avtransport -import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables" +import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/statevariables" var A_ARG_TYPE_InstanceID = func() *sv.StateVariable { diff --git a/upnp/devices/mediarenderer/AVTransport/sv_A_ARG_TYPE_PlaySpeed.go b/pmoupnp/devices/mediarenderer/AVTransport/sv_A_ARG_TYPE_PlaySpeed.go similarity index 61% rename from upnp/devices/mediarenderer/AVTransport/sv_A_ARG_TYPE_PlaySpeed.go rename to pmoupnp/devices/mediarenderer/AVTransport/sv_A_ARG_TYPE_PlaySpeed.go index c593b314..c78bfa14 100644 --- a/upnp/devices/mediarenderer/AVTransport/sv_A_ARG_TYPE_PlaySpeed.go +++ b/pmoupnp/devices/mediarenderer/AVTransport/sv_A_ARG_TYPE_PlaySpeed.go @@ -1,6 +1,6 @@ package avtransport -import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables" +import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/statevariables" var A_ARG_TYPE_PlaySpeed = func() *sv.StateVariable { diff --git a/upnp/devices/mediarenderer/AVTransport/sv_CurrentTrackDuration.go b/pmoupnp/devices/mediarenderer/AVTransport/sv_CurrentTrackDuration.go similarity index 61% rename from upnp/devices/mediarenderer/AVTransport/sv_CurrentTrackDuration.go rename to pmoupnp/devices/mediarenderer/AVTransport/sv_CurrentTrackDuration.go index 52450b69..4f7be8e8 100644 --- a/upnp/devices/mediarenderer/AVTransport/sv_CurrentTrackDuration.go +++ b/pmoupnp/devices/mediarenderer/AVTransport/sv_CurrentTrackDuration.go @@ -1,6 +1,6 @@ package avtransport -import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables" +import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/statevariables" var CurrentTrackDuration = func() *sv.StateVariable { diff --git a/upnp/devices/mediarenderer/AVTransport/sv_SeekMode.go b/pmoupnp/devices/mediarenderer/AVTransport/sv_SeekMode.go similarity index 57% rename from upnp/devices/mediarenderer/AVTransport/sv_SeekMode.go rename to pmoupnp/devices/mediarenderer/AVTransport/sv_SeekMode.go index a5582ef9..c3587d0c 100644 --- a/upnp/devices/mediarenderer/AVTransport/sv_SeekMode.go +++ b/pmoupnp/devices/mediarenderer/AVTransport/sv_SeekMode.go @@ -1,6 +1,6 @@ package avtransport -import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables" +import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/statevariables" var SeekMode = func() *sv.StateVariable { diff --git a/upnp/devices/mediarenderer/AVTransport/sv_TransportPlaySpeed.go b/pmoupnp/devices/mediarenderer/AVTransport/sv_TransportPlaySpeed.go similarity index 64% rename from upnp/devices/mediarenderer/AVTransport/sv_TransportPlaySpeed.go rename to pmoupnp/devices/mediarenderer/AVTransport/sv_TransportPlaySpeed.go index 91acf03f..4d92d0ba 100644 --- a/upnp/devices/mediarenderer/AVTransport/sv_TransportPlaySpeed.go +++ b/pmoupnp/devices/mediarenderer/AVTransport/sv_TransportPlaySpeed.go @@ -1,6 +1,6 @@ package avtransport -import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables" +import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/statevariables" var TransportPlaySpeed = func() *sv.StateVariable { diff --git a/upnp/devices/mediarenderer/AVTransport/sv_TransportState.go b/pmoupnp/devices/mediarenderer/AVTransport/sv_TransportState.go similarity index 76% rename from upnp/devices/mediarenderer/AVTransport/sv_TransportState.go rename to pmoupnp/devices/mediarenderer/AVTransport/sv_TransportState.go index 14949987..a546bdf9 100644 --- a/upnp/devices/mediarenderer/AVTransport/sv_TransportState.go +++ b/pmoupnp/devices/mediarenderer/AVTransport/sv_TransportState.go @@ -1,6 +1,6 @@ package avtransport -import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables" +import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/statevariables" var TransportState = func() *sv.StateVariable { diff --git a/upnp/devices/mediarenderer/AVTransport/sv_TransportStatus.go b/pmoupnp/devices/mediarenderer/AVTransport/sv_TransportStatus.go similarity index 69% rename from upnp/devices/mediarenderer/AVTransport/sv_TransportStatus.go rename to pmoupnp/devices/mediarenderer/AVTransport/sv_TransportStatus.go index 4eae52e8..fe87fb04 100644 --- a/upnp/devices/mediarenderer/AVTransport/sv_TransportStatus.go +++ b/pmoupnp/devices/mediarenderer/AVTransport/sv_TransportStatus.go @@ -1,6 +1,6 @@ package avtransport -import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables" +import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/statevariables" var TransportStatus = func() *sv.StateVariable { diff --git a/upnp/devices/mediarenderer/AVTransport/svc_avtransport.go b/pmoupnp/devices/mediarenderer/AVTransport/svc_avtransport.go similarity index 74% rename from upnp/devices/mediarenderer/AVTransport/svc_avtransport.go rename to pmoupnp/devices/mediarenderer/AVTransport/svc_avtransport.go index b235845a..27ec7547 100644 --- a/upnp/devices/mediarenderer/AVTransport/svc_avtransport.go +++ b/pmoupnp/devices/mediarenderer/AVTransport/svc_avtransport.go @@ -1,9 +1,9 @@ package avtransport -import "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp" +import "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp" -var AVTransport = func() *upnp.Service { - svc := upnp.NewService("AVTransport") +var AVTransport = func() *pmoupnp.Service { + svc := pmoupnp.NewService("AVTransport") svc.AddAction(SetAVTransportURI) svc.AddAction(Play) diff --git a/upnp/devices/mediarenderer/ConnectionManager/ac_GetCurrentConnectionIDs.go b/pmoupnp/devices/mediarenderer/ConnectionManager/ac_GetCurrentConnectionIDs.go similarity index 73% rename from upnp/devices/mediarenderer/ConnectionManager/ac_GetCurrentConnectionIDs.go rename to pmoupnp/devices/mediarenderer/ConnectionManager/ac_GetCurrentConnectionIDs.go index 92e302ec..2b11f56d 100644 --- a/upnp/devices/mediarenderer/ConnectionManager/ac_GetCurrentConnectionIDs.go +++ b/pmoupnp/devices/mediarenderer/ConnectionManager/ac_GetCurrentConnectionIDs.go @@ -1,6 +1,6 @@ package connectionmanager -import "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/actions" +import "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/actions" var GetCurrentConnectionIDs = func() *actions.Action { diff --git a/upnp/devices/mediarenderer/ConnectionManager/ac_GetCurrentConnectionInfo.go b/pmoupnp/devices/mediarenderer/ConnectionManager/ac_GetCurrentConnectionInfo.go similarity index 91% rename from upnp/devices/mediarenderer/ConnectionManager/ac_GetCurrentConnectionInfo.go rename to pmoupnp/devices/mediarenderer/ConnectionManager/ac_GetCurrentConnectionInfo.go index 7caabd98..5a1057ea 100644 --- a/upnp/devices/mediarenderer/ConnectionManager/ac_GetCurrentConnectionInfo.go +++ b/pmoupnp/devices/mediarenderer/ConnectionManager/ac_GetCurrentConnectionInfo.go @@ -1,6 +1,6 @@ package connectionmanager -import "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/actions" +import "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/actions" var GetCurrentConnectionInfo = func() *actions.Action { diff --git a/upnp/devices/mediarenderer/ConnectionManager/ac_GetProtocolInfo.go b/pmoupnp/devices/mediarenderer/ConnectionManager/ac_GetProtocolInfo.go similarity index 77% rename from upnp/devices/mediarenderer/ConnectionManager/ac_GetProtocolInfo.go rename to pmoupnp/devices/mediarenderer/ConnectionManager/ac_GetProtocolInfo.go index 36ca9aeb..d1f63531 100644 --- a/upnp/devices/mediarenderer/ConnectionManager/ac_GetProtocolInfo.go +++ b/pmoupnp/devices/mediarenderer/ConnectionManager/ac_GetProtocolInfo.go @@ -1,6 +1,6 @@ package connectionmanager -import "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/actions" +import "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/actions" var GetProtocolInfo = func() *actions.Action { diff --git a/upnp/devices/mediarenderer/ConnectionManager/sv_AVTransportID.go b/pmoupnp/devices/mediarenderer/ConnectionManager/sv_AVTransportID.go similarity index 59% rename from upnp/devices/mediarenderer/ConnectionManager/sv_AVTransportID.go rename to pmoupnp/devices/mediarenderer/ConnectionManager/sv_AVTransportID.go index 9cc2619b..0894aed5 100644 --- a/upnp/devices/mediarenderer/ConnectionManager/sv_AVTransportID.go +++ b/pmoupnp/devices/mediarenderer/ConnectionManager/sv_AVTransportID.go @@ -1,6 +1,6 @@ package connectionmanager -import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables" +import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/statevariables" var AVTransportID = func() *sv.StateVariable { diff --git a/upnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_AVTransportID.go b/pmoupnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_AVTransportID.go similarity index 62% rename from upnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_AVTransportID.go rename to pmoupnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_AVTransportID.go index ef4e4b06..375d8c5e 100644 --- a/upnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_AVTransportID.go +++ b/pmoupnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_AVTransportID.go @@ -1,6 +1,6 @@ package connectionmanager -import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables" +import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/statevariables" var A_ARG_TYPE_AVTransportID = func() *sv.StateVariable { diff --git a/upnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_ConnectionID.go b/pmoupnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_ConnectionID.go similarity index 62% rename from upnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_ConnectionID.go rename to pmoupnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_ConnectionID.go index 57cdbb57..dc289754 100644 --- a/upnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_ConnectionID.go +++ b/pmoupnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_ConnectionID.go @@ -1,6 +1,6 @@ package connectionmanager -import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables" +import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/statevariables" var A_ARG_TYPE_ConnectionID = func() *sv.StateVariable { diff --git a/upnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_ConnectionManager.go b/pmoupnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_ConnectionManager.go similarity index 64% rename from upnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_ConnectionManager.go rename to pmoupnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_ConnectionManager.go index d5877b32..73a30f33 100644 --- a/upnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_ConnectionManager.go +++ b/pmoupnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_ConnectionManager.go @@ -1,6 +1,6 @@ package connectionmanager -import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables" +import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/statevariables" var A_ARG_TYPE_ConnectionManager = func() *sv.StateVariable { diff --git a/upnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_ConnectionStatus.go b/pmoupnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_ConnectionStatus.go similarity index 75% rename from upnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_ConnectionStatus.go rename to pmoupnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_ConnectionStatus.go index de06cd36..973761ff 100644 --- a/upnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_ConnectionStatus.go +++ b/pmoupnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_ConnectionStatus.go @@ -1,6 +1,6 @@ package connectionmanager -import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables" +import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/statevariables" var A_ARG_TYPE_ConnectionStatus = func() *sv.StateVariable { diff --git a/upnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_Direction.go b/pmoupnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_Direction.go similarity index 67% rename from upnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_Direction.go rename to pmoupnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_Direction.go index f6aa01eb..ed912541 100644 --- a/upnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_Direction.go +++ b/pmoupnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_Direction.go @@ -1,6 +1,6 @@ package connectionmanager -import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables" +import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/statevariables" var A_ARG_TYPE_Direction = func() *sv.StateVariable { diff --git a/upnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_ProtocolInfo.go b/pmoupnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_ProtocolInfo.go similarity index 63% rename from upnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_ProtocolInfo.go rename to pmoupnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_ProtocolInfo.go index f3e6fc67..228b4824 100644 --- a/upnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_ProtocolInfo.go +++ b/pmoupnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_ProtocolInfo.go @@ -1,6 +1,6 @@ package connectionmanager -import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables" +import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/statevariables" var A_ARG_TYPE_ProtocolInfo = func() *sv.StateVariable { diff --git a/upnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_RcsID.go b/pmoupnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_RcsID.go similarity index 60% rename from upnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_RcsID.go rename to pmoupnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_RcsID.go index 7a762df7..bb5f1642 100644 --- a/upnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_RcsID.go +++ b/pmoupnp/devices/mediarenderer/ConnectionManager/sv_A_ARG_TYPE_RcsID.go @@ -1,6 +1,6 @@ package connectionmanager -import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables" +import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/statevariables" var A_ARG_TYPE_RcsID = func() *sv.StateVariable { diff --git a/upnp/devices/mediarenderer/ConnectionManager/sv_ConnectionID.go b/pmoupnp/devices/mediarenderer/ConnectionManager/sv_ConnectionID.go similarity index 59% rename from upnp/devices/mediarenderer/ConnectionManager/sv_ConnectionID.go rename to pmoupnp/devices/mediarenderer/ConnectionManager/sv_ConnectionID.go index 793ca4cb..bd6e6d4b 100644 --- a/upnp/devices/mediarenderer/ConnectionManager/sv_ConnectionID.go +++ b/pmoupnp/devices/mediarenderer/ConnectionManager/sv_ConnectionID.go @@ -1,6 +1,6 @@ package connectionmanager -import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables" +import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/statevariables" var ConnectionID = func() *sv.StateVariable { diff --git a/upnp/devices/mediarenderer/ConnectionManager/sv_CurrentConnectionIDs.go b/pmoupnp/devices/mediarenderer/ConnectionManager/sv_CurrentConnectionIDs.go similarity index 62% rename from upnp/devices/mediarenderer/ConnectionManager/sv_CurrentConnectionIDs.go rename to pmoupnp/devices/mediarenderer/ConnectionManager/sv_CurrentConnectionIDs.go index 12024ca5..aa4b60be 100644 --- a/upnp/devices/mediarenderer/ConnectionManager/sv_CurrentConnectionIDs.go +++ b/pmoupnp/devices/mediarenderer/ConnectionManager/sv_CurrentConnectionIDs.go @@ -1,6 +1,6 @@ package connectionmanager -import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables" +import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/statevariables" var CurrentConnectionIDs = func() *sv.StateVariable { diff --git a/upnp/devices/mediarenderer/ConnectionManager/sv_Direction.go b/pmoupnp/devices/mediarenderer/ConnectionManager/sv_Direction.go similarity index 64% rename from upnp/devices/mediarenderer/ConnectionManager/sv_Direction.go rename to pmoupnp/devices/mediarenderer/ConnectionManager/sv_Direction.go index 7fbdb023..9766fe63 100644 --- a/upnp/devices/mediarenderer/ConnectionManager/sv_Direction.go +++ b/pmoupnp/devices/mediarenderer/ConnectionManager/sv_Direction.go @@ -1,6 +1,6 @@ package connectionmanager -import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables" +import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/statevariables" var Direction = func() *sv.StateVariable { diff --git a/upnp/devices/mediarenderer/ConnectionManager/sv_PeerConnectionID.go b/pmoupnp/devices/mediarenderer/ConnectionManager/sv_PeerConnectionID.go similarity index 60% rename from upnp/devices/mediarenderer/ConnectionManager/sv_PeerConnectionID.go rename to pmoupnp/devices/mediarenderer/ConnectionManager/sv_PeerConnectionID.go index d11519ec..44bfd94f 100644 --- a/upnp/devices/mediarenderer/ConnectionManager/sv_PeerConnectionID.go +++ b/pmoupnp/devices/mediarenderer/ConnectionManager/sv_PeerConnectionID.go @@ -1,6 +1,6 @@ package connectionmanager -import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables" +import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/statevariables" var PeerConnectionID = func() *sv.StateVariable { diff --git a/upnp/devices/mediarenderer/ConnectionManager/sv_PeerConnectionManager.go b/pmoupnp/devices/mediarenderer/ConnectionManager/sv_PeerConnectionManager.go similarity index 62% rename from upnp/devices/mediarenderer/ConnectionManager/sv_PeerConnectionManager.go rename to pmoupnp/devices/mediarenderer/ConnectionManager/sv_PeerConnectionManager.go index 9729597f..69ac0d39 100644 --- a/upnp/devices/mediarenderer/ConnectionManager/sv_PeerConnectionManager.go +++ b/pmoupnp/devices/mediarenderer/ConnectionManager/sv_PeerConnectionManager.go @@ -1,6 +1,6 @@ package connectionmanager -import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables" +import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/statevariables" var PeerConnectionManager = func() *sv.StateVariable { diff --git a/upnp/devices/mediarenderer/ConnectionManager/sv_RcsID.go b/pmoupnp/devices/mediarenderer/ConnectionManager/sv_RcsID.go similarity index 56% rename from upnp/devices/mediarenderer/ConnectionManager/sv_RcsID.go rename to pmoupnp/devices/mediarenderer/ConnectionManager/sv_RcsID.go index 9beab284..a28e177c 100644 --- a/upnp/devices/mediarenderer/ConnectionManager/sv_RcsID.go +++ b/pmoupnp/devices/mediarenderer/ConnectionManager/sv_RcsID.go @@ -1,6 +1,6 @@ package connectionmanager -import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables" +import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/statevariables" var RcsID = func() *sv.StateVariable { diff --git a/upnp/devices/mediarenderer/ConnectionManager/sv_SinkProtocolInfo.go b/pmoupnp/devices/mediarenderer/ConnectionManager/sv_SinkProtocolInfo.go similarity index 61% rename from upnp/devices/mediarenderer/ConnectionManager/sv_SinkProtocolInfo.go rename to pmoupnp/devices/mediarenderer/ConnectionManager/sv_SinkProtocolInfo.go index 8cbd2023..8283af8f 100644 --- a/upnp/devices/mediarenderer/ConnectionManager/sv_SinkProtocolInfo.go +++ b/pmoupnp/devices/mediarenderer/ConnectionManager/sv_SinkProtocolInfo.go @@ -1,6 +1,6 @@ package connectionmanager -import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables" +import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/statevariables" var SinkProtocolInfo = func() *sv.StateVariable { diff --git a/upnp/devices/mediarenderer/ConnectionManager/sv_SourceProtocolInfo.go b/pmoupnp/devices/mediarenderer/ConnectionManager/sv_SourceProtocolInfo.go similarity index 61% rename from upnp/devices/mediarenderer/ConnectionManager/sv_SourceProtocolInfo.go rename to pmoupnp/devices/mediarenderer/ConnectionManager/sv_SourceProtocolInfo.go index 5e7f3eea..8df32697 100644 --- a/upnp/devices/mediarenderer/ConnectionManager/sv_SourceProtocolInfo.go +++ b/pmoupnp/devices/mediarenderer/ConnectionManager/sv_SourceProtocolInfo.go @@ -1,6 +1,6 @@ package connectionmanager -import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables" +import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/statevariables" var SourceProtocolInfo = func() *sv.StateVariable { diff --git a/upnp/devices/mediarenderer/ConnectionManager/sv_Status.go b/pmoupnp/devices/mediarenderer/ConnectionManager/sv_Status.go similarity index 72% rename from upnp/devices/mediarenderer/ConnectionManager/sv_Status.go rename to pmoupnp/devices/mediarenderer/ConnectionManager/sv_Status.go index 22100057..7c92d3ec 100644 --- a/upnp/devices/mediarenderer/ConnectionManager/sv_Status.go +++ b/pmoupnp/devices/mediarenderer/ConnectionManager/sv_Status.go @@ -1,6 +1,6 @@ package connectionmanager -import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables" +import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/statevariables" var Status = func() *sv.StateVariable { diff --git a/upnp/devices/mediarenderer/ConnectionManager/svc_connectionmanager.go b/pmoupnp/devices/mediarenderer/ConnectionManager/svc_connectionmanager.go similarity index 82% rename from upnp/devices/mediarenderer/ConnectionManager/svc_connectionmanager.go rename to pmoupnp/devices/mediarenderer/ConnectionManager/svc_connectionmanager.go index 25e03656..c9e1b03b 100644 --- a/upnp/devices/mediarenderer/ConnectionManager/svc_connectionmanager.go +++ b/pmoupnp/devices/mediarenderer/ConnectionManager/svc_connectionmanager.go @@ -1,9 +1,9 @@ package connectionmanager -import "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp" +import "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp" -var ConnectionManager = func() *upnp.Service { - svc := upnp.NewService("ConnectionManager") +var ConnectionManager = func() *pmoupnp.Service { + svc := pmoupnp.NewService("ConnectionManager") svc.AddVariable(AVTransportID) svc.AddVariable(ConnectionID) diff --git a/upnp/devices/mediarenderer/RenderingControl/sv_Channel.go b/pmoupnp/devices/mediarenderer/RenderingControl/sv_Channel.go similarity index 64% rename from upnp/devices/mediarenderer/RenderingControl/sv_Channel.go rename to pmoupnp/devices/mediarenderer/RenderingControl/sv_Channel.go index eed43b96..ac17719a 100644 --- a/upnp/devices/mediarenderer/RenderingControl/sv_Channel.go +++ b/pmoupnp/devices/mediarenderer/RenderingControl/sv_Channel.go @@ -1,6 +1,6 @@ package renderingcontrol -import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables" +import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/statevariables" var Channel = func() *sv.StateVariable { diff --git a/upnp/devices/mediarenderer/RenderingControl/sv_InstanceID.go b/pmoupnp/devices/mediarenderer/RenderingControl/sv_InstanceID.go similarity index 58% rename from upnp/devices/mediarenderer/RenderingControl/sv_InstanceID.go rename to pmoupnp/devices/mediarenderer/RenderingControl/sv_InstanceID.go index 8491ed33..ce964a5d 100644 --- a/upnp/devices/mediarenderer/RenderingControl/sv_InstanceID.go +++ b/pmoupnp/devices/mediarenderer/RenderingControl/sv_InstanceID.go @@ -1,6 +1,6 @@ package renderingcontrol -import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables" +import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/statevariables" var InstanceID = func() *sv.StateVariable { diff --git a/upnp/devices/mediarenderer/RenderingControl/sv_Mute.go b/pmoupnp/devices/mediarenderer/RenderingControl/sv_Mute.go similarity index 64% rename from upnp/devices/mediarenderer/RenderingControl/sv_Mute.go rename to pmoupnp/devices/mediarenderer/RenderingControl/sv_Mute.go index a5074d46..e441095f 100644 --- a/upnp/devices/mediarenderer/RenderingControl/sv_Mute.go +++ b/pmoupnp/devices/mediarenderer/RenderingControl/sv_Mute.go @@ -1,6 +1,6 @@ package renderingcontrol -import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables" +import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/statevariables" var Mute = func() *sv.StateVariable { diff --git a/upnp/devices/mediarenderer/RenderingControl/sv_SourceProtocolInfo.go b/pmoupnp/devices/mediarenderer/RenderingControl/sv_SourceProtocolInfo.go similarity index 100% rename from upnp/devices/mediarenderer/RenderingControl/sv_SourceProtocolInfo.go rename to pmoupnp/devices/mediarenderer/RenderingControl/sv_SourceProtocolInfo.go diff --git a/upnp/devices/mediarenderer/RenderingControl/sv_Volume.go b/pmoupnp/devices/mediarenderer/RenderingControl/sv_Volume.go similarity index 66% rename from upnp/devices/mediarenderer/RenderingControl/sv_Volume.go rename to pmoupnp/devices/mediarenderer/RenderingControl/sv_Volume.go index d2f355ac..487c3133 100644 --- a/upnp/devices/mediarenderer/RenderingControl/sv_Volume.go +++ b/pmoupnp/devices/mediarenderer/RenderingControl/sv_Volume.go @@ -1,6 +1,6 @@ package renderingcontrol -import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables" +import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/statevariables" var Volume = func() *sv.StateVariable { diff --git a/pmoupnp/devices/mediarenderer/RenderingControl/svc_renderingcontrol.go b/pmoupnp/devices/mediarenderer/RenderingControl/svc_renderingcontrol.go new file mode 100644 index 00000000..1fe6e123 --- /dev/null +++ b/pmoupnp/devices/mediarenderer/RenderingControl/svc_renderingcontrol.go @@ -0,0 +1,14 @@ +package renderingcontrol + +import "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp" + +var RenderingControl = func() *pmoupnp.Service { + svc := pmoupnp.NewService("RenderingControl") + + svc.AddVariable(InstanceID) + svc.AddVariable(Channel) + svc.AddVariable(Mute) + svc.AddVariable(Volume) + + return svc +}() diff --git a/pmoupnp/devices/mediarenderer/mr_fake_renderer.go b/pmoupnp/devices/mediarenderer/mr_fake_renderer.go new file mode 100644 index 00000000..b61c8f52 --- /dev/null +++ b/pmoupnp/devices/mediarenderer/mr_fake_renderer.go @@ -0,0 +1,18 @@ +package mediarenderer + +import ( + "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp" + avtransport "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/mediarenderer/AVTransport" + connectionmanager "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/mediarenderer/ConnectionManager" + renderingcontrol "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/mediarenderer/RenderingControl" +) + +var FakeRenderer = func() *pmoupnp.Device { + + renderer := pmoupnp.NewDevice("FakeRenderer", "MediaRenderer") + + renderer.AddService(avtransport.AVTransport) + renderer.AddService(connectionmanager.ConnectionManager) + renderer.AddService(renderingcontrol.RenderingControl) + return renderer +}() diff --git a/upnp/devices/services/actions/action.go b/pmoupnp/devices/services/actions/action.go similarity index 100% rename from upnp/devices/services/actions/action.go rename to pmoupnp/devices/services/actions/action.go diff --git a/upnp/devices/services/actions/actioninstance.go b/pmoupnp/devices/services/actions/actioninstance.go similarity index 100% rename from upnp/devices/services/actions/actioninstance.go rename to pmoupnp/devices/services/actions/actioninstance.go diff --git a/upnp/devices/services/actions/actioninstanceset.go b/pmoupnp/devices/services/actions/actioninstanceset.go similarity index 90% rename from upnp/devices/services/actions/actioninstanceset.go rename to pmoupnp/devices/services/actions/actioninstanceset.go index b204d7c6..bada65a3 100644 --- a/upnp/devices/services/actions/actioninstanceset.go +++ b/pmoupnp/devices/services/actions/actioninstanceset.go @@ -3,7 +3,7 @@ package actions import ( "iter" - "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/objectstore" + "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/objectstore" "github.com/beevik/etree" ) diff --git a/upnp/devices/services/actions/actionset.go b/pmoupnp/devices/services/actions/actionset.go similarity index 88% rename from upnp/devices/services/actions/actionset.go rename to pmoupnp/devices/services/actions/actionset.go index 46c08c82..f076e271 100644 --- a/upnp/devices/services/actions/actionset.go +++ b/pmoupnp/devices/services/actions/actionset.go @@ -3,7 +3,7 @@ package actions import ( "iter" - "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/objectstore" + "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/objectstore" ) type ActionSet objectstore.ObjectSet[*Action] diff --git a/upnp/devices/services/actions/argument.go b/pmoupnp/devices/services/actions/argument.go similarity index 95% rename from upnp/devices/services/actions/argument.go rename to pmoupnp/devices/services/actions/argument.go index 4aaaa8c3..5827047c 100644 --- a/upnp/devices/services/actions/argument.go +++ b/pmoupnp/devices/services/actions/argument.go @@ -1,7 +1,7 @@ package actions import ( - "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables" + "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/statevariables" "github.com/beevik/etree" ) diff --git a/upnp/devices/services/actions/argumentset.go b/pmoupnp/devices/services/actions/argumentset.go similarity index 89% rename from upnp/devices/services/actions/argumentset.go rename to pmoupnp/devices/services/actions/argumentset.go index 967037c0..45efed73 100644 --- a/upnp/devices/services/actions/argumentset.go +++ b/pmoupnp/devices/services/actions/argumentset.go @@ -3,7 +3,7 @@ package actions import ( "iter" - "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/objectstore" + "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/objectstore" "github.com/beevik/etree" ) diff --git a/upnp/devices/services/statevariables/statevalueinstance.go b/pmoupnp/devices/services/statevariables/statevalueinstance.go similarity index 100% rename from upnp/devices/services/statevariables/statevalueinstance.go rename to pmoupnp/devices/services/statevariables/statevalueinstance.go diff --git a/upnp/devices/services/statevariables/statevalueinstanceset.go b/pmoupnp/devices/services/statevariables/statevalueinstanceset.go similarity index 92% rename from upnp/devices/services/statevariables/statevalueinstanceset.go rename to pmoupnp/devices/services/statevariables/statevalueinstanceset.go index a1ca407a..765c9a59 100644 --- a/upnp/devices/services/statevariables/statevalueinstanceset.go +++ b/pmoupnp/devices/services/statevariables/statevalueinstanceset.go @@ -3,7 +3,7 @@ package statevariables import ( "iter" - "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/objectstore" + "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/objectstore" "github.com/beevik/etree" ) diff --git a/upnp/devices/services/statevariables/statevariable.go b/pmoupnp/devices/services/statevariables/statevariable.go similarity index 100% rename from upnp/devices/services/statevariables/statevariable.go rename to pmoupnp/devices/services/statevariables/statevariable.go diff --git a/upnp/devices/services/statevariables/statevariable_TransferStatus.go b/pmoupnp/devices/services/statevariables/statevariable_TransferStatus.go similarity index 100% rename from upnp/devices/services/statevariables/statevariable_TransferStatus.go rename to pmoupnp/devices/services/statevariables/statevariable_TransferStatus.go diff --git a/upnp/devices/services/statevariables/statevariable_arithm.go b/pmoupnp/devices/services/statevariables/statevariable_arithm.go similarity index 100% rename from upnp/devices/services/statevariables/statevariable_arithm.go rename to pmoupnp/devices/services/statevariables/statevariable_arithm.go diff --git a/upnp/devices/services/statevariables/statevariable_cast.go b/pmoupnp/devices/services/statevariables/statevariable_cast.go similarity index 100% rename from upnp/devices/services/statevariables/statevariable_cast.go rename to pmoupnp/devices/services/statevariables/statevariable_cast.go diff --git a/upnp/devices/services/statevariables/statevariable_cmp.go b/pmoupnp/devices/services/statevariables/statevariable_cmp.go similarity index 100% rename from upnp/devices/services/statevariables/statevariable_cmp.go rename to pmoupnp/devices/services/statevariables/statevariable_cmp.go diff --git a/upnp/devices/services/statevariables/statevariable_typechecking.go b/pmoupnp/devices/services/statevariables/statevariable_typechecking.go similarity index 100% rename from upnp/devices/services/statevariables/statevariable_typechecking.go rename to pmoupnp/devices/services/statevariables/statevariable_typechecking.go diff --git a/upnp/devices/services/statevariables/statevariableset.go b/pmoupnp/devices/services/statevariables/statevariableset.go similarity index 89% rename from upnp/devices/services/statevariables/statevariableset.go rename to pmoupnp/devices/services/statevariables/statevariableset.go index 5530ae51..4bd213a8 100644 --- a/upnp/devices/services/statevariables/statevariableset.go +++ b/pmoupnp/devices/services/statevariables/statevariableset.go @@ -3,7 +3,7 @@ package statevariables import ( "iter" - "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/objectstore" + "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/objectstore" ) type StateVariableSet objectstore.ObjectSet[*StateVariable] diff --git a/upnp/devices/services/statevariables/statevartype.go b/pmoupnp/devices/services/statevariables/statevartype.go similarity index 100% rename from upnp/devices/services/statevariables/statevartype.go rename to pmoupnp/devices/services/statevariables/statevartype.go diff --git a/upnp/devices/services/statevariables/statevartype_arithm.go b/pmoupnp/devices/services/statevariables/statevartype_arithm.go similarity index 100% rename from upnp/devices/services/statevariables/statevartype_arithm.go rename to pmoupnp/devices/services/statevariables/statevartype_arithm.go diff --git a/upnp/devices/services/statevariables/statevartype_cast.go b/pmoupnp/devices/services/statevariables/statevartype_cast.go similarity index 100% rename from upnp/devices/services/statevariables/statevartype_cast.go rename to pmoupnp/devices/services/statevariables/statevartype_cast.go diff --git a/upnp/devices/services/statevariables/statevartype_cmp.go b/pmoupnp/devices/services/statevariables/statevartype_cmp.go similarity index 100% rename from upnp/devices/services/statevariables/statevartype_cmp.go rename to pmoupnp/devices/services/statevariables/statevartype_cmp.go diff --git a/upnp/devices/services/statevariables/statevartype_typechecking.go b/pmoupnp/devices/services/statevariables/statevartype_typechecking.go similarity index 100% rename from upnp/devices/services/statevariables/statevartype_typechecking.go rename to pmoupnp/devices/services/statevariables/statevartype_typechecking.go diff --git a/upnp/devices/services/statevariables/utils_binary.go b/pmoupnp/devices/services/statevariables/utils_binary.go similarity index 100% rename from upnp/devices/services/statevariables/utils_binary.go rename to pmoupnp/devices/services/statevariables/utils_binary.go diff --git a/upnp/devices/services/statevariables/utils_bool.go b/pmoupnp/devices/services/statevariables/utils_bool.go similarity index 100% rename from upnp/devices/services/statevariables/utils_bool.go rename to pmoupnp/devices/services/statevariables/utils_bool.go diff --git a/upnp/devices/services/statevariables/utils_float.go b/pmoupnp/devices/services/statevariables/utils_float.go similarity index 100% rename from upnp/devices/services/statevariables/utils_float.go rename to pmoupnp/devices/services/statevariables/utils_float.go diff --git a/upnp/devices/services/statevariables/utils_int.go b/pmoupnp/devices/services/statevariables/utils_int.go similarity index 100% rename from upnp/devices/services/statevariables/utils_int.go rename to pmoupnp/devices/services/statevariables/utils_int.go diff --git a/upnp/devices/services/statevariables/utils_numeric_operandes.go b/pmoupnp/devices/services/statevariables/utils_numeric_operandes.go similarity index 100% rename from upnp/devices/services/statevariables/utils_numeric_operandes.go rename to pmoupnp/devices/services/statevariables/utils_numeric_operandes.go diff --git a/upnp/devices/services/statevariables/utils_string.go b/pmoupnp/devices/services/statevariables/utils_string.go similarity index 100% rename from upnp/devices/services/statevariables/utils_string.go rename to pmoupnp/devices/services/statevariables/utils_string.go diff --git a/upnp/devices/services/statevariables/utils_time.go b/pmoupnp/devices/services/statevariables/utils_time.go similarity index 100% rename from upnp/devices/services/statevariables/utils_time.go rename to pmoupnp/devices/services/statevariables/utils_time.go diff --git a/upnp/devices/services/statevariables/utils_uint.go b/pmoupnp/devices/services/statevariables/utils_uint.go similarity index 100% rename from upnp/devices/services/statevariables/utils_uint.go rename to pmoupnp/devices/services/statevariables/utils_uint.go diff --git a/upnp/devices/services/statevariables/utils_uri.go b/pmoupnp/devices/services/statevariables/utils_uri.go similarity index 100% rename from upnp/devices/services/statevariables/utils_uri.go rename to pmoupnp/devices/services/statevariables/utils_uri.go diff --git a/upnp/devices/services/statevariables/utils_uuid.go b/pmoupnp/devices/services/statevariables/utils_uuid.go similarity index 100% rename from upnp/devices/services/statevariables/utils_uuid.go rename to pmoupnp/devices/services/statevariables/utils_uuid.go diff --git a/upnp/devices/services/statevariables/valuerange.go b/pmoupnp/devices/services/statevariables/valuerange.go similarity index 100% rename from upnp/devices/services/statevariables/valuerange.go rename to pmoupnp/devices/services/statevariables/valuerange.go diff --git a/upnp/events.go b/pmoupnp/events.go similarity index 80% rename from upnp/events.go rename to pmoupnp/events.go index ee8d0b4c..377fbfd1 100644 --- a/upnp/events.go +++ b/pmoupnp/events.go @@ -1,4 +1,4 @@ -package upnp +package pmoupnp import "github.com/beevik/etree" diff --git a/upnp/interface.go b/pmoupnp/interface.go similarity index 77% rename from upnp/interface.go rename to pmoupnp/interface.go index aac55023..6aaac7a1 100644 --- a/upnp/interface.go +++ b/pmoupnp/interface.go @@ -1,4 +1,4 @@ -package upnp +package pmoupnp type Markdownable interface { ToMarkdown() string diff --git a/upnp/objectstore/object.go b/pmoupnp/objectstore/object.go similarity index 100% rename from upnp/objectstore/object.go rename to pmoupnp/objectstore/object.go diff --git a/upnp/objectstore/objectset.go b/pmoupnp/objectstore/objectset.go similarity index 100% rename from upnp/objectstore/objectset.go rename to pmoupnp/objectstore/objectset.go diff --git a/upnp/server.go b/pmoupnp/server.go similarity index 91% rename from upnp/server.go rename to pmoupnp/server.go index 4027f3f0..014e0cc2 100644 --- a/upnp/server.go +++ b/pmoupnp/server.go @@ -1,4 +1,4 @@ -package upnp +package pmoupnp import ( "bytes" @@ -15,6 +15,8 @@ import ( "gargoton.petite-maison-orange.fr/eric/pmomusic/netutils" "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoapp" + "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoconfig" + "gargoton.petite-maison-orange.fr/eric/pmomusic/pmocover" "gargoton.petite-maison-orange.fr/eric/pmomusic/pmolog" "gargoton.petite-maison-orange.fr/eric/pmomusic/ssdp" ) @@ -35,7 +37,7 @@ type Server struct { } func NewServer(name string, opts ...ServerOption) *Server { - config := GetConfig() + config := pmoconfig.GetConfig() baseURL := config.GetBaseURL() httpPort := config.GetHTTPPort() @@ -79,6 +81,14 @@ func (s *Server) Start() error { s.mu.RLock() + cover_cache, err := pmocover.GetCoverCache() + if err != nil { + log.Panicf("❌ Cannot initialize the Cover Cache") + } + + log.Info("✅ Cover cache activated") + + cover_cache.ServeMux(mux) pmoapp.Handler(mux) s.httpSrv = &http.Server{ diff --git a/upnp/service.go b/pmoupnp/service.go similarity index 90% rename from upnp/service.go rename to pmoupnp/service.go index 21dcb5cb..9b67c148 100644 --- a/upnp/service.go +++ b/pmoupnp/service.go @@ -1,11 +1,11 @@ -package upnp +package pmoupnp import ( "fmt" "iter" - "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/actions" - sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables" + "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/actions" + sv "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/statevariables" ) type Service struct { diff --git a/upnp/serviceinstance.go b/pmoupnp/serviceinstance.go similarity index 98% rename from upnp/serviceinstance.go rename to pmoupnp/serviceinstance.go index 410e384f..1d230d38 100644 --- a/upnp/serviceinstance.go +++ b/pmoupnp/serviceinstance.go @@ -1,4 +1,4 @@ -package upnp +package pmoupnp import ( "context" @@ -11,9 +11,9 @@ import ( "time" "gargoton.petite-maison-orange.fr/eric/pmomusic/pmolog" + "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/actions" + "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/devices/services/statevariables" "gargoton.petite-maison-orange.fr/eric/pmomusic/soap" - "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/actions" - "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables" "github.com/beevik/etree" "github.com/google/uuid" log "github.com/sirupsen/logrus" diff --git a/upnp/serviceinstanceset.go b/pmoupnp/serviceinstanceset.go similarity index 90% rename from upnp/serviceinstanceset.go rename to pmoupnp/serviceinstanceset.go index ec72bb27..0086d4d4 100644 --- a/upnp/serviceinstanceset.go +++ b/pmoupnp/serviceinstanceset.go @@ -1,9 +1,9 @@ -package upnp +package pmoupnp import ( "iter" - "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/objectstore" + "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/objectstore" "github.com/beevik/etree" ) diff --git a/upnp/serviceset.go b/pmoupnp/serviceset.go similarity index 85% rename from upnp/serviceset.go rename to pmoupnp/serviceset.go index c2359e6e..c70a4d59 100644 --- a/upnp/serviceset.go +++ b/pmoupnp/serviceset.go @@ -1,9 +1,9 @@ -package upnp +package pmoupnp import ( "iter" - "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/objectstore" + "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoupnp/objectstore" ) type ServiceSet objectstore.ObjectSet[*Service] diff --git a/upnp/srv_reg_devices.go b/pmoupnp/srv_reg_devices.go similarity index 81% rename from upnp/srv_reg_devices.go rename to pmoupnp/srv_reg_devices.go index d6813be5..20661835 100644 --- a/upnp/srv_reg_devices.go +++ b/pmoupnp/srv_reg_devices.go @@ -1,6 +1,7 @@ -package upnp +package pmoupnp import ( + "gargoton.petite-maison-orange.fr/eric/pmomusic/pmoconfig" log "github.com/sirupsen/logrus" ) @@ -16,8 +17,8 @@ func (s *Server) RegisterDevice(name string, d *Device) { name = d.Name() } - config := GetConfig() - udn := config.GetDeviceUDN(d.DeviceType(), name) + config := pmoconfig.GetConfig() + udn := config.GetDeviceUDN(string(d.DeviceType()), name) instance := d.NewInstance(s, udn) diff --git a/upnp/devices/mediarenderer/RenderingControl/svc_renderingcontrol.go b/upnp/devices/mediarenderer/RenderingControl/svc_renderingcontrol.go deleted file mode 100644 index 3a90be3e..00000000 --- a/upnp/devices/mediarenderer/RenderingControl/svc_renderingcontrol.go +++ /dev/null @@ -1,14 +0,0 @@ -package renderingcontrol - -import "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp" - -var RenderingControl = func() *upnp.Service { - svc := upnp.NewService("RenderingControl") - - svc.AddVariable(InstanceID) - svc.AddVariable(Channel) - svc.AddVariable(Mute) - svc.AddVariable(Volume) - - return svc -}() diff --git a/upnp/devices/mediarenderer/mr_fake_renderer.go b/upnp/devices/mediarenderer/mr_fake_renderer.go deleted file mode 100644 index 98404116..00000000 --- a/upnp/devices/mediarenderer/mr_fake_renderer.go +++ /dev/null @@ -1,18 +0,0 @@ -package mediarenderer - -import ( - "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp" - avtransport "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/mediarenderer/AVTransport" - connectionmanager "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/mediarenderer/ConnectionManager" - renderingcontrol "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/mediarenderer/RenderingControl" -) - -var FakeRenderer = func() *upnp.Device { - - renderer := upnp.NewDevice("FakeRenderer", "MediaRenderer") - - renderer.AddService(avtransport.AVTransport) - renderer.AddService(connectionmanager.ConnectionManager) - renderer.AddService(renderingcontrol.RenderingControl) - return renderer -}()