func extractFpUtil(archive []byte) { zipReader, err := zip.NewReader(bytes.NewReader(archive), int64(len(archive))) if err != nil { utils.Log(utils.ERROR, "recognizer.extractFpUtil: failed to unzip fingerprint util archive: %v", err) return } for _, file := range zipReader.File { if len(file.Name) < len(fpUtil()) || file.Name[len(file.Name)-len(fpUtil()):] != fpUtil() { continue } src, err := file.Open() if err != nil { utils.Log(utils.ERROR, "recognizer.extractFpUtil: failed to extract fingerprint util from archive: %v", err) return } defer src.Close() dst, err := os.OpenFile(pathToFpUtil(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755) if err != nil { utils.Log(utils.ERROR, "recognizer.extractFpUtil: failed to open file '%v' for writing: %v", pathToFpUtil(), err) return } defer dst.Close() if _, err := io.Copy(dst, src); err != nil { utils.Log(utils.ERROR, "recognizer.extractFpUtil: failed to save fingerprint util to '%v': %v", pathToFpUtil(), err) return } break } }
func extractFpUtil(archive []byte) { gzipReader, err := gzip.NewReader(bytes.NewReader(archive)) if err != nil { utils.Log(utils.ERROR, "recognizer.extractFpUtil: failed to ungzip fingerprint util archive: %v", err) return } tarReader := tar.NewReader(gzipReader) for { header, err := tarReader.Next() if err == io.EOF || err != nil { break } if header.Typeflag != tar.TypeReg || len(header.Name) < len(fpUtil()) || header.Name[len(header.Name)-len(fpUtil()):] != fpUtil() { continue } dst, err := os.OpenFile(pathToFpUtil(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755) if err != nil { utils.Log(utils.ERROR, "recognizer.extractFpUtil: failed to open file '%v' for writing: %v", pathToFpUtil(), err) return } defer dst.Close() if _, err := io.Copy(dst, tarReader); err != nil { utils.Log(utils.ERROR, "recognizer.extractFpUtil: failed to save fingerprint util to '%v': %v", pathToFpUtil(), err) return } } }
func lookupByFingerPrint(fingetPrint string, duration int) (string, error) { data := "client=" + appKey + "&meta=releases+tracks+compress&duration=" + strconv.Itoa(duration) + "&fingerprint=" + fingetPrint var zippedData bytes.Buffer zipper := gzip.NewWriter(&zippedData) zipper.Write([]byte(data)) zipper.Close() request, err := http.NewRequest("POST", "http://api.acoustid.org/v2/lookup", bytes.NewReader(zippedData.Bytes())) if err != nil { utils.Log(utils.ERROR, "Failed to make new http request: %v", err) return "", err } request.Header.Set("Content-Type", "application/x-www-form-urlencoded") request.Header.Add("Content-Encoding", "gzip") response, err := (&http.Client{}).Do(request) if err != nil { utils.Log(utils.ERROR, "Failed to send http request: %v", err) return "", err } reply, err := ioutil.ReadAll(response.Body) response.Body.Close() if err != nil { utils.Log(utils.ERROR, "Failed to read http response: %v", err) return "", err } return string(reply), nil }
func (tagger *Tagger) processDir(src, dst string) error { utils.Log(utils.INFO, "Start processing directory '%v'", src) allFiles := getAllFiles(src) tagger.counter.setTotal(len(allFiles)) utils.Log(utils.INFO, "Found %v files", len(allFiles)) var result atomic.Value var index int32 = -1 var wg sync.WaitGroup for i := 0; i < numberOfThreads; i++ { wg.Add(1) go func() { defer wg.Done() for { if tagger.stop.Load().(bool) { utils.Log(utils.WARNING, "Processing directory '%v' interrupted by application stop", src) return } i := atomic.AddInt32(&index, 1) if i >= int32(len(allFiles)) { return } fmt.Printf("\rProcessing %v/%v", i, len(allFiles)) destination, err := tagger.getDestinationPath(allFiles[i]) if err != nil { result.Store(err) utils.Log(utils.ERROR, "Failed to get destination path: %v", err) continue } if err := tagger.processFile(allFiles[i], destination); err != nil { utils.Log(utils.ERROR, "Failed to process file '%v': %v", allFiles[i], err) result.Store(err) } } }() } wg.Wait() fmt.Printf("\r \r") if result.Load() != nil { return result.Load().(error) } return nil }
func Recognize(path string, existingTag ...editor.Tag) (editor.Tag, error) { fingerPrint, duration, err := getFingerPrint(path) if err != nil { utils.Log(utils.ERROR, "Failed to get finger print for file '%v': %v", path, err) return editor.Tag{}, err } return askMusicBrainz(fingerPrint, duration, existingTag...) }
func urlToFpUtil() string { switch runtime.GOARCH { case "386": return "https://bitbucket.org/acoustid/chromaprint/downloads/chromaprint-fpcalc-" + fpUtilVersion + "-win-i686.zip" case "amd64": return "https://bitbucket.org/acoustid/chromaprint/downloads/chromaprint-fpcalc-" + fpUtilVersion + "-win-x86_64.zip" } utils.Log(utils.ERROR, "recognizer.urlToFpUtil: unsupported architecture: %v", runtime.GOARCH) return "" }
func askCoverArtArchive(releaseId string) editor.Cover { response, err := http.Get("http://coverartarchive.org/release/" + releaseId) if err != nil { utils.Log(utils.ERROR, "Failed to send http request '%v' and get response: %v", "http://coverartarchive.org/release/"+releaseId, err) return editor.Cover{} } if response.StatusCode != 200 { return editor.Cover{} } reply, err := ioutil.ReadAll(response.Body) response.Body.Close() if err != nil { utils.Log(utils.ERROR, "Failed to read http response: %v", err) return editor.Cover{} } imageUrl := parseCoverArtArchiveReply(string(reply)) return getCover(imageUrl) }
func (tagger *Tagger) trapSignal() { tagger.stop.Store(false) ch := make(chan os.Signal, 1) signal.Notify(ch, os.Interrupt) go func() { _ = <-ch tagger.stop.Store(true) utils.Log(utils.INFO, "Got signal to stop") }() }
func getFpUtil() { // fingerprint util is here, do nothing if _, err := os.Stat(pathToFpUtil()); err == nil { return } // download fingerprint util url := urlToFpUtil() response, err := http.Get(url) if err != nil || response.StatusCode != 200 { utils.Log(utils.ERROR, "recognizer.getFpUtil: failed to download fingerprint util from '%v': %v", url, err) return } content, err := ioutil.ReadAll(response.Body) response.Body.Close() if err != nil { utils.Log(utils.ERROR, "recognizer.getFpUtil: failed to read content of '%v': %v", url, err) return } // extract fingerprint util extractFpUtil(content) }
func askMusicBrainz(fingerPrint string, duration int, existingTag ...editor.Tag) (editor.Tag, error) { waitIfNeeded() reply, err := lookupByFingerPrint(fingerPrint, duration) if err != nil { utils.Log(utils.ERROR, "Failed to lookup by finger print: %v", err) return editor.Tag{}, err } tag, releaseId := parseAcousticIdReply(reply, existingTag...) if len(releaseId) > 0 { tag.Cover = askCoverArtArchive(releaseId) } return tag, nil }
func main() { utils.Log(utils.INFO, "Starting Go Music Tagger %v", version) if !parseCommandLineArguments() { printUsage() return } tagger, err := logic.NewTagger(source, destination, filter, useExistingTag) if err != nil { fmt.Fprintln(os.Stderr, err) return } if err = tagger.Run(); err != nil { fmt.Fprintln(os.Stderr, err) } tagger.PrintReport() }
func (tagger *Tagger) init(source, dest, filter string) error { utils.Log(utils.INFO, "Initializing tagger with source = '%v', destination = '%v', filter = '%v'", source, dest, filter) var err error tagger.source, err = filepath.Abs(source) if err != nil { utils.Log(utils.ERROR, "Failed to make source path '%v' absolute: %v", source, err) return err } tagger.sourceInfo, err = os.Stat(tagger.source) if err != nil { utils.Log(utils.ERROR, "Failed to get source path '%v' info: %v", tagger.source, err) return err } if !tagger.sourceInfo.IsDir() && !isSupportedFile(tagger.source) { return fmt.Errorf("Input file '%v' is unsupported", tagger.source) } if len(dest) == 0 { tagger.destination = tagger.source } else { tagger.destination, err = filepath.Abs(dest) if err != nil { utils.Log(utils.ERROR, "Failed to make destination path '%v' absolute: %v", dest, err) return err } destinationInfo, err := os.Stat(tagger.destination) if err != nil { // if input is file consider destination as path to non-existent file if !tagger.sourceInfo.IsDir() && !isSupportedFile(tagger.destination) { utils.Log(utils.ERROR, "Output file '%v' is unsupported", tagger.destination) return fmt.Errorf("Output file '%v' is unsupported", tagger.destination) } // if input is directory consider destination as non-existent directory and try to create it err = os.MkdirAll(tagger.destination, 0666) if err != nil { utils.Log(utils.ERROR, "Failed to create directory '%v': %v", tagger.destination, err) return err } } else { if tagger.sourceInfo.IsDir() && !destinationInfo.IsDir() { return fmt.Errorf("Cannot output directory '%v' into file '%v'", tagger.source, tagger.destination) } if !tagger.sourceInfo.IsDir() && destinationInfo.IsDir() { tagger.destination = filepath.Join(tagger.destination, filepath.Base(tagger.source)) } if !destinationInfo.IsDir() && !isSupportedFile(tagger.destination) { return fmt.Errorf("Output file '%v' is unsupported", tagger.destination) } } } tagger.filter, err = filterStringToType(filter) if err != nil { return err } tagger.counter = &Counter{} return nil }
func (tagger *Tagger) processFile(src, dst string) error { utils.Log(utils.INFO, "Start processing file '%v'", src) tagEditor := makeEditor(src) tag, err := tagEditor.ReadTag(src) if err != nil { tagger.counter.addFail() utils.Log(utils.ERROR, "Failed to read tags from file '%v': %v", src, err) return err } if !tagger.filterByTag(tag) { utils.Log(utils.INFO, "Update tag is not required for file '%v'", src) return nil } tagger.counter.addFiltered() if tagger.stop.Load().(bool) { utils.Log(utils.WARNING, "Processing file '%v' interrupted by application stop", src) return fmt.Errorf("Processing file '%v' interrupted by application stop", src) } var newTag editor.Tag if tagger.useExistingTag { newTag, err = recognizer.Recognize(src, tag) } else { newTag, err = recognizer.Recognize(src) } if err != nil { tagger.counter.addFail() utils.Log(utils.ERROR, "Failed to recognize composition from file '%v': %v", src, err) return err } if newTag.Empty() { tagger.counter.addFail() utils.Log(utils.WARNING, "Composition from file '%v' is not recognized", src) return nil } // if we need only cover and there is no cover, return here if tagger.filter == NoCover && newTag.Cover.Empty() { tagger.counter.addFail() utils.Log(utils.WARNING, "Cover for file '%v' is not found", src) return nil } // if we need only cover and already has smth else, take only cover if tagger.filter == NoCover && !tag.Empty() { tag.Cover = newTag.Cover newTag = tag } else { newTag.MergeWith(tag) } err = tagger.preparePath(dst) if err != nil { tagger.counter.addFail() utils.Log(utils.ERROR, "Failed to prepare path '%v': %v", dst, err) return err } err = tagEditor.WriteTag(src, dst, newTag) if err != nil { tagger.counter.addFail() utils.Log(utils.ERROR, "Failed to write tag and save file '%v': %v", dst, err) return err } tagger.counter.addSuccess(!newTag.Cover.Empty()) utils.Log(utils.INFO, "File '%v' successfully processed, cover found: %v", src, newTag.Cover.Empty()) return nil }