Remove format grouping and add support for playlists
This commit is contained in:
parent
79a0629da9
commit
db1f91606e
2
go.mod
2
go.mod
|
@ -6,7 +6,9 @@ require (
|
||||||
github.com/adrg/xdg v0.4.0
|
github.com/adrg/xdg v0.4.0
|
||||||
github.com/gofiber/fiber/v2 v2.43.0
|
github.com/gofiber/fiber/v2 v2.43.0
|
||||||
github.com/gofiber/template v1.8.0
|
github.com/gofiber/template v1.8.0
|
||||||
|
github.com/htfy96/reformism v0.0.0-20160819020323-e5bfca398e73
|
||||||
github.com/samber/lo v1.38.1
|
github.com/samber/lo v1.38.1
|
||||||
|
github.com/samber/mo v1.8.0
|
||||||
github.com/spf13/cobra v1.7.0
|
github.com/spf13/cobra v1.7.0
|
||||||
github.com/spf13/viper v1.10.0
|
github.com/spf13/viper v1.10.0
|
||||||
github.com/sujit-baniya/flash v0.1.8
|
github.com/sujit-baniya/flash v0.1.8
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -244,6 +244,8 @@ github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn
|
||||||
github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
||||||
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
|
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
|
||||||
github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
|
github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
|
||||||
|
github.com/htfy96/reformism v0.0.0-20160819020323-e5bfca398e73 h1:Shcv21tstWAyUkKxbn5bTARYej9sgEgFgTRxUPk1J8o=
|
||||||
|
github.com/htfy96/reformism v0.0.0-20160819020323-e5bfca398e73/go.mod h1:i2jduFeVras6pm8GnBWdfVKj97mGEXJogjMHzyJhukY=
|
||||||
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
|
@ -348,6 +350,8 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb
|
||||||
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
|
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
|
||||||
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
|
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
|
||||||
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
||||||
|
github.com/samber/mo v1.8.0 h1:vYjHTfg14JF9tD2NLhpoUsRi9bjyRoYwa4+do0nvbVw=
|
||||||
|
github.com/samber/mo v1.8.0/go.mod h1:BfkrCPuYzVG3ZljnZB783WIJIGk1mcZr9c9CPf8tAxs=
|
||||||
github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 h1:rmMl4fXJhKMNWl+K+r/fq4FbbKI+Ia2m9hYBLm2h4G4=
|
github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 h1:rmMl4fXJhKMNWl+K+r/fq4FbbKI+Ia2m9hYBLm2h4G4=
|
||||||
github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94/go.mod h1:90zrgN3D/WJsDd1iXHT96alCoN2KJo6/4x1DZC3wZs8=
|
github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94/go.mod h1:90zrgN3D/WJsDd1iXHT96alCoN2KJo6/4x1DZC3wZs8=
|
||||||
github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4=
|
github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4=
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/samber/lo"
|
||||||
|
"go.fifitido.net/ytdl-web/ytdl"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Video struct {
|
||||||
|
Meta ytdl.Metadata
|
||||||
|
Formats []ytdl.Format
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetVideos(meta ytdl.Metadata) []Video {
|
||||||
|
if meta.Type == "playlist" {
|
||||||
|
return lo.Map(meta.Entries, func(video ytdl.Metadata, _ int) Video {
|
||||||
|
return GetVideos(video)[0]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
formats := lo.Filter(meta.Formats, func(item ytdl.Format, _ int) bool {
|
||||||
|
return item.ACodec != "none" && item.VCodec != "none" && item.Protocol != "m3u8_native"
|
||||||
|
})
|
||||||
|
|
||||||
|
for i, j := 0, len(formats)-1; i < j; i, j = i+1, j-1 {
|
||||||
|
formats[i], formats[j] = formats[j], formats[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return []Video{
|
||||||
|
{
|
||||||
|
Meta: meta,
|
||||||
|
Formats: formats,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
11
web/serve.go
11
web/serve.go
|
@ -3,7 +3,6 @@ package web
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
|
@ -52,19 +51,11 @@ func Serve() error {
|
||||||
}).Redirect("/")
|
}).Redirect("/")
|
||||||
}
|
}
|
||||||
|
|
||||||
formats := lo.Filter(meta.Formats, func(item ytdl.Format, _ int) bool {
|
|
||||||
return item.ACodec != "none" && item.VCodec != "none" && item.Protocol != "m3u8_native"
|
|
||||||
})
|
|
||||||
|
|
||||||
sort.Slice(formats, func(i, j int) bool {
|
|
||||||
return formats[i].Width > formats[j].Width
|
|
||||||
})
|
|
||||||
|
|
||||||
return c.Render("views/download", fiber.Map{
|
return c.Render("views/download", fiber.Map{
|
||||||
"BasePath": viper.GetString("base_path"),
|
"BasePath": viper.GetString("base_path"),
|
||||||
"Url": url,
|
"Url": url,
|
||||||
"Meta": meta,
|
"Meta": meta,
|
||||||
"Formats": formats,
|
"Videos": GetVideos(meta),
|
||||||
"Version": version.Version,
|
"Version": version.Version,
|
||||||
"Build": version.Build,
|
"Build": version.Build,
|
||||||
"YtdlpVersion": ytdl.GetVersion(),
|
"YtdlpVersion": ytdl.GetVersion(),
|
||||||
|
|
14
web/views.go
14
web/views.go
|
@ -8,6 +8,8 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/gofiber/template/html"
|
"github.com/gofiber/template/html"
|
||||||
|
"github.com/htfy96/reformism"
|
||||||
|
"go.fifitido.net/ytdl-web/ytdl"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed views/*
|
//go:embed views/*
|
||||||
|
@ -34,5 +36,17 @@ func ViewsEngine() *html.Engine {
|
||||||
return string(j), nil
|
return string(j), nil
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
engine.AddFunc(
|
||||||
|
"downloadContext", func(meta ytdl.Metadata, url, basePath string, format ytdl.Format) map[string]any {
|
||||||
|
return map[string]any{
|
||||||
|
"Meta": meta,
|
||||||
|
"Url": url,
|
||||||
|
"BasePath": basePath,
|
||||||
|
"Format": format,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
engine.AddFuncMap(reformism.FuncsHTML)
|
||||||
return engine
|
return engine
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +1,50 @@
|
||||||
<div class="d-flex flex-column align-items-center">
|
<div class="d-flex flex-column align-items-center">
|
||||||
<h1>Download Video</h1>
|
<h1>Download Video</h1>
|
||||||
<h2 class="fs-4 text-muted">{{.Meta.Title}}</h2>
|
<h2 class="fs-4 text-muted text-center">{{.Meta.Title}}</h2>
|
||||||
<p style="font-size: 0.85rem">{{.Url}}</p>
|
<p style="font-size: 0.85rem">{{.Url}}</p>
|
||||||
<img src="{{.Meta.Thumbnail}}" alt="{{.Meta.Title}}" style="max-height: 25rem; max-width: 100%" />
|
|
||||||
<a href="{{.BasePath}}/" class="btn btn-secondary btn-sm mt-3" style="width: 30rem; max-width: 100%">
|
<a href="{{.BasePath}}/" class="btn btn-secondary btn-sm mt-3" style="width: 30rem; max-width: 100%">
|
||||||
Download Another Video
|
Download Another Video
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{$id := .Meta.ID}}
|
{{$root := .}}
|
||||||
{{$url := .Url}}
|
|
||||||
{{$basePath := .BasePath}}
|
|
||||||
|
|
||||||
<div class="d-flex flex-column gap-4 mt-5">
|
{{define "download"}}
|
||||||
{{range .Formats}}
|
<div style="font-size: smaller">{{.Format.Format}}</div>
|
||||||
<div class="d-flex gap-3 align-items-center">
|
|
||||||
<div style="width: 10rem">{{.Format}}</div>
|
|
||||||
<div class="flex-grow-1 d-flex gap-3">
|
<div class="flex-grow-1 d-flex gap-3">
|
||||||
<a class="btn btn-primary flex-grow-1" download="{{$id}}-{{.Resolution}}.{{.Ext}}" P href="{{.Url}}">
|
<a class="btn btn-primary flex-grow-1" download="{{.Meta.ID}}-{{.Format.Resolution}}.{{.Format.Ext}}" P
|
||||||
|
href="{{.Format.Url}}">
|
||||||
Download (direct)
|
Download (direct)
|
||||||
</a>
|
</a>
|
||||||
<a class="btn btn-primary flex-grow-1" download="{{$id}}-{{.Resolution}}.{{.Ext}}"
|
<a class="btn btn-primary flex-grow-1" download="{{.Meta.ID}}-{{.Format.Resolution}}.{{.Format.Ext}}"
|
||||||
href="{{$basePath}}/download/proxy?url={{queryEscape $url}}&format={{.FormatID}}">
|
href="{{.BasePath}}/download/proxy?url={{queryEscape .Url}}&format={{.Format.FormatID}}">
|
||||||
Download (proxied)
|
Download (proxied)
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{range $vidIndex, $video := .Videos}}
|
||||||
|
{{if not (eq $vidIndex 0)}}
|
||||||
|
<hr class="mt-5" />
|
||||||
|
{{end}}
|
||||||
|
<div class="d-flex flex-column flex-lg-row justify-content-center gap-5 mt-5">
|
||||||
|
<div class="d-flex justify-content-center">
|
||||||
|
<img src="{{.Meta.Thumbnail}}" alt="{{.Meta.Title}}" style="max-height: 25rem; max-width: 100%; margin: 0 auto;" />
|
||||||
|
</div>
|
||||||
|
<div class="downloads flex-lg-grow-1">
|
||||||
|
{{range $index, $format := $video.Formats}}
|
||||||
|
<div style="font-size: smaller">{{$format.Format}}</div>
|
||||||
|
<div class="flex-grow-1 d-flex gap-3">
|
||||||
|
<a class="btn btn-primary flex-grow-1" download="{{$root.Meta.ID}}-{{$format.Resolution}}.{{$format.Ext}}" P
|
||||||
|
href="{{$format.Url}}">
|
||||||
|
Download (direct)
|
||||||
|
</a>
|
||||||
|
<a class="btn btn-primary flex-grow-1" download="{{$root.Meta.ID}}-{{$format.Resolution}}.{{$format.Ext}}"
|
||||||
|
href="{{$root.BasePath}}/download/proxy?url={{queryEscape $root.Url}}&format={{$format.FormatID}}">
|
||||||
|
Download (proxied)
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
|
@ -25,6 +25,18 @@
|
||||||
filter: none;
|
filter: none;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.downloads {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(auto, max-content) auto;
|
||||||
|
gap: 1.5rem;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.see-more-btn,
|
||||||
|
.collapse {
|
||||||
|
grid-column: span 2;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
package ytdl
|
package ytdl
|
||||||
|
|
||||||
type Metdata struct {
|
type Metadata struct {
|
||||||
|
Type string `json:"_type"`
|
||||||
|
|
||||||
|
// A list of videos in the playlist
|
||||||
|
Entries []Metadata
|
||||||
|
|
||||||
// Video identifier.
|
// Video identifier.
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
|
|
||||||
|
|
|
@ -6,10 +6,11 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetMetadata(url string) (Metdata, error) {
|
func GetMetadata(url string) (Metadata, error) {
|
||||||
cmd := exec.Command(
|
cmd := exec.Command(
|
||||||
"yt-dlp",
|
"yt-dlp",
|
||||||
"-J",
|
"-J",
|
||||||
|
"--cookies-from-browser", "firefox",
|
||||||
url,
|
url,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -17,12 +18,12 @@ func GetMetadata(url string) (Metdata, error) {
|
||||||
cmd.Stdout = &out
|
cmd.Stdout = &out
|
||||||
|
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
return Metdata{}, err
|
return Metadata{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var meta Metdata
|
var meta Metadata
|
||||||
if err := json.Unmarshal(out.Bytes(), &meta); err != nil {
|
if err := json.Unmarshal(out.Bytes(), &meta); err != nil {
|
||||||
return Metdata{}, err
|
return Metadata{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return meta, nil
|
return meta, nil
|
||||||
|
|
|
@ -11,6 +11,7 @@ func Stream(wr io.Writer, url string, format Format) error {
|
||||||
"-o", "-",
|
"-o", "-",
|
||||||
"-f", format.FormatID,
|
"-f", format.FormatID,
|
||||||
"--merge-output-format", "mkv",
|
"--merge-output-format", "mkv",
|
||||||
|
"--cookies-from-browser", "firefox",
|
||||||
url,
|
url,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue