From 9161423a4b9692610d17d36af1312a54f0385cc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=CC=81scar=20M=2E=20Lage?= Date: Fri, 11 Oct 2024 13:05:46 +0200 Subject: [PATCH] Added music --- env.sample | 1 + internal/music/api_spotify.go | 155 ++++++++++++++++++++++++++++++++++ internal/music/controller.go | 82 ++++++++++++++++++ internal/music/model.go | 23 ++--- main.go | 18 +++- obsidian/music.yml.md | 6 +- templates/music.md.tpl | 4 +- 7 files changed, 274 insertions(+), 15 deletions(-) create mode 100644 internal/music/api_spotify.go diff --git a/env.sample b/env.sample index 80478b7..8e59989 100644 --- a/env.sample +++ b/env.sample @@ -9,6 +9,7 @@ GOOGLE_CX=your_google_cx TWITCH_CLIENT_ID=your_twitch_client_id TWITCH_SECRET_ID=your_twitch_secret_id SPOTIFY_API_KEY=your_spotify_api_key +SPOTIFY_SECRET_ID=your_spotify_secret_id # PATH to Obisdian files where the information is OBSIDIAN_BOOKS_FILE=/path/to/your/obsidian/books.yml.md diff --git a/internal/music/api_spotify.go b/internal/music/api_spotify.go new file mode 100644 index 0000000..ad4d8c6 --- /dev/null +++ b/internal/music/api_spotify.go @@ -0,0 +1,155 @@ +package music + +import ( + "encoding/json" + "fmt" + "hugo-medialog/utils" + "io/ioutil" + "net/http" + "net/url" + "os" + "path/filepath" + "strconv" + "strings" +) + +const ( + spotifyAPIBaseURL = "https://api.spotify.com/v1/search" + spotifyTokenURL = "https://accounts.spotify.com/api/token" +) + +func getSpotifyToken() (string, error) { + apiKey := os.Getenv("SPOTIFY_API_KEY") + apiSecret := os.Getenv("SPOTIFY_SECRET_ID") + + // Prepare the request body + data := url.Values{} + data.Set("grant_type", "client_credentials") + + // Prepare the request + req, err := http.NewRequest("POST", spotifyTokenURL, strings.NewReader(data.Encode())) + if err != nil { + return "", err + } + + // Add the headers + req.SetBasicAuth(apiKey, apiSecret) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + // Send the request + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + + // Read the response + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", err + } + + // Parse the response + var result map[string]interface{} + if err := json.Unmarshal(body, &result); err != nil { + return "", err + } + + // Extract the access token + accessToken, ok := result["access_token"].(string) + if !ok { + return "", fmt.Errorf("could not retrieve access token") + } + + return accessToken, nil +} + +func SearchAlbumByTitle(title string, album *Album) error { + // Obtain the access token + accessToken, err := getSpotifyToken() + if err != nil { + return err + } + + // Search URL + titleEsc := url.QueryEscape(fmt.Sprintf("%s", title)) + url := fmt.Sprintf("%s?q=%s&type=track&limit=1", spotifyAPIBaseURL, titleEsc) + + // Prepare the request and add the auth header + req, _ := http.NewRequest("GET", url, nil) + if err != nil { + return err + } + req.Header.Set("Authorization", "Bearer "+accessToken) + + // Send the request + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + + // Decode the response + var result map[string]interface{} + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return err + } + + tracks := result["tracks"].(map[string]interface{})["items"].([]interface{}) + track := tracks[0].(map[string]interface{}) + albumData := track["album"].(map[string]interface{}) + + album.Artist = track["artists"].([]interface{})[0].(map[string]interface{})["name"].(string) + album.ID = albumData["id"].(string) + album.Album = albumData["name"].(string) + album.Date = albumData["release_date"].(string) + album.Year, _ = strconv.Atoi(strings.Split(album.Date, "-")[0]) + album.Subtitle = strconv.Itoa(album.Year) + album.Tracks = int(albumData["total_tracks"].(float64)) // Spotify envía números como float64 + album.Link = albumData["href"].(string) + images := albumData["images"].([]interface{}) + if len(images) > 0 { + firstImage := images[0].(map[string]interface{}) + album.Image = firstImage["url"].(string) + } + + return nil +} + +func DownloadImage(url, slug string) error { + imageDir := filepath.Join(os.Getenv("MARKDOWN_OUTPUT_MUSIC_DIR"), os.Getenv("IMAGES_OUTPUT_DIR")) + if err := utils.CreateDirIfNotExists(imageDir); err != nil { + return err + } + filename := fmt.Sprintf("%s.jpg", slug) + filePath := filepath.Join(imageDir, filename) + + resp, err := http.Get(url) + if err != nil { + return fmt.Errorf("error downloading image: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("non-200 response while downloading image: %d", resp.StatusCode) + } + + file, err := os.Create(filePath) + if err != nil { + return fmt.Errorf("error creating image file: %w", err) + } + defer file.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("error reading image data: %w", err) + } + + _, err = file.Write(body) + if err != nil { + return fmt.Errorf("error writing image data: %w", err) + } + + fmt.Printf(" - Image saved successfully at: %s\n", filePath) + return nil +} diff --git a/internal/music/controller.go b/internal/music/controller.go index 8b13789..4913979 100644 --- a/internal/music/controller.go +++ b/internal/music/controller.go @@ -1 +1,83 @@ +package music +import ( + "fmt" + "hugo-medialog/utils" + "os" + "path/filepath" + + "gopkg.in/yaml.v2" +) + +func LoadAlbums() ([]Album, error) { + albumsFile := os.Getenv("OBSIDIAN_MUSIC_FILE") + fileData, err := os.ReadFile(albumsFile) + if err != nil { + return nil, err + } + + var albums []Album + err = yaml.Unmarshal(fileData, &albums) + if err != nil { + return nil, err + } + + return albums, nil +} + +func ProcessAlbum(albumList []Album) error { + for _, album := range albumList { + fmt.Printf("Title: %s\n", album.Title) + + // If we dont have ID, search album by Title and get the ID + if album.ID == "" { + err := SearchAlbumByTitle(album.Title, &album) + if err != nil { + fmt.Printf("Error searching album by title %s: %s\n", album.Title, err) + continue + } + } + album.Slug = utils.Sluggify(album.Title) + + // Now we need to get the image + if album.Image != "" { + err := DownloadImage(album.Image, album.Slug) + if err != nil { + fmt.Printf("Error downloading %s: %s\n", album.Image, err) + } + } + album.Image = fmt.Sprintf("%s.jpg", album.Slug) + + err := generateAlbumMarkdown(album) + if err != nil { + return err + } + + utils.Sep() + } + return nil +} + +func generateAlbumMarkdown(album Album) error { + templatePath := filepath.Join(os.Getenv("TEMPLATES_DIR"), "music.md.tpl") + outputDir := os.Getenv("MARKDOWN_OUTPUT_MUSIC_DIR") + if err := utils.CreateDirIfNotExists(outputDir); err != nil { + return err + } + outputPath := filepath.Join(outputDir, fmt.Sprintf("%s.md", album.Slug)) + + data := map[string]interface{}{ + "Title": album.Title, + "Artist": album.Artist, + "Link": album.Link, + "Subtitle": album.Year, + "Year": album.Year, + "Rate": album.Rate, + "Tracks": album.Tracks, + "Image": album.Image, + "Date": album.Date, + "Tags": "listening", + } + + return utils.GenerateMarkdown(templatePath, outputPath, data) +} diff --git a/internal/music/model.go b/internal/music/model.go index 49f6b74..c5d9082 100644 --- a/internal/music/model.go +++ b/internal/music/model.go @@ -1,16 +1,19 @@ package music type Album struct { - Title string `yaml:"title"` - Subtitle string `yaml:"subtitle"` - Link string `yaml:"link"` - Year int `yaml:"year"` - Rate float64 `yaml:"rate"` - Image string `yaml:"image"` - Poster string `yaml:"poster-image"` - Background string `yaml:"background-image"` - Date string `yaml:"date"` - Tags []string + Title string `yaml:"title"` + Artist string `yaml:"artist"` + Album string `yaml:"album"` + Slug string `yaml:"slug"` + ID string `yaml:"spotify_id"` + Subtitle string `yaml:"subtitle"` + Link string `yaml:"link"` + Year int `yaml:"year"` + Rate float64 `yaml:"rate"` + Tracks int `yaml:"tracks"` + Image string `yaml:"image"` + Date string `yaml:"date"` + Tags []string } type Albums []Album diff --git a/main.go b/main.go index 38195ab..493b51b 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( "hugo-medialog/internal/books" "hugo-medialog/internal/games" "hugo-medialog/internal/movies" + "hugo-medialog/internal/music" "hugo-medialog/internal/series" "hugo-medialog/utils" ) @@ -30,8 +31,8 @@ func main() { processBooks() case "games": processGames() - // case "music": - // processMusic() + case "music": + processMusic() default: fmt.Printf("Invalid media type: %s. Please use movies, series, books, games, or music.\n", *media) } @@ -43,6 +44,7 @@ func processAll() { processSeries() processBooks() processGames() + processMusic() } func processMovies() { @@ -92,3 +94,15 @@ func processGames() { fmt.Printf("Error processing games: %v\n", err) } } + +func processMusic() { + albumList, err := music.LoadAlbums() + if err != nil { + fmt.Printf("Error reading music file: %v\n", err) + return + } + err = music.ProcessAlbum(albumList) + if err != nil { + fmt.Printf("Error processing music: %v\n", err) + } +} diff --git a/obsidian/music.yml.md b/obsidian/music.yml.md index 0387bee..ce36101 100644 --- a/obsidian/music.yml.md +++ b/obsidian/music.yml.md @@ -1,4 +1,8 @@ - title: "Random Access Memories" - spotify_id: "4m2880jivSbbyEGAKfITCa" + #spotify_id: "4m2880jivSbbyEGAKfITCa" rate: 9.8 date: 2024-10-08 + +- title: "Lofi [No copyright]" + rate: 5.5 + date: 2024-10-11 diff --git a/templates/music.md.tpl b/templates/music.md.tpl index 7c8938b..f02b5da 100644 --- a/templates/music.md.tpl +++ b/templates/music.md.tpl @@ -2,11 +2,11 @@ title: "{{ .Title }}" link: "{{ .Link }}" subtitle: "{{ .Subtitle }}" +artist: "{{ .Artist }}" year: {{ .Year }} rate: {{ .Rate }} image: {{ .Image }} -poster-image: {{ .PosterImage }} -background-image: {{ .BackgroundImage }} +tracks: {{ .Tracks }} date: {{ .Date }} draft: false tags: {{ .Tags }}