gomu/tageditor.go

487 lines
12 KiB
Go
Raw Normal View History

2021-03-05 16:39:33 +08:00
// Copyright (C) 2020 Raziman
package main
import (
"errors"
"fmt"
2021-03-13 01:13:45 +08:00
"strings"
"sync"
2021-03-05 16:39:33 +08:00
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
"github.com/tramhao/id3v2"
2021-03-05 16:39:33 +08:00
"github.com/ztrue/tracerr"
2021-03-13 01:13:45 +08:00
"github.com/issadarkthing/gomu/lyric"
"github.com/issadarkthing/gomu/player"
2021-03-05 16:39:33 +08:00
)
2021-03-26 00:48:01 +08:00
// lyricFlex extend the flex control to modify the Focus item
type lyricFlex struct {
*tview.Flex
FocusedItem tview.Primitive
2021-03-26 00:48:01 +08:00
inputs []tview.Primitive
2021-03-31 16:45:15 +08:00
box *tview.Box
}
2021-03-10 10:55:38 +08:00
// tagPopup is used to edit tag, delete and fetch lyrics
func tagPopup(node *player.AudioFile) (err error) {
2021-03-05 16:39:33 +08:00
popupID := "tag-editor-input-popup"
tag, popupLyricMap, options, err := node.LoadTagMap()
2021-03-05 16:39:33 +08:00
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")
getLyricDropDown *tview.DropDown = tview.NewDropDown()
getLyricButton *tview.Button = tview.NewButton("Fetch Lyric")
2021-03-31 16:45:15 +08:00
lyricTextView *tview.TextView = tview.NewTextView()
leftGrid *tview.Grid = tview.NewGrid()
rightFlex *tview.Flex = tview.NewFlex()
2021-03-05 16:39:33 +08:00
)
2021-03-14 18:20:48 +08:00
2021-03-05 16:39:33 +08:00
artistInputField.SetLabel("Artist: ").
SetFieldWidth(20).
SetText(tag.Artist()).
2021-03-14 18:20:48 +08:00
SetFieldBackgroundColor(gomu.colors.popup)
2021-03-05 16:39:33 +08:00
titleInputField.SetLabel("Title: ").
SetFieldWidth(20).
SetText(tag.Title()).
2021-03-14 18:20:48 +08:00
SetFieldBackgroundColor(gomu.colors.popup)
2021-03-05 16:39:33 +08:00
albumInputField.SetLabel("Album: ").
SetFieldWidth(20).
SetText(tag.Album()).
2021-03-14 18:20:48 +08:00
SetFieldBackgroundColor(gomu.colors.popup)
leftBox := tview.NewBox().
SetBorder(true).
SetTitle(node.Name()).
SetBackgroundColor(gomu.colors.popup).
SetBorderColor(gomu.colors.accent).
SetTitleColor(gomu.colors.accent).
SetBorderPadding(1, 1, 2, 2)
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
go func() {
var lyricFetcher lyric.LyricFetcherCn
results, err := lyricFetcher.LyricOptions(audioFile.Name())
2021-03-26 00:48:01 +08:00
if err != nil {
errorPopup(err)
return
}
for _, v := range results {
titles = append(titles, v.TitleForPopup)
}
2021-03-26 00:48:01 +08:00
go func() {
searchPopup(" Song Tags ", titles, func(selected string) {
if selected == "" {
return
}
2021-03-26 00:48:01 +08:00
var selectedIndex int
for i, v := range results {
if v.TitleForPopup == selected {
selectedIndex = i
break
}
}
2021-03-26 00:48:01 +08:00
newTag := results[selectedIndex]
artistInputField.SetText(newTag.Artist)
titleInputField.SetText(newTag.Title)
albumInputField.SetText(newTag.Album)
tag, err = id3v2.Open(node.Path(), id3v2.Options{Parse: true})
2021-03-26 00:48:01 +08:00
if err != nil {
errorPopup(err)
return
}
defer tag.Close()
tag.SetArtist(newTag.Artist)
tag.SetTitle(newTag.Title)
tag.SetAlbum(newTag.Album)
err = tag.Save()
if err != nil {
errorPopup(err)
return
}
if gomu.anko.GetBool("General.rename_bytag") {
newName := fmt.Sprintf("%s-%s", newTag.Artist, newTag.Title)
err = gomu.playlist.rename(newName)
if err != nil {
errorPopup(err)
return
}
gomu.playlist.refresh()
leftBox.SetTitle(newName)
2021-04-01 16:09:09 +08:00
// update queue
err = gomu.playlist.refreshAfterRename(node, newName)
2021-04-01 16:09:09 +08:00
if err != nil {
errorPopup(err)
return
}
node = gomu.playlist.getCurrentFile()
}
2021-03-26 00:48:01 +08:00
defaultTimedPopup(" Success ", "Tag update successfully")
})
2021-04-01 11:25:14 +08:00
gomu.app.Draw()
2021-03-26 00:48:01 +08:00
}()
}()
2021-03-06 02:32:30 +08:00
}).
2021-03-14 18:20:48 +08:00
SetBackgroundColorActivated(gomu.colors.popup).
SetLabelColorActivated(gomu.colors.accent).
2021-03-06 02:32:30 +08:00
SetBorder(true).
2021-03-14 18:20:48 +08:00
SetBackgroundColor(gomu.colors.popup).
2021-03-05 16:39:33 +08:00
SetTitleColor(gomu.colors.accent)
2021-03-14 18:20:48 +08:00
2021-03-05 16:39:33 +08:00
saveTagButton.SetSelectedFunc(func() {
tag, err = id3v2.Open(node.Path(), id3v2.Options{
Parse: true,
ParseFrames: []string{},
})
2021-03-05 16:39:33 +08:00
if err != nil {
errorPopup(err)
return
2021-03-05 16:39:33 +08:00
}
defer tag.Close()
newArtist := artistInputField.GetText()
newTitle := titleInputField.GetText()
newAlbum := albumInputField.GetText()
tag.SetArtist(newArtist)
tag.SetTitle(newTitle)
tag.SetAlbum(newAlbum)
err = tag.Save()
2021-03-05 16:39:33 +08:00
if err != nil {
errorPopup(err)
return
2021-03-05 16:39:33 +08:00
}
if gomu.anko.GetBool("General.rename_bytag") {
newName := fmt.Sprintf("%s-%s", newArtist, newTitle)
err = gomu.playlist.rename(newName)
if err != nil {
errorPopup(err)
return
}
gomu.playlist.refresh()
leftBox.SetTitle(newName)
2021-04-01 16:09:09 +08:00
// update queue
err = gomu.playlist.refreshAfterRename(node, newName)
2021-04-01 16:09:09 +08:00
if err != nil {
errorPopup(err)
return
}
node = gomu.playlist.getCurrentFile()
}
2021-03-25 12:01:12 +08:00
defaultTimedPopup(" Success ", "Tag update successfully")
2021-03-05 16:39:33 +08:00
}).
2021-03-14 18:20:48 +08:00
SetBackgroundColorActivated(gomu.colors.popup).
SetLabelColorActivated(gomu.colors.accent).
2021-03-05 16:39:33 +08:00
SetBorder(true).
2021-03-14 18:20:48 +08:00
SetBackgroundColor(gomu.colors.popup).
2021-03-05 16:39:33 +08:00
SetTitleColor(gomu.colors.foreground)
lyricDropDown.SetOptions(options, nil).
SetCurrentOption(0).
2021-03-14 18:20:48 +08:00
SetFieldBackgroundColor(gomu.colors.popup).
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)
2021-03-05 16:39:33 +08:00
deleteLyricButton.SetSelectedFunc(func() {
_, langExt := lyricDropDown.GetCurrentOption()
lyric := &lyric.Lyric{
LangExt: langExt,
}
2021-03-05 16:39:33 +08:00
if len(options) > 0 {
err := embedLyric(node.Path(), lyric, true)
2021-03-05 16:39:33 +08:00
if err != nil {
errorPopup(err)
return
2021-03-05 16:39:33 +08:00
}
2021-03-25 12:01:12 +08:00
infoPopup(langExt + " lyric deleted successfully.")
2021-03-05 16:39:33 +08:00
// 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.")
}
}).
2021-03-14 18:20:48 +08:00
SetBackgroundColorActivated(gomu.colors.popup).
SetLabelColorActivated(gomu.colors.accent).
2021-03-05 16:39:33 +08:00
SetBorder(true).
2021-03-14 18:20:48 +08:00
SetBackgroundColor(gomu.colors.popup).
2021-03-05 16:39:33 +08:00
SetTitleColor(gomu.colors.accent)
getLyricDropDownOptions := []string{"en", "zh-CN"}
getLyricDropDown.SetOptions(getLyricDropDownOptions, nil).
SetCurrentOption(0).
2021-03-14 18:20:48 +08:00
SetFieldBackgroundColor(gomu.colors.popup).
SetFieldTextColor(gomu.colors.accent).
SetPrefixTextColor(gomu.colors.accent).
2021-03-14 18:20:48 +08:00
SetLabel("Fetch Lyrics: ").
SetBackgroundColor(gomu.colors.popup)
2021-03-13 01:13:45 +08:00
langLyricFromConfig := gomu.anko.GetString("General.lang_lyric")
if strings.Contains(langLyricFromConfig, "zh-CN") {
getLyricDropDown.SetCurrentOption(1)
}
getLyricButton.SetSelectedFunc(func() {
2021-03-05 16:39:33 +08:00
2021-03-09 01:48:01 +08:00
audioFile := gomu.playlist.getCurrentFile()
_, lang := getLyricDropDown.GetCurrentOption()
if !audioFile.IsAudioFile() {
2021-03-19 21:30:09 +08:00
errorPopup(errors.New("not an audio file"))
return
}
var wg sync.WaitGroup
wg.Add(1)
2021-03-19 21:30:09 +08:00
go func() {
err := lyricPopup(lang, audioFile, &wg)
2021-03-19 21:30:09 +08:00
if err != nil {
errorPopup(err)
return
2021-03-19 21:30:09 +08:00
}
}()
2021-03-19 21:30:09 +08:00
go func() {
// This is to ensure that the above go routine finish.
wg.Wait()
_, popupLyricMap, newOptions, err := audioFile.LoadTagMap()
if err != nil {
errorPopup(err)
gomu.app.Draw()
return
2021-03-19 21:30:09 +08:00
}
options = newOptions
// Update dropdown options
gomu.app.QueueUpdateDraw(func() {
lyricDropDown.SetOptions(newOptions, nil).
SetCurrentOption(0).
SetSelectedFunc(func(text string, _ int) {
lyricTextView.SetText(popupLyricMap[text]).
SetTitle(" " + text + " lyric preview ")
})
2021-03-19 21:30:09 +08:00
// 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-19 21:30:09 +08:00
}()
2021-03-05 16:39:33 +08:00
}).
2021-03-14 18:20:48 +08:00
SetBackgroundColorActivated(gomu.colors.popup).
SetLabelColorActivated(gomu.colors.accent).
2021-03-05 16:39:33 +08:00
SetBorder(true).
2021-03-14 18:20:48 +08:00
SetTitleColor(gomu.colors.accent).
SetBackgroundColor(gomu.colors.popup)
2021-03-09 01:48:01 +08:00
2021-03-05 16:39:33 +08:00
var lyricText string
_, langExt := lyricDropDown.GetCurrentOption()
lyricText = popupLyricMap[langExt]
if lyricText == "" {
lyricText = "No lyric embeded."
langExt = ""
}
2021-03-31 16:45:15 +08:00
2021-03-05 16:39:33 +08:00
lyricTextView.
SetDynamicColors(true).
SetRegions(true).
SetScrollable(true).
SetTitle(" " + langExt + " lyric preview ").
SetBorder(true)
lyricTextView.SetText(lyricText).
SetScrollable(true).
SetWordWrap(true).
SetWrap(true).
SetBorder(true)
lyricTextView.SetChangedFunc(func() {
gomu.app.QueueUpdate(func() {
lyricTextView.ScrollToBeginning()
})
})
2021-03-05 16:39:33 +08:00
2021-04-28 22:37:06 +08:00
leftGrid.SetRows(3, 1, 2, 2, 2, 3, 0, 3, 3, 1, 3, 3).
2021-03-05 16:39:33 +08:00
SetColumns(30).
AddItem(getTagButton, 0, 0, 1, 3, 1, 10, true).
AddItem(artistInputField, 2, 0, 1, 3, 1, 10, true).
AddItem(titleInputField, 3, 0, 1, 3, 1, 10, true).
AddItem(albumInputField, 4, 0, 1, 3, 1, 10, true).
AddItem(saveTagButton, 5, 0, 1, 3, 1, 10, true).
AddItem(getLyricDropDown, 7, 0, 1, 3, 1, 20, true).
AddItem(getLyricButton, 8, 0, 1, 3, 1, 10, true).
AddItem(lyricDropDown, 10, 0, 1, 3, 1, 10, true).
AddItem(deleteLyricButton, 11, 0, 1, 3, 1, 10, true)
2021-03-05 16:39:33 +08:00
rightFlex.SetDirection(tview.FlexColumn).
AddItem(lyricTextView, 0, 1, true)
2021-03-26 00:48:01 +08:00
lyricFlex := &lyricFlex{
tview.NewFlex().SetDirection(tview.FlexColumn).
AddItem(leftGrid, 0, 2, true).
AddItem(rightFlex, 0, 3, true),
nil,
2021-03-26 00:48:01 +08:00
nil,
leftBox,
}
2021-03-05 16:39:33 +08:00
2021-03-31 16:45:15 +08:00
leftGrid.Box = lyricFlex.box
2021-03-26 00:48:01 +08:00
lyricFlex.inputs = []tview.Primitive{
getTagButton,
2021-03-05 16:39:33 +08:00
artistInputField,
titleInputField,
albumInputField,
saveTagButton,
getLyricDropDown,
getLyricButton,
lyricDropDown,
deleteLyricButton,
2021-03-05 16:39:33 +08:00
lyricTextView,
}
2021-04-13 23:34:30 +08:00
if gomu.playingBar.albumPhoto != nil {
gomu.playingBar.albumPhoto.Clear()
}
2021-04-28 22:37:06 +08:00
gomu.pages.AddPage(popupID, center(lyricFlex, 90, 30), true, true)
2021-03-05 16:39:33 +08:00
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()
2021-03-30 14:01:24 +08:00
case tcell.KeyTab, tcell.KeyCtrlN, tcell.KeyCtrlJ:
2021-03-26 00:48:01 +08:00
lyricFlex.cycleFocus(gomu.app, false)
2021-03-30 14:01:24 +08:00
case tcell.KeyBacktab, tcell.KeyCtrlP, tcell.KeyCtrlK:
2021-03-26 00:48:01 +08:00
lyricFlex.cycleFocus(gomu.app, true)
case tcell.KeyDown:
2021-03-26 00:48:01 +08:00
lyricFlex.cycleFocus(gomu.app, false)
case tcell.KeyUp:
2021-03-26 00:48:01 +08:00
lyricFlex.cycleFocus(gomu.app, true)
2021-03-05 16:39:33 +08:00
}
2021-03-06 03:21:25 +08:00
switch e.Rune() {
2021-03-13 01:13:45 +08:00
case 'q':
if artistInputField.HasFocus() || titleInputField.HasFocus() || albumInputField.HasFocus() {
return e
}
gomu.pages.RemovePage(popupID)
gomu.popups.pop()
2021-03-06 03:21:25 +08:00
}
2021-03-05 16:39:33 +08:00
return e
})
return err
}
2021-03-10 10:55:38 +08:00
// This is a hack to cycle Focus in a flex
2021-03-26 00:48:01 +08:00
func (f *lyricFlex) cycleFocus(app *tview.Application, reverse bool) {
for i, el := range f.inputs {
2021-03-05 16:39:33 +08:00
if !el.HasFocus() {
continue
}
if reverse {
i = i - 1
if i < 0 {
2021-03-26 00:48:01 +08:00
i = len(f.inputs) - 1
2021-03-05 16:39:33 +08:00
}
} else {
i = i + 1
2021-03-26 00:48:01 +08:00
i = i % len(f.inputs)
2021-03-05 16:39:33 +08:00
}
2021-03-26 00:48:01 +08:00
app.SetFocus(f.inputs[i])
f.FocusedItem = f.inputs[i]
// below code is setting the border highlight of left and right flex
if f.inputs[9].HasFocus() {
f.inputs[9].(*tview.TextView).SetBorderColor(gomu.colors.accent).
SetTitleColor(gomu.colors.accent)
2021-03-31 16:45:15 +08:00
f.box.SetBorderColor(gomu.colors.background).
SetTitleColor(gomu.colors.background)
} else {
2021-03-26 00:48:01 +08:00
f.inputs[9].(*tview.TextView).SetBorderColor(gomu.colors.background).
SetTitleColor(gomu.colors.background)
2021-03-31 16:45:15 +08:00
f.box.SetBorderColor(gomu.colors.accent).
SetTitleColor(gomu.colors.accent)
}
2021-03-05 16:39:33 +08:00
return
}
}
2021-03-10 10:55:38 +08:00
// Focus is an override of Focus function in tview.flex.
// This is to ensure that the focus of flex remain unchanged
// when returning from popups or search lists
2021-03-26 00:48:01 +08:00
func (f *lyricFlex) Focus(delegate func(p tview.Primitive)) {
if f.FocusedItem != nil {
gomu.app.SetFocus(f.FocusedItem)
} else {
f.Flex.Focus(delegate)
}
}