mirror of
https://github.com/gabehf/Koito.git
synced 2026-04-22 20:11:50 -07:00
wip: endpoint working
This commit is contained in:
parent
3b585f748a
commit
6b73f83484
19 changed files with 510 additions and 243 deletions
|
|
@ -1,54 +1,19 @@
|
|||
package summary
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
_ "image/jpeg"
|
||||
"image/png"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
|
||||
"github.com/gabehf/koito/internal/cfg"
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/font/opentype"
|
||||
"golang.org/x/image/math/fixed"
|
||||
_ "golang.org/x/image/webp"
|
||||
)
|
||||
|
||||
type Summary struct {
|
||||
Title string
|
||||
TopArtistImage string
|
||||
TopArtists []struct {
|
||||
Name string
|
||||
Plays int
|
||||
MinutesListened int
|
||||
}
|
||||
TopAlbumImage string
|
||||
TopAlbums []struct {
|
||||
Title string
|
||||
Plays int
|
||||
MinutesListened int
|
||||
}
|
||||
TopTrackImage string
|
||||
TopTracks []struct {
|
||||
Title string
|
||||
Plays int
|
||||
MinutesListened int
|
||||
}
|
||||
MinutesListened int
|
||||
Plays int
|
||||
AvgPlaysPerDay float32
|
||||
UniqueTracks int32
|
||||
UniqueAlbums int32
|
||||
UniqueArtists int32
|
||||
NewTracks int32
|
||||
NewAlbums int32
|
||||
NewArtists int32
|
||||
}
|
||||
|
||||
var (
|
||||
assetPath = path.Join("..", "..", "assets")
|
||||
titleFontPath = path.Join(assetPath, "LeagueSpartan-Medium.ttf")
|
||||
|
|
@ -63,69 +28,69 @@ var (
|
|||
)
|
||||
|
||||
// lots of code borrowed from https://medium.com/@daniel.ruizcamacho/how-to-create-an-image-in-golang-step-by-step-4416affe088f
|
||||
func GenerateImage(summary *Summary) error {
|
||||
base := image.NewRGBA(image.Rect(0, 0, 750, 1100))
|
||||
draw.Draw(base, base.Bounds(), image.NewUniform(color.Black), image.Pt(0, 0), draw.Over)
|
||||
// func GenerateImage(summary *Summary) error {
|
||||
// base := image.NewRGBA(image.Rect(0, 0, 750, 1100))
|
||||
// draw.Draw(base, base.Bounds(), image.NewUniform(color.Black), image.Pt(0, 0), draw.Over)
|
||||
|
||||
file, err := os.Create(path.Join(cfg.ConfigDir(), "summary.png"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("GenerateImage: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
// file, err := os.Create(path.Join(cfg.ConfigDir(), "summary.png"))
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("GenerateImage: %w", err)
|
||||
// }
|
||||
// defer file.Close()
|
||||
|
||||
// add title
|
||||
if err := addText(base, summary.Title, "", image.Pt(paddingLg, 60), titleFontPath, titleFontSize); err != nil {
|
||||
return fmt.Errorf("GenerateImage: %w", err)
|
||||
}
|
||||
// add images
|
||||
if err := addImage(base, summary.TopArtistImage, image.Pt(-paddingLg, -120), featuredImageSize); err != nil {
|
||||
return fmt.Errorf("GenerateImage: %w", err)
|
||||
}
|
||||
if err := addImage(base, summary.TopArtistImage, image.Pt(-paddingLg, -120-(featuredImageSize+paddingLg)), featuredImageSize); err != nil {
|
||||
return fmt.Errorf("GenerateImage: %w", err)
|
||||
}
|
||||
if err := addImage(base, summary.TopArtistImage, image.Pt(-paddingLg, -120-(featuredImageSize+paddingLg)*2), featuredImageSize); err != nil {
|
||||
return fmt.Errorf("GenerateImage: %w", err)
|
||||
}
|
||||
// top artists text
|
||||
if err := addText(base, "Top Artists", "", image.Pt(featureTextStart, 132), textFontPath, textFontSize); err != nil {
|
||||
return fmt.Errorf("GenerateImage: %w", err)
|
||||
}
|
||||
for rank, artist := range summary.TopArtists {
|
||||
if rank == 0 {
|
||||
if err := addText(base, artist.Name, strconv.Itoa(artist.Plays)+" plays", image.Pt(featureTextStart, featuredImageSize+10), titleFontPath, titleFontSize); err != nil {
|
||||
return fmt.Errorf("GenerateImage: %w", err)
|
||||
}
|
||||
} else {
|
||||
if err := addText(base, artist.Name, strconv.Itoa(artist.Plays)+" plays", image.Pt(featureTextStart, 210+(rank*(int(textFontSize)+paddingSm))), textFontPath, textFontSize); err != nil {
|
||||
return fmt.Errorf("GenerateImage: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
// top albums text
|
||||
if err := addText(base, "Top Albums", "", image.Pt(featureTextStart, 132+featuredImageSize+paddingLg), textFontPath, textFontSize); err != nil {
|
||||
return fmt.Errorf("GenerateImage: %w", err)
|
||||
}
|
||||
for rank, album := range summary.TopAlbums {
|
||||
if rank == 0 {
|
||||
if err := addText(base, album.Title, strconv.Itoa(album.Plays)+" plays", image.Pt(featureTextStart, featuredImageSize+10), titleFontPath, titleFontSize); err != nil {
|
||||
return fmt.Errorf("GenerateImage: %w", err)
|
||||
}
|
||||
} else {
|
||||
if err := addText(base, album.Title, strconv.Itoa(album.Plays)+" plays", image.Pt(featureTextStart, 210+(rank*(int(textFontSize)+paddingSm))), textFontPath, textFontSize); err != nil {
|
||||
return fmt.Errorf("GenerateImage: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
// top tracks text
|
||||
// // add title
|
||||
// if err := addText(base, summary.Title, "", image.Pt(paddingLg, 60), titleFontPath, titleFontSize); err != nil {
|
||||
// return fmt.Errorf("GenerateImage: %w", err)
|
||||
// }
|
||||
// // add images
|
||||
// if err := addImage(base, summary.TopArtistImage, image.Pt(-paddingLg, -120), featuredImageSize); err != nil {
|
||||
// return fmt.Errorf("GenerateImage: %w", err)
|
||||
// }
|
||||
// if err := addImage(base, summary.TopArtistImage, image.Pt(-paddingLg, -120-(featuredImageSize+paddingLg)), featuredImageSize); err != nil {
|
||||
// return fmt.Errorf("GenerateImage: %w", err)
|
||||
// }
|
||||
// if err := addImage(base, summary.TopArtistImage, image.Pt(-paddingLg, -120-(featuredImageSize+paddingLg)*2), featuredImageSize); err != nil {
|
||||
// return fmt.Errorf("GenerateImage: %w", err)
|
||||
// }
|
||||
// // top artists text
|
||||
// if err := addText(base, "Top Artists", "", image.Pt(featureTextStart, 132), textFontPath, textFontSize); err != nil {
|
||||
// return fmt.Errorf("GenerateImage: %w", err)
|
||||
// }
|
||||
// for rank, artist := range summary.TopArtists {
|
||||
// if rank == 0 {
|
||||
// if err := addText(base, artist.Name, strconv.Itoa(artist.Plays)+" plays", image.Pt(featureTextStart, featuredImageSize+10), titleFontPath, titleFontSize); err != nil {
|
||||
// return fmt.Errorf("GenerateImage: %w", err)
|
||||
// }
|
||||
// } else {
|
||||
// if err := addText(base, artist.Name, strconv.Itoa(artist.Plays)+" plays", image.Pt(featureTextStart, 210+(rank*(int(textFontSize)+paddingSm))), textFontPath, textFontSize); err != nil {
|
||||
// return fmt.Errorf("GenerateImage: %w", err)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// // top albums text
|
||||
// if err := addText(base, "Top Albums", "", image.Pt(featureTextStart, 132+featuredImageSize+paddingLg), textFontPath, textFontSize); err != nil {
|
||||
// return fmt.Errorf("GenerateImage: %w", err)
|
||||
// }
|
||||
// for rank, album := range summary.TopAlbums {
|
||||
// if rank == 0 {
|
||||
// if err := addText(base, album.Title, strconv.Itoa(album.Plays)+" plays", image.Pt(featureTextStart, featuredImageSize+10), titleFontPath, titleFontSize); err != nil {
|
||||
// return fmt.Errorf("GenerateImage: %w", err)
|
||||
// }
|
||||
// } else {
|
||||
// if err := addText(base, album.Title, strconv.Itoa(album.Plays)+" plays", image.Pt(featureTextStart, 210+(rank*(int(textFontSize)+paddingSm))), textFontPath, textFontSize); err != nil {
|
||||
// return fmt.Errorf("GenerateImage: %w", err)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// // top tracks text
|
||||
|
||||
// stats text
|
||||
// // stats text
|
||||
|
||||
if err := png.Encode(file, base); err != nil {
|
||||
return fmt.Errorf("GenerateImage: png.Encode: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// if err := png.Encode(file, base); err != nil {
|
||||
// return fmt.Errorf("GenerateImage: png.Encode: %w", err)
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func addImage(baseImage *image.RGBA, path string, point image.Point, height int) error {
|
||||
templateFile, err := os.Open(path)
|
||||
|
|
|
|||
141
internal/summary/summary.go
Normal file
141
internal/summary/summary.go
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
package summary
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gabehf/koito/internal/db"
|
||||
"github.com/gabehf/koito/internal/models"
|
||||
)
|
||||
|
||||
type Summary struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
TopArtists []*models.Artist `json:"top_artists"` // ListenCount and TimeListened are overriden with stats from timeframe
|
||||
TopAlbums []*models.Album `json:"top_albums"` // ListenCount and TimeListened are overriden with stats from timeframe
|
||||
TopTracks []*models.Track `json:"top_tracks"` // ListenCount and TimeListened are overriden with stats from timeframe
|
||||
MinutesListened int `json:"minutes_listened"`
|
||||
AvgMinutesPerDay int `json:"avg_minutes_listened_per_day"`
|
||||
Plays int `json:"plays"`
|
||||
AvgPlaysPerDay float32 `json:"avg_plays_per_day"`
|
||||
UniqueTracks int `json:"unique_tracks"`
|
||||
UniqueAlbums int `json:"unique_albums"`
|
||||
UniqueArtists int `json:"unique_artists"`
|
||||
NewTracks int `json:"new_tracks"`
|
||||
NewAlbums int `json:"new_albums"`
|
||||
NewArtists int `json:"new_artists"`
|
||||
}
|
||||
|
||||
func GenerateSummary(ctx context.Context, store db.DB, userId int32, timeframe db.Timeframe, title string) (summary *Summary, err error) {
|
||||
// l := logger.FromContext(ctx)
|
||||
|
||||
summary = new(Summary)
|
||||
|
||||
topArtists, err := store.GetTopArtistsPaginated(ctx, db.GetItemsOpts{Page: 1, Limit: 5, From: timeframe.T1u, To: timeframe.T2u, Period: timeframe.Period})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GenerateSummary: %w", err)
|
||||
}
|
||||
summary.TopArtists = topArtists.Items
|
||||
// replace ListenCount and TimeListened with stats from timeframe
|
||||
for i, artist := range summary.TopArtists {
|
||||
timelistened, err := store.CountTimeListenedToItem(ctx, db.TimeListenedOpts{ArtistID: artist.ID, Timeframe: timeframe})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GenerateSummary: %w", err)
|
||||
}
|
||||
listens, err := store.CountListensToItem(ctx, db.TimeListenedOpts{ArtistID: artist.ID, Timeframe: timeframe})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GenerateSummary: %w", err)
|
||||
}
|
||||
summary.TopArtists[i].TimeListened = timelistened
|
||||
summary.TopArtists[i].ListenCount = listens
|
||||
}
|
||||
|
||||
topAlbums, err := store.GetTopAlbumsPaginated(ctx, db.GetItemsOpts{Page: 1, Limit: 5, From: timeframe.T1u, To: timeframe.T2u, Period: timeframe.Period})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GenerateSummary: %w", err)
|
||||
}
|
||||
summary.TopAlbums = topAlbums.Items
|
||||
// replace ListenCount and TimeListened with stats from timeframe
|
||||
for i, album := range summary.TopAlbums {
|
||||
timelistened, err := store.CountTimeListenedToItem(ctx, db.TimeListenedOpts{AlbumID: album.ID, Timeframe: timeframe})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GenerateSummary: %w", err)
|
||||
}
|
||||
listens, err := store.CountListensToItem(ctx, db.TimeListenedOpts{AlbumID: album.ID, Timeframe: timeframe})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GenerateSummary: %w", err)
|
||||
}
|
||||
summary.TopAlbums[i].TimeListened = timelistened
|
||||
summary.TopAlbums[i].ListenCount = listens
|
||||
}
|
||||
|
||||
topTracks, err := store.GetTopTracksPaginated(ctx, db.GetItemsOpts{Page: 1, Limit: 5, From: timeframe.T1u, To: timeframe.T2u, Period: timeframe.Period})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GenerateSummary: %w", err)
|
||||
}
|
||||
summary.TopTracks = topTracks.Items
|
||||
// replace ListenCount and TimeListened with stats from timeframe
|
||||
for i, track := range summary.TopTracks {
|
||||
timelistened, err := store.CountTimeListenedToItem(ctx, db.TimeListenedOpts{TrackID: track.ID, Timeframe: timeframe})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GenerateSummary: %w", err)
|
||||
}
|
||||
listens, err := store.CountListensToItem(ctx, db.TimeListenedOpts{TrackID: track.ID, Timeframe: timeframe})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GenerateSummary: %w", err)
|
||||
}
|
||||
summary.TopTracks[i].TimeListened = timelistened
|
||||
summary.TopTracks[i].ListenCount = listens
|
||||
}
|
||||
|
||||
t1, t2 := db.TimeframeToTimeRange(timeframe)
|
||||
daycount := int(t2.Sub(t1).Hours() / 24)
|
||||
// bandaid
|
||||
if daycount == 0 {
|
||||
daycount = 1
|
||||
}
|
||||
|
||||
tmp, err := store.CountTimeListened(ctx, timeframe)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GenerateSummary: %w", err)
|
||||
}
|
||||
summary.MinutesListened = int(tmp) / 60
|
||||
summary.AvgMinutesPerDay = summary.MinutesListened / daycount
|
||||
tmp, err = store.CountListens(ctx, timeframe)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GenerateSummary: %w", err)
|
||||
}
|
||||
summary.Plays = int(tmp)
|
||||
summary.AvgPlaysPerDay = float32(summary.Plays) / float32(daycount)
|
||||
tmp, err = store.CountTracks(ctx, timeframe)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GenerateSummary: %w", err)
|
||||
}
|
||||
summary.UniqueTracks = int(tmp)
|
||||
tmp, err = store.CountAlbums(ctx, timeframe)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GenerateSummary: %w", err)
|
||||
}
|
||||
summary.UniqueAlbums = int(tmp)
|
||||
tmp, err = store.CountArtists(ctx, timeframe)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GenerateSummary: %w", err)
|
||||
}
|
||||
summary.UniqueArtists = int(tmp)
|
||||
tmp, err = store.CountNewTracks(ctx, timeframe)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GenerateSummary: %w", err)
|
||||
}
|
||||
summary.NewTracks = int(tmp)
|
||||
tmp, err = store.CountNewAlbums(ctx, timeframe)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GenerateSummary: %w", err)
|
||||
}
|
||||
summary.NewAlbums = int(tmp)
|
||||
tmp, err = store.CountNewArtists(ctx, timeframe)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GenerateSummary: %w", err)
|
||||
}
|
||||
summary.NewArtists = int(tmp)
|
||||
|
||||
return summary, nil
|
||||
}
|
||||
|
|
@ -1,12 +1,9 @@
|
|||
package summary_test
|
||||
|
||||
import (
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/gabehf/koito/internal/cfg"
|
||||
"github.com/gabehf/koito/internal/summary"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMain(t *testing.M) {
|
||||
|
|
@ -33,55 +30,55 @@ func TestMain(t *testing.M) {
|
|||
t.Run()
|
||||
}
|
||||
|
||||
func TestGenerateImage(t *testing.T) {
|
||||
s := summary.Summary{
|
||||
Title: "20XX Rewind",
|
||||
TopArtistImage: path.Join("..", "..", "test_assets", "yuu.jpg"),
|
||||
TopArtists: []struct {
|
||||
Name string
|
||||
Plays int
|
||||
MinutesListened int
|
||||
}{
|
||||
{"CHUU", 738, 7321},
|
||||
{"Paramore", 738, 7321},
|
||||
{"ano", 738, 7321},
|
||||
{"NELKE", 738, 7321},
|
||||
{"ILLIT", 738, 7321},
|
||||
},
|
||||
TopAlbumImage: "",
|
||||
TopAlbums: []struct {
|
||||
Title string
|
||||
Plays int
|
||||
MinutesListened int
|
||||
}{
|
||||
{"Only cry in the rain", 738, 7321},
|
||||
{"Paramore", 738, 7321},
|
||||
{"ano", 738, 7321},
|
||||
{"NELKE", 738, 7321},
|
||||
{"ILLIT", 738, 7321},
|
||||
},
|
||||
TopTrackImage: "",
|
||||
TopTracks: []struct {
|
||||
Title string
|
||||
Plays int
|
||||
MinutesListened int
|
||||
}{
|
||||
{"虹の色よ鮮やかであれ (NELKE ver.)", 321, 12351},
|
||||
{"Paramore", 738, 7321},
|
||||
{"ano", 738, 7321},
|
||||
{"NELKE", 738, 7321},
|
||||
{"ILLIT", 738, 7321},
|
||||
},
|
||||
MinutesListened: 0,
|
||||
Plays: 0,
|
||||
AvgPlaysPerDay: 0,
|
||||
UniqueTracks: 0,
|
||||
UniqueAlbums: 0,
|
||||
UniqueArtists: 0,
|
||||
NewTracks: 0,
|
||||
NewAlbums: 0,
|
||||
NewArtists: 0,
|
||||
}
|
||||
func TestGenerateSummary(t *testing.T) {
|
||||
// s := summary.Summary{
|
||||
// Title: "20XX Rewind",
|
||||
// TopArtistImage: path.Join("..", "..", "test_assets", "yuu.jpg"),
|
||||
// TopArtists: []struct {
|
||||
// Name string
|
||||
// Plays int
|
||||
// MinutesListened int
|
||||
// }{
|
||||
// {"CHUU", 738, 7321},
|
||||
// {"Paramore", 738, 7321},
|
||||
// {"ano", 738, 7321},
|
||||
// {"NELKE", 738, 7321},
|
||||
// {"ILLIT", 738, 7321},
|
||||
// },
|
||||
// TopAlbumImage: "",
|
||||
// TopAlbums: []struct {
|
||||
// Title string
|
||||
// Plays int
|
||||
// MinutesListened int
|
||||
// }{
|
||||
// {"Only cry in the rain", 738, 7321},
|
||||
// {"Paramore", 738, 7321},
|
||||
// {"ano", 738, 7321},
|
||||
// {"NELKE", 738, 7321},
|
||||
// {"ILLIT", 738, 7321},
|
||||
// },
|
||||
// TopTrackImage: "",
|
||||
// TopTracks: []struct {
|
||||
// Title string
|
||||
// Plays int
|
||||
// MinutesListened int
|
||||
// }{
|
||||
// {"虹の色よ鮮やかであれ (NELKE ver.)", 321, 12351},
|
||||
// {"Paramore", 738, 7321},
|
||||
// {"ano", 738, 7321},
|
||||
// {"NELKE", 738, 7321},
|
||||
// {"ILLIT", 738, 7321},
|
||||
// },
|
||||
// MinutesListened: 0,
|
||||
// Plays: 0,
|
||||
// AvgPlaysPerDay: 0,
|
||||
// UniqueTracks: 0,
|
||||
// UniqueAlbums: 0,
|
||||
// UniqueArtists: 0,
|
||||
// NewTracks: 0,
|
||||
// NewAlbums: 0,
|
||||
// NewArtists: 0,
|
||||
// }
|
||||
|
||||
assert.NoError(t, summary.GenerateImage(&s))
|
||||
// assert.NoError(t, summary.GenerateImage(&s))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue