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/gofiber/fiber/v2 v2.43.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/mo v1.8.0
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/spf13/viper v1.10.0
|
||||
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/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
|
||||
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/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=
|
||||
|
@ -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/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
|
||||
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/go.mod h1:90zrgN3D/WJsDd1iXHT96alCoN2KJo6/4x1DZC3wZs8=
|
||||
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 (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"sort"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/samber/lo"
|
||||
|
@ -52,19 +51,11 @@ func Serve() error {
|
|||
}).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{
|
||||
"BasePath": viper.GetString("base_path"),
|
||||
"Url": url,
|
||||
"Meta": meta,
|
||||
"Formats": formats,
|
||||
"Videos": GetVideos(meta),
|
||||
"Version": version.Version,
|
||||
"Build": version.Build,
|
||||
"YtdlpVersion": ytdl.GetVersion(),
|
||||
|
|
14
web/views.go
14
web/views.go
|
@ -8,6 +8,8 @@ import (
|
|||
"net/url"
|
||||
|
||||
"github.com/gofiber/template/html"
|
||||
"github.com/htfy96/reformism"
|
||||
"go.fifitido.net/ytdl-web/ytdl"
|
||||
)
|
||||
|
||||
//go:embed views/*
|
||||
|
@ -34,5 +36,17 @@ func ViewsEngine() *html.Engine {
|
|||
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
|
||||
}
|
||||
|
|
|
@ -1,30 +1,50 @@
|
|||
<div class="d-flex flex-column align-items-center">
|
||||
<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>
|
||||
<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%">
|
||||
Download Another Video
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{{$id := .Meta.ID}}
|
||||
{{$url := .Url}}
|
||||
{{$basePath := .BasePath}}
|
||||
{{$root := .}}
|
||||
|
||||
<div class="d-flex flex-column gap-4 mt-5">
|
||||
{{range .Formats}}
|
||||
<div class="d-flex gap-3 align-items-center">
|
||||
<div style="width: 10rem">{{.Format}}</div>
|
||||
{{define "download"}}
|
||||
<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="{{.Meta.ID}}-{{.Format.Resolution}}.{{.Format.Ext}}" P
|
||||
href="{{.Format.Url}}">
|
||||
Download (direct)
|
||||
</a>
|
||||
<a class="btn btn-primary flex-grow-1" download="{{.Meta.ID}}-{{.Format.Resolution}}.{{.Format.Ext}}"
|
||||
href="{{.BasePath}}/download/proxy?url={{queryEscape .Url}}&format={{.Format.FormatID}}">
|
||||
Download (proxied)
|
||||
</a>
|
||||
</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="{{$id}}-{{.Resolution}}.{{.Ext}}" P href="{{.Url}}">
|
||||
<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="{{$id}}-{{.Resolution}}.{{.Ext}}"
|
||||
href="{{$basePath}}/download/proxy?url={{queryEscape $url}}&format={{.FormatID}}">
|
||||
<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>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
|
@ -25,6 +25,18 @@
|
|||
filter: none;
|
||||
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>
|
||||
</head>
|
||||
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
package ytdl
|
||||
|
||||
type Metdata struct {
|
||||
type Metadata struct {
|
||||
Type string `json:"_type"`
|
||||
|
||||
// A list of videos in the playlist
|
||||
Entries []Metadata
|
||||
|
||||
// Video identifier.
|
||||
ID string `json:"id"`
|
||||
|
||||
|
|
|
@ -6,10 +6,11 @@ import (
|
|||
"os/exec"
|
||||
)
|
||||
|
||||
func GetMetadata(url string) (Metdata, error) {
|
||||
func GetMetadata(url string) (Metadata, error) {
|
||||
cmd := exec.Command(
|
||||
"yt-dlp",
|
||||
"-J",
|
||||
"--cookies-from-browser", "firefox",
|
||||
url,
|
||||
)
|
||||
|
||||
|
@ -17,12 +18,12 @@ func GetMetadata(url string) (Metdata, error) {
|
|||
cmd.Stdout = &out
|
||||
|
||||
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 {
|
||||
return Metdata{}, err
|
||||
return Metadata{}, err
|
||||
}
|
||||
|
||||
return meta, nil
|
||||
|
|
|
@ -11,6 +11,7 @@ func Stream(wr io.Writer, url string, format Format) error {
|
|||
"-o", "-",
|
||||
"-f", format.FormatID,
|
||||
"--merge-output-format", "mkv",
|
||||
"--cookies-from-browser", "firefox",
|
||||
url,
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in New Issue