Add Downloader interface

This commit is contained in:
Andrea Fazzi 2021-10-25 07:49:48 +02:00
parent 814e47ef40
commit 2e1c34f0c4
14 changed files with 701 additions and 131 deletions

View file

@ -6,4 +6,5 @@ require (
github.com/gin-contrib/cors v1.3.1 github.com/gin-contrib/cors v1.3.1
github.com/gin-gonic/gin v1.7.4 github.com/gin-gonic/gin v1.7.4
github.com/kkdai/youtube/v2 v2.7.4 github.com/kkdai/youtube/v2 v2.7.4
github.com/remogatto/prettytest v0.0.0-20200211072524-6d385e11dcb8 // indirect
) )

View file

@ -227,8 +227,10 @@ github.com/kkdai/youtube/v2 v2.7.4/go.mod h1:XMmc0kbC9q/gPc6i881DjFYK53CuAAFrg1c
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
@ -282,6 +284,8 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/remogatto/prettytest v0.0.0-20200211072524-6d385e11dcb8 h1:nRDwTcxV9B3elxMt+1xINX0bwaPdpouqp5fbynexY8U=
github.com/remogatto/prettytest v0.0.0-20200211072524-6d385e11dcb8/go.mod h1:jOEnp79oIHy5cvQSHeLcgVJk1GHOOHJHQWps/d1N5Yo=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
@ -675,6 +679,7 @@ google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/l
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=

View file

@ -6,43 +6,53 @@ import (
"net/http" "net/http"
"time" "time"
"git.andreafazzi.eu/andrea/youtube-dl-service/task"
"git.andreafazzi.eu/andrea/youtube-dl-service/youtube"
youtube_dl "git.andreafazzi.eu/andrea/youtube-dl-service/youtube/youtube-dl"
"github.com/gin-contrib/cors" "github.com/gin-contrib/cors"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/kkdai/youtube/v2"
) )
const ( var tasks task.Tasks
StatusDownloading = iota
StatusCompleted
StatusError
)
type Task struct {
Video *youtube.Video
Status int
}
var taskCh chan *Task
func init() { func init() {
log.SetPrefix("[youtube-dl-service] ") log.SetPrefix("[youtube-dl-service] ")
taskCh = make(chan *Task) tasks = make(task.Tasks, 0)
}
func download(c *gin.Context, downloader youtube.Downloader) error {
video, err := downloader.GetVideo()
if err != nil {
return err
}
c.JSON(200, gin.H{
"title": video.Title,
"duration": float64(video.Duration) / float64(time.Minute),
"thumbnails": video.Thumbnails[0],
})
go downloader.StartDownload(video, tasks)
return nil
}
func status(c *gin.Context, downloader youtube.Downloader) error {
id, err := downloader.ExtractVideoID()
if err != nil {
return err
}
task, ok := tasks[id]
if ok {
c.JSON(200, gin.H{
"status": task.Status,
"download_path": task.DownloadPath,
})
}
return nil
} }
func main() { func main() {
go func() {
log.Println("Start task scheduler...")
for {
select {
case task := <-taskCh:
log.Println(task.Status)
}
}
log.Println("Stop task scheduler...")
}()
r := gin.Default() r := gin.Default()
r.Use(cors.New(cors.Config{ r.Use(cors.New(cors.Config{
AllowOrigins: []string{"http://localhost:8080", "http://localhost:5000"}, AllowOrigins: []string{"http://localhost:8080", "http://localhost:5000"},
@ -57,44 +67,18 @@ func main() {
c.AbortWithStatus(http.StatusInternalServerError) c.AbortWithStatus(http.StatusInternalServerError)
})) }))
r.GET("/get_video", func(c *gin.Context) { r.Static("/data", "./data")
id, err := youtube.ExtractVideoID(c.Query("url"))
if err != nil { r.GET("/download", func(c *gin.Context) {
if err := download(c, youtube_dl.NewYoutubeDlDownloader(c.Query("url"), "yt-dlp")); err != nil {
panic(err) panic(err)
} }
})
log.Printf("Extracted video ID: %s", id) r.GET("/status", func(c *gin.Context) {
if err := status(c, youtube_dl.NewYoutubeDlDownloader(c.Query("url"), "yt-dlp")); err != nil {
client := youtube.Client{}
video, err := client.GetVideo(id)
if err != nil {
panic(err) panic(err)
} }
c.JSON(200, gin.H{
"title": video.Title,
"duration": float64(video.Duration) / float64(time.Minute),
"thumbnails": video.Thumbnails[0],
})
go func() {
log.Println("Download started.")
taskCh <- &Task{
video,
StatusDownloading,
}
// simulate a long task with time.Sleep(). 5 seconds
time.Sleep(5 * time.Second)
// note that you are using the copied context "cCp", IMPORTANT
log.Printf("Video with id %s downloaded.", video.ID)
taskCh <- &Task{
video,
StatusCompleted,
}
}()
}) })
r.Run() r.Run()

14
backend/task/task.go Normal file
View file

@ -0,0 +1,14 @@
package task
const (
StatusDownloading = iota + 1
StatusCompleted
StatusError
)
type Tasks map[string]*Task
type Task struct {
Status int
DownloadPath string
}

Binary file not shown.

View file

@ -0,0 +1,29 @@
package youtube
import (
"time"
"git.andreafazzi.eu/andrea/youtube-dl-service/task"
)
type Thumbnails []Thumbnail
type Thumbnail struct {
URL string
Width uint
Height uint
}
type Video struct {
ID string
Title string
Duration time.Duration
Thumbnails Thumbnails
DownloadPath string
}
type Downloader interface {
GetVideo() (*Video, error)
StartDownload(video *Video, tasks task.Tasks)
ExtractVideoID() (string, error)
}

View file

@ -0,0 +1,80 @@
package kkdai_youtube
import (
"io"
"log"
"os"
"path/filepath"
"git.andreafazzi.eu/andrea/youtube-dl-service/task"
"git.andreafazzi.eu/andrea/youtube-dl-service/youtube"
lib "github.com/kkdai/youtube/v2"
)
type LibDownloader struct {
Url string
}
func NewLibDownloader(url string) *LibDownloader {
return &LibDownloader{url}
}
func (d *LibDownloader) GetVideo() (*youtube.Video, error) {
id, err := lib.ExtractVideoID(d.Url)
if err != nil {
panic(err)
}
log.Printf("Extracted video ID: %s", id)
client := lib.Client{}
video, err := client.GetVideo(id)
thumbnails := make(youtube.Thumbnails, 0)
for _, t := range video.Thumbnails {
thumbnails = append(thumbnails, youtube.Thumbnail{URL: t.URL, Width: t.Width, Height: t.Height})
}
return &youtube.Video{
ID: id,
Title: video.Title,
Duration: video.Duration,
Thumbnails: thumbnails,
}, err
}
func (d *LibDownloader) StartDownload(video *youtube.Video, tasks task.Tasks) {
client := lib.Client{}
v, err := client.GetVideo(video.ID)
tasks[video.ID] = &task.Task{
Status: task.StatusDownloading,
}
log.Printf("Download of video ID %s STARTED.", video.ID)
formats := v.Formats.WithAudioChannels() // only get videos with audio
stream, _, err := client.GetStream(v, &formats[0])
if err != nil {
panic(err)
}
file, err := os.Create(filepath.Join("data", video.ID+".mp4"))
if err != nil {
panic(err)
}
defer file.Close()
_, err = io.Copy(file, stream)
if err != nil {
panic(err)
}
log.Printf("Download of video ID %s COMPLETED.", video.ID)
tasks[video.ID] = &task.Task{
Status: task.StatusCompleted,
DownloadPath: filepath.Join("data", video.ID+".mp4"),
}
}
func (d *LibDownloader) ExtractVideoID() (string, error) {
return lib.ExtractVideoID(d.Url)
}

View file

@ -0,0 +1,144 @@
package youtube_dl
import (
"context"
"encoding/json"
"fmt"
"log"
"os/exec"
"path/filepath"
"strconv"
"strings"
"time"
"git.andreafazzi.eu/andrea/youtube-dl-service/task"
"git.andreafazzi.eu/andrea/youtube-dl-service/youtube"
lib "github.com/kkdai/youtube/v2"
)
type YoutubeDlDownloader struct {
Url string
CommandName string
}
func NewYoutubeDlDownloader(url string, commandName string) *YoutubeDlDownloader {
return &YoutubeDlDownloader{url, commandName}
}
func (d *YoutubeDlDownloader) GetVideo() (*youtube.Video, error) {
videoJson, err := d.getVideoJson(d.Url)
thumbnails := make([]youtube.Thumbnail, 0)
thumbnailsJson := videoJson["thumbnails"].([]interface{})
for _, i := range thumbnailsJson {
t := i.(map[string]interface{})
var err error
wString, wOk := t["width"].(string)
hString, hOk := t["height"].(string)
w := 0
h := 0
if wString != "" && hString != "" {
w, err = strconv.Atoi(wString)
if err != nil {
return nil, err
}
h, err = strconv.Atoi(hString)
if err != nil {
return nil, err
}
}
thumbnails = append(thumbnails, youtube.Thumbnail{
URL: t["url"].(string),
Width: uint(w),
Height: uint(h),
})
}
return &youtube.Video{
ID: videoJson["id"].(string),
Title: videoJson["title"].(string),
Duration: time.Duration(videoJson["duration"].(int64) * time.Hour.Nanoseconds()),
Thumbnails: thumbnails,
}, err
}
func (d *YoutubeDlDownloader) StartDownload(video *youtube.Video, tasks task.Tasks) {
tasks[video.ID] = &task.Task{
Status: task.StatusDownloading,
}
log.Printf("Download of video ID %s STARTED.", video.ID)
cmd := exec.CommandContext(context.Background(), d.CommandName, "--newline", d.Url)
stdout, err := cmd.StdoutPipe()
if err != nil {
panic(err)
}
if err = cmd.Start(); err != nil {
panic(err)
}
for {
tmp := make([]byte, 1024)
_, err := stdout.Read(tmp)
fmt.Print(string(tmp))
if err != nil {
break
}
}
log.Printf("Download of video ID %s COMPLETED.", video.ID)
tasks[video.ID] = &task.Task{
Status: task.StatusCompleted,
DownloadPath: filepath.Join("data", video.ID+".mp4"),
}
}
func (d *YoutubeDlDownloader) ExtractVideoID() (string, error) {
return lib.ExtractVideoID(d.Url)
}
func (d *YoutubeDlDownloader) getVideoJson(url string) (map[string]interface{}, error) {
cmd := exec.CommandContext(context.Background(), d.CommandName, "-j", url)
output, err := cmd.Output()
if err != nil {
return nil, err
}
result := make(map[string]interface{}, 0)
err = json.Unmarshal(output, &result)
if err != nil {
return nil, err
}
return result, nil
}
func (d *YoutubeDlDownloader) getThumbnails() ([]youtube.Thumbnail, error) {
thumbnails := make([]youtube.Thumbnail, 0)
cmd := exec.CommandContext(context.Background(), d.CommandName, "--list-thumbnails", d.Url)
output, err := cmd.Output()
if err != nil {
return nil, err
}
lines := strings.Split(string(output), "\n")[3:]
for _, line := range lines {
splits := strings.Split(line, " ")
if len(splits) == 4 {
wString := strings.TrimSpace(splits[1])
hString := strings.TrimSpace(splits[2])
w, err := strconv.Atoi(wString)
if err != nil {
return nil, err
}
h, err := strconv.Atoi(hString)
if err != nil {
return nil, err
}
thumbnails = append(thumbnails, youtube.Thumbnail{
URL: strings.TrimSpace(splits[3]),
Width: uint(w),
Height: uint(h),
})
}
}
return thumbnails, nil
}

View file

@ -0,0 +1 @@
andrea@lv5.88143:1634702979

View file

@ -0,0 +1,144 @@
package youtube_dl
import (
"context"
"encoding/json"
"fmt"
"log"
"os/exec"
"path/filepath"
"strconv"
"strings"
"time"
"git.andreafazzi.eu/andrea/youtube-dl-service/task"
"git.andreafazzi.eu/andrea/youtube-dl-service/youtube"
lib "github.com/kkdai/youtube/v2"
)
type YoutubeDlDownloader struct {
Url string
CommandName string
}
func NewYoutubeDlDownloader(url string, commandName string) *YoutubeDlDownloader {
return &YoutubeDlDownloader{url, commandName}
}
func (d *YoutubeDlDownloader) GetVideo() (*youtube.Video, error) {
videoJson, err := d.getVideoJson(d.Url)
thumbnails := make([]youtube.Thumbnail, 0)
thumbnailsJson := videoJson["thumbnails"].([]interface{})
for _, i := range thumbnailsJson {
t := i.(map[string]interface{})
var err error
wString := t["width"].(string)
hString := t["height"].(string)
w := 0
h := 0
if wString != "" && hString != "" {
w, err = strconv.Atoi(wString)
if err != nil {
return nil, err
}
h, err = strconv.Atoi(hString)
if err != nil {
return nil, err
}
}
thumbnails = append(thumbnails, youtube.Thumbnail{
URL: t["url"].(string),
Width: uint(w),
Height: uint(h),
})
}
return &youtube.Video{
ID: videoJson["id"].(string),
Title: videoJson["title"].(string),
Duration: time.Duration(videoJson["duration"].(int64) * time.Hour.Nanoseconds()),
Thumbnails: thumbnails,
}, err
}
func (d *YoutubeDlDownloader) StartDownload(video *youtube.Video, tasks task.Tasks) {
tasks[video.ID] = &task.Task{
Status: task.StatusDownloading,
}
log.Printf("Download of video ID %s STARTED.", video.ID)
cmd := exec.CommandContext(context.Background(), d.CommandName, "--newline", d.Url)
stdout, err := cmd.StdoutPipe()
if err != nil {
panic(err)
}
if err = cmd.Start(); err != nil {
panic(err)
}
for {
tmp := make([]byte, 1024)
_, err := stdout.Read(tmp)
fmt.Print(string(tmp))
if err != nil {
break
}
}
log.Printf("Download of video ID %s COMPLETED.", video.ID)
tasks[video.ID] = &task.Task{
Status: task.StatusCompleted,
DownloadPath: filepath.Join("data", video.ID+".mp4"),
}
}
func (d *YoutubeDlDownloader) ExtractVideoID() (string, error) {
return lib.ExtractVideoID(d.Url)
}
func (d *YoutubeDlDownloader) getVideoJson(url string) (map[string]interface{}, error) {
cmd := exec.CommandContext(context.Background(), d.CommandName, "-j", url)
output, err := cmd.Output()
if err != nil {
return nil, err
}
result := make(map[string]interface{}, 0)
err = json.Unmarshal(output, &result)
if err != nil {
return nil, err
}
return result, nil
}
func (d *YoutubeDlDownloader) getThumbnails() ([]youtube.Thumbnail, error) {
thumbnails := make([]youtube.Thumbnail, 0)
cmd := exec.CommandContext(context.Background(), d.CommandName, "--list-thumbnails", d.Url)
output, err := cmd.Output()
if err != nil {
return nil, err
}
lines := strings.Split(string(output), "\n")[3:]
for _, line := range lines {
splits := strings.Split(line, " ")
if len(splits) == 4 {
wString := strings.TrimSpace(splits[1])
hString := strings.TrimSpace(splits[2])
w, err := strconv.Atoi(wString)
if err != nil {
return nil, err
}
h, err := strconv.Atoi(hString)
if err != nil {
return nil, err
}
thumbnails = append(thumbnails, youtube.Thumbnail{
URL: strings.TrimSpace(splits[3]),
Width: uint(w),
Height: uint(h),
})
}
}
return thumbnails, nil
}

View file

@ -0,0 +1,29 @@
package youtube_dl
import (
"testing"
"github.com/remogatto/prettytest"
)
// Start of setup
type testSuite struct {
prettytest.Suite
}
func TestRunner(t *testing.T) {
prettytest.Run(
t,
new(testSuite),
)
}
// End of setup
// Tests start here
func (t *testSuite) TestGetThumbnails() {
d := NewYoutubeDlDownloader("https://www.youtube.com/watch?v=mWZ6b_I-Djg", "yt-dlp")
video, err := d.GetVideo()
t.Nil(err)
t.True(video.Thumbnails[0].URL != "")
}

View file

@ -701,7 +701,7 @@ var app = (function () {
} }
// (36:4) {#if url_is_ok} // (36:4) {#if url_is_ok}
function create_if_block$1(ctx) { function create_if_block$2(ctx) {
let button; let button;
let mounted; let mounted;
let dispose; let dispose;
@ -732,7 +732,7 @@ var app = (function () {
dispatch_dev("SvelteRegisterBlock", { dispatch_dev("SvelteRegisterBlock", {
block, block,
id: create_if_block$1.name, id: create_if_block$2.name,
type: "if", type: "if",
source: "(36:4) {#if url_is_ok}", source: "(36:4) {#if url_is_ok}",
ctx ctx
@ -750,7 +750,7 @@ var app = (function () {
let dispose; let dispose;
function select_block_type(ctx, dirty) { function select_block_type(ctx, dirty) {
if (/*url_is_ok*/ ctx[1]) return create_if_block$1; if (/*url_is_ok*/ ctx[1]) return create_if_block$2;
return create_else_block; return create_else_block;
} }
@ -915,10 +915,10 @@ var app = (function () {
const { Error: Error_1 } = globals; const { Error: Error_1 } = globals;
const file$2 = "src/Task.svelte"; const file$2 = "src/Task.svelte";
// (31:0) {:catch error} // (58:0) {:catch error}
function create_catch_block(ctx) { function create_catch_block(ctx) {
let p; let p;
let t_value = /*error*/ ctx[3].message + ""; let t_value = /*error*/ ctx[8].message + "";
let t; let t;
const block = { const block = {
@ -926,7 +926,7 @@ var app = (function () {
p = element("p"); p = element("p");
t = text(t_value); t = text(t_value);
set_style(p, "color", "red"); set_style(p, "color", "red");
add_location(p, file$2, 31, 2, 847); add_location(p, file$2, 58, 2, 1594);
}, },
m: function mount(target, anchor) { m: function mount(target, anchor) {
insert_dev(target, p, anchor); insert_dev(target, p, anchor);
@ -942,38 +942,38 @@ var app = (function () {
block, block,
id: create_catch_block.name, id: create_catch_block.name,
type: "catch", type: "catch",
source: "(31:0) {:catch error}", source: "(58:0) {:catch error}",
ctx ctx
}); });
return block; return block;
} }
// (21:0) {:then video_info} // (46:0) {:then video_info}
function create_then_block(ctx) { function create_then_block(ctx) {
let a; let a;
let div3; let div2;
let div0; let div0;
let h5; let h5;
let t0_value = /*video_info*/ ctx[1].title + ""; let t0_value = /*video_info*/ ctx[3].title + "";
let t0; let t0;
let t1; let t1;
let div1; let div1;
let small0; let small0;
let t3; let t3;
let div2; let t4;
let span; let p;
let small1;
let t5; let t5;
let t6;
let img; let img;
let img_src_value; let img_src_value;
let t6; let if_block = /*status*/ ctx[1].status && create_if_block$1(ctx);
let small1;
let t7;
const block = { const block = {
c: function create() { c: function create() {
a = element("a"); a = element("a");
div3 = element("div"); div2 = element("div");
div0 = element("div"); div0 = element("div");
h5 = element("h5"); h5 = element("h5");
t0 = text(t0_value); t0 = text(t0_value);
@ -982,57 +982,71 @@ var app = (function () {
small0 = element("small"); small0 = element("small");
small0.textContent = "2 minutes ago"; small0.textContent = "2 minutes ago";
t3 = space(); t3 = space();
div2 = element("div"); if (if_block) if_block.c();
span = element("span"); t4 = space();
span.textContent = "Status"; p = element("p");
t5 = space();
img = element("img");
t6 = space();
small1 = element("small"); small1 = element("small");
t7 = text(/*url*/ ctx[0]); t5 = text(/*url*/ ctx[0]);
add_location(h5, file$2, 23, 49, 542); t6 = space();
img = element("img");
add_location(h5, file$2, 48, 49, 1206);
attr_dev(div0, "class", "pb-2 flex-grow-1 bd-highlight"); attr_dev(div0, "class", "pb-2 flex-grow-1 bd-highlight");
add_location(div0, file$2, 23, 6, 499); add_location(div0, file$2, 48, 6, 1163);
add_location(small0, file$2, 24, 42, 618); add_location(small0, file$2, 49, 42, 1282);
attr_dev(div1, "class", "pb-2 px-2 bd-highlight"); attr_dev(div1, "class", "pb-2 px-2 bd-highlight");
add_location(div1, file$2, 24, 6, 582); add_location(div1, file$2, 49, 6, 1246);
attr_dev(span, "class", "badge bg-primary"); attr_dev(div2, "class", "d-flex bd-highlight");
add_location(span, file$2, 25, 42, 695); add_location(div2, file$2, 47, 4, 1123);
attr_dev(div2, "class", "pb-2 px-2 bd-highlight"); add_location(small1, file$2, 54, 7, 1504);
add_location(div2, file$2, 25, 6, 659); add_location(p, file$2, 54, 4, 1501);
attr_dev(div3, "class", "d-flex bd-highlight"); if (!src_url_equal(img.src, img_src_value = /*video_info*/ ctx[3].thumbnails.URL)) attr_dev(img, "src", img_src_value);
add_location(div3, file$2, 22, 4, 459); add_location(img, file$2, 55, 4, 1533);
if (!src_url_equal(img.src, img_src_value = /*video_info*/ ctx[1].thumbnails.URL)) attr_dev(img, "src", img_src_value); attr_dev(a, "href", /*download_path*/ ctx[2]);
add_location(img, file$2, 27, 4, 761);
add_location(small1, file$2, 28, 4, 804);
attr_dev(a, "href", "#");
attr_dev(a, "class", "list-group-item list-group-item-action"); attr_dev(a, "class", "list-group-item list-group-item-action");
attr_dev(a, "aria-current", "true"); attr_dev(a, "aria-current", "true");
add_location(a, file$2, 21, 2, 375); add_location(a, file$2, 46, 2, 1027);
}, },
m: function mount(target, anchor) { m: function mount(target, anchor) {
insert_dev(target, a, anchor); insert_dev(target, a, anchor);
append_dev(a, div3); append_dev(a, div2);
append_dev(div3, div0); append_dev(div2, div0);
append_dev(div0, h5); append_dev(div0, h5);
append_dev(h5, t0); append_dev(h5, t0);
append_dev(div3, t1); append_dev(div2, t1);
append_dev(div3, div1); append_dev(div2, div1);
append_dev(div1, small0); append_dev(div1, small0);
append_dev(div3, t3); append_dev(div2, t3);
append_dev(div3, div2); if (if_block) if_block.m(div2, null);
append_dev(div2, span); append_dev(a, t4);
append_dev(a, t5); append_dev(a, p);
append_dev(a, img); append_dev(p, small1);
append_dev(small1, t5);
append_dev(a, t6); append_dev(a, t6);
append_dev(a, small1); append_dev(a, img);
append_dev(small1, t7);
}, },
p: function update(ctx, dirty) { p: function update(ctx, dirty) {
if (dirty & /*url*/ 1) set_data_dev(t7, /*url*/ ctx[0]); if (/*status*/ ctx[1].status) {
if (if_block) {
if_block.p(ctx, dirty);
} else {
if_block = create_if_block$1(ctx);
if_block.c();
if_block.m(div2, null);
}
} else if (if_block) {
if_block.d(1);
if_block = null;
}
if (dirty & /*url*/ 1) set_data_dev(t5, /*url*/ ctx[0]);
if (dirty & /*download_path*/ 4) {
attr_dev(a, "href", /*download_path*/ ctx[2]);
}
}, },
d: function destroy(detaching) { d: function destroy(detaching) {
if (detaching) detach_dev(a); if (detaching) detach_dev(a);
if (if_block) if_block.d();
} }
}; };
@ -1040,14 +1054,60 @@ var app = (function () {
block, block,
id: create_then_block.name, id: create_then_block.name,
type: "then", type: "then",
source: "(21:0) {:then video_info}", source: "(46:0) {:then video_info}",
ctx ctx
}); });
return block; return block;
} }
// (19:23) <p>waiting...</p> {:then video_info} // (51:6) {#if status.status}
function create_if_block$1(ctx) {
let div;
let span;
let t_value = /*statusBadge*/ ctx[4][/*status*/ ctx[1].status].text + "";
let t;
let span_class_value;
const block = {
c: function create() {
div = element("div");
span = element("span");
t = text(t_value);
attr_dev(span, "class", span_class_value = /*statusBadge*/ ctx[4][/*status*/ ctx[1].status].class);
add_location(span, file$2, 51, 37, 1380);
attr_dev(div, "class", "pb-2 px-2 bd-highlight");
add_location(div, file$2, 51, 1, 1344);
},
m: function mount(target, anchor) {
insert_dev(target, div, anchor);
append_dev(div, span);
append_dev(span, t);
},
p: function update(ctx, dirty) {
if (dirty & /*status*/ 2 && t_value !== (t_value = /*statusBadge*/ ctx[4][/*status*/ ctx[1].status].text + "")) set_data_dev(t, t_value);
if (dirty & /*status*/ 2 && span_class_value !== (span_class_value = /*statusBadge*/ ctx[4][/*status*/ ctx[1].status].class)) {
attr_dev(span, "class", span_class_value);
}
},
d: function destroy(detaching) {
if (detaching) detach_dev(div);
}
};
dispatch_dev("SvelteRegisterBlock", {
block,
id: create_if_block$1.name,
type: "if",
source: "(51:6) {#if status.status}",
ctx
});
return block;
}
// (44:24) <p>waiting...</p> {:then video_info}
function create_pending_block(ctx) { function create_pending_block(ctx) {
let p; let p;
@ -1055,7 +1115,7 @@ var app = (function () {
c: function create() { c: function create() {
p = element("p"); p = element("p");
p.textContent = "waiting..."; p.textContent = "waiting...";
add_location(p, file$2, 19, 2, 336); add_location(p, file$2, 44, 2, 988);
}, },
m: function mount(target, anchor) { m: function mount(target, anchor) {
insert_dev(target, p, anchor); insert_dev(target, p, anchor);
@ -1070,7 +1130,7 @@ var app = (function () {
block, block,
id: create_pending_block.name, id: create_pending_block.name,
type: "pending", type: "pending",
source: "(19:23) <p>waiting...</p> {:then video_info}", source: "(44:24) <p>waiting...</p> {:then video_info}",
ctx ctx
}); });
@ -1088,11 +1148,11 @@ var app = (function () {
pending: create_pending_block, pending: create_pending_block,
then: create_then_block, then: create_then_block,
catch: create_catch_block, catch: create_catch_block,
value: 1, value: 3,
error: 3 error: 8
}; };
handle_promise(/*getVideoInfo*/ ctx[2](), info); handle_promise(/*startDownload*/ ctx[5](), info);
const block = { const block = {
c: function create() { c: function create() {
@ -1138,10 +1198,28 @@ var app = (function () {
validate_slots('Task', slots, []); validate_slots('Task', slots, []);
let { url } = $$props; let { url } = $$props;
let video_info = {}; let video_info = {};
let status = {};
async function getVideoInfo() { let statusBadge = {
const res = await fetch(`http://localhost:8080/get_video?url=${url}`); 1: {
$$invalidate(1, video_info = await res.json()); "class": "badge bg-secondary",
"text": "Downloading"
},
2: {
"class": "badge bg-success",
"text": "Completed"
},
3: {
"class": "badge bg-error",
"text": "Error"
}
};
let download_path = "#";
async function startDownload() {
const res = await fetch(`http://localhost:8080/download?url=${url}`);
$$invalidate(3, video_info = await res.json());
if (res.ok) { if (res.ok) {
return video_info; return video_info;
@ -1150,6 +1228,28 @@ var app = (function () {
} }
} }
async function getStatus() {
const res = await fetch(`http://localhost:8080/status?url=${url}`);
$$invalidate(1, status = await res.json());
if (res.ok) {
if (status.download_path) {
$$invalidate(2, download_path = `http://localhost:8080/${status.download_path}`);
}
return status;
} else {
throw new Error(status);
}
}
const interval = setInterval(
() => {
getStatus();
},
1000
);
const writable_props = ['url']; const writable_props = ['url'];
Object.keys($$props).forEach(key => { Object.keys($$props).forEach(key => {
@ -1160,18 +1260,30 @@ var app = (function () {
if ('url' in $$props) $$invalidate(0, url = $$props.url); if ('url' in $$props) $$invalidate(0, url = $$props.url);
}; };
$$self.$capture_state = () => ({ url, video_info, getVideoInfo }); $$self.$capture_state = () => ({
url,
video_info,
status,
statusBadge,
download_path,
startDownload,
getStatus,
interval
});
$$self.$inject_state = $$props => { $$self.$inject_state = $$props => {
if ('url' in $$props) $$invalidate(0, url = $$props.url); if ('url' in $$props) $$invalidate(0, url = $$props.url);
if ('video_info' in $$props) $$invalidate(1, video_info = $$props.video_info); if ('video_info' in $$props) $$invalidate(3, video_info = $$props.video_info);
if ('status' in $$props) $$invalidate(1, status = $$props.status);
if ('statusBadge' in $$props) $$invalidate(4, statusBadge = $$props.statusBadge);
if ('download_path' in $$props) $$invalidate(2, download_path = $$props.download_path);
}; };
if ($$props && "$$inject" in $$props) { if ($$props && "$$inject" in $$props) {
$$self.$inject_state($$props.$$inject); $$self.$inject_state($$props.$$inject);
} }
return [url, video_info, getVideoInfo]; return [url, status, download_path, video_info, statusBadge, startDownload];
} }
class Task extends SvelteComponentDev { class Task extends SvelteComponentDev {

File diff suppressed because one or more lines are too long

View file

@ -2,9 +2,16 @@
export let url; export let url;
let video_info = {}; let video_info = {};
let status = {};
async function getVideoInfo() { let statusBadge = {
const res = await fetch(`http://localhost:8080/get_video?url=${url}`); 1: {"class": "badge bg-secondary", "text": "Downloading"},
2: {"class": "badge bg-success", "text": "Completed"},
3: {"class": "badge bg-error", "text": "Error"}
};
let download_path = "#";
async function startDownload() {
const res = await fetch(`http://localhost:8080/download?url=${url}`);
video_info = await res.json(); video_info = await res.json();
if (res.ok) { if (res.ok) {
@ -14,19 +21,39 @@
} }
} }
async function getStatus() {
const res = await fetch(`http://localhost:8080/status?url=${url}`);
status = await res.json();
if (res.ok) {
if (status.download_path) {
download_path = `http://localhost:8080/${status.download_path}`;
}
return status;
} else {
throw new Error(status);
}
}
const interval = setInterval(() => { getStatus(); }, 1000);
</script> </script>
{#await getVideoInfo()} {#await startDownload()}
<p>waiting...</p> <p>waiting...</p>
{:then video_info} {:then video_info}
<a href="#" class="list-group-item list-group-item-action" aria-current="true"> <a href={download_path} class="list-group-item list-group-item-action" aria-current="true">
<div class="d-flex bd-highlight"> <div class="d-flex bd-highlight">
<div class="pb-2 flex-grow-1 bd-highlight"><h5>{video_info.title}</h5></div> <div class="pb-2 flex-grow-1 bd-highlight"><h5>{video_info.title}</h5></div>
<div class="pb-2 px-2 bd-highlight"><small>2 minutes ago</small></div> <div class="pb-2 px-2 bd-highlight"><small>2 minutes ago</small></div>
<div class="pb-2 px-2 bd-highlight"><span class="badge bg-primary">Status</span></div> {#if status.status}
<div class="pb-2 px-2 bd-highlight"><span class={statusBadge[status.status].class}>{statusBadge[status.status].text}</span></div>
{/if}
</div> </div>
<p><small>{url}</small></p>
<img src={video_info.thumbnails.URL}/> <img src={video_info.thumbnails.URL}/>
<small>{url}</small>
</a> </a>
{:catch error} {:catch error}
<p style="color: red">{error.message}</p> <p style="color: red">{error.message}</p>