Go port now up to date with C version

This commit is contained in:
2025-12-11 22:14:06 -08:00
parent ee7973b1a5
commit 963c88f679
3 changed files with 191 additions and 15 deletions

10
go.mod
View File

@@ -1,3 +1,13 @@
module muzi module muzi
go 1.25.4 go 1.25.4
require (
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx v3.6.2+incompatible // indirect
github.com/jackc/pgx/v5 v5.7.6 // indirect
github.com/pkg/errors v0.9.1 // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/text v0.31.0 // indirect
)

21
go.sum Normal file
View File

@@ -0,0 +1,21 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o=
github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
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=

163
muzi.go
View File

@@ -1,11 +1,155 @@
package main package main
import "archive/zip" import (
import "path/filepath" "archive/zip"
import "fmt" "context"
import "strings" "encoding/json"
import "os" "fmt"
import "io" "github.com/jackc/pgx/v5"
"io"
"os"
"path/filepath"
"strings"
)
const (
spotify = iota
lastfm
apple
)
func tableExists(name string, conn *pgx.Conn) bool {
var exists bool
err := conn.QueryRow(context.Background(), "SELECT EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'public' AND tablename = $1);", name).Scan(&exists)
if err != nil {
fmt.Fprintf(os.Stderr, "SELECT EXISTS failed: %v\n", err)
return false
}
return exists
}
func dbExists() bool {
conn, err := pgx.Connect(context.Background(), "postgres://postgres:postgres@localhost:5432/muzi")
if err != nil {
fmt.Fprintf(os.Stderr, "Cannot connect to muzi database: %v\n", err)
return false
}
defer conn.Close(context.Background())
return true
}
func createDB() error {
conn, err := pgx.Connect(context.Background(), "postgres://postgres:postgres@localhost:5432")
if err != nil {
fmt.Fprintf(os.Stderr, "Cannot connect to PostgreSQL: %v\n", err)
return err
}
defer conn.Close(context.Background())
_, err = conn.Exec(context.Background(), "CREATE DATABASE muzi")
if err != nil {
fmt.Fprintf(os.Stderr, "Cannot create muzi database: %v\n", err)
return err
}
return nil
}
func jsonToDB(jsonFile string, platform int) {
if !dbExists() {
err := createDB()
if err != nil {
panic(err)
}
}
conn, err := pgx.Connect(context.Background(), "postgres://postgres:postgres@localhost:5432/muzi")
if err != nil {
fmt.Fprintf(os.Stderr, "Cannot connect to muzi database: %v\n", err)
panic(err)
}
defer conn.Close(context.Background())
if !tableExists("history", conn) {
_, err = conn.Exec(context.Background(), "CREATE TABLE history ( ms_played INTEGER, timestamp TIMESTAMPTZ, song_name TEXT, artist TEXT, album_name TEXT, PRIMARY KEY (timestamp, ms_played, artist, song_name));")
}
if err != nil {
fmt.Fprintf(os.Stderr, "Cannot create history table: %v\n", err)
panic(err)
}
jsonData, err := os.ReadFile(jsonFile)
if err != nil {
fmt.Fprintf(os.Stderr, "Cannot read %s: %v\n", jsonFile, err)
panic(err)
}
if platform == spotify {
type Track struct {
Timestamp string `json:"ts"`
//Platform string `json:"platform"`
Played int `json:"ms_played"`
//Country string `json:"conn_country"`
//IP string `json:"ip_addr"`
Name string `json:"master_metadata_track_name"`
Artist string `json:"master_metadata_album_artist_name"`
Album string `json:"master_metadata_album_album_name"`
//TrackURI string `json:"spotify_track_uri"`
//Episode string `json:"episode_name"`
//Show string `json:"episode_show_name"`
//EpisodeURI string `json:"spotify_episode_uri"`
//Audiobook string `json:"audiobook_title"`
//AudiobookURI string `json:"audiobook_uri"`
//AudiobookChapterURI string `json:"audiobook_chapter_uri"`
//AudiobookChapter string `json:"audiobook_chapter_title"`
//ReasonStart string `json:"reason_start"`
//ReasonEnd string `json:"reason_end"`
//Shuffle bool `json:"shuffle"`
//Skipped bool `json:"skipped"`
//Offline bool `json:"offline"`
//OfflineTimestamp int `json:"offline_timestamp"`
//Incognito bool `json:"incognito_mode"`
}
var tracks []Track
err := json.Unmarshal(jsonData, &tracks)
if err != nil {
fmt.Fprintf(os.Stderr, "Cannot unmarshal %s: %v\n", jsonFile, err)
panic(err)
}
for _, track := range tracks {
// skip adding a song if it was only listed to for less than 20 seconds
if track.Played < 20000 {
continue
}
_, err = conn.Exec(context.Background(), "INSERT INTO history (timestamp, song_name, artist, album_name, ms_played) VALUES ($1, $2, $3, $4, $5);", track.Timestamp, track.Name, track.Artist, track.Album, track.Played)
if err != nil {
fmt.Fprintf(os.Stderr, "Couldn't add track to muzi DB (%s): %v\n", (track.Artist + " - " + track.Name), err)
}
}
}
}
func addDirToDB(path string, platform int) {
dirs, err := os.ReadDir(path)
if err != nil {
panic(err)
}
for _, dir := range dirs {
subPath := filepath.Join(path, dir.Name(), "Spotify Extended Streaming History")
entries, err := os.ReadDir(subPath)
if err != nil {
panic(err)
}
for _, f := range entries {
jsonFileName := f.Name()
if platform == spotify {
if !strings.Contains(jsonFileName, ".json") {
continue
}
// prevents parsing spotify video data that causes duplicates
if strings.Contains(jsonFileName, "Video") {
continue
}
}
jsonFilePath := filepath.Join(subPath, jsonFileName)
jsonToDB(jsonFilePath, platform)
}
}
}
func importSpotify() { func importSpotify() {
path := filepath.Join(".", "spotify-data", "zip") path := filepath.Join(".", "spotify-data", "zip")
@@ -16,7 +160,7 @@ func importSpotify() {
} }
for _, f := range entries { for _, f := range entries {
_, err := zip.OpenReader(filepath.Join(path, f.Name())) _, err := zip.OpenReader(filepath.Join(path, f.Name()))
if (err == nil) { if err == nil {
fileName := f.Name() fileName := f.Name()
fileFullPath := filepath.Join(path, fileName) fileFullPath := filepath.Join(path, fileName)
fileBaseName := fileName[:(strings.LastIndex(fileName, "."))] fileBaseName := fileName[:(strings.LastIndex(fileName, "."))]
@@ -25,18 +169,18 @@ func importSpotify() {
extract(fileFullPath, targetDirFullPath) extract(fileFullPath, targetDirFullPath)
} }
} }
addDirToDB(targetBase, spotify)
} }
func extract(path string, target string) { func extract(path string, target string) {
archive, err := zip.OpenReader(path) archive, err := zip.OpenReader(path)
if (err != nil) { if err != nil {
panic(err) panic(err)
} }
defer archive.Close() defer archive.Close()
zipDir := filepath.Base(path) zipDir := filepath.Base(path)
zipDir = zipDir[:(strings.LastIndex(zipDir, "."))] zipDir = zipDir[:(strings.LastIndex(zipDir, "."))]
target = filepath.Join(target, zipDir)
for _, f := range archive.File { for _, f := range archive.File {
filePath := filepath.Join(target, f.Name) filePath := filepath.Join(target, f.Name)
@@ -71,4 +215,5 @@ func extract(path string, target string) {
} }
func main() { func main() {
importSpotify()
} }