Repository: kalbhor/MusicRepair Branch: master Commit: 0865ce68d543 Files: 7 Total size: 11.4 KB Directory structure: gitextract_xfdxu3u9/ ├── .gitignore ├── LICENSE ├── README.md ├── main.go ├── repair.go ├── spotify.go └── utils.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ #### joe made this: http://goel.io/joe #### go #### # Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof # Output of the go coverage tool, specifically when used with LiteIDE *.out # External packages folder vendor/ ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2016 Lakshay Kalbhor Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

Adds Metadata to Music files

License stars

## Features 1. Fixes songs in nested directories recursively. 2. Fetches metadata from [Spotify](https://www.spotify.com) 3. Multiple options to format file (Options to revert file back) 4. Simple binary ## Dependencies ### [Spotify API](https://developer.spotify.com/my-applications) 1. Create an account and register an application. 2. Copy the Client ID and Client Secret. 3. Set them in *config file* after running ```musicrepair -config``` ###### *config file* will be created after running `musicrepair -config`, and located at `$HOME/.musicrepair/config.json` ### Set them using ```-config``` ```sh $ musicrepair -config Enter Spotify client ID : Enter Spotify client secret : ``` ## Installing ### Via Binary Download the latest binary from the [releases page](https://github.com/kalbhor/MusicRepair/releases). Make sure to add the binary to your `$PATH` ### Via Go ```sh $ go get -u -v github.com/kalbhor/musicrepair $ which musicrepair $ $GOPATH/bin/musicrepair ``` ## Usage Initially, you'll have to add the Spotify credentials. ```sh $ musicrepair -config ``` After that, always a simple command ```sh $ musicrepair ✨ 🍰 ``` ### Options ``` $ musicrepair -help Usage of musicrepair: -config If set, MusicRepair will ask for credentials -dir string Specifies the directory where the music files are located (default "./") -recursive If set, Musicrepair will run recursively in the given directory -revert If set, Musicrepair will revert the files -threads int Specify the number of threads to use (default 1) ``` ## Discussions/Write-Ups

           

## Contribute Found an issue? Post it in the [issue tracker](https://github.com/kalbhor/MusicRepair/issues). Want to add another awesome feature? [Fork](https://github.com/kalbhor/MusicRepair/fork) this repository and add your feature, then send a pull request. ## License The MIT License (MIT) Copyright (c) 2017 Lakshay Kalbhor ================================================ FILE: main.go ================================================ package main import ( "flag" "fmt" "log" "os" ) var ( root = flag.String("dir", "./", "Specifies the directory where the music files are located") isRecursive = flag.Bool("recursive", false, "If set, Musicrepair will run recursively in the given directory") setConfig = flag.Bool("config", false, "If set, MusicRepair will ask for credentials") isRevert = flag.Bool("revert", false, "If set, Musicrepair will revert the files") threads = flag.Int("threads", 1, "Specify the number of threads to use") ) func main() { flag.Parse() if *setConfig { SetConfig() fmt.Println("Your config has been saved.") os.Exit(1) } config, err := LoadConfig() if err != nil { log.Fatal(err) } client, err := SpotifyAuth(config.ID, config.Secret) if err != nil { log.Fatal("Invalid spotify credentials. Error : %v", err) } fileList := WalkDir(*root) // List of all files to work on jobs := make(chan string) results := make(chan string) for w := 1; w <= *threads; w++ { if *isRevert { go RevertWorker(jobs, results) } else { go RepairWorker(client, jobs, results) } } for _, job := range fileList { jobs <- job } close(jobs) for r := 1; r <= len(fileList); r++ { fmt.Printf("[%v] %v\n", r, <-results) } } ================================================ FILE: repair.go ================================================ package main import ( "fmt" "path/filepath" "strconv" "strings" "github.com/bogem/id3v2" "github.com/headzoo/surf/errors" "github.com/zmb3/spotify" ) func RepairWorker(client spotify.Client, job <-chan string, results chan<- string) { for filePath := range job { _, fileName := filepath.Split(filePath) results <- fmt.Sprintf("Fixing : %v\n", fileName) if err := Repair(client, filePath); err != nil { results <- fmt.Sprintf("Error : %v\n", err) } } } func RevertWorker(job <-chan string, results chan<- string) { for filePath := range job { _, fileName := filepath.Split(filePath) results <- fmt.Sprintf("Reverting : %v\n", fileName) if err := Revert(filePath); err != nil { results <- fmt.Sprintf("Error : %v\n", err) } } } func Revert(path string) error { tag, err := id3v2.Open(path, id3v2.Options{Parse: true}) if err != nil { return err } defer tag.Close() tag.DeleteAllFrames() if err = tag.Save(); err != nil { return err } return nil } func Repair(client spotify.Client, path string) error { tag, err := id3v2.Open(path, id3v2.Options{Parse: true}) if err != nil { return err } if CheckFrames(tag.AllFrames()) { return errors.New("Already contains tags") } _, filename := filepath.Split(path) metadata, err := GetMetadata(client, filename[0:len(filename)-4]) if err != nil { return err } tag.SetTitle(metadata.Title) tag.SetAlbum(metadata.Album) tag.SetArtist(strings.Join(metadata.Artists, ",")) TrackNumber := strconv.Itoa(metadata.TrackNumber) tag.AddFrame("TRCK", id3v2.TextFrame{id3v2.EncodingUTF8, TrackNumber}) DiscNumber := strconv.Itoa(metadata.DiscNumber) tag.AddFrame("TPOS", id3v2.TextFrame{id3v2.EncodingUTF8, DiscNumber}) pic := id3v2.PictureFrame{ Encoding: id3v2.EncodingUTF8, MimeType: "image/jpeg", PictureType: id3v2.PTFrontCover, Description: "Front cover", Picture: metadata.Image, } tag.AddAttachedPicture(pic) if err = tag.Save(); err != nil { return err } return nil } ================================================ FILE: spotify.go ================================================ package main import ( "context" "errors" "io/ioutil" "net/http" "golang.org/x/oauth2" "golang.org/x/oauth2/clientcredentials" "github.com/zmb3/spotify" ) //Metadata : Structure for one track's metadata type Metadata struct { Title string Artists []string Album string Image []byte DiscNumber int TrackNumber int } //Load : Sets values from search results func (m *Metadata) Load(track spotify.FullTrack) error { m.Title = track.SimpleTrack.Name m.Album = track.Album.Name m.DiscNumber = track.SimpleTrack.DiscNumber m.TrackNumber = track.SimpleTrack.TrackNumber for _, artist := range track.SimpleTrack.Artists { m.Artists = append(m.Artists, artist.Name) } imageURL := track.Album.Images[0].URL resp, err := http.Get(imageURL) if err != nil { return err } defer resp.Body.Close() b, err := ioutil.ReadAll(resp.Body) if err != nil { return err } m.Image = b return nil } //GetMetadata : Searches spotify and returns a loaded metadata struct func GetMetadata(client spotify.Client, query string) (*Metadata, error) { m := new(Metadata) results, err := client.Search(query, spotify.SearchTypeTrack) if err != nil { return nil, err } else if len(results.Tracks.Tracks) == 0 { // Search results were empty return nil, errors.New("Couldn't fetch metadata") } err = m.Load(results.Tracks.Tracks[0]) // Pass in the top result if err != nil { return m, err } return m, nil } //Auth : Returns a usable spotify "client" that can request spotify content func SpotifyAuth(Id, Secret string) (spotify.Client, error) { config := &clientcredentials.Config{ ClientID: Id, ClientSecret: Secret, TokenURL: spotify.TokenURL, } token, err := config.Token(context.Background()) if err != nil { return spotify.Authenticator{}.NewClient(&oauth2.Token{}), err } client := spotify.Authenticator{}.NewClient(token) return client, nil } ================================================ FILE: utils.go ================================================ package main import ( "encoding/json" "fmt" "io/ioutil" "os" "path" "path/filepath" "github.com/bogem/id3v2" ) type Config struct { ID string Secret string } var configFolder string = path.Join(os.Getenv("HOME"), ".musicrepair") var configPath string = path.Join(configFolder, "config.json") func LoadConfig() (*Config, error) { file, err := os.Open(configPath) if err != nil { return nil, err } decoder := json.NewDecoder(file) config := new(Config) err = decoder.Decode(&config) if err != nil { return nil, err } return config, nil } func SetConfig() error { var id, secret string fmt.Print("Enter Spotify ID : ") fmt.Scanln(&id) fmt.Print("Enter Spotify Secret : ") fmt.Scanln(&secret) config := Config{id, secret} b, err := json.Marshal(config) if err != nil { return err } if err := os.Mkdir(configFolder, os.ModePerm); err != nil { return err } if err := ioutil.WriteFile(configPath, b, os.ModePerm); err != nil { return err } return nil } func WalkDir(root string) (fileList []string) { filepath.Walk(root, func(path string, fi os.FileInfo, err error) error { // Fills fileList with all mp3 files in the `root` file tree if err != nil { return err } if !*isRecursive && filepath.Dir(path) != filepath.Dir(root) { return filepath.SkipDir } if filepath.Ext(path) == ".mp3" { fileList = append(fileList, path) } return nil }) return } // Checks if a file already contains metadata func CheckFrames(frames map[string][]id3v2.Framer) bool { if _, ok := frames["TALB"]; !ok { return false } if _, ok := frames["TIT2"]; !ok { return false } if _, ok := frames["APIC"]; !ok { return false } if _, ok := frames["TRCK"]; !ok { return false } if _, ok := frames["TPOS"]; !ok { return false } return true }