aboutsummaryrefslogtreecommitdiff
path: root/packages/music-organizer/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'packages/music-organizer/main.go')
-rw-r--r--packages/music-organizer/main.go271
1 files changed, 0 insertions, 271 deletions
diff --git a/packages/music-organizer/main.go b/packages/music-organizer/main.go
deleted file mode 100644
index 253afef..0000000
--- a/packages/music-organizer/main.go
+++ /dev/null
@@ -1,271 +0,0 @@
-package main
-
-import (
- "archive/zip"
- "crypto/md5"
- "encoding/hex"
- "flag"
- "fmt"
- "io"
- "io/ioutil"
- "log"
- "os"
- "path/filepath"
- "strings"
-
- "github.com/dhowden/tag"
-)
-
-const (
- // the max lenght for a track can only be 255 characters minus 3 for the
- // track number (followed by a space), and 4 for the format. The limit of
- // 255 is coming from HFS+.
- TrackTitleMaxLenght = 255 - 3 - 4
-)
-
-var musicDest = flag.String("dest", fmt.Sprintf("%s/media/music", os.Getenv("HOME")), "where to store the music")
-
-// replace slashes with dashes
-func stripSlash(s string) string {
- return strings.ReplaceAll(s, "/", "-")
-}
-
-// return the name of the artist, album and the title of the track
-// the title of the track has the following format:
-//
-// {track #} {track title}.{track format}
-func generatePath(m tag.Metadata) (string, string, string) {
- var artist, album, title string
- var track int
-
- // if there's no artist, let's fallback to "Unknown Artists"
- if len(m.Artist()) == 0 {
- artist = "Unknown Artists"
- } else {
- artist = stripSlash(m.Artist())
- }
-
- // if there's no album name, let's fallback to "Unknown Album"
- if len(m.Album()) == 0 {
- album = "Unknown Album"
- } else {
- album = stripSlash(m.Album())
- }
-
- track, _ = m.Track()
-
- // ok, there must be a better way
- format := strings.ToLower(string(m.FileType()))
-
- title = fmt.Sprintf("%02d %s.%s", track, stripSlash(m.Title()), format)
- if len(title) > TrackTitleMaxLenght {
- r := []rune(title)
- title = string(r[0:255])
- }
-
- return artist, album, title
-}
-
-// create all the required directories. if we fail to create one, we die
-func makeParents(path string) error {
- if err := os.MkdirAll(path, 0o777); err != nil {
- return fmt.Errorf("failed to create %s: %v", path, err)
- }
- return nil
-}
-
-func md5sum(path string) (string, error) {
- var sum string
- f, err := os.Open(path)
- if err != nil {
- return sum, err
- }
-
- defer f.Close()
-
- h := md5.New()
- if _, err := io.Copy(h, f); err != nil {
- return sum, err
- }
- sum = hex.EncodeToString(h.Sum(nil)[:16])
- return sum, nil
-}
-
-func makeCopy(src, dst string) error {
- f, err := os.Open(src)
- if err != nil {
- return err
- }
- defer f.Close()
-
- t, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE, 0o666)
- if err != nil {
- return err
- }
- defer t.Close()
-
- _, err = io.Copy(t, f)
- if err != nil {
- return err
- }
- log.Printf("copied %s → %s\n", src, dst)
- return nil
-}
-
-// ensure the file is named correctly and is moved to the correct destination
-// before we can do that, we need to:
-// 1. check if the track already exists, if it does, does it have the same md5 ?
-// if they are similar, we skip them. if they are not, we log and don't do
-// anything
-// 2. we can move the file to the destination
-// 3. we can delete the original file
-func renameFile(originalPath string, artist, album, title string) error {
- directories := filepath.Join(*musicDest, artist, album)
- destination := filepath.Join(directories, title)
-
- // check if the file is present
- _, err := os.Stat(destination)
- if err == nil {
- var originalSum, destinationSum string
- if originalSum, err = md5sum(originalPath); err != nil {
- return err
- }
- if destinationSum, err = md5sum(destination); err != nil {
- return err
- }
-
- if destinationSum != originalSum {
- log.Printf("md5 sum are different: %s(%s) %s(%s)", originalPath, originalSum, destination, destinationSum)
- }
- return nil
- }
-
- if err := makeParents(directories); err != nil {
- return err
- }
-
- if err := makeCopy(originalPath, destination); err != nil {
- return err
- }
-
- // TODO delete original file
- // os.Remove(originalPath)
- return nil
-}
-
-// we try to open any files and read the metadata.
-// if the file has metadata we can read, we will try to move the file to the
-// correct destination
-func processFile(path string) error {
- f, err := os.Open(path)
- if err != nil {
- return err
- }
-
- defer f.Close()
- m, err := tag.ReadFrom(f)
- if err != nil {
- // this is fine, this might not be a music file
- log.Printf("SKIP failed to read tags from %s: %v", path, err)
- return nil
- }
-
- var artist, album, title string
- artist, album, title = generatePath(m)
- if err := renameFile(path, artist, album, title); err != nil {
- return fmt.Errorf("failed to move %s: %v", path, err)
- }
- return nil
-}
-
-func processPath(path string, f os.FileInfo, err error) error {
- if stat, err := os.Stat(path); err == nil && !stat.IsDir() {
- if err := processFile(path); err != nil {
- return err
- }
- }
- return nil
-}
-
-// unzip takes two paths, a source and destination. The source is the
-// name of the archive and we will extract the content into the
-// destination directory. The destination directory has to already
-// exists, we are not going to create it here or delete it at the end.
-func unzip(src, dst string) error {
- r, err := zip.OpenReader(src)
- if err != nil {
- return err
- }
-
- defer r.Close()
-
- for _, f := range r.File {
- fpath := filepath.Join(dst, f.Name)
- outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
- if err != nil {
- return err
- }
-
- rc, err := f.Open()
- if err != nil {
- return err
- }
-
- _, err = io.Copy(outFile, rc)
- if err != nil {
- log.Printf("failed to copy %s: %s", outFile.Name(), err)
- }
-
- outFile.Close()
- rc.Close()
- }
- return nil
-}
-
-func main() {
- flag.Parse()
-
- if *musicDest == "" {
- log.Fatal("-dest is required")
- }
-
- paths := make([]string, flag.NArg())
-
- // For our temp directory, we use what ever the value of
- // XDG_RUNTIME_DIR is. If the value is unset, we will default to
- // the system default temp directory.
- tmpDir := os.Getenv("XDG_RUNTIME_DIR")
-
- for i, d := range flag.Args() {
- if filepath.Ext(d) == ".zip" {
- // If we have an extension and it's '.zip', we consider the
- // path to be an archive. In this case we want to create a new
- // temporary directory and extract the content of the archive
- // in that path. The temporary directory is removed once we're
- // done.
- out, err := ioutil.TempDir(tmpDir, "music-organizer")
- if err != nil {
- log.Printf("failed to create a temp directory to extract %s: %v", d, err)
- continue
- }
- defer os.RemoveAll(out)
-
- if err := unzip(d, out); err != nil {
- log.Printf("failed to extract %s: %v", d, err)
- continue
- }
- paths[i] = out
- } else {
- paths[i] = d
- }
- }
-
- for _, d := range paths {
- // XXX deal with filenames that are too long
- // scan the directory and try to find any file that we want to move
- err := filepath.Walk(d, processPath)
- if err != nil {
- log.Fatalf("error while processing files: %v", err)
- }
- }
-}