gomu/utils.go

379 lines
8.4 KiB
Go
Raw Permalink Normal View History

2020-06-22 00:05:56 +08:00
// Copyright (C) 2020 Raziman
2020-06-19 16:22:20 +08:00
package main
2020-06-24 11:34:20 +08:00
import (
"bytes"
"errors"
2020-06-27 17:16:49 +08:00
"fmt"
2020-07-02 16:11:10 +08:00
"log"
2020-06-26 12:54:48 +08:00
"net/http"
2020-06-24 11:34:20 +08:00
"os"
"os/exec"
2020-06-25 14:12:23 +08:00
"path"
2020-08-11 13:39:02 +08:00
"path/filepath"
2020-06-27 17:16:49 +08:00
"regexp"
"strconv"
2020-06-24 11:34:20 +08:00
"strings"
"time"
2020-07-21 12:22:00 +08:00
"github.com/tramhao/id3v2"
2020-07-21 12:22:00 +08:00
"github.com/ztrue/tracerr"
2021-03-13 01:13:45 +08:00
"github.com/issadarkthing/gomu/lyric"
"github.com/issadarkthing/gomu/player"
2020-06-24 11:34:20 +08:00
)
2020-06-19 16:22:20 +08:00
2021-02-15 12:14:03 +08:00
// logError logs the error message.
2020-07-23 15:15:39 +08:00
func logError(err error) {
2021-02-14 13:45:43 +08:00
log.Println("[ERROR]", tracerr.Sprint(err))
}
2021-02-15 12:14:03 +08:00
func logDebug(msg string) {
log.Println("[DEBUG]", msg)
}
2021-02-14 13:45:43 +08:00
// die logs the error message and call os.Exit(1)
2021-02-15 12:14:03 +08:00
// prefer this instead of panic
2021-02-14 13:45:43 +08:00
func die(err error) {
logError(err)
2021-02-18 09:49:41 +08:00
fmt.Fprintln(os.Stderr, err)
2021-02-14 13:45:43 +08:00
os.Exit(1)
}
2020-07-23 15:34:56 +08:00
// Formats duration to my desired output mm:ss
2020-06-24 11:34:20 +08:00
func fmtDuration(input time.Duration) string {
val := input.Round(time.Second).String()
if !strings.Contains(val, "m") {
val = "0m" + val
}
val = strings.ReplaceAll(val, "h", ":")
val = strings.ReplaceAll(val, "m", ":")
val = strings.ReplaceAll(val, "s", "")
var result []string
for _, v := range strings.Split(val, ":") {
2020-06-26 12:54:48 +08:00
2020-06-24 11:34:20 +08:00
if len(v) < 2 {
2020-06-26 12:54:48 +08:00
result = append(result, "0"+v)
2020-06-24 11:34:20 +08:00
} else {
result = append(result, v)
}
}
return strings.Join(result, ":")
}
2020-06-25 14:12:23 +08:00
2020-07-24 14:52:42 +08:00
// fmtDurationH returns the formatted duration `x hr x min`
func fmtDurationH(input time.Duration) string {
re := regexp.MustCompile(`\d+s`)
val := input.Round(time.Second).String()
// remove seconds
result := re.ReplaceAllString(val, "")
result = strings.Replace(result, "h", " hr ", 1)
2020-07-24 17:08:34 +08:00
result = strings.Replace(result, "m", " min", 1)
2020-07-24 14:52:42 +08:00
2020-07-26 00:09:13 +08:00
if result == "" {
return "0 hr 0 min"
}
2020-07-24 14:52:42 +08:00
return result
}
2020-08-11 13:39:02 +08:00
// Expands relative path to absolute path and tilde to /home/(user)
func expandFilePath(path string) string {
p := expandTilde(path)
if filepath.IsAbs(p) {
return p
}
p, err := filepath.Abs(p)
if err != nil {
2021-02-15 12:14:03 +08:00
die(err)
2020-08-11 13:39:02 +08:00
}
return p
}
2020-07-23 15:34:56 +08:00
// Expands tilde alias to /home/user
2020-06-25 14:12:23 +08:00
func expandTilde(_path string) string {
if !strings.HasPrefix(_path, "~") {
return _path
}
2020-06-26 12:54:48 +08:00
home, err := os.UserHomeDir()
2020-06-25 14:12:23 +08:00
if err != nil {
2021-02-15 12:14:03 +08:00
die(err)
2020-06-25 14:12:23 +08:00
}
return path.Join(home, strings.TrimPrefix(_path, "~"))
}
2020-06-26 12:54:48 +08:00
2020-07-23 15:15:39 +08:00
// Detects the filetype of file
func getFileContentType(out *os.File) (string, error) {
2020-06-26 12:54:48 +08:00
buffer := make([]byte, 512)
_, err := out.Read(buffer)
if err != nil {
2020-07-21 12:22:00 +08:00
return "", tracerr.Wrap(err)
2020-06-26 12:54:48 +08:00
}
contentType := http.DetectContentType(buffer)
return strings.SplitAfter(contentType, "/")[1], nil
}
2020-07-23 15:15:39 +08:00
// Gets the file name by removing extension and path
func getName(fn string) string {
2020-07-12 17:04:33 +08:00
return strings.TrimSuffix(path.Base(fn), ".mp3")
2020-06-26 12:54:48 +08:00
}
2020-06-27 17:16:49 +08:00
2020-07-23 15:34:56 +08:00
// This just parsing the output from the ytdl to get the audio path
// This is used because we need to get the song name
2020-06-27 17:16:49 +08:00
// example ~/path/to/song/song.mp3
2020-07-12 17:04:33 +08:00
func extractFilePath(output []byte, dir string) string {
2020-06-27 17:16:49 +08:00
regexSearch := fmt.Sprintf(`\[ffmpeg\] Destination: %s\/.*.mp3`,
escapeBackSlash(dir))
parseAudioPathOnly := regexp.MustCompile(`\/.*mp3$`)
re := regexp.MustCompile(regexSearch)
return string(parseAudioPathOnly.Find(re.Find(output)))
}
func escapeBackSlash(input string) string {
return strings.ReplaceAll(input, "/", `\/`)
}
2020-07-24 17:08:34 +08:00
// progresStr creates a simple progress bar
// example: =====-----
func progresStr(progress, maxProgress, maxLength int,
fill, empty string) string {
currLength := maxLength * progress / maxProgress
return fmt.Sprintf("%s%s",
strings.Repeat(fill, currLength),
strings.Repeat(empty, maxLength-currLength),
)
}
2020-07-25 22:53:25 +08:00
// padHex pad the neccessary 0 to create six hex digit
func padHex(r, g, b int32) string {
var result strings.Builder
for _, v := range []int32{r, g, b} {
hex := fmt.Sprintf("%x", v)
if len(hex) == 1 {
result.WriteString(fmt.Sprintf("0%s", hex))
} else {
result.WriteString(hex)
}
}
return result.String()
2020-07-28 11:24:58 +08:00
}
2020-07-25 22:53:25 +08:00
2020-08-22 14:41:03 +08:00
func validHexColor(color string) bool {
2020-07-28 11:24:58 +08:00
reg := regexp.MustCompile(`^#([A-Fa-f0-9]{6})$`)
return reg.MatchString(color)
2020-07-25 22:53:25 +08:00
}
2020-08-18 00:26:28 +08:00
func contains(needle int, haystack []int) bool {
for _, i := range haystack {
if needle == i {
return true
}
}
return false
}
2020-08-18 16:09:43 +08:00
// appendFile appends to a file, create the file if not exists
2020-08-18 16:09:43 +08:00
func appendFile(path string, content string) error {
f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
if err != os.ErrNotExist {
return tracerr.Wrap(err)
}
// create the neccessary parent directory
err = os.MkdirAll(filepath.Dir(expandFilePath(path)), os.ModePerm)
if err != nil {
return tracerr.Wrap(err)
}
2020-08-18 16:09:43 +08:00
}
2020-09-25 17:14:07 +08:00
defer f.Close()
2020-08-18 16:09:43 +08:00
if _, err := f.WriteString(content); err != nil {
return tracerr.Wrap(err)
}
return nil
}
func shell(input string) (string, error) {
args := strings.Split(input, " ")
for i, arg := range args {
args[i] = strings.Trim(arg, " ")
}
cmd := exec.Command(args[0], args[1:]...)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
return "", err
}
if stderr.Len() != 0 {
return "", errors.New(stderr.String())
}
return stdout.String(), nil
}
func embedLyric(songPath string, lyricTobeWritten *lyric.Lyric, isDelete bool) (err error) {
var tag *id3v2.Tag
tag, err = id3v2.Open(songPath, id3v2.Options{Parse: true})
if err != nil {
return tracerr.Wrap(err)
}
defer tag.Close()
usltFrames := tag.GetFrames(tag.CommonID("Unsynchronised lyrics/text transcription"))
tag.DeleteFrames(tag.CommonID("Unsynchronised lyrics/text transcription"))
// We delete the lyric frame with same language by delete all and add others back
for _, f := range usltFrames {
uslf, ok := f.(id3v2.UnsynchronisedLyricsFrame)
if !ok {
die(errors.New("uslt error"))
}
if uslf.ContentDescriptor == lyricTobeWritten.LangExt {
continue
}
tag.AddUnsynchronisedLyricsFrame(uslf)
}
2021-03-13 01:13:45 +08:00
syltFrames := tag.GetFrames(tag.CommonID("Synchronised lyrics/text"))
tag.DeleteFrames(tag.CommonID("Synchronised lyrics/text"))
for _, f := range syltFrames {
sylf, ok := f.(id3v2.SynchronisedLyricsFrame)
if !ok {
die(errors.New("sylt error"))
}
if strings.Contains(sylf.ContentDescriptor, lyricTobeWritten.LangExt) {
2021-03-13 01:13:45 +08:00
continue
}
tag.AddSynchronisedLyricsFrame(sylf)
}
if !isDelete {
tag.AddUnsynchronisedLyricsFrame(id3v2.UnsynchronisedLyricsFrame{
Encoding: id3v2.EncodingUTF8,
Language: "eng",
ContentDescriptor: lyricTobeWritten.LangExt,
Lyrics: lyricTobeWritten.AsLRC(),
})
2021-03-25 16:57:44 +08:00
var lyric lyric.Lyric
err := lyric.NewFromLRC(lyricTobeWritten.AsLRC())
if err != nil {
return tracerr.Wrap(err)
2021-03-13 01:13:45 +08:00
}
tag.AddSynchronisedLyricsFrame(id3v2.SynchronisedLyricsFrame{
Encoding: id3v2.EncodingUTF8,
Language: "eng",
TimestampFormat: 2,
ContentType: 1,
ContentDescriptor: lyricTobeWritten.LangExt,
SynchronizedTexts: lyric.SyncedCaptions,
2021-03-13 01:13:45 +08:00
})
2021-03-13 01:13:45 +08:00
}
err = tag.Save()
if err != nil {
return tracerr.Wrap(err)
}
2021-03-13 01:13:45 +08:00
return err
2021-03-13 01:13:45 +08:00
}
func embedLength(songPath string) (time.Duration, error) {
tag, err := id3v2.Open(songPath, id3v2.Options{Parse: true})
if err != nil {
return 0, tracerr.Wrap(err)
}
defer tag.Close()
var lengthSongTimeDuration time.Duration
lengthSongTimeDuration, err = player.GetLength(songPath)
if err != nil {
2021-03-13 01:13:45 +08:00
return 0, tracerr.Wrap(err)
}
2021-03-07 01:29:54 +08:00
lengthSongString := strconv.FormatInt(lengthSongTimeDuration.Milliseconds(), 10)
lengthFrame := id3v2.UserDefinedTextFrame{
Encoding: id3v2.EncodingUTF8,
Description: "TLEN",
2021-03-07 01:29:54 +08:00
Value: lengthSongString,
}
tag.AddUserDefinedTextFrame(lengthFrame)
err = tag.Save()
if err != nil {
2021-03-13 01:13:45 +08:00
return 0, tracerr.Wrap(err)
}
2021-03-13 01:13:45 +08:00
return lengthSongTimeDuration, err
}
func getTagLength(songPath string) (songLength time.Duration, err error) {
var tag *id3v2.Tag
tag, err = id3v2.Open(songPath, id3v2.Options{Parse: true})
if err != nil {
return 0, tracerr.Wrap(err)
}
defer tag.Close()
tlenFrames := tag.GetFrames(tag.CommonID("User defined text information frame"))
if tlenFrames == nil {
2021-03-13 01:13:45 +08:00
songLength, err = embedLength(songPath)
if err != nil {
return 0, tracerr.Wrap(err)
}
return songLength, nil
}
for _, tlenFrame := range tlenFrames {
if tlenFrame.(id3v2.UserDefinedTextFrame).Description == "TLEN" {
songLengthString := tlenFrame.(id3v2.UserDefinedTextFrame).Value
songLengthInt64, err := strconv.ParseInt(songLengthString, 10, 64)
if err != nil {
return 0, tracerr.Wrap(err)
}
songLength = (time.Duration)(songLengthInt64) * time.Millisecond
break
}
}
2021-03-13 01:13:45 +08:00
if songLength != 0 {
return songLength, nil
}
songLength, err = embedLength(songPath)
if err != nil {
return 0, tracerr.Wrap(err)
}
return songLength, err
}