append metadata when downloading episodes
This commit is contained in:
6
go.mod
6
go.mod
@@ -6,3 +6,9 @@ require (
|
||||
github.com/jmoiron/sqlx v1.4.0
|
||||
github.com/mattn/go-sqlite3 v1.14.34
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/tetratelabs/wazero v1.11.0 // indirect
|
||||
go.senan.xyz/taglib v0.11.1 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
)
|
||||
|
||||
6
go.sum
6
go.sum
@@ -9,3 +9,9 @@ github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk=
|
||||
github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA=
|
||||
github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU=
|
||||
go.senan.xyz/taglib v0.11.1 h1:S3mO5e3HRRG0Ehw1jLUodYbAJK8TtqdOoNgqkC0D3uU=
|
||||
go.senan.xyz/taglib v0.11.1/go.mod h1:qyTl978MnGeZ/ny4d/t0ErLXxysA+39X4+SNSCk56Zs=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
|
||||
26
main.go
26
main.go
@@ -23,6 +23,7 @@ type Podcast struct {
|
||||
Feed string `json:"feed" db:"feed"`
|
||||
Language string `json:"language" db:"language"`
|
||||
Link string `json:"link" db:"link"`
|
||||
Image string `json:"image" db:"image"`
|
||||
CreatedAt time.Time `json:"createdAt" db:"created_at"`
|
||||
}
|
||||
|
||||
@@ -33,6 +34,7 @@ type Episode struct {
|
||||
Guid string `json:"guid" db:"guid"`
|
||||
Url string `json:"url" db:"url"`
|
||||
PodcastId int64 `json:"podcastId" db:"podcast_id"`
|
||||
Number int `json:"number" db:"number"`
|
||||
CreatedAt time.Time `json:"createdAt" db:"created_at"`
|
||||
}
|
||||
|
||||
@@ -72,6 +74,7 @@ type RSSFeed struct {
|
||||
Description string `xml:"description"`
|
||||
Language string `xml:"language"`
|
||||
Link string `xml:"link"`
|
||||
Image string `xml:"image>url"`
|
||||
Items []RSSFeedItem `xml:"item"`
|
||||
} `xml:"channel"`
|
||||
}
|
||||
@@ -126,12 +129,13 @@ func getPodcasts(db *sqlx.DB) ([]*Podcast, error) {
|
||||
}
|
||||
|
||||
func createPodcast(db *sqlx.DB, feed RSSFeed, feedUrl string) error {
|
||||
_, err := db.NamedExec("INSERT INTO podcasts (name, description, feed, language, link) VALUES (:name, :description, :feed, :language, :link)", map[string]any{
|
||||
_, err := db.NamedExec("INSERT INTO podcasts (name, description, feed, language, link, image) VALUES (:name, :description, :feed, :language, :link, :image)", map[string]any{
|
||||
"name": feed.Channel.Title,
|
||||
"description": feed.Channel.Description,
|
||||
"feed": feedUrl,
|
||||
"language": feed.Channel.Language,
|
||||
"link": feed.Channel.Link,
|
||||
"image": feed.Channel.Image,
|
||||
})
|
||||
return err
|
||||
}
|
||||
@@ -150,11 +154,12 @@ func getEpisodeByGuid(db *sqlx.DB, guid string) (*Episode, error) {
|
||||
return episode, nil
|
||||
}
|
||||
|
||||
func addEpisode(db *sqlx.DB, episode RSSFeedItem, podcastId int64) error {
|
||||
_, err := db.NamedExec("INSERT INTO episodes (title, pubdate, guid, url, podcast_id) VALUES (:title, :pubdate, :guid, :url, :podcast_id)", map[string]any{
|
||||
func addEpisode(db *sqlx.DB, episode RSSFeedItem, episodeNumber int, podcastId int64) error {
|
||||
_, err := db.NamedExec("INSERT INTO episodes (title, pubdate, guid, url, number, podcast_id) VALUES (:title, :pubdate, :guid, :url, :number, :podcast_id)", map[string]any{
|
||||
"title": episode.Title,
|
||||
"pubdate": episode.PubDate.Time,
|
||||
"url": episode.Enclosure.URL,
|
||||
"number": episodeNumber,
|
||||
"guid": episode.Guid,
|
||||
"podcast_id": podcastId,
|
||||
})
|
||||
@@ -220,6 +225,7 @@ func main() {
|
||||
feed varchar not null,
|
||||
language varchar not null,
|
||||
link varchar not null,
|
||||
image varchar not null,
|
||||
created_at datetime default current_timestamp
|
||||
);
|
||||
|
||||
@@ -229,6 +235,7 @@ func main() {
|
||||
pubdate datetime not null,
|
||||
guid varchar not null,
|
||||
url varchar not null,
|
||||
number integer not null,
|
||||
created_at datetime default current_timestamp,
|
||||
podcast_id integer not null,
|
||||
FOREIGN KEY (podcast_id) REFERENCES podcasts(id)
|
||||
@@ -258,12 +265,16 @@ func main() {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := addEpisode(db, newestEpisode, podcast.ID); err != nil {
|
||||
episodeNumber := len(feed.Channel.Items) - 1
|
||||
|
||||
if err := addEpisode(db, newestEpisode, episodeNumber, podcast.ID); err != nil {
|
||||
log.Printf("failed to add new episode [%s]: %v\n", podcast.Name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(path.Join(podcastsDirPath, podcast.Name, newestEpisode.Title+".mp3"), os.O_CREATE|os.O_RDWR, 0644)
|
||||
episodeFilePath := path.Join(podcastsDirPath, podcast.Name, fmt.Sprintf("Episode %d.mp3", episodeNumber))
|
||||
|
||||
f, err := os.OpenFile(episodeFilePath, os.O_CREATE|os.O_RDWR, 0644)
|
||||
if err != nil {
|
||||
log.Printf("failed to create file for newest episode: %v\n", err)
|
||||
continue
|
||||
@@ -274,6 +285,11 @@ func main() {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := appendPodcastMetadata(episodeFilePath, newestEpisode, episodeNumber, *podcast); err != nil {
|
||||
log.Printf("failed to append episode metadata: %v\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
f.Close()
|
||||
}
|
||||
}
|
||||
|
||||
51
metadata.go
Normal file
51
metadata.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"go.senan.xyz/taglib"
|
||||
)
|
||||
|
||||
func getPodcastCover(imageUrl string) ([]byte, error) {
|
||||
resp, err := http.Get(imageUrl)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch podcast cover image: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read image data from response: %v", err)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func appendPodcastMetadata(filePath string, feedItem RSSFeedItem, episodeNumber int, podcast Podcast) error {
|
||||
if err := taglib.WriteTags(filePath, map[string][]string{
|
||||
taglib.Artist: {podcast.Name},
|
||||
taglib.AlbumArtist: {podcast.Name},
|
||||
taglib.Album: {podcast.Name},
|
||||
taglib.TrackNumber: {strconv.Itoa(episodeNumber)},
|
||||
taglib.Podcast: {podcast.Name},
|
||||
taglib.PodcastURL: {podcast.Link},
|
||||
taglib.PodcastDesc: {podcast.Description},
|
||||
taglib.Title: {feedItem.Title},
|
||||
}, 0); err != nil {
|
||||
return fmt.Errorf("failed to append tags: %v", err)
|
||||
}
|
||||
|
||||
imgData, err := getPodcastCover(podcast.Image)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch podcast cover: %v", err)
|
||||
}
|
||||
|
||||
if err := taglib.WriteImage(filePath, imgData); err != nil {
|
||||
return fmt.Errorf("failed to append cover image: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user