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-gonic/gin v1.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/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/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
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/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
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.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-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
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/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=
@ -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=
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 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/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=

View file

@ -6,43 +6,53 @@ import (
"net/http"
"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-gonic/gin"
"github.com/kkdai/youtube/v2"
)
const (
StatusDownloading = iota
StatusCompleted
StatusError
)
type Task struct {
Video *youtube.Video
Status int
}
var taskCh chan *Task
var tasks task.Tasks
func init() {
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() {
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.Use(cors.New(cors.Config{
AllowOrigins: []string{"http://localhost:8080", "http://localhost:5000"},
@ -57,44 +67,18 @@ func main() {
c.AbortWithStatus(http.StatusInternalServerError)
}))
r.GET("/get_video", func(c *gin.Context) {
id, err := youtube.ExtractVideoID(c.Query("url"))
if err != nil {
r.Static("/data", "./data")
r.GET("/download", func(c *gin.Context) {
if err := download(c, youtube_dl.NewYoutubeDlDownloader(c.Query("url"), "yt-dlp")); err != nil {
panic(err)
}
log.Printf("Extracted video ID: %s", id)
client := youtube.Client{}
video, err := client.GetVideo(id)
if err != nil {
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,
r.GET("/status", func(c *gin.Context) {
if err := status(c, youtube_dl.NewYoutubeDlDownloader(c.Query("url"), "yt-dlp")); err != nil {
panic(err)
}
// 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()

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

File diff suppressed because one or more lines are too long

View file

@ -2,9 +2,16 @@
export let url;
let video_info = {};
let status = {};
let statusBadge = {
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 getVideoInfo() {
const res = await fetch(`http://localhost:8080/get_video?url=${url}`);
async function startDownload() {
const res = await fetch(`http://localhost:8080/download?url=${url}`);
video_info = await res.json();
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>
{#await getVideoInfo()}
{#await startDownload()}
<p>waiting...</p>
{: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="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"><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>
<p><small>{url}</small></p>
<img src={video_info.thumbnails.URL}/>
<small>{url}</small>
</a>
{:catch error}
<p style="color: red">{error.message}</p>