218 lines
4.0 KiB
Go
218 lines
4.0 KiB
Go
package lib
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"image"
|
|
"image/jpeg"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/disintegration/imaging"
|
|
"github.com/gosimple/slug"
|
|
ffmpeg "github.com/u2takey/ffmpeg-go"
|
|
"github.com/wader/goutubedl"
|
|
"golang.org/x/image/webp"
|
|
"rul.sh/go-ytmp3/utils"
|
|
)
|
|
|
|
func fetchAudio(video *goutubedl.Result, out string, ch chan error) {
|
|
dl, err := video.DownloadWithOptions(context.Background(), goutubedl.DownloadOptions{
|
|
DownloadAudioOnly: true,
|
|
AudioFormats: "best",
|
|
})
|
|
if err != nil {
|
|
ch <- err
|
|
return
|
|
}
|
|
defer dl.Close()
|
|
|
|
f, err := os.Create(out)
|
|
if err != nil {
|
|
ch <- err
|
|
return
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
fmt.Println("Downloading...")
|
|
if _, err := io.Copy(f, dl); err != nil {
|
|
ch <- err
|
|
return
|
|
}
|
|
|
|
ch <- nil
|
|
}
|
|
|
|
func resizeImage(imgName string, src io.Reader, dst *os.File, width int, height int) error {
|
|
var img image.Image
|
|
var err error
|
|
|
|
ext := imgName[strings.LastIndex(imgName, "."):]
|
|
|
|
switch ext {
|
|
case "webp":
|
|
img, err = webp.Decode(src)
|
|
default:
|
|
img, _, err = image.Decode(src)
|
|
}
|
|
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return err
|
|
}
|
|
|
|
img = imaging.Fill(img, width, height, imaging.Center, imaging.Lanczos)
|
|
|
|
return jpeg.Encode(dst, img, nil)
|
|
}
|
|
|
|
func fetchThumbnail(video *goutubedl.Result, thumbnail string, ch chan error) {
|
|
if video.Info.Thumbnail == "" {
|
|
ch <- fmt.Errorf("no thumbnail found")
|
|
return
|
|
}
|
|
|
|
fmt.Println("Downloading thumbnail...")
|
|
|
|
f, err := os.Create(thumbnail)
|
|
if err != nil {
|
|
ch <- err
|
|
return
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
resp, err := http.Get(video.Info.Thumbnail)
|
|
if err != nil {
|
|
ch <- err
|
|
return
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
if err := resizeImage(video.Info.Thumbnail, resp.Body, f, 512, 512); err != nil {
|
|
ch <- err
|
|
return
|
|
}
|
|
|
|
ch <- nil
|
|
}
|
|
|
|
type ConvertOptions struct {
|
|
Video string
|
|
Thumbnail string
|
|
Title string
|
|
Artist string
|
|
Album string
|
|
Output string
|
|
}
|
|
|
|
func convertToMp3(data ConvertOptions, ch chan error) {
|
|
fmt.Println("Converting...")
|
|
|
|
input := []*ffmpeg.Stream{ffmpeg.Input(data.Video).Audio()}
|
|
args := ffmpeg.KwArgs{
|
|
"format": "mp3",
|
|
"ab": "320k",
|
|
"id3v2_version": "3",
|
|
"write_id3v1": "1",
|
|
"metadata": []string{
|
|
fmt.Sprintf("title=%s", data.Title),
|
|
fmt.Sprintf("artist=%s", data.Artist),
|
|
fmt.Sprintf("album=%s", data.Album),
|
|
},
|
|
}
|
|
|
|
if data.Thumbnail != "" {
|
|
input = append(input, ffmpeg.Input(data.Thumbnail).Video())
|
|
}
|
|
|
|
if err := ffmpeg.Output(input, data.Output, args).OverWriteOutput().Run(); err != nil {
|
|
ch <- err
|
|
return
|
|
}
|
|
|
|
ch <- nil
|
|
}
|
|
|
|
func YtGetVideo(url string) (goutubedl.Result, error) {
|
|
return goutubedl.New(context.Background(), url, goutubedl.Options{})
|
|
}
|
|
|
|
type Yt2Mp3Options struct {
|
|
OutDir string
|
|
Slug string
|
|
Thumbnail string
|
|
Title string
|
|
Artist string
|
|
Album string
|
|
}
|
|
|
|
func Yt2Mp3(video *goutubedl.Result, options Yt2Mp3Options) (string, error) {
|
|
if video == nil {
|
|
return "", fmt.Errorf("no video found")
|
|
}
|
|
|
|
tmpDir := utils.GetEnv("TMP_DIR", "/tmp")
|
|
|
|
title := video.Info.Title
|
|
artist := video.Info.Channel
|
|
album := video.Info.Album
|
|
|
|
if video.Info.Artist != "" {
|
|
artist = video.Info.Artist
|
|
}
|
|
|
|
videoSlug := options.Slug
|
|
if len(options.Slug) == 0 {
|
|
videoSlug = slug.Make(title)
|
|
}
|
|
|
|
audioSrc := fmt.Sprintf("%s/%s-src.mp3", tmpDir, videoSlug)
|
|
thumbnail := fmt.Sprintf("%s/%s.jpg", tmpDir, videoSlug)
|
|
out := fmt.Sprintf("%s/%s.mp3", options.OutDir, videoSlug)
|
|
|
|
if err := os.MkdirAll(options.OutDir, os.ModePerm); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
videoCh := make(chan error)
|
|
thumbCh := make(chan error)
|
|
|
|
go fetchAudio(video, audioSrc, videoCh)
|
|
go fetchThumbnail(video, thumbnail, thumbCh)
|
|
|
|
err := <-videoCh
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
err = <-thumbCh
|
|
if err != nil {
|
|
thumbnail = ""
|
|
}
|
|
|
|
fmt.Println(artist, album)
|
|
|
|
convertCh := make(chan error)
|
|
|
|
go convertToMp3(ConvertOptions{
|
|
Video: audioSrc,
|
|
Thumbnail: thumbnail,
|
|
Title: title,
|
|
Artist: artist,
|
|
Album: album,
|
|
Output: out,
|
|
}, convertCh)
|
|
|
|
err = <-convertCh
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return out, nil
|
|
}
|