commit
b5d41a5d07
@ -3,7 +3,7 @@ authorization-token: "your-authorization-token" #You don't need to change it; it
|
|||||||
language: "" #supportedLanguage by each storefront --> https://gist.github.com/itouakirai/c8ba9df9dc65bd300094103b058731d0
|
language: "" #supportedLanguage by each storefront --> https://gist.github.com/itouakirai/c8ba9df9dc65bd300094103b058731d0
|
||||||
lrc-type: "lyrics" #lyrics or syllable-lyrics
|
lrc-type: "lyrics" #lyrics or syllable-lyrics
|
||||||
lrc-format: "lrc" #lrc or ttml
|
lrc-format: "lrc" #lrc or ttml
|
||||||
embed-lrc: true #Unable to embed ttml lyrics
|
embed-lrc: true
|
||||||
save-lrc-file: false
|
save-lrc-file: false
|
||||||
save-artist-cover: false
|
save-artist-cover: false
|
||||||
save-animated-artwork: false # If enabled, requires ffmpeg
|
save-animated-artwork: false # If enabled, requires ffmpeg
|
||||||
@ -43,4 +43,4 @@ use-songinfo-for-playlist: false
|
|||||||
#if set true,will download album cover for playlist
|
#if set true,will download album cover for playlist
|
||||||
dl-albumcover-for-playlist: false
|
dl-albumcover-for-playlist: false
|
||||||
mv-audio-type: atmos #atmos ac3 aac
|
mv-audio-type: atmos #atmos ac3 aac
|
||||||
mv-max: 2160
|
mv-max: 2160
|
||||||
|
241
main.go
241
main.go
@ -23,8 +23,8 @@ import (
|
|||||||
"main/utils/runv2"
|
"main/utils/runv2"
|
||||||
"main/utils/runv3"
|
"main/utils/runv3"
|
||||||
"main/utils/structs"
|
"main/utils/structs"
|
||||||
|
"main/utils/lyrics"
|
||||||
|
|
||||||
"github.com/beevik/etree"
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/grafov/m3u8"
|
"github.com/grafov/m3u8"
|
||||||
"github.com/olekukonko/tablewriter"
|
"github.com/olekukonko/tablewriter"
|
||||||
@ -393,31 +393,6 @@ func getMeta(albumId string, token string, storefront string) (*structs.AutoGene
|
|||||||
return obj, nil
|
return obj, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSongLyrics(songId string, storefront string, token string, userToken string) (string, error) {
|
|
||||||
req, err := http.NewRequest("GET",
|
|
||||||
fmt.Sprintf("https://amp-api.music.apple.com/v1/catalog/%s/songs/%s/%s?l=%s", storefront, songId, Config.LrcType, Config.Language), nil)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
req.Header.Set("Origin", "https://music.apple.com")
|
|
||||||
req.Header.Set("Referer", "https://music.apple.com/")
|
|
||||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
|
||||||
cookie := http.Cookie{Name: "media-user-token", Value: userToken}
|
|
||||||
req.AddCookie(&cookie)
|
|
||||||
do, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer do.Body.Close()
|
|
||||||
obj := new(structs.SongLyrics)
|
|
||||||
_ = json.NewDecoder(do.Body).Decode(&obj)
|
|
||||||
if obj.Data != nil {
|
|
||||||
return obj.Data[0].Attributes.Ttml, nil
|
|
||||||
} else {
|
|
||||||
return "", errors.New("failed to get lyrics")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeCover(sanAlbumFolder, name string, url string) (string, error) {
|
func writeCover(sanAlbumFolder, name string, url string) (string, error) {
|
||||||
covPath := filepath.Join(sanAlbumFolder, name+"."+Config.CoverFormat)
|
covPath := filepath.Join(sanAlbumFolder, name+"."+Config.CoverFormat)
|
||||||
if Config.CoverFormat == "original" {
|
if Config.CoverFormat == "original" {
|
||||||
@ -601,36 +576,23 @@ func downloadTrack(trackNum int, trackTotal int, meta *structs.AutoGenerated, tr
|
|||||||
|
|
||||||
//get lrc
|
//get lrc
|
||||||
var lrc string = ""
|
var lrc string = ""
|
||||||
if len(mediaUserToken) > 50 {
|
if Config.EmbedLrc || Config.SaveLrcFile {
|
||||||
ttml, err := getSongLyrics(track.ID, storefront, token, mediaUserToken)
|
lrcStr, err := lyrics.Get(storefront, track.ID, Config.LrcType, Config.Language, Config.LrcFormat, token, mediaUserToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Failed to get lyrics")
|
fmt.Println(err)
|
||||||
} else if Config.LrcFormat == "ttml" {
|
} else {
|
||||||
if Config.SaveLrcFile {
|
if Config.SaveLrcFile {
|
||||||
lrc = ttml
|
err := writeLyrics(sanAlbumFolder, lrcFilename, lrcStr)
|
||||||
err := writeLyrics(sanAlbumFolder, lrcFilename, lrc)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed to write lyrics")
|
fmt.Printf("Failed to write lyrics")
|
||||||
}
|
}
|
||||||
lrc = ""
|
|
||||||
}
|
}
|
||||||
} else {
|
if Config.EmbedLrc {
|
||||||
lrc, err = conventTTMLToLRC(ttml)
|
lrc = lrcStr
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Failed to parse lyrics: %s \n", err)
|
|
||||||
} else {
|
|
||||||
if Config.SaveLrcFile {
|
|
||||||
err := writeLyrics(sanAlbumFolder, lrcFilename, lrc)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Failed to write lyrics")
|
|
||||||
}
|
|
||||||
if !Config.EmbedLrc {
|
|
||||||
lrc = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exists, err := fileExists(trackPath)
|
exists, err := fileExists(trackPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Failed to check if track exists.")
|
fmt.Println("Failed to check if track exists.")
|
||||||
@ -671,7 +633,7 @@ func downloadTrack(trackNum int, trackTotal int, meta *structs.AutoGenerated, tr
|
|||||||
tags := []string{
|
tags := []string{
|
||||||
"tool=",
|
"tool=",
|
||||||
fmt.Sprintf("artist=%s", meta.Data[0].Attributes.ArtistName),
|
fmt.Sprintf("artist=%s", meta.Data[0].Attributes.ArtistName),
|
||||||
fmt.Sprintf("lyrics=%s", lrc),
|
//fmt.Sprintf("lyrics=%s", lrc),
|
||||||
}
|
}
|
||||||
var trackCovPath string
|
var trackCovPath string
|
||||||
if Config.EmbedCover {
|
if Config.EmbedCover {
|
||||||
@ -699,7 +661,7 @@ func downloadTrack(trackNum int, trackTotal int, meta *structs.AutoGenerated, tr
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err = writeMP4Tags(trackPath, meta, trackNum, trackTotal)
|
err = writeMP4Tags(trackPath, lrc, meta, trackNum, trackTotal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("\u26A0 Failed to write tags in media:", err)
|
fmt.Println("\u26A0 Failed to write tags in media:", err)
|
||||||
counter.Unavailable++
|
counter.Unavailable++
|
||||||
@ -1103,7 +1065,7 @@ func rip(albumId string, token string, storefront string, mediaUserToken string,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeMP4Tags(trackPath string, meta *structs.AutoGenerated, trackNum, trackTotal int) error {
|
func writeMP4Tags(trackPath, lrc string, meta *structs.AutoGenerated, trackNum, trackTotal int) error {
|
||||||
index := trackNum - 1
|
index := trackNum - 1
|
||||||
|
|
||||||
t := &mp4tag.MP4Tags{
|
t := &mp4tag.MP4Tags{
|
||||||
@ -1124,6 +1086,7 @@ func writeMP4Tags(trackPath string, meta *structs.AutoGenerated, trackNum, track
|
|||||||
CustomGenre: meta.Data[0].Relationships.Tracks.Data[index].Attributes.GenreNames[0],
|
CustomGenre: meta.Data[0].Relationships.Tracks.Data[index].Attributes.GenreNames[0],
|
||||||
Copyright: meta.Data[0].Attributes.Copyright,
|
Copyright: meta.Data[0].Attributes.Copyright,
|
||||||
Publisher: meta.Data[0].Attributes.RecordLabel,
|
Publisher: meta.Data[0].Attributes.RecordLabel,
|
||||||
|
Lyrics: lrc,
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.Contains(meta.Data[0].ID, "pl.") {
|
if !strings.Contains(meta.Data[0].ID, "pl.") {
|
||||||
@ -1563,184 +1526,6 @@ func extractMvAudio(c string) (string, error) {
|
|||||||
return audioStreams[0].URL, nil
|
return audioStreams[0].URL, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func conventSyllableTTMLToLRC(ttml string) (string, error) {
|
|
||||||
parsedTTML := etree.NewDocument()
|
|
||||||
err := parsedTTML.ReadFromString(ttml)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
var lrcLines []string
|
|
||||||
parseTime := func(timeValue string) (string, error) {
|
|
||||||
var h, m, s, ms int
|
|
||||||
if strings.Contains(timeValue, ":") {
|
|
||||||
_, err = fmt.Sscanf(timeValue, "%d:%d:%d.%d", &h, &m, &s, &ms)
|
|
||||||
if err != nil {
|
|
||||||
_, err = fmt.Sscanf(timeValue, "%d:%d.%d", &m, &s, &ms)
|
|
||||||
h = 0
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_, err = fmt.Sscanf(timeValue, "%d.%d", &s, &ms)
|
|
||||||
h, m = 0, 0
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
m += h * 60
|
|
||||||
ms = ms / 10
|
|
||||||
return fmt.Sprintf("[%02d:%02d.%02d]", m, s, ms), nil
|
|
||||||
}
|
|
||||||
divs := parsedTTML.FindElement("tt").FindElement("body").FindElements("div")
|
|
||||||
//get trans
|
|
||||||
if len(parsedTTML.FindElement("tt").FindElements("head")) > 0 {
|
|
||||||
if len(parsedTTML.FindElement("tt").FindElement("head").FindElements("metadata")) > 0 {
|
|
||||||
Metadata := parsedTTML.FindElement("tt").FindElement("head").FindElement("metadata")
|
|
||||||
if len(Metadata.FindElements("iTunesMetadata")) > 0 {
|
|
||||||
iTunesMetadata := Metadata.FindElement("iTunesMetadata")
|
|
||||||
if len(iTunesMetadata.FindElements("translations")) > 0 {
|
|
||||||
if len(iTunesMetadata.FindElement("translations").FindElements("translation")) > 0 {
|
|
||||||
divs = iTunesMetadata.FindElement("translations").FindElements("translation")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, div := range divs {
|
|
||||||
for _, item := range div.ChildElements() {
|
|
||||||
var lrcSyllables []string
|
|
||||||
var i int = 0
|
|
||||||
var endTime string
|
|
||||||
for _, lyrics := range item.Child {
|
|
||||||
if _, ok := lyrics.(*etree.CharData); ok {
|
|
||||||
if i > 0 {
|
|
||||||
lrcSyllables = append(lrcSyllables, " ")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
lyric := lyrics.(*etree.Element)
|
|
||||||
if lyric.SelectAttr("begin") == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
beginTime, err := parseTime(lyric.SelectAttr("begin").Value)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
endTime, err = parseTime(lyric.SelectAttr("end").Value)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
var text string
|
|
||||||
if lyric.SelectAttr("text") == nil {
|
|
||||||
var textTmp []string
|
|
||||||
for _, span := range lyric.Child {
|
|
||||||
if _, ok := span.(*etree.CharData); ok {
|
|
||||||
textTmp = append(textTmp, span.(*etree.CharData).Data)
|
|
||||||
} else {
|
|
||||||
textTmp = append(textTmp, span.(*etree.Element).Text())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
text = strings.Join(textTmp, "")
|
|
||||||
} else {
|
|
||||||
text = lyric.SelectAttr("text").Value
|
|
||||||
}
|
|
||||||
lrcSyllables = append(lrcSyllables, fmt.Sprintf("%s%s", beginTime, text))
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
//endTime, err := parseTime(item.SelectAttr("end").Value)
|
|
||||||
//if err != nil {
|
|
||||||
// return "", err
|
|
||||||
//}
|
|
||||||
lrcLines = append(lrcLines, strings.Join(lrcSyllables, "")+endTime)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strings.Join(lrcLines, "\n"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func conventTTMLToLRC(ttml string) (string, error) {
|
|
||||||
parsedTTML := etree.NewDocument()
|
|
||||||
err := parsedTTML.ReadFromString(ttml)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
var lrcLines []string
|
|
||||||
timingAttr := parsedTTML.FindElement("tt").SelectAttr("itunes:timing")
|
|
||||||
if timingAttr != nil {
|
|
||||||
if timingAttr.Value == "Word" {
|
|
||||||
lrc, err := conventSyllableTTMLToLRC(ttml)
|
|
||||||
return lrc, err
|
|
||||||
}
|
|
||||||
if timingAttr.Value == "None" {
|
|
||||||
for _, p := range parsedTTML.FindElements("//p") {
|
|
||||||
line := p.Text()
|
|
||||||
line = strings.TrimSpace(line)
|
|
||||||
if line != "" {
|
|
||||||
lrcLines = append(lrcLines, line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strings.Join(lrcLines, "\n"), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, item := range parsedTTML.FindElement("tt").FindElement("body").ChildElements() {
|
|
||||||
for _, lyric := range item.ChildElements() {
|
|
||||||
var h, m, s, ms int
|
|
||||||
if lyric.SelectAttr("begin") == nil {
|
|
||||||
return "", errors.New("no synchronised lyrics")
|
|
||||||
}
|
|
||||||
if strings.Contains(lyric.SelectAttr("begin").Value, ":") {
|
|
||||||
_, err = fmt.Sscanf(lyric.SelectAttr("begin").Value, "%d:%d:%d.%d", &h, &m, &s, &ms)
|
|
||||||
if err != nil {
|
|
||||||
_, err = fmt.Sscanf(lyric.SelectAttr("begin").Value, "%d:%d.%d", &m, &s, &ms)
|
|
||||||
if err != nil {
|
|
||||||
_, err = fmt.Sscanf(lyric.SelectAttr("begin").Value, "%d:%d", &m, &s)
|
|
||||||
}
|
|
||||||
h = 0
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_, err = fmt.Sscanf(lyric.SelectAttr("begin").Value, "%d.%d", &s, &ms)
|
|
||||||
h, m = 0, 0
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
var text string
|
|
||||||
//GET trans
|
|
||||||
if len(parsedTTML.FindElement("tt").FindElements("head")) > 0 {
|
|
||||||
if len(parsedTTML.FindElement("tt").FindElement("head").FindElements("metadata")) > 0 {
|
|
||||||
Metadata := parsedTTML.FindElement("tt").FindElement("head").FindElement("metadata")
|
|
||||||
if len(Metadata.FindElements("iTunesMetadata")) > 0 {
|
|
||||||
iTunesMetadata := Metadata.FindElement("iTunesMetadata")
|
|
||||||
if len(iTunesMetadata.FindElements("translations")) > 0 {
|
|
||||||
if len(iTunesMetadata.FindElement("translations").FindElements("translation")) > 0 {
|
|
||||||
xpath := fmt.Sprintf("//text[@for='%s']", lyric.SelectAttr("itunes:key").Value)
|
|
||||||
trans := iTunesMetadata.FindElement("translations").FindElement("translation").FindElement(xpath)
|
|
||||||
lyric = trans
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if lyric.SelectAttr("text") == nil {
|
|
||||||
var textTmp []string
|
|
||||||
for _, span := range lyric.Child {
|
|
||||||
if _, ok := span.(*etree.CharData); ok {
|
|
||||||
textTmp = append(textTmp, span.(*etree.CharData).Data)
|
|
||||||
} else {
|
|
||||||
textTmp = append(textTmp, span.(*etree.Element).Text())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
text = strings.Join(textTmp, "")
|
|
||||||
} else {
|
|
||||||
text = lyric.SelectAttr("text").Value
|
|
||||||
}
|
|
||||||
m += h * 60
|
|
||||||
ms = ms / 10
|
|
||||||
lrcLines = append(lrcLines, fmt.Sprintf("[%02d:%02d.%02d]%s", m, s, ms, text))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strings.Join(lrcLines, "\n"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkM3u8(b string, f string) (string, error) {
|
func checkM3u8(b string, f string) (string, error) {
|
||||||
var EnhancedHls string
|
var EnhancedHls string
|
||||||
|
253
utils/lyrics/lyrics.go
Normal file
253
utils/lyrics/lyrics.go
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
package lyrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/beevik/etree"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SongLyrics struct {
|
||||||
|
Data []struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Attributes struct {
|
||||||
|
Ttml string `json:"ttml"`
|
||||||
|
PlayParams struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
CatalogId string `json:"catalogId"`
|
||||||
|
DisplayType int `json:"displayType"`
|
||||||
|
} `json:"playParams"`
|
||||||
|
} `json:"attributes"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func Get(storefront, songId, lrcType, language, lrcFormat, token, mediaUserToken string) (string, error) {
|
||||||
|
if len(mediaUserToken) < 50 {
|
||||||
|
return "", errors.New("MediaUserToken not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
ttml, err := getSongLyrics(songId, storefront, token, mediaUserToken, lrcType, language)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if lrcFormat == "ttml" {
|
||||||
|
return ttml, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
lrc, err := TtmlToLrc(ttml)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return lrc, nil
|
||||||
|
}
|
||||||
|
func getSongLyrics(songId string, storefront string, token string, userToken string, lrcType string, language string) (string, error) {
|
||||||
|
req, err := http.NewRequest("GET",
|
||||||
|
fmt.Sprintf("https://amp-api.music.apple.com/v1/catalog/%s/songs/%s/%s?l=%s", storefront, songId, lrcType, language), nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
req.Header.Set("Origin", "https://music.apple.com")
|
||||||
|
req.Header.Set("Referer", "https://music.apple.com/")
|
||||||
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||||
|
cookie := http.Cookie{Name: "media-user-token", Value: userToken}
|
||||||
|
req.AddCookie(&cookie)
|
||||||
|
do, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer do.Body.Close()
|
||||||
|
obj := new(SongLyrics)
|
||||||
|
_ = json.NewDecoder(do.Body).Decode(&obj)
|
||||||
|
if obj.Data != nil {
|
||||||
|
return obj.Data[0].Attributes.Ttml, nil
|
||||||
|
} else {
|
||||||
|
return "", errors.New("failed to get lyrics")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TtmlToLrc(ttml string) (string, error) {
|
||||||
|
parsedTTML := etree.NewDocument()
|
||||||
|
err := parsedTTML.ReadFromString(ttml)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var lrcLines []string
|
||||||
|
timingAttr := parsedTTML.FindElement("tt").SelectAttr("itunes:timing")
|
||||||
|
if timingAttr != nil {
|
||||||
|
if timingAttr.Value == "Word" {
|
||||||
|
lrc, err := conventSyllableTTMLToLRC(ttml)
|
||||||
|
return lrc, err
|
||||||
|
}
|
||||||
|
if timingAttr.Value == "None" {
|
||||||
|
for _, p := range parsedTTML.FindElements("//p") {
|
||||||
|
line := p.Text()
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if line != "" {
|
||||||
|
lrcLines = append(lrcLines, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(lrcLines, "\n"), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range parsedTTML.FindElement("tt").FindElement("body").ChildElements() {
|
||||||
|
for _, lyric := range item.ChildElements() {
|
||||||
|
var h, m, s, ms int
|
||||||
|
if lyric.SelectAttr("begin") == nil {
|
||||||
|
return "", errors.New("no synchronised lyrics")
|
||||||
|
}
|
||||||
|
if strings.Contains(lyric.SelectAttr("begin").Value, ":") {
|
||||||
|
_, err = fmt.Sscanf(lyric.SelectAttr("begin").Value, "%d:%d:%d.%d", &h, &m, &s, &ms)
|
||||||
|
if err != nil {
|
||||||
|
_, err = fmt.Sscanf(lyric.SelectAttr("begin").Value, "%d:%d.%d", &m, &s, &ms)
|
||||||
|
if err != nil {
|
||||||
|
_, err = fmt.Sscanf(lyric.SelectAttr("begin").Value, "%d:%d", &m, &s)
|
||||||
|
}
|
||||||
|
h = 0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_, err = fmt.Sscanf(lyric.SelectAttr("begin").Value, "%d.%d", &s, &ms)
|
||||||
|
h, m = 0, 0
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
var text string
|
||||||
|
//GET trans
|
||||||
|
if len(parsedTTML.FindElement("tt").FindElements("head")) > 0 {
|
||||||
|
if len(parsedTTML.FindElement("tt").FindElement("head").FindElements("metadata")) > 0 {
|
||||||
|
Metadata := parsedTTML.FindElement("tt").FindElement("head").FindElement("metadata")
|
||||||
|
if len(Metadata.FindElements("iTunesMetadata")) > 0 {
|
||||||
|
iTunesMetadata := Metadata.FindElement("iTunesMetadata")
|
||||||
|
if len(iTunesMetadata.FindElements("translations")) > 0 {
|
||||||
|
if len(iTunesMetadata.FindElement("translations").FindElements("translation")) > 0 {
|
||||||
|
xpath := fmt.Sprintf("//text[@for='%s']", lyric.SelectAttr("itunes:key").Value)
|
||||||
|
trans := iTunesMetadata.FindElement("translations").FindElement("translation").FindElement(xpath)
|
||||||
|
lyric = trans
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if lyric.SelectAttr("text") == nil {
|
||||||
|
var textTmp []string
|
||||||
|
for _, span := range lyric.Child {
|
||||||
|
if _, ok := span.(*etree.CharData); ok {
|
||||||
|
textTmp = append(textTmp, span.(*etree.CharData).Data)
|
||||||
|
} else {
|
||||||
|
textTmp = append(textTmp, span.(*etree.Element).Text())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text = strings.Join(textTmp, "")
|
||||||
|
} else {
|
||||||
|
text = lyric.SelectAttr("text").Value
|
||||||
|
}
|
||||||
|
m += h * 60
|
||||||
|
ms = ms / 10
|
||||||
|
lrcLines = append(lrcLines, fmt.Sprintf("[%02d:%02d.%02d]%s", m, s, ms, text))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(lrcLines, "\n"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func conventSyllableTTMLToLRC(ttml string) (string, error) {
|
||||||
|
parsedTTML := etree.NewDocument()
|
||||||
|
err := parsedTTML.ReadFromString(ttml)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
var lrcLines []string
|
||||||
|
parseTime := func(timeValue string) (string, error) {
|
||||||
|
var h, m, s, ms int
|
||||||
|
if strings.Contains(timeValue, ":") {
|
||||||
|
_, err = fmt.Sscanf(timeValue, "%d:%d:%d.%d", &h, &m, &s, &ms)
|
||||||
|
if err != nil {
|
||||||
|
_, err = fmt.Sscanf(timeValue, "%d:%d.%d", &m, &s, &ms)
|
||||||
|
h = 0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_, err = fmt.Sscanf(timeValue, "%d.%d", &s, &ms)
|
||||||
|
h, m = 0, 0
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
m += h * 60
|
||||||
|
ms = ms / 10
|
||||||
|
return fmt.Sprintf("[%02d:%02d.%02d]", m, s, ms), nil
|
||||||
|
}
|
||||||
|
divs := parsedTTML.FindElement("tt").FindElement("body").FindElements("div")
|
||||||
|
//get trans
|
||||||
|
if len(parsedTTML.FindElement("tt").FindElements("head")) > 0 {
|
||||||
|
if len(parsedTTML.FindElement("tt").FindElement("head").FindElements("metadata")) > 0 {
|
||||||
|
Metadata := parsedTTML.FindElement("tt").FindElement("head").FindElement("metadata")
|
||||||
|
if len(Metadata.FindElements("iTunesMetadata")) > 0 {
|
||||||
|
iTunesMetadata := Metadata.FindElement("iTunesMetadata")
|
||||||
|
if len(iTunesMetadata.FindElements("translations")) > 0 {
|
||||||
|
if len(iTunesMetadata.FindElement("translations").FindElements("translation")) > 0 {
|
||||||
|
divs = iTunesMetadata.FindElement("translations").FindElements("translation")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, div := range divs {
|
||||||
|
for _, item := range div.ChildElements() {
|
||||||
|
var lrcSyllables []string
|
||||||
|
var i int = 0
|
||||||
|
var endTime string
|
||||||
|
for _, lyrics := range item.Child {
|
||||||
|
if _, ok := lyrics.(*etree.CharData); ok {
|
||||||
|
if i > 0 {
|
||||||
|
lrcSyllables = append(lrcSyllables, " ")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lyric := lyrics.(*etree.Element)
|
||||||
|
if lyric.SelectAttr("begin") == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
beginTime, err := parseTime(lyric.SelectAttr("begin").Value)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
endTime, err = parseTime(lyric.SelectAttr("end").Value)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
var text string
|
||||||
|
if lyric.SelectAttr("text") == nil {
|
||||||
|
var textTmp []string
|
||||||
|
for _, span := range lyric.Child {
|
||||||
|
if _, ok := span.(*etree.CharData); ok {
|
||||||
|
textTmp = append(textTmp, span.(*etree.CharData).Data)
|
||||||
|
} else {
|
||||||
|
textTmp = append(textTmp, span.(*etree.Element).Text())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text = strings.Join(textTmp, "")
|
||||||
|
} else {
|
||||||
|
text = lyric.SelectAttr("text").Value
|
||||||
|
}
|
||||||
|
lrcSyllables = append(lrcSyllables, fmt.Sprintf("%s%s", beginTime, text))
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
//endTime, err := parseTime(item.SelectAttr("end").Value)
|
||||||
|
//if err != nil {
|
||||||
|
// return "", err
|
||||||
|
//}
|
||||||
|
lrcLines = append(lrcLines, strings.Join(lrcSyllables, "")+endTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(lrcLines, "\n"), nil
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user