package converter import ( "encoding/json" "fmt" "io" "io/ioutil" "os" "path" "path/filepath" "sort" "strconv" "strings" "github.com/pkg/errors" "github.com/xfrr/goffmpeg/transcoder" "k8s.io/klog" ) type Converter struct { *message // albumFolder is populated at runtime and contains the album name albumFolder string // the "automatically add to iTunes.localized" directory iTunesDir string } // CreateMessage is basically the reverse process to the conversion. // instead of getting the message and using it, this function can // read a directory and create the message from it. // Only populates what is necessary to do the conversion. // artist must match the directory name. func CreateMessage(artistName, albumFolder string) ([]byte, error) { albumName := path.Base(albumFolder) artistPath := path.Clean(strings.ReplaceAll(albumFolder, albumName, "")) var trackFiles []trackFile err := filepath.Walk(albumFolder, func(path string, info os.FileInfo, err error) error { if err != nil { return errors.Wrapf(err, "error visiting file") } if filepath.Ext(path) == ".flac" { trackFiles = append(trackFiles, trackFile{Path: path}) } return nil }) if err != nil { return nil, errors.Wrapf(err, "error walking path") } sort.Slice(trackFiles, func(i, j int) bool { return trackFiles[i].Path < trackFiles[j].Path }) tracks := func() []track { var tracks []track for i, file := range trackFiles { tracks = append( tracks, track{ Title: strings.ReplaceAll(path.Base(file.Path), ".flac", ""), TrackNumber: strconv.Itoa(i + 1), }, ) } return tracks }() m := message{ Tracks: tracks, TrackFiles: trackFiles, EventType: "Download", Artist: artist{ Name: artistName, Path: artistPath, }, } return json.Marshal(m) } type message struct { Tracks []track `json:"tracks"` TrackFiles []trackFile `json:"trackFiles"` IsUpgrade bool `json:"isUpgrade"` EventType string `json:"eventType"` Artist artist `json:"artist"` } type artist struct { ID int `json:"id"` Name string `json:"name"` Path string `json:"path"` MBID string `json:"mbID"` } type track struct { ID int `json:"id"` Title string `json:"title"` TrackNumber string `json:"trackNumber"` Quality string `json:"quality"` QualityVersion int `json:"qualityVersion"` } func (t *track) mp3Name() string { return fmt.Sprintf("%s - %s.mp3", t.TrackNumber, t.Title) } type trackFile struct { ID int `json:"id"` Path string `json:"path"` Quality string `json:"quality"` QualityVersion int `json:"qualityVersion"` SceneName string `json:"sceneName"` } // New creates a new converter func New(iTunesDir string) *Converter { return &Converter{ iTunesDir: iTunesDir, } } func (tf *trackFile) Convert(newFile string) error { trans := new(transcoder.Transcoder) if err := trans.Initialize(tf.Path, newFile); err != nil { return err } trans.MediaFile().SetAudioVariableBitrate() trans.MediaFile().SetAudioBitRate("0") return <-trans.Run(false) } // convertFiles converts all files from the message into the // iTunesDir and changes the permissions. func (c *Converter) convertFiles() error { for i, track := range c.Tracks { destination := path.Join(c.iTunesDir, c.albumFolder, track.mp3Name()) if err := c.TrackFiles[i].Convert(destination); err != nil { return errors.Wrapf(err, "could not convert %v", c.TrackFiles[i].Path) } if err := changePermission(path.Join(destination)); err != nil { return errors.Wrapf(err, "could not change permissions on %v", destination) } } return nil } func (m *message) extractAlbumFolder() string { artistDir := filepath.Clean(m.Artist.Path) + "/" fileDir := filepath.Dir(m.TrackFiles[0].Path) return strings.ReplaceAll(fileDir, artistDir, "") } // Process the message, converting the files, changing permissions and copying artwork func (c *Converter) Process(msgSource io.Reader) error { // read and parse the message encMessage, err := ioutil.ReadAll(msgSource) if err != nil { return errors.Wrapf(err, "could not read given source") } c.message = new(message) if err := json.Unmarshal(encMessage, c.message); err != nil { return errors.Wrapf(err, "could not unmarshal given source") } if c.message.EventType != "Download" { klog.Infof("eventType is %v, nothing to do", c.message.EventType) } c.albumFolder = c.TrackFiles[0].SceneName if c.albumFolder == "" { c.albumFolder = c.extractAlbumFolder() } klog.Infof("got request to transcode %v", c.albumFolder) // actual conversion work if err := os.MkdirAll(path.Join(c.iTunesDir, c.albumFolder), os.ModePerm); err != nil { return errors.Wrapf(err, "could not create iTunes Directory") } if err := os.Chmod(path.Join(c.iTunesDir, c.albumFolder), 0775); err != nil { return errors.Wrapf(err, "could not change permissions on iTunes dir") } if err := c.convertFiles(); err != nil { return errors.Wrapf(err, "could not convert files") } klog.Infof("Successfully converted %v", path.Join(c.Artist.Path, c.albumFolder)) return nil } func changePermission(newFile string) error { err := os.Chmod(newFile, 0777) return errors.Wrapf(err, "chmod failed") }