gomu/tageditor.go

433 lines
11 KiB
Go
Raw Normal View History

2021-03-05 16:39:33 +08:00
// Copyright (C) 2020 Raziman
package main
import (
"errors"
"fmt"
"sort"
"github.com/bogem/id3v2"
"github.com/gdamore/tcell/v2"
2021-03-09 01:48:01 +08:00
"github.com/issadarkthing/gomu/lyric"
2021-03-05 16:39:33 +08:00
"github.com/rivo/tview"
"github.com/ztrue/tracerr"
)
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()
2021-03-06 03:21:25 +08:00
getTagButton *tview.Button = tview.NewButton("Get Tag")
saveTagButton *tview.Button = tview.NewButton("Save Tag")
2021-03-05 16:39:33 +08:00
lyricDropDown *tview.DropDown = tview.NewDropDown()
2021-03-06 03:21:25 +08:00
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)")
2021-03-05 16:39:33 +08:00
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)
2021-03-06 02:32:30 +08:00
getTagButton.SetSelectedFunc(func() {
2021-03-09 01:48:01 +08:00
var titles []string
2021-03-06 02:32:30 +08:00
audioFile := node
2021-03-09 01:48:01 +08:00
lang := "zh-CN"
results, err := lyric.GetLyricOptions(lang, audioFile.name)
2021-03-06 02:32:30 +08:00
if err != nil {
errorPopup(err)
return
}
2021-03-09 01:48:01 +08:00
for _, v := range results {
titles = append(titles, v.TitleForPopup)
2021-03-06 02:32:30 +08:00
}
searchPopup(" Song Tags ", titles, func(selected string) {
2021-03-06 02:32:30 +08:00
if selected == "" {
return
}
2021-03-09 01:48:01 +08:00
var selectedIndex int
for i, v := range results {
if v.TitleForPopup == selected {
selectedIndex = i
break
}
}
newTag := results[selectedIndex]
2021-03-06 02:32:30 +08:00
artistInputField.SetText(newTag.Artist)
titleInputField.SetText(newTag.Title)
albumInputField.SetText(newTag.Album)
2021-03-06 02:32:30 +08:00
tag, err = id3v2.Open(node.path, id3v2.Options{Parse: true})
if err != nil {
errorPopup(err)
}
defer tag.Close()
2021-03-09 01:48:01 +08:00
tag.SetArtist(newTag.Artist)
tag.SetTitle(newTag.Title)
tag.SetAlbum(newTag.Album)
2021-03-06 02:32:30 +08:00
err = tag.Save()
if err != nil {
errorPopup(err)
} else {
defaultTimedPopup(" Success ", "Tag update successfully")
}
})
}).
SetBorder(true).
2021-03-05 16:39:33 +08:00
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()
2021-03-05 16:39:33 +08:00
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 ")
})
2021-03-05 16:39:33 +08:00
// 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() {
2021-03-09 01:48:01 +08:00
audioFile := gomu.playlist.getCurrentFile()
lang := "en"
err := getLyricFromTagEditor(audioFile, lang, lyricDropDown, lyricTextView)
2021-03-05 16:39:33 +08:00
if err != nil {
errorPopup(err)
2021-03-09 01:48:01 +08:00
gomu.app.Draw()
2021-03-05 16:39:33 +08:00
}
}).
SetBorder(true).
SetBackgroundColor(gomu.colors.background).
SetTitleColor(gomu.colors.accent)
2021-03-09 01:48:01 +08:00
getLyric2Button.SetSelectedFunc(func() {
audioFile := gomu.playlist.getCurrentFile()
lang := "zh-CN"
err := getLyricFromTagEditor(audioFile, lang, lyricDropDown, lyricTextView)
2021-03-05 16:39:33 +08:00
if err != nil {
errorPopup(err)
2021-03-09 01:48:01 +08:00
gomu.app.Draw()
2021-03-05 16:39:33 +08:00
}
}).
SetBorder(true).
SetBackgroundColor(gomu.colors.background).
SetTitleColor(gomu.colors.accent)
2021-03-09 01:48:01 +08:00
2021-03-05 16:39:33 +08:00
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)
2021-03-09 01:48:01 +08:00
leftGrid.SetRows(3, 3, 3, 3, 3, 0, 3, 3, 3, 3).
2021-03-05 16:39:33 +08:00
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).
2021-03-09 01:48:01 +08:00
AddItem(getLyric2Button, 9, 0, 1, 3, 1, 10, true)
2021-03-05 16:39:33 +08:00
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,
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:
2021-03-06 03:21:25 +08:00
cycleFocus(gomu.app, inputs, false)
case tcell.KeyLeft:
2021-03-06 03:21:25 +08:00
cycleFocus(gomu.app, inputs, true)
2021-03-05 16:39:33 +08:00
}
2021-03-06 03:21:25 +08:00
switch e.Rune() {
case '1':
case '2':
case '3':
}
2021-03-05 16:39:33 +08:00
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
}
2021-03-09 01:48:01 +08:00
func getLyricFromTagEditor(audioFile *AudioFile, lang string, lyricDropDown *tview.DropDown, lyricTextView *tview.TextView) (err error) {
2021-03-09 12:59:54 +08:00
if !audioFile.isAudioFile {
return errors.New("not an audio file")
}
go func() {
gomu.app.QueueUpdateDraw(func() {
2021-03-09 01:48:01 +08:00
2021-03-09 12:59:54 +08:00
var titles []string
results, err := lyric.GetLyricOptions(lang, audioFile.name)
if err != nil {
errorPopup(err)
gomu.app.Draw()
}
2021-03-09 01:48:01 +08:00
2021-03-09 12:59:54 +08:00
for _, v := range results {
titles = append(titles, v.TitleForPopup)
}
2021-03-09 01:48:01 +08:00
2021-03-09 12:59:54 +08:00
searchPopup(" Lyrics ", titles, func(selected string) {
if selected == "" {
return
}
2021-03-09 01:48:01 +08:00
2021-03-09 12:59:54 +08:00
go func() {
var selectedIndex int
for i, v := range results {
if v.TitleForPopup == selected {
selectedIndex = i
break
2021-03-09 01:48:01 +08:00
}
2021-03-09 12:59:54 +08:00
}
lyric, err := lyric.GetLyric(results[selectedIndex].LangExt, results[selectedIndex])
if err != nil {
errorPopup(err)
gomu.app.Draw()
}
2021-03-09 01:48:01 +08:00
2021-03-09 12:59:54 +08:00
err = embedLyric(audioFile.path, lyric, lang, false)
if err != nil {
errorPopup(err)
gomu.app.Draw()
} else {
infoPopup(lang + " lyric added successfully")
gomu.app.Draw()
}
2021-03-09 01:48:01 +08:00
2021-03-09 12:59:54 +08:00
// This is to ensure that the above go routine finish.
_, popupLyricMap, newOptions, err := loadTagMap(audioFile)
if err != nil {
errorPopup(err)
gomu.app.Draw()
return
}
2021-03-09 01:48:01 +08:00
2021-03-09 12:59:54 +08:00
// 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 ")
}
2021-03-09 01:48:01 +08:00
2021-03-09 12:59:54 +08:00
}()
2021-03-09 01:48:01 +08:00
})
2021-03-09 12:59:54 +08:00
})
}()
2021-03-09 01:48:01 +08:00
return err
}