diff --git a/go.mod b/go.mod index cfcbc57..9ef9dd8 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/hajimehoshi/oto v0.7.1 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/logrusorgru/aurora v2.0.3+incompatible // indirect + github.com/martinlindhe/subtitles v0.0.0-20210219114018-c133f18cfb3d github.com/mattn/anko v0.1.8 github.com/pkg/errors v0.9.1 // indirect github.com/rivo/tview v0.0.0-20210125085121-dbc1f32bb1d0 diff --git a/go.sum b/go.sum index d8a2a30..439516b 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,13 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/bogem/id3v2 v1.2.0 h1:hKDF+F1gOgQ5r1QmBCEZUk4MveJbKxCeIDSBU7CQ4oI= github.com/bogem/id3v2 v1.2.0/go.mod h1:t78PK5AQ56Q47kizpYiV6gtjj3jfxlz87oFpty8DYs8= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/faiface/beep v1.0.2 h1:UB5DiRNmA4erfUYnHbgU4UB6DlBOrsdEFRtcc8sCkdQ= github.com/faiface/beep v1.0.2/go.mod h1:1yLb5yRdHMsovYYWVqYLioXkVuziCSITW1oarTeduQM= github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= @@ -34,6 +38,8 @@ github.com/hajimehoshi/oto v0.7.1 h1:I7maFPz5MBCwiutOrz++DLdbr4rTzBsbBuV2VpgU9kk github.com/hajimehoshi/oto v0.7.1/go.mod h1:wovJ8WWMfFKvP587mhHgot/MBr4DnNy9m6EepeVGnos= github.com/jfreymuth/oggvorbis v1.0.0/go.mod h1:abe6F9QRjuU9l+2jek3gj46lu40N4qlYxh2grqkLEDM= github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0= +github.com/kennygrant/sanitize v1.2.4 h1:gN25/otpP5vAsO2djbMhF/LQX6R7+O1TB4yv8NzpJ3o= +github.com/kennygrant/sanitize v1.2.4/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2eObZ0u0qvak= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e h1:9MlwzLdW7QSDrhDjFlsEYmxpFyIoXmYRon3dt0io31k= @@ -43,6 +49,10 @@ github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/z github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4= github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/magefile/mage v1.10.0 h1:3HiXzCUY12kh9bIuyXShaVe529fJfyqoVM42o/uom2g= +github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +github.com/martinlindhe/subtitles v0.0.0-20210219114018-c133f18cfb3d h1:7pTPyGn1BlepA/DoNsDKuhDovj5cGdIiQpRjHwPHNlk= +github.com/martinlindhe/subtitles v0.0.0-20210219114018-c133f18cfb3d/go.mod h1:EjV1EvmBWHZGVRjE+IolE0t6LnkbhyRiwD7SlXDQsuw= github.com/mattn/anko v0.1.8 h1:wDGM0Rwgbzhk1h8xE6qAR4n+PO/9clzf3tLGtrwsqJg= github.com/mattn/anko v0.1.8/go.mod h1:C5D2zw4NIv/sB2SrQ3qs5wqPw0wKiA2GZqexy4ctNH0= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= @@ -64,7 +74,11 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= +github.com/sirupsen/logrus v1.8.0 h1:nfhvjKcUMhBMVqbKHJlk5RPrrfYr/NMo3692g0dwfWU= +github.com/sirupsen/logrus v1.8.0/go.mod h1:4GuYW9TZmE769R5STWrRakJc4UqQ3+QQ95fyz7ENv1A= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/tj/go-spin v1.1.0 h1:lhdWZsvImxvZ3q1C5OIB7d72DuOwP4O2NdBg9PyzNds= @@ -97,6 +111,8 @@ golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hM golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -105,10 +121,14 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 h1:nVuTkr9L6Bq62qpUqKo/RnZCFfzDBL0bYo6w9OJUqZY= golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -120,7 +140,9 @@ golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapK golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/playingbar.go b/playingbar.go index d9f297a..c0fef2b 100644 --- a/playingbar.go +++ b/playingbar.go @@ -3,11 +3,16 @@ package main import ( + // "bytes" "fmt" + "log" "strconv" "strings" "time" + // "github.com/asticode/go-astisub" + "github.com/bogem/id3v2" + "github.com/martinlindhe/subtitles" "github.com/rivo/tview" "github.com/ztrue/tracerr" ) @@ -19,8 +24,18 @@ type PlayingBar struct { _progress int skip bool text *tview.TextView + hasTag bool + tag *id3v2.Tag } +type lyricParsed struct { + timestart time.Duration + timeend time.Duration + lyricText string +} + +var lyricsParsed []lyricParsed + func (p *PlayingBar) help() []string { return []string{} } @@ -71,13 +86,21 @@ func (p *PlayingBar) run() error { _, _, width, _ := p.GetInnerRect() progressBar := progresStr(p._progress, p.full, width/2, "█", "━") // our progress bar - p.text.SetText(fmt.Sprintf("%s ┃%s┫ %s", - fmtDuration(start), - progressBar, - fmtDuration(end), - )) + if p.hasTag { + p.text.SetText(fmt.Sprintf("%s ┃%s┫ %s\n%s", + fmtDuration(start), + progressBar, + fmtDuration(end), + p.tag.Title(), + )) + } else { + p.text.SetText(fmt.Sprintf("%s ┃%s┫ %s", + fmtDuration(start), + progressBar, + fmtDuration(end), + )) + } gomu.app.Draw() - } return nil @@ -95,6 +118,35 @@ func (p *PlayingBar) newProgress(songTitle string, full int) { p.full = full p._progress = 0 p.setSongTitle(songTitle) + var tag *id3v2.Tag + var err error + tag, err = id3v2.Open(gomu.player.currentSong.path, id3v2.Options{Parse: true}) + if tag == nil || err != nil { + logError(err) + } else { + p.hasTag = true + p.tag = tag + + usltFrames := tag.GetFrames(tag.CommonID("Unsynchronised lyrics/text transcription")) + + for _, f := range usltFrames { + uslf, ok := f.(id3v2.UnsynchronisedLyricsFrame) + if !ok { + log.Fatal("USLT error!") + } + /* subtitleLyric, err := astisub.ReadFromWebVTT(bytes.NewBufferString(uslf.Lyrics)) + if err != nil { + logError(err) + } + _ = subtitleLyric */ + res, err := subtitles.NewFromSRT(uslf.Lyrics) + if err != nil { + logError(err) + } + fmt.Println(res.Captions[3]) + } + } + defer tag.Close() } // Sets default title and progress bar diff --git a/playlist.go b/playlist.go index f8c582f..d780a7c 100644 --- a/playlist.go +++ b/playlist.go @@ -553,6 +553,8 @@ func ytdl(url string, selPlaylist *tview.TreeNode) error { metaData := fmt.Sprintf("%%(artist)s - %%(title)s") + langSubtitle := "en,zh-Hans" + args := []string{ "--extract-audio", "--audio-format", @@ -563,6 +565,11 @@ func ytdl(url string, selPlaylist *tview.TreeNode) error { "--embed-thumbnail", "--metadata-from-title", metaData, + "--write-sub", + "--sub-lang", + langSubtitle, + "--convert-subs", + "srt", // "--cookies", // "~/Downloads/youtube.com_cookies.txt", url, @@ -608,65 +615,6 @@ func ytdl(url string, selPlaylist *tview.TreeNode) error { return nil } -// Download audio subtitle from youtube audio -func ytdlSubtitle(url string, selPlaylist *tview.TreeNode) error { - - // lookup if youtube-dl exists - _, err := exec.LookPath("youtube-dl") - - if err != nil { - defaultTimedPopup(" Error ", "youtube-dl is not in your $PATH") - - return tracerr.Wrap(err) - } - - selAudioFile := selPlaylist.GetReference().(*AudioFile) - dir := selAudioFile.path - - // defaultTimedPopup(" Ytdl ", "Downloading subtitles") - - // specify the output path for ytdl - outputDir := fmt.Sprintf( - "%s/%%(title)s.%%(ext)s", - dir) - - langSubtitle := "en,zh-Hans" - - args := []string{ - "--skip-download", - "--output", - outputDir, - "--write-sub", - "--sub-lang", - langSubtitle, - "--convert-subs", - "lrc", - url, - } - - cmd := exec.Command("youtube-dl", args...) - var stdout, stderr bytes.Buffer - cmd.Stdout = &stdout - cmd.Stderr = &stderr - - // blocking - err = cmd.Run() - - if err != nil { - defaultTimedPopup(" Error ", "Error running youtube-dl") - return tracerr.Wrap(err) - } - - playlistPath := dir - audioPath := extractFilePath(stdout.Bytes(), playlistPath) - - downloadFinishedMessage := fmt.Sprintf("Finished downloading subtitles\n%s", getName(audioPath)) - defaultTimedPopup(" Ytdl ", downloadFinishedMessage) - gomu.app.Draw() - - return nil -} - // Add songs and their directories in Playlist panel func populate(root *tview.TreeNode, rootPath string) error { diff --git a/popup.go b/popup.go index 67b234d..165c470 100644 --- a/popup.go +++ b/popup.go @@ -303,11 +303,6 @@ func downloadMusicPopup(selPlaylist *tview.TreeNode) { logError(err) } }() - go func() { - if err := ytdlSubtitle(url, selPlaylist); err != nil { - logError(err) - } - }() } else { defaultTimedPopup("Invalid url", "Invalid youtube url was given") } @@ -826,7 +821,7 @@ func tagPopup(node *AudioFile) bool { for _, file := range files { - if filepath.Ext(file.Name()) == ".lrc" { + if filepath.Ext(file.Name()) == ".srt" { lyricsAvailable = append(lyricsAvailable, file.Name()) } } @@ -834,13 +829,12 @@ func tagPopup(node *AudioFile) bool { AddInputField("Artist", tag.Artist(), 20, nil, nil). AddInputField("Title", tag.Title(), 20, nil, nil). AddInputField("Album", tag.Album(), 20, nil, nil). - AddCheckbox("Embed Lyrics", false, nil). - AddDropDown("Lyrics Available", lyricsAvailable, 0, nil) + AddDropDown("Lyrics Available", lyricsAvailable, 0, nil). + AddCheckbox("Embed Lyrics", true, nil) form.SetBackgroundColor(gomu.colors.popup). SetBackgroundColor(gomu.colors.popup). SetTitle(node.name). - SetTitleAlign(tview.AlignLeft). SetBorder(true). SetBorderPadding(1, 0, 2, 2) @@ -856,17 +850,51 @@ func tagPopup(node *AudioFile) bool { logError(err) } defer tag.Close() - tag.SetArtist(form.GetFormItemByLabel("Artist").(*tview.InputField).GetText()) - tag.SetTitle(form.GetFormItemByLabel("Title").(*tview.InputField).GetText()) + tagArtist := form.GetFormItemByLabel("Artist").(*tview.InputField).GetText() + tagTitle := form.GetFormItemByLabel("Title").(*tview.InputField).GetText() + tag.SetArtist(tagArtist) + tag.SetTitle(tagTitle) tag.SetAlbum(form.GetFormItemByLabel("Album").(*tview.InputField).GetText()) + + if form.GetFormItemByLabel("Embed Lyrics").(*tview.Checkbox).IsChecked() { + _, fileName := form.GetFormItemByLabel("Lyrics Available").(*tview.DropDown).GetCurrentOption() + lyricFileName := filepath.Join(pathToFile, fileName) + // Read entire file content, giving us little control but + // making it very simple. No need to close the file. + content, err := ioutil.ReadFile(lyricFileName) + if err != nil { + logError(err) + } + + // Convert []byte to string and print to screen + lyric := string(content) + tag.AddUnsynchronisedLyricsFrame(id3v2.UnsynchronisedLyricsFrame{ + Encoding: id3v2.EncodingUTF8, + Language: "eng", + ContentDescriptor: tagArtist + "-" + tagTitle, + Lyrics: lyric, + }) + // lyrics := "'first line',12343\n\r'secondline',23455\n\r" + /* tag.AddSynchronisedLyricsFrame(id3v2.SynchronisedLyricsFrame{ + Encoding: id3v2.EncodingUTF8, + Language: "eng", + TimeStampFormat: 2, + ContentType: 1, + ContentDescriptor: tagArtist + "-" + tagTitle, + SynchronizedTextSpec: lyric, + }) */ + + } + // Write tag to mp3. if err := tag.Save(); err != nil { defaultTimedPopup(" Error ", err.Error()) logError(err) + } else { + defaultTimedPopup(" Success ", "Tag update successfully") + gomu.pages.RemovePage(popupID) + gomu.popups.pop() } - defaultTimedPopup(" Success ", "Tag update successfully") - gomu.pages.RemovePage(popupID) - gomu.popups.pop() case tcell.KeyEsc: gomu.pages.RemovePage(popupID)