Remove format grouping and add support for playlists

This commit is contained in:
Evan Fiordeliso 2023-04-15 12:38:33 -04:00
parent 79a0629da9
commit db1f91606e
10 changed files with 113 additions and 29 deletions

2
go.mod
View File

@ -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
View File

@ -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=

34
web/formats.go Normal file
View File

@ -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,
},
}
}

View File

@ -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(),

View File

@ -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
} }

View File

@ -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}}

View File

@ -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>

View File

@ -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"`

View File

@ -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

View File

@ -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,
) )