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
================================================
<p align="center">
<b>Adds Metadata to Music files</b>
</p>
<p align="center">
<a href="https://github.com/kalbhor/musicrepair/LICENSE">
<img alt="License" src="https://img.shields.io/github/license/mashape/apistatus.svg?style=flat-square"/>
</a>
<a href="https://github.com/kalbhor/musicrepair">
<img alt="stars" src="https://img.shields.io/github/stars/kalbhor/musicrepair.svg?style=social&label=Star"/>
</a>
</p>
## 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 ID>
Enter Spotify client secret : <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
<p align="left">
<a href="https://mavielinux.com/2016/12/11/musicrepair-pour-corriger-les-titresajouter-les-metadonnees-et-les-pochettes-de-vos-musiques/">
<img width="70px" src="http://i.imgur.com/TklsaII.png"/>
</a>
<a href="http://blog.desdelinux.net/reparar-archivos-de-musica/">
<img width="160px" src="http://i.imgur.com/eV1WxYZ.png"/>
</a>
<a href="https://www.reddit.com/r/learnpython/comments/5gzvcb/i_made_a_script_that_would_fix_your_music_files/">
<img width="160px" src="http://i.imgur.com/Jk8PgIb.png"/>
</a>
</p>
## 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
}
gitextract_xfdxu3u9/ ├── .gitignore ├── LICENSE ├── README.md ├── main.go ├── repair.go ├── spotify.go └── utils.go
SYMBOL INDEX (14 symbols across 4 files)
FILE: main.go
function main (line 18) | func main() {
FILE: repair.go
function RepairWorker (line 15) | func RepairWorker(client spotify.Client, job <-chan string, results chan...
function RevertWorker (line 25) | func RevertWorker(job <-chan string, results chan<- string) {
function Revert (line 36) | func Revert(path string) error {
function Repair (line 51) | func Repair(client spotify.Client, path string) error {
FILE: spotify.go
type Metadata (line 16) | type Metadata struct
method Load (line 26) | func (m *Metadata) Load(track spotify.FullTrack) error {
function GetMetadata (line 53) | func GetMetadata(client spotify.Client, query string) (*Metadata, error) {
function SpotifyAuth (line 73) | func SpotifyAuth(Id, Secret string) (spotify.Client, error) {
FILE: utils.go
type Config (line 14) | type Config struct
function LoadConfig (line 22) | func LoadConfig() (*Config, error) {
function SetConfig (line 36) | func SetConfig() error {
function WalkDir (line 58) | func WalkDir(root string) (fileList []string) {
function CheckFrames (line 77) | func CheckFrames(frames map[string][]id3v2.Framer) bool {
Condensed preview — 7 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (13K chars).
[
{
"path": ".gitignore",
"chars": 432,
"preview": "#### joe made this: http://goel.io/joe\n\n#### go ####\n# Compiled Object files, Static and Dynamic libs (Shared Objects)\n*"
},
{
"path": "LICENSE",
"chars": 1072,
"preview": "MIT License\n\nCopyright (c) 2016 Lakshay Kalbhor\n\nPermission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "README.md",
"chars": 3143,
"preview": "<p align=\"center\">\n <b>Adds Metadata to Music files</b>\n</p>\n\n<p align=\"center\">\n <a href=\"https://github.com/kalbho"
},
{
"path": "main.go",
"chars": 1268,
"preview": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n)\n\nvar (\n\troot = flag.String(\"dir\", \"./\", \"Specifies the direc"
},
{
"path": "repair.go",
"chars": 2015,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/bogem/id3v2\"\n\t\"github.com/headzoo/sur"
},
{
"path": "spotify.go",
"chars": 1923,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\n\t\"golang.org/x/oauth2\"\n\t\"golang.org/x/oauth2/clien"
},
{
"path": "utils.go",
"chars": 1808,
"preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n\n\t\"github.com/bogem/id3v2\"\n)\n"
}
]
About this extraction
This page contains the full source code of the kalbhor/MusicRepair GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 7 files (11.4 KB), approximately 3.3k tokens, and a symbol index with 14 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.