// Copyright (C) 2020 Raziman package main import ( "errors" "fmt" "sort" "github.com/bogem/id3v2" "github.com/gdamore/tcell/v2" "github.com/rivo/tview" "github.com/ztrue/tracerr" "github.com/issadarkthing/gomu/lyric" ) func tagPopup(node *AudioFile) (err error) { popupID := "tag-editor-input-popup" tag, popupLyricMap, options, err := loadTagMap(node) if err != nil { return tracerr.Wrap(err) } var ( artistInputField *tview.InputField = tview.NewInputField() titleInputField *tview.InputField = tview.NewInputField() albumInputField *tview.InputField = tview.NewInputField() getTagButton *tview.Button = tview.NewButton("Get Tag") saveTagButton *tview.Button = tview.NewButton("Save Tag") lyricDropDown *tview.DropDown = tview.NewDropDown() deleteLyricButton *tview.Button = tview.NewButton("Delete Lyric") getLyric1Button *tview.Button = tview.NewButton("Get Lyric 1(en)") getLyric2Button *tview.Button = tview.NewButton("Get Lyric 2(zh-CN)") getLyric3Button *tview.Button = tview.NewButton("Get Lyric 3(zh-CN)") lyricTextView *tview.TextView leftGrid *tview.Grid = tview.NewGrid() rightFlex *tview.Flex = tview.NewFlex() ) artistInputField.SetLabel("Artist: "). SetFieldWidth(20). SetText(tag.Artist()). SetFieldBackgroundColor(gomu.colors.popup). SetBackgroundColor(gomu.colors.background) titleInputField.SetLabel("Title: "). SetFieldWidth(20). SetText(tag.Title()). SetFieldBackgroundColor(gomu.colors.popup). SetBackgroundColor(gomu.colors.background) albumInputField.SetLabel("Album: "). SetFieldWidth(20). SetText(tag.Album()). SetFieldBackgroundColor(gomu.colors.popup). SetBackgroundColor(gomu.colors.background) getTagButton.SetSelectedFunc(func() { audioFile := node serviceProvider := "netease" results, resultsTag, err := lyric.GetLyricOptionsChinese(audioFile.name, serviceProvider) if err != nil { errorPopup(err) return } serviceProvider = "kugou" results2, resultsTag2, err2 := lyric.GetLyricOptionsChinese(audioFile.name, serviceProvider) if err2 != nil { errorPopup(err2) return } for i, v := range results2 { results[i] = v } for i, v := range resultsTag2 { resultsTag[i] = v } titles := make([]string, 0, len(results)) for result := range results { titles = append(titles, result) } searchPopup(" Lyrics ", titles, func(selected string) { if selected == "" { return } lyricID := results[selected] newTag := resultsTag[lyricID] artistInputField.SetText(newTag.Artist) titleInputField.SetText(newTag.Title) albumInputField.SetText(newTag.Album) tag, err = id3v2.Open(node.path, id3v2.Options{Parse: true}) if err != nil { errorPopup(err) } defer tag.Close() tag.SetArtist(artistInputField.GetText()) tag.SetTitle(titleInputField.GetText()) tag.SetAlbum(albumInputField.GetText()) err = tag.Save() if err != nil { errorPopup(err) } else { defaultTimedPopup(" Success ", "Tag update successfully") } }) }). SetBorder(true). SetBackgroundColor(gomu.colors.background). SetTitleColor(gomu.colors.accent) saveTagButton.SetSelectedFunc(func() { tag, err = id3v2.Open(node.path, id3v2.Options{Parse: true}) if err != nil { errorPopup(err) } defer tag.Close() tag.SetArtist(artistInputField.GetText()) tag.SetTitle(titleInputField.GetText()) tag.SetAlbum(albumInputField.GetText()) err := tag.Save() if err != nil { errorPopup(err) } else { defaultTimedPopup(" Success ", "Tag update successfully") } }). SetBorder(true). SetBackgroundColor(gomu.colors.background). SetTitleColor(gomu.colors.foreground) deleteLyricButton.SetSelectedFunc(func() { _, langExt := lyricDropDown.GetCurrentOption() if len(options) > 0 { err := embedLyric(node.path, "", langExt, true) if err != nil { errorPopup(err) } else { infoPopup(langExt + " lyric deleted successfully.") } // Update map delete(popupLyricMap, langExt) // Update dropdown options var newOptions []string for _, v := range options { if v == langExt { continue } newOptions = append(newOptions, v) } options = newOptions lyricDropDown.SetOptions(newOptions, nil). SetCurrentOption(0). SetSelectedFunc(func(text string, _ int) { lyricTextView.SetText(popupLyricMap[text]). SetTitle(" " + text + " lyric preview ") }) // Update lyric preview if len(newOptions) > 0 { _, langExt = lyricDropDown.GetCurrentOption() lyricTextView.SetText(popupLyricMap[langExt]). SetTitle(" " + langExt + " lyric preview ") } else { langExt = "" lyricTextView.SetText("No lyric embeded."). SetTitle(" " + langExt + " lyric preview ") } } else { infoPopup("No lyric embeded.") } }). SetBorder(true). SetBackgroundColor(gomu.colors.background). SetTitleColor(gomu.colors.accent) getLyric1Button.SetSelectedFunc(func() { audioFile := node if audioFile.isAudioFile { go func() { gomu.app.QueueUpdateDraw(func() { results, err := lyric.GetLyricOptions(audioFile.name) if err != nil { errorPopup(err) gomu.app.Draw() return } titles := make([]string, 0, len(results)) for result := range results { titles = append(titles, result) } searchPopup(" Lyrics ", titles, func(selected string) { if selected == "" { return } go func() { url := results[selected] lyric, err := lyric.GetLyric(url) if err != nil { errorPopup(err) gomu.app.Draw() } langExt := "en" err = embedLyric(audioFile.path, lyric, langExt, false) if err != nil { errorPopup(err) gomu.app.Draw() } else { infoPopup("en Lyric added successfully") gomu.app.Draw() } // This is to ensure that the above go routine finish. gomu.app.QueueUpdateDraw(func() { _, popupLyricMap, newOptions, err := loadTagMap(node) if err != nil { errorPopup(err) gomu.app.Draw() return } // Update dropdown options lyricDropDown.SetOptions(newOptions, nil). SetCurrentOption(0). SetSelectedFunc(func(text string, _ int) { lyricTextView.SetText(popupLyricMap[text]). SetTitle(" " + text + " lyric preview ") }) // Update lyric preview if len(newOptions) > 0 { _, langExt := lyricDropDown.GetCurrentOption() lyricTextView.SetText(popupLyricMap[langExt]). SetTitle(" " + langExt + " lyric preview ") infoPopup(langExt + " lyric embeded successfully.") } else { lyricTextView.SetText("No lyric embeded."). SetTitle(" lyric preview ") } }) }() }) }) }() } }). SetBorder(true). SetBackgroundColor(gomu.colors.background). SetTitleColor(gomu.colors.accent) getLyric2Button.SetSelectedFunc(func() { audioFile := node serviceProvider := "netease" results, _, err := lyric.GetLyricOptionsChinese(audioFile.name, serviceProvider) if err != nil { errorPopup(err) return } titles := make([]string, 0, len(results)) for result := range results { titles = append(titles, result) } searchPopup(" Lyrics ", titles, func(selected string) { if selected == "" { return } go func() { lyricID := results[selected] lyric, err := lyric.GetLyricChinese(lyricID, serviceProvider) if err != nil { errorPopup(err) gomu.app.Draw() return } langExt := "zh-CN" err = embedLyric(audioFile.path, lyric, langExt, false) if err != nil { errorPopup(err) gomu.app.Draw() } else { infoPopup("cn Lyric added successfully") gomu.app.Draw() } // This is to ensure that the above go routine finish. gomu.app.QueueUpdateDraw(func() { _, popupLyricMap, newOptions, err := loadTagMap(node) if err != nil { errorPopup(err) gomu.app.Draw() return } options = newOptions // Update dropdown options lyricDropDown.SetOptions(newOptions, nil). SetCurrentOption(0). SetSelectedFunc(func(text string, _ int) { lyricTextView.SetText(popupLyricMap[text]). SetTitle(" " + text + " lyric preview ") }) // Update lyric preview if len(newOptions) > 0 { _, langExt := lyricDropDown.GetCurrentOption() lyricTextView.SetText(popupLyricMap[langExt]). SetTitle(" " + langExt + " lyric preview ") infoPopup(langExt + " lyric embeded successfully.") } else { lyricTextView.SetText("No lyric embeded."). SetTitle(" lyric preview ") } }) }() }) }). SetBorder(true). SetBackgroundColor(gomu.colors.background). SetTitleColor(gomu.colors.accent) getLyric3Button.SetSelectedFunc(func() { audioFile := node serviceProvider := "kugou" results, _, err := lyric.GetLyricOptionsChinese(audioFile.name, serviceProvider) if err != nil { errorPopup(err) return } titles := make([]string, 0, len(results)) for result := range results { titles = append(titles, result) } searchPopup(" Lyrics ", titles, func(selected string) { if selected == "" { return } go func() { lyricID := results[selected] lyric, err := lyric.GetLyricChinese(lyricID, serviceProvider) if err != nil { errorPopup(err) gomu.app.Draw() return } langExt := "zh-CN" err = embedLyric(audioFile.path, lyric, langExt, false) if err != nil { errorPopup(err) gomu.app.Draw() } else { infoPopup(langExt + " Lyric embeded successfully.") gomu.app.Draw() } // This is to ensure that the above go routine finish. _, popupLyricMap, newOptions, err := loadTagMap(node) if err != nil { errorPopup(err) gomu.app.Draw() return } // Update dropdown options lyricDropDown.SetOptions(newOptions, nil). SetCurrentOption(0). SetSelectedFunc(func(text string, _ int) { lyricTextView.SetText(popupLyricMap[text]). SetTitle(" " + text + " lyric preview ") }) // Update lyric preview if len(newOptions) > 0 { _, langExt := lyricDropDown.GetCurrentOption() lyricTextView.SetText(popupLyricMap[langExt]). SetTitle(" " + langExt + " lyric preview ") } else { lyricTextView.SetText("No lyric embeded."). SetTitle(" lyric preview ") } }() }) }). SetBorder(true). SetBackgroundColor(gomu.colors.background). SetTitleColor(gomu.colors.accent) lyricDropDown.SetOptions(options, nil). SetCurrentOption(0). SetFieldBackgroundColor(gomu.colors.background). SetFieldTextColor(gomu.colors.accent). SetPrefixTextColor(gomu.colors.accent). SetSelectedFunc(func(text string, _ int) { lyricTextView.SetText(popupLyricMap[text]). SetTitle(" " + text + " lyric preview ") }). SetLabel("Embeded Lyrics: ") lyricDropDown.SetBackgroundColor(gomu.colors.popup) var lyricText string _, langExt := lyricDropDown.GetCurrentOption() lyricText = popupLyricMap[langExt] if lyricText == "" { lyricText = "No lyric embeded." langExt = "" } lyricTextView = tview.NewTextView() lyricTextView. SetDynamicColors(true). SetRegions(true). SetScrollable(true). SetTitle(" " + langExt + " lyric preview "). SetBorder(true) lyricTextView.SetText(lyricText). SetScrollable(true). SetWordWrap(true). SetWrap(true). SetBackgroundColor(gomu.colors.popup). SetBorder(true) leftGrid.SetRows(3, 3, 3, 3, 3, 0, 3, 3, 3, 3, 3). SetColumns(30). AddItem(artistInputField, 0, 0, 1, 3, 1, 10, true). AddItem(titleInputField, 1, 0, 1, 3, 1, 10, true). AddItem(albumInputField, 2, 0, 1, 3, 1, 10, true). AddItem(getTagButton, 3, 0, 1, 3, 1, 10, true). AddItem(saveTagButton, 4, 0, 1, 3, 1, 10, true). AddItem(lyricDropDown, 6, 0, 1, 3, 1, 10, true). AddItem(deleteLyricButton, 7, 0, 1, 3, 1, 10, true). AddItem(getLyric1Button, 8, 0, 1, 3, 1, 10, true). AddItem(getLyric2Button, 9, 0, 1, 3, 1, 10, true). AddItem(getLyric3Button, 10, 0, 1, 3, 1, 10, true) leftGrid.SetBorder(true). SetTitle(node.name). SetBorderPadding(1, 1, 2, 2) rightFlex.SetDirection(tview.FlexColumn). AddItem(lyricTextView, 0, 1, true) lyricFlex := tview.NewFlex().SetDirection(tview.FlexColumn). AddItem(leftGrid, 0, 2, true). AddItem(rightFlex, 0, 3, true) lyricFlex. SetTitle(node.name). SetBorderPadding(1, 1, 2, 2). SetBackgroundColor(gomu.colors.popup) inputs := []tview.Primitive{ artistInputField, titleInputField, albumInputField, getTagButton, saveTagButton, lyricDropDown, deleteLyricButton, getLyric1Button, getLyric2Button, getLyric3Button, lyricTextView, } gomu.pages. AddPage(popupID, center(lyricFlex, 90, 40), true, true) gomu.popups.push(lyricFlex) lyricFlex.SetInputCapture(func(e *tcell.EventKey) *tcell.EventKey { switch e.Key() { case tcell.KeyEnter: case tcell.KeyEsc: gomu.pages.RemovePage(popupID) gomu.popups.pop() case tcell.KeyTab: cycleFocus(gomu.app, inputs, false) case tcell.KeyBacktab: cycleFocus(gomu.app, inputs, true) case tcell.KeyRight: cycleFocus(gomu.app, inputs, false) case tcell.KeyLeft: cycleFocus(gomu.app, inputs, true) } switch e.Rune() { case '1': case '2': case '3': } return e }) return err } func cycleFocus(app *tview.Application, elements []tview.Primitive, reverse bool) { for i, el := range elements { if !el.HasFocus() { continue } if reverse { i = i - 1 if i < 0 { i = len(elements) - 1 } } else { i = i + 1 i = i % len(elements) } app.SetFocus(elements[i]) return } } func loadTagMap(node *AudioFile) (tag *id3v2.Tag, popupLyricMap map[string]string, options []string, err error) { popupLyricMap = make(map[string]string) if node.isAudioFile { tag, err = id3v2.Open(node.path, id3v2.Options{Parse: true}) if err != nil { return nil, nil, nil, tracerr.Wrap(err) } defer tag.Close() } else { return nil, nil, nil, fmt.Errorf("not an audio file") } usltFrames := tag.GetFrames(tag.CommonID("Unsynchronised lyrics/text transcription")) for _, f := range usltFrames { uslf, ok := f.(id3v2.UnsynchronisedLyricsFrame) if !ok { die(errors.New("USLT error")) } res := uslf.Lyrics popupLyricMap[uslf.ContentDescriptor] = res } for option := range popupLyricMap { options = append(options, option) } sort.Strings(options) return tag, popupLyricMap, options, err }