Compare commits
No commits in common. "main" and "v1.1.1" have entirely different histories.
|
@ -3,7 +3,7 @@ testdata_dir = "testdata"
|
|||
tmp_dir = "tmp"
|
||||
|
||||
[build]
|
||||
args_bin = ["-l", "0.0.0.0", "-p", "3000"]
|
||||
args_bin = ["-l", "0.0.0.0"]
|
||||
bin = "./out/ytdl-web"
|
||||
cmd = "build"
|
||||
delay = 1000
|
||||
|
|
12
.envrc
12
.envrc
|
@ -1,11 +1,3 @@
|
|||
if ! has nix_direnv_version || ! nix_direnv_version 2.2.1; then
|
||||
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.2.1/direnvrc" "sha256-zelF0vLbEl5uaqrfIzbgNzJWGmLzCmYAkInj/LNxvKs="
|
||||
fi
|
||||
source_url "https://raw.githubusercontent.com/cachix/devenv/d1f7b48e35e6dee421cfd0f51481d17f77586997/direnvrc" "sha256-YBzqskFZxmNb3kYVoKD9ZixoPXJh1C9ZvTLGFRkauZ0="
|
||||
|
||||
nix_direnv_watch_file nix/devenv.nix
|
||||
nix_direnv_watch_file devenv.lock
|
||||
nix_direnv_watch_file devenv.yaml
|
||||
if ! use flake . --impure
|
||||
then
|
||||
echo "devenv could not be built. The devenv environment was not loaded. Make the necessary changes to devenv.nix and hit enter to try again." >&2
|
||||
fi
|
||||
use devenv
|
|
@ -3,9 +3,8 @@ name: "Test"
|
|||
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
{}
|
||||
# pull_request:
|
||||
# push:
|
||||
pull_request:
|
||||
push:
|
||||
|
||||
jobs:
|
||||
tests:
|
18
Dockerfile
18
Dockerfile
|
@ -1,7 +1,7 @@
|
|||
ARG GOLANG_VERSION="1.22.1"
|
||||
ARG DEBIAN_VERSION="bookworm"
|
||||
ARG GOLANG_VERSION="1.20.5"
|
||||
ARG ALPINE_VERSION="3.18"
|
||||
|
||||
FROM golang:${GOLANG_VERSION}-${DEBIAN_VERSION} AS build
|
||||
FROM golang:${GOLANG_VERSION}-alpine${ALPINE_VERSION} AS build
|
||||
|
||||
ENV VERSION_PKG="go.fifitido.net/ytdl-web/version"
|
||||
ARG VERSION=latest
|
||||
|
@ -25,18 +25,12 @@ RUN go build \
|
|||
-X \"$VERSION_PKG.BuiltBy=$BUILT_BY\" \
|
||||
" -o /ytdl-web .
|
||||
|
||||
FROM python:${DEBIAN_VERSION}
|
||||
|
||||
ARG YTDLP_VERSION="2024.11.04"
|
||||
FROM alpine:${ALPINE_VERSION}
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# hadolint ignore=DL3008
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends -y wget \
|
||||
&& wget --progress=dot:giga "https://github.com/yt-dlp/yt-dlp/releases/download/${YTDLP_VERSION}/yt-dlp" \
|
||||
&& install -pm755 yt-dlp /usr/bin/yt-dlp \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
ARG YTDLP_VERSION="2023.07.06-r0"
|
||||
RUN apk add --no-cache yt-dlp==${YTDLP_VERSION}
|
||||
|
||||
COPY --from=build /ytdl-web ./
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ You can configure the application using environment variables
|
|||
| ---------------------------------- | ----------------------------------------------------------------------------------------------- | --------------- | ------------------------------------------------------------------------------ |
|
||||
| YTDL_CONFIGDIR | Add a custom config directory to search for the config file | ` ` | |
|
||||
| YTDL_ENV | The application environment | `Production` | `Development`, `Staging`, `Production` |
|
||||
| YTDL_YTDLP_BINARYPATH | The path to the yt-dlp binary | `yt-dlp` | |
|
||||
| YTDL_BINARYPATH | The path to the yt-dlp binary | `yt-dlp` | |
|
||||
| YTDL_HTTP_PORT | The tcp port for the web server to listen on | `8080` | |
|
||||
| YTDL_HTTP_LISTEN | The address for the web server to listen on | `127.0.0.1` | `0.0.0.0`, `127.0.0.1`, etc. |
|
||||
| YTDL_HTTP_BASEPATH | The base path of the application, useful for reverse proxies | `/` | |
|
||||
|
@ -36,7 +36,7 @@ You can configure the application using environment variables
|
|||
| YTDL_COOKIES_FROMBROWSER_BROWSER | The name of the browser to load cookies from. (if specified, it disables YTDL_COOKIES_FILEPATH) | ` ` | `brave`, `chrome`, `chromium`, `edge`, `firefox`, `opera`, `safari`, `vivaldi` |
|
||||
| YTDL_COOKIES_FROMBROWSER_KEYRING | The name of the keyring for decrypting cookies for the chromium browser on linux | ` ` | `basictext`, `gnomekeyring`, `kwallet` |
|
||||
| YTDL_COOKIES_FROMBROWSER_PROFILE | The browser profile to load cookies from | ` ` | |
|
||||
| YTDL_COOKIES_FROMBROWSER_CONTAINER | The container name (if firefox) to load the cookies from | ` ` | |
|
||||
| YTDL_COOKIES_FROMBROWSER_CONTAINER | The container name (if firefox) top load the cookies from | ` ` | |
|
||||
|
||||
## Building from source
|
||||
|
||||
|
|
|
@ -2,11 +2,11 @@ package controllers
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/samber/lo"
|
||||
"github.com/spf13/viper"
|
||||
"go.fifitido.net/ytdl-web/app"
|
||||
"go.fifitido.net/ytdl-web/app/models"
|
||||
|
@ -16,7 +16,6 @@ import (
|
|||
"go.fifitido.net/ytdl-web/pkg/view"
|
||||
"go.fifitido.net/ytdl-web/pkg/ytdl"
|
||||
"go.fifitido.net/ytdl-web/pkg/ytdl/metadata"
|
||||
"go.fifitido.net/ytdl-web/version"
|
||||
"golang.org/x/exp/slog"
|
||||
)
|
||||
|
||||
|
@ -49,26 +48,20 @@ func (c *DownloadController) getUrlParam(r *http.Request) (string, bool) {
|
|||
return "", false
|
||||
}
|
||||
|
||||
return urlBytes, true
|
||||
return string(urlBytes), true
|
||||
}
|
||||
|
||||
func (c *DownloadController) ListDownloadLinks(w http.ResponseWriter, r *http.Request) {
|
||||
hx := htmx.New(w, r)
|
||||
var layout []string
|
||||
layout := []string{}
|
||||
if !hx.IsHtmxRequest() {
|
||||
layout = append(layout, "layouts/main")
|
||||
}
|
||||
|
||||
isSecure := r.URL.Scheme == "https" || r.Header.Get("X-Forwarded-Proto") == "https"
|
||||
|
||||
videoUrl, ok := c.getUrlParam(r)
|
||||
if !ok {
|
||||
app.Views.Render(w, "index", view.Data{
|
||||
"BasePath": viper.GetString("base_path"),
|
||||
"Version": version.Version,
|
||||
"Build": version.Build,
|
||||
"BinaryVersion": c.ytdl.Version(),
|
||||
"IsSecure": isSecure,
|
||||
"Error": view.Data{
|
||||
"Message": "Invalid URL",
|
||||
},
|
||||
|
@ -80,10 +73,6 @@ func (c *DownloadController) ListDownloadLinks(w http.ResponseWriter, r *http.Re
|
|||
if err != nil {
|
||||
app.Views.Render(w, "index", view.Data{
|
||||
"BasePath": viper.GetString("base_path"),
|
||||
"Version": version.Version,
|
||||
"Build": version.Build,
|
||||
"BinaryVersion": c.ytdl.Version(),
|
||||
"IsSecure": isSecure,
|
||||
"Error": view.Data{
|
||||
"Message": "Could not find a video at that url",
|
||||
"RetryUrl": videoUrl,
|
||||
|
@ -98,20 +87,12 @@ func (c *DownloadController) ListDownloadLinks(w http.ResponseWriter, r *http.Re
|
|||
|
||||
app.Views.Render(w, "download", view.Data{
|
||||
"BasePath": viper.GetString("base_path"),
|
||||
"Version": version.Version,
|
||||
"Build": version.Build,
|
||||
"BinaryVersion": c.ytdl.Version(),
|
||||
"IsSecure": isSecure,
|
||||
"Url": videoUrl,
|
||||
"Meta": meta,
|
||||
"Videos": models.GetVideosFromMetadata(meta),
|
||||
}, layout...)
|
||||
}
|
||||
|
||||
var (
|
||||
BUF_LEN = 1024
|
||||
)
|
||||
|
||||
func (c *DownloadController) ProxyDownload(w http.ResponseWriter, r *http.Request) {
|
||||
videoUrl, ok := c.getUrlParam(r)
|
||||
if !ok {
|
||||
|
@ -140,15 +121,10 @@ func (c *DownloadController) ProxyDownload(w http.ResponseWriter, r *http.Reques
|
|||
}
|
||||
|
||||
video := videos[index]
|
||||
|
||||
var format *metadata.Format
|
||||
for _, f := range video.Formats {
|
||||
if f.FormatID == formatId {
|
||||
format = &f
|
||||
break
|
||||
}
|
||||
}
|
||||
if format == nil {
|
||||
format, ok := lo.Find(video.Formats, func(format metadata.Format) bool {
|
||||
return format.FormatID == formatId
|
||||
})
|
||||
if !ok {
|
||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
@ -156,25 +132,17 @@ func (c *DownloadController) ProxyDownload(w http.ResponseWriter, r *http.Reques
|
|||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s-%s.%s\"", meta.ID, format.Resolution, format.Ext))
|
||||
if format.Filesize != nil {
|
||||
w.Header().Set("Content-Length", fmt.Sprint(*format.Filesize))
|
||||
} else if format.FilesizeApprox != nil {
|
||||
w.Header().Set("Content-Length", fmt.Sprint(*format.FilesizeApprox))
|
||||
}
|
||||
|
||||
if len(videos) == 1 {
|
||||
index = -1
|
||||
}
|
||||
|
||||
read, write := io.Pipe()
|
||||
|
||||
go func() {
|
||||
_, err := io.Copy(w, read)
|
||||
if err != nil {
|
||||
slog.Error("Failed to copy", slog.String("error", err.Error()))
|
||||
}
|
||||
}()
|
||||
|
||||
if err := c.ytdl.Download(write, videoUrl, format.FormatID, index); err != nil {
|
||||
if err := c.ytdl.Download(w, videoUrl, format.FormatID, index); err != nil {
|
||||
slog.Error("Failed to download", slog.String("error", err.Error()))
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
write.Close()
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ func (c *HomeController) Router(r chi.Router) {
|
|||
|
||||
func (c *HomeController) Index(w http.ResponseWriter, r *http.Request) {
|
||||
hx := htmx.New(w, r)
|
||||
isSecure := r.URL.Scheme == "https" || r.Header.Get("X-Forwarded-Proto") == "https"
|
||||
isSecure := r.URL.Scheme == "https"
|
||||
|
||||
if hx.IsHtmxRequest() {
|
||||
hx.PushUrl("/")
|
||||
|
|
|
@ -1,36 +1,25 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"github.com/samber/lo"
|
||||
"go.fifitido.net/ytdl-web/pkg/ytdl/metadata"
|
||||
)
|
||||
|
||||
type Video struct {
|
||||
Meta *metadata.Metadata
|
||||
Formats []metadata.Format
|
||||
OtherFormats []metadata.Format
|
||||
}
|
||||
|
||||
func GetVideosFromMetadata(meta *metadata.Metadata) []Video {
|
||||
if meta.IsPlaylist() {
|
||||
videos := make([]Video, 0, len(meta.Entries))
|
||||
|
||||
for _, entry := range meta.Entries {
|
||||
videos = append(videos, GetVideosFromMetadata(&entry)...)
|
||||
return lo.Map(meta.Entries, func(video metadata.Metadata, _ int) Video {
|
||||
return GetVideosFromMetadata(&video)[0]
|
||||
})
|
||||
}
|
||||
|
||||
return videos
|
||||
}
|
||||
|
||||
formats := []metadata.Format{}
|
||||
otherFormats := []metadata.Format{}
|
||||
|
||||
for _, format := range meta.Formats {
|
||||
if format.ACodec != "none" && format.VCodec != "none" && format.Protocol != "m3u8_native" {
|
||||
formats = append(formats, format)
|
||||
} else {
|
||||
otherFormats = append(otherFormats, format)
|
||||
}
|
||||
}
|
||||
formats := lo.Filter(meta.Formats, func(item metadata.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]
|
||||
|
@ -40,7 +29,6 @@ func GetVideosFromMetadata(meta *metadata.Metadata) []Video {
|
|||
{
|
||||
Meta: meta,
|
||||
Formats: formats,
|
||||
OtherFormats: otherFormats,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
20
app/views.go
20
app/views.go
|
@ -3,7 +3,6 @@ package app
|
|||
import (
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/url"
|
||||
|
||||
|
@ -40,25 +39,6 @@ var Views = html.New(
|
|||
"Format": format,
|
||||
}
|
||||
}).
|
||||
WithFunction("sprintf", func(format string, args ...any) string {
|
||||
return fmt.Sprintf(format, args...)
|
||||
}).
|
||||
WithFunction("filesize", func(size *int) string {
|
||||
if size == nil {
|
||||
return "unknown"
|
||||
}
|
||||
const unit = 1000
|
||||
if *size < unit {
|
||||
return fmt.Sprintf("%d B", *size)
|
||||
}
|
||||
div, exp := int64(unit), 0
|
||||
for n := *size / unit; n >= unit; n /= unit {
|
||||
div *= unit
|
||||
exp++
|
||||
}
|
||||
return fmt.Sprintf("%.1f %cB",
|
||||
float64(*size)/float64(div), "kMGTPE"[exp])
|
||||
}).
|
||||
WithFunctions(reformism.FuncsHTML),
|
||||
)
|
||||
|
||||
|
|
|
@ -2,62 +2,51 @@
|
|||
<h1>Download Video</h1>
|
||||
<h2 class="fs-4 text-muted text-center">{{.Meta.Title}}</h2>
|
||||
<p style="font-size: 0.85rem">{{.Url}}</p>
|
||||
<a href="{{.BasePath}}/" hx-get="{{.BasePath}}/" hx-trigger="click" hx-target="#main-content"
|
||||
class="btn btn-secondary btn-sm mt-3" style="width: 30rem; max-width: 100%">
|
||||
<a
|
||||
href="{{.BasePath}}/"
|
||||
hx-get="{{.BasePath}}/"
|
||||
hx-trigger="click"
|
||||
hx-target="#main-content"
|
||||
class="btn btn-secondary btn-sm mt-3"
|
||||
style="width: 30rem; max-width: 100%"
|
||||
>
|
||||
Download Another Video
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{{$root := .}}
|
||||
{{range $vidIndex, $video := .Videos}}
|
||||
{{if not (eq $vidIndex 0)}}
|
||||
{{$root := .}} {{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="{{$video.Meta.Thumbnail}}" alt="{{$video.Meta.Title}}" style="max-height: 25rem; max-width: 100%; margin: 0 auto" />
|
||||
<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}}
|
||||
{{template "format" (map "root" $root "format" $format "label" $format.Format "vidIndex" $vidIndex)}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{if gt (len $video.OtherFormats) 0}}
|
||||
<div class="d-grid my-3">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#collapse-{{$root.Meta.ID}}-{{$vidIndex}}"
|
||||
aria-expanded="false"
|
||||
aria-controls="collapse-{{$root.Meta.ID}}-{{$vidIndex}}"
|
||||
>
|
||||
See More Formats
|
||||
</button>
|
||||
</div>
|
||||
<div id="collapse-{{$root.Meta.ID}}-{{$vidIndex}}" class="collapse">
|
||||
<div class="downloads d-flex d-md-grid flex-column">
|
||||
{{range $index, $format := $video.OtherFormats}}
|
||||
{{$label := sprintf "ext: %s, resolution: %s, filesize: %s, note: %s" $format.Ext $format.Resolution
|
||||
(filesize $format.Filesize) $format.FormatNote}}
|
||||
{{template "format" (map "root" $root "format" $format "label" $label "vidIndex" $vidIndex)}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{define "format"}}
|
||||
<div style="font-size: smaller">{{.label}}</div>
|
||||
<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}}">
|
||||
<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}}&index={{.vidIndex}}">
|
||||
<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}}&index={{$vidIndex}}"
|
||||
>
|
||||
Download (proxied)
|
||||
</a>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
|
|
@ -5,21 +5,48 @@
|
|||
with more features and fixes.
|
||||
<br />
|
||||
View a complete list of supported websites
|
||||
<a href="https://github.com/yt-dlp/yt-dlp/blob/master/supportedsites.md">here</a>.
|
||||
<a href="https://github.com/yt-dlp/yt-dlp/blob/master/supportedsites.md"
|
||||
>here</a
|
||||
>.
|
||||
</p>
|
||||
|
||||
<form hx-get="{{.BasePath}}/download" hx-trigger="submit" hx-target="#main-content" hx-swap="innerHTML">
|
||||
<form
|
||||
hx-get="{{.BasePath}}/download"
|
||||
hx-trigger="submit"
|
||||
hx-target="#main-content"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
<div class="mb-3">
|
||||
<label for="url" class="form-label visually-hidden">Url</label>
|
||||
<div class="input-group">
|
||||
<input type="url" name="url" id="url" class="form-control" required
|
||||
placeholder="Enter url here then click download" />
|
||||
<input
|
||||
type="url"
|
||||
name="url"
|
||||
id="url"
|
||||
class="form-control"
|
||||
required
|
||||
placeholder="Enter url here then click download"
|
||||
/>
|
||||
{{if .IsSecure}}
|
||||
<button id="paste-button" class="btn btn-outline-secondary" type="button" title="Paste">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
|
||||
style="width: 1.5rem; height: 1.5rem">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184" />
|
||||
<button
|
||||
id="paste-button"
|
||||
class="btn btn-outline-secondary"
|
||||
type="button"
|
||||
title="Paste"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
style="width: 1.5rem; height: 1.5rem"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
{{end}}
|
||||
|
@ -28,7 +55,10 @@
|
|||
<div class="d-grid">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
Download
|
||||
<div class="spinner-border spinner-border-sm htmx-indicator ms-1" role="status">
|
||||
<div
|
||||
class="spinner-border spinner-border-sm htmx-indicator ms-1"
|
||||
role="status"
|
||||
>
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</button>
|
||||
|
@ -40,10 +70,19 @@
|
|||
<span>{{.Error.Message}}</span>
|
||||
|
||||
{{if .Error.RetryUrl}}
|
||||
<button class="btn btn-link btn-sm pt-0 lh-base text-decoration-none" hx-get="/download" hx-trigger="click"
|
||||
hx-target="#main-content" hx-swap="innerHTML" hx-vals='{"url": "{{.Error.RetryUrl}}"}'>
|
||||
<button
|
||||
class="btn btn-link btn-sm pt-0 lh-base text-decoration-none"
|
||||
hx-get="/download"
|
||||
hx-trigger="click"
|
||||
hx-target="#main-content"
|
||||
hx-swap="innerHTML"
|
||||
hx-vals='{"url": "{{.Error.RetryUrl}}"}'
|
||||
>
|
||||
<span class="text-decoration-underline">Try Again</span>
|
||||
<div class="spinner-border spinner-border-sm htmx-indicator ms-1" role="status">
|
||||
<div
|
||||
class="spinner-border spinner-border-sm htmx-indicator ms-1"
|
||||
role="status"
|
||||
>
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</button>
|
||||
|
|
|
@ -1,23 +1,43 @@
|
|||
<!doctype html>
|
||||
<html lang="en" class="h-100">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>YTDL Web</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous" />
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/toastr@2.1.4/build/toastr.min.css"
|
||||
integrity="sha256-R91pD48xW+oHbpJYGn5xR0Q7tMhH4xOrWn1QqMRINtA=" crossorigin="anonymous" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"
|
||||
defer></script>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/toastr@2.1.4/build/toastr.min.js"
|
||||
integrity="sha256-Hgwq1OBpJ276HUP9H3VJkSv9ZCGRGQN+JldPJ8pNcUM=" crossorigin="anonymous" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/htmx.org@1.9.4/dist/htmx.min.js"
|
||||
integrity="sha256-XIivRAE99i/eil5P31JNihaDSiix0V40rgmUrCfNTH4=" crossorigin="anonymous"></script>
|
||||
<link
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css"
|
||||
rel="stylesheet"
|
||||
integrity="sha384-KK94CHFLLe+nY2dmCWGMq91rCGa5gtU4mk92HdvYe+M/SXH301p5ILy+dN9+nJOZ"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/toastr@2.1.4/build/toastr.min.css"
|
||||
integrity="sha256-R91pD48xW+oHbpJYGn5xR0Q7tMhH4xOrWn1QqMRINtA="
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
<script
|
||||
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-ENjdO4Dr2bkBIFxQpeoTz1HIcje39Wm4jDKdf19U8gI4ddQ3GYNS7NTKfAdVQSZe"
|
||||
crossorigin="anonymous"
|
||||
defer
|
||||
></script>
|
||||
<script
|
||||
src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js"
|
||||
defer
|
||||
></script>
|
||||
<script
|
||||
src="https://cdn.jsdelivr.net/npm/toastr@2.1.4/build/toastr.min.js"
|
||||
integrity="sha256-Hgwq1OBpJ276HUP9H3VJkSv9ZCGRGQN+JldPJ8pNcUM="
|
||||
crossorigin="anonymous"
|
||||
defer
|
||||
></script>
|
||||
<script
|
||||
src="https://cdn.jsdelivr.net/npm/htmx.org@1.9.4/dist/htmx.min.js"
|
||||
integrity="sha256-XIivRAE99i/eil5P31JNihaDSiix0V40rgmUrCfNTH4="
|
||||
crossorigin="anonymous"
|
||||
></script>
|
||||
<style>
|
||||
#toast-container > div {
|
||||
-moz-box-shadow: none !important;
|
||||
|
@ -71,5 +91,4 @@
|
|||
htmx.onLoad(setupPaste);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -5,7 +5,9 @@
|
|||
<div class="d-flex gap-1 align-items-baseline">
|
||||
Version:
|
||||
<span class="text-muted">{{.Version}}</span>
|
||||
<span class="text-muted text-nowrap" style="font-size: smaller">(Build: {{.Build}})</span>
|
||||
<span class="text-muted text-nowrap" style="font-size: smaller"
|
||||
>(Build: {{.Build}})</span
|
||||
>
|
||||
</div>
|
||||
<div class="d-flex gap-1 align-items-baseline text-nowrap">
|
||||
yt-dlp version: <span class="text-muted">{{.BinaryVersion}}</span>
|
||||
|
@ -13,7 +15,7 @@
|
|||
</div>
|
||||
<div class="d-flex gap-2 col-md justify-content-center">
|
||||
<a href="https://git.fifitido.net/apps/ytdl-web">Git Repository</a>
|
||||
<span class="text-muted">© 2024 FiFiTiDo</span>
|
||||
<span class="text-muted">© 2023 FiFiTiDo</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,85 +0,0 @@
|
|||
/*
|
||||
Copyright © 2024 NAME HERE <EMAIL ADDRESS>
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// completionCmd represents the completion command
|
||||
var completionCmd = &cobra.Command{
|
||||
Use: "completion [bash|zsh|fish|powershell]",
|
||||
Short: "Generate completion script",
|
||||
Long: fmt.Sprintf(`To load completions:
|
||||
|
||||
Bash:
|
||||
|
||||
$ source <(%[1]s completion bash)
|
||||
|
||||
# To load completions for each session, execute once:
|
||||
# Linux:
|
||||
$ %[1]s completion bash > /etc/bash_completion.d/%[1]s
|
||||
# macOS:
|
||||
$ %[1]s completion bash > $(brew --prefix)/etc/bash_completion.d/%[1]s
|
||||
|
||||
Zsh:
|
||||
|
||||
# If shell completion is not already enabled in your environment,
|
||||
# you will need to enable it. You can execute the following once:
|
||||
|
||||
$ echo "autoload -U compinit; compinit" >> ~/.zshrc
|
||||
|
||||
# To load completions for each session, execute once:
|
||||
$ %[1]s completion zsh > "${fpath[1]}/_%[1]s"
|
||||
|
||||
# You will need to start a new shell for this setup to take effect.
|
||||
|
||||
fish:
|
||||
|
||||
$ %[1]s completion fish | source
|
||||
|
||||
# To load completions for each session, execute once:
|
||||
$ %[1]s completion fish > ~/.config/fish/completions/%[1]s.fish
|
||||
|
||||
PowerShell:
|
||||
|
||||
PS> %[1]s completion powershell | Out-String | Invoke-Expression
|
||||
|
||||
# To load completions for every new session, run:
|
||||
PS> %[1]s completion powershell > %[1]s.ps1
|
||||
# and source this file from your PowerShell profile.
|
||||
`, rootCmd.Root().Name()),
|
||||
DisableFlagsInUseLine: true,
|
||||
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
|
||||
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
switch args[0] {
|
||||
case "bash":
|
||||
cmd.Root().GenBashCompletion(os.Stdout)
|
||||
case "zsh":
|
||||
cmd.Root().GenZshCompletion(os.Stdout)
|
||||
case "fish":
|
||||
cmd.Root().GenFishCompletion(os.Stdout, true)
|
||||
case "powershell":
|
||||
cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(completionCmd)
|
||||
|
||||
// Here you will define your flags and configuration settings.
|
||||
|
||||
// Cobra supports Persistent Flags which will work for this command
|
||||
// and all subcommands, e.g.:
|
||||
// completionCmd.PersistentFlags().String("foo", "", "A help for foo")
|
||||
|
||||
// Cobra supports local flags which will only run when this command
|
||||
// is called directly, e.g.:
|
||||
// completionCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||
}
|
68
cmd/root.go
68
cmd/root.go
|
@ -1,16 +1,20 @@
|
|||
/*
|
||||
Copyright © 2024 Evan Fiordeliso <evan.fiordeliso@gmail.com>
|
||||
Copyright © 2023 Evan Fiordeliso <evan.fiordeliso@gmail.com>
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/dgraph-io/badger/v2"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"go.fifitido.net/ytdl-web/app/controllers"
|
||||
"go.fifitido.net/ytdl-web/config"
|
||||
"go.fifitido.net/ytdl-web/pkg/server"
|
||||
"go.fifitido.net/ytdl-web/pkg/utils"
|
||||
"go.fifitido.net/ytdl-web/pkg/ytdl"
|
||||
"go.fifitido.net/ytdl-web/pkg/ytdl/cache"
|
||||
"golang.org/x/exp/slog"
|
||||
)
|
||||
|
||||
|
@ -21,13 +25,47 @@ var (
|
|||
rootCmd = &cobra.Command{
|
||||
Use: "ytdl-web",
|
||||
Short: "A web frontend for yt-dlp",
|
||||
Long: `YTDL Web is a web application that grabs the links to videos
|
||||
from over a thousand websites using the yt-dlp project under the hood.`,
|
||||
Long: `YTDL Web
|
||||
|
||||
A web application that grabs the links to videos from over a
|
||||
thousand websites using the yt-dlp project under the hood.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
logger := slog.Default()
|
||||
|
||||
db, err := badger.Open(
|
||||
badger.
|
||||
DefaultOptions(cfg.Cache.DirPath).
|
||||
WithLogger(utils.NewBadgerLogger(logger.With("module", "badger"))),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
cache := cache.NewDefaultMetadataCache(db)
|
||||
ytdl := ytdl.NewYtdl(cfg, slog.Default(), cache)
|
||||
|
||||
s := server.New(
|
||||
server.
|
||||
DefaultOptions().
|
||||
WithListenAddr(viper.GetString("http.listen")).
|
||||
WithListenPort(viper.GetInt("http.port")).
|
||||
WithLogger(logger.With("module", "server")),
|
||||
)
|
||||
|
||||
s.MountController("/", controllers.NewHomeController(ytdl))
|
||||
s.MountController("/download", controllers.NewDownloadController(ytdl))
|
||||
|
||||
return s.ListenAndServe()
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func Execute() error {
|
||||
return rootCmd.Execute()
|
||||
func Execute() {
|
||||
err := rootCmd.Execute()
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -60,16 +98,14 @@ func initConfig() {
|
|||
cfg, err = config.LoadConfig()
|
||||
}
|
||||
|
||||
notFound := &viper.ConfigFileNotFoundError{}
|
||||
switch {
|
||||
case err != nil && !errors.As(err, notFound):
|
||||
cobra.CheckErr(err)
|
||||
case err != nil && errors.As(err, notFound):
|
||||
// The config file is optional, we shouldn't exit when the config is not found
|
||||
break
|
||||
default:
|
||||
fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
|
||||
if err != nil {
|
||||
slog.Error("Error loading configuration", slog.String("error", err.Error()))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
initLogging()
|
||||
|
||||
slog.Info("Configuration loaded")
|
||||
}
|
||||
|
||||
func initLogging() {
|
||||
|
|
68
cmd/serve.go
68
cmd/serve.go
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
Copyright © 2024 Evan Fiordeliso <evan.fiordeliso@gmail.com>
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/dgraph-io/badger/v2"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"go.fifitido.net/ytdl-web/app/controllers"
|
||||
"go.fifitido.net/ytdl-web/pkg/server"
|
||||
"go.fifitido.net/ytdl-web/pkg/utils"
|
||||
"go.fifitido.net/ytdl-web/pkg/ytdl"
|
||||
"go.fifitido.net/ytdl-web/pkg/ytdl/cache"
|
||||
"golang.org/x/exp/slog"
|
||||
)
|
||||
|
||||
var (
|
||||
serveCmd = &cobra.Command{
|
||||
Use: "serve",
|
||||
Short: "Serve the ytdl-web application",
|
||||
Long: `Serve the ytdl-web application`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
initLogging()
|
||||
logger := slog.Default()
|
||||
|
||||
db, err := badger.Open(
|
||||
badger.
|
||||
DefaultOptions(cfg.Cache.DirPath).
|
||||
WithLogger(utils.NewBadgerLogger(logger.With("module", "badger"))),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
cache := cache.NewDefaultMetadataCache(db)
|
||||
ytdl := ytdl.NewYtdl(cfg, slog.Default(), cache)
|
||||
|
||||
s := server.New(
|
||||
server.
|
||||
DefaultOptions().
|
||||
WithListenAddr(viper.GetString("http.listen")).
|
||||
WithListenPort(viper.GetInt("http.port")).
|
||||
WithLogger(logger.With("module", "server")),
|
||||
)
|
||||
|
||||
s.MountController("/", controllers.NewHomeController(ytdl))
|
||||
s.MountController("/download", controllers.NewDownloadController(ytdl))
|
||||
|
||||
return s.ListenAndServe()
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(serveCmd)
|
||||
|
||||
// Here you will define your flags and configuration settings.
|
||||
|
||||
// Cobra supports Persistent Flags which will work for this command
|
||||
// and all subcommands, e.g.:
|
||||
// completionCmd.PersistentFlags().String("foo", "", "A help for foo")
|
||||
|
||||
// Cobra supports local flags which will only run when this command
|
||||
// is called directly, e.g.:
|
||||
// completionCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||
}
|
|
@ -2,7 +2,6 @@ package config
|
|||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -12,7 +11,7 @@ import (
|
|||
|
||||
type Config struct {
|
||||
Env string `mapstructure:"env"`
|
||||
Ytdlp ConfigYtdlp `mapstructure:"ytdlp"`
|
||||
BinaryPath string `mapstructure:"binaryPath"`
|
||||
HTTP ConfigHTTP `mapstructure:"http"`
|
||||
Cache ConfigCache `mapstructure:"cache"`
|
||||
Cookies ConfigCookies `mapstructure:"cookies"`
|
||||
|
@ -30,10 +29,6 @@ func (c *Config) IsStaging() bool {
|
|||
return c.Env == "Staging"
|
||||
}
|
||||
|
||||
type ConfigYtdlp struct {
|
||||
BinaryPath string `mapstructure:"binaryPath"`
|
||||
}
|
||||
|
||||
type ConfigHTTP struct {
|
||||
Port int `mapstructure:"port"`
|
||||
Listen string `mapstructure:"listen"`
|
||||
|
@ -62,7 +57,7 @@ type ConfigCookiesFromBrowser struct {
|
|||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
Env: "Production",
|
||||
Ytdlp: ConfigYtdlp{BinaryPath: "yt-dlp"},
|
||||
BinaryPath: "yt-dlp",
|
||||
HTTP: ConfigHTTP{
|
||||
Port: 8080,
|
||||
Listen: "127.0.0.1",
|
||||
|
@ -81,44 +76,46 @@ func DefaultConfig() *Config {
|
|||
}
|
||||
|
||||
func LoadConfig(paths ...string) (*Config, error) {
|
||||
viper.SetEnvPrefix("YTDL")
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
viper.AutomaticEnv()
|
||||
v := viper.New()
|
||||
|
||||
viper.SetConfigName("config")
|
||||
viper.SetConfigType("yaml")
|
||||
v.SetEnvPrefix("YTDL")
|
||||
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
v.AutomaticEnv()
|
||||
|
||||
v.SetConfigName("config")
|
||||
v.SetConfigType("yaml")
|
||||
|
||||
if len(paths) > 0 {
|
||||
for _, p := range paths {
|
||||
viper.AddConfigPath(path.Dir(p))
|
||||
for _, path := range paths {
|
||||
v.AddConfigPath(path)
|
||||
}
|
||||
} else {
|
||||
envDir := os.Getenv("YTDL_CONFIGDIR")
|
||||
if envDir != "" {
|
||||
viper.AddConfigPath(envDir)
|
||||
v.AddConfigPath(envDir)
|
||||
}
|
||||
|
||||
viper.AddConfigPath(".")
|
||||
v.AddConfigPath(".")
|
||||
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err == nil {
|
||||
viper.AddConfigPath(homeDir + "/.config/ytdl-web")
|
||||
v.AddConfigPath(homeDir + "/.config/ytdl-web")
|
||||
}
|
||||
|
||||
viper.AddConfigPath(xdg.ConfigHome + "/ytdl-web")
|
||||
v.AddConfigPath(xdg.ConfigHome + "/ytdl-web")
|
||||
for _, dir := range xdg.ConfigDirs {
|
||||
viper.AddConfigPath(dir + "/ytdl-web")
|
||||
v.AddConfigPath(dir + "/ytdl-web")
|
||||
}
|
||||
}
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
if err := v.ReadInConfig(); err != nil {
|
||||
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
config := DefaultConfig()
|
||||
if err := viper.Unmarshal(config); err != nil {
|
||||
if err := v.Unmarshal(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
44
devenv.lock
44
devenv.lock
|
@ -3,11 +3,11 @@
|
|||
"devenv": {
|
||||
"locked": {
|
||||
"dir": "src/modules",
|
||||
"lastModified": 1708141957,
|
||||
"narHash": "sha256-IWkw+jsVpu7HFNPbOTJaQeMYQ5/eh7ZVScPvtlSo8vc=",
|
||||
"lastModified": 1689175844,
|
||||
"narHash": "sha256-+ZAcAnogqNXz5P2/NiZonmgUiv+vCC7/swiSepyTulc=",
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"rev": "40b567388381137a3c49acdff5f4b6946d645a5f",
|
||||
"rev": "db59403d5bdad71dce137705ed7cb926681e5f95",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -20,11 +20,11 @@
|
|||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1696426674,
|
||||
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
||||
"lastModified": 1673956053,
|
||||
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -38,11 +38,11 @@
|
|||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1701680307,
|
||||
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
|
||||
"lastModified": 1685518550,
|
||||
"narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
|
||||
"rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -59,11 +59,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1703887061,
|
||||
"narHash": "sha256-gGPa9qWNc6eCXT/+Z5/zMkyYOuRZqeFZBDbopNZQkuY=",
|
||||
"lastModified": 1660459072,
|
||||
"narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"rev": "43e1aa1308018f37118e34d3a9cb4f5e75dc11d5",
|
||||
"rev": "a20de23b925fd8264fd7fad6454652e142fd7f73",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -74,11 +74,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1708151420,
|
||||
"narHash": "sha256-MGT/4aGCWQPQiu6COqJdCj9kSpLPiShgbwpbC38YXC8=",
|
||||
"lastModified": 1689168768,
|
||||
"narHash": "sha256-mCw3LPg2jJkapvJpkd1IZ8k0IJlSG2ECvz3vcOAu+Uo=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "6e2f00c83911461438301db0dba5281197fe4b3a",
|
||||
"rev": "6fd9edc94426a3c050ad589c8f033b5ca55454c7",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -90,16 +90,16 @@
|
|||
},
|
||||
"nixpkgs-stable": {
|
||||
"locked": {
|
||||
"lastModified": 1704874635,
|
||||
"narHash": "sha256-YWuCrtsty5vVZvu+7BchAxmcYzTMfolSPP5io8+WYCg=",
|
||||
"lastModified": 1685801374,
|
||||
"narHash": "sha256-otaSUoFEMM+LjBI1XL/xGB5ao6IwnZOXc47qhIgJe8U=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "3dc440faeee9e889fe2d1b4d25ad0f430d449356",
|
||||
"rev": "c37ca420157f4abc31e26f436c1145f8951ff373",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-23.11",
|
||||
"ref": "nixos-23.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
|
@ -115,11 +115,11 @@
|
|||
"nixpkgs-stable": "nixpkgs-stable"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1708018599,
|
||||
"narHash": "sha256-M+Ng6+SePmA8g06CmUZWi1AjG2tFBX9WCXElBHEKnyM=",
|
||||
"lastModified": 1688596063,
|
||||
"narHash": "sha256-9t7RxBiKWHygsqXtiNATTJt4lim/oSYZV3RG8OjDDng=",
|
||||
"owner": "cachix",
|
||||
"repo": "pre-commit-hooks.nix",
|
||||
"rev": "5df5a70ad7575f6601d91f0efec95dd9bc619431",
|
||||
"rev": "c8d18ba345730019c3faf412c96a045ade171895",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
# https://devenv.sh/basics/
|
||||
env.NAME = "ytdl-web";
|
||||
env.BINARY_OUT = "./out/ytdl-web";
|
||||
env.VERSION = "v1.2.3";
|
||||
env.VERSION = "v1.1.1";
|
||||
env.VERSION_PKG = "go.fifitido.net/ytdl-web/version";
|
||||
env.DOCKER_REGISTRY = "git.fifitido.net";
|
||||
env.DOCKER_ORG = "apps";
|
||||
|
@ -22,7 +22,6 @@
|
|||
buildkit
|
||||
docker-buildx
|
||||
yt-dlp
|
||||
cobra-cli
|
||||
];
|
||||
|
||||
# https://devenv.sh/scripts/
|
757
flake.lock
757
flake.lock
|
@ -1,757 +0,0 @@
|
|||
{
|
||||
"nodes": {
|
||||
"cachix": {
|
||||
"inputs": {
|
||||
"devenv": "devenv_2",
|
||||
"flake-compat": [
|
||||
"devenv",
|
||||
"flake-compat"
|
||||
],
|
||||
"git-hooks": [
|
||||
"devenv",
|
||||
"pre-commit-hooks"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1726520618,
|
||||
"narHash": "sha256-jOsaBmJ/EtX5t/vbylCdS7pWYcKGmWOKg4QKUzKr6dA=",
|
||||
"owner": "cachix",
|
||||
"repo": "cachix",
|
||||
"rev": "695525f9086542dfb09fde0871dbf4174abbf634",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "cachix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"cachix_2": {
|
||||
"inputs": {
|
||||
"devenv": "devenv_3",
|
||||
"flake-compat": [
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"flake-compat"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
],
|
||||
"pre-commit-hooks": [
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"pre-commit-hooks"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1712055811,
|
||||
"narHash": "sha256-7FcfMm5A/f02yyzuavJe06zLa9hcMHsagE28ADcmQvk=",
|
||||
"owner": "cachix",
|
||||
"repo": "cachix",
|
||||
"rev": "02e38da89851ec7fec3356a5c04bc8349cae0e30",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "cachix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"devenv": {
|
||||
"inputs": {
|
||||
"cachix": "cachix",
|
||||
"flake-compat": "flake-compat_2",
|
||||
"nix": "nix_3",
|
||||
"nixpkgs": "nixpkgs_3",
|
||||
"pre-commit-hooks": "pre-commit-hooks_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1730890834,
|
||||
"narHash": "sha256-ogmpmsPOlX4qeWVW4NZkTd0Lx8V4rvnjwlgOX7WnTZo=",
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"rev": "c5353d1a0483b8f0dc15933de91c6b1b9a892831",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"devenv_2": {
|
||||
"inputs": {
|
||||
"cachix": "cachix_2",
|
||||
"flake-compat": [
|
||||
"devenv",
|
||||
"cachix",
|
||||
"flake-compat"
|
||||
],
|
||||
"nix": "nix_2",
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"cachix",
|
||||
"nixpkgs"
|
||||
],
|
||||
"pre-commit-hooks": [
|
||||
"devenv",
|
||||
"cachix",
|
||||
"git-hooks"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1723156315,
|
||||
"narHash": "sha256-0JrfahRMJ37Rf1i0iOOn+8Z4CLvbcGNwa2ChOAVrp/8=",
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"rev": "ff5eb4f2accbcda963af67f1a1159e3f6c7f5f91",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"devenv_3": {
|
||||
"inputs": {
|
||||
"flake-compat": [
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"flake-compat"
|
||||
],
|
||||
"nix": "nix",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"poetry2nix": "poetry2nix",
|
||||
"pre-commit-hooks": [
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"pre-commit-hooks"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1708704632,
|
||||
"narHash": "sha256-w+dOIW60FKMaHI1q5714CSibk99JfYxm0CzTinYWr+Q=",
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"rev": "2ee4450b0f4b95a1b90f2eb5ffea98b90e48c196",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"ref": "python-rewrite",
|
||||
"repo": "devenv",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1673956053,
|
||||
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-compat_2": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1696426674,
|
||||
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-parts": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": [
|
||||
"devenv",
|
||||
"nix",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1712014858,
|
||||
"narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-parts_2": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1730504689,
|
||||
"narHash": "sha256-hgmguH29K2fvs9szpq2r3pz2/8cJd2LPS+b4tfNFCwE=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "506278e768c2a08bec68eb62932193e341f55c90",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "flake-parts",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1689068808,
|
||||
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"locked": {
|
||||
"lastModified": 1667395993,
|
||||
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_3": {
|
||||
"inputs": {
|
||||
"systems": "systems_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"gitignore": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"pre-commit-hooks",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1709087332,
|
||||
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"libgit2": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1697646580,
|
||||
"narHash": "sha256-oX4Z3S9WtJlwvj0uH9HlYcWv+x1hqp8mhXl7HsLu2f0=",
|
||||
"owner": "libgit2",
|
||||
"repo": "libgit2",
|
||||
"rev": "45fd9ed7ae1a9b74b957ef4f337bc3c8b3df01b5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "libgit2",
|
||||
"repo": "libgit2",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"mk-shell-bin": {
|
||||
"locked": {
|
||||
"lastModified": 1677004959,
|
||||
"narHash": "sha256-/uEkr1UkJrh11vD02aqufCxtbF5YnhRTIKlx5kyvf+I=",
|
||||
"owner": "rrbutani",
|
||||
"repo": "nix-mk-shell-bin",
|
||||
"rev": "ff5d8bd4d68a347be5042e2f16caee391cd75887",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "rrbutani",
|
||||
"repo": "nix-mk-shell-bin",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-regression": "nixpkgs-regression"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1712911606,
|
||||
"narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=",
|
||||
"owner": "domenkozar",
|
||||
"repo": "nix",
|
||||
"rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "domenkozar",
|
||||
"ref": "devenv-2.21",
|
||||
"repo": "nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-github-actions": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"poetry2nix",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1688870561,
|
||||
"narHash": "sha256-4UYkifnPEw1nAzqqPOTL2MvWtm3sNGw1UTYTalkTcGY=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nix-github-actions",
|
||||
"rev": "165b1650b753316aa7f1787f3005a8d2da0f5301",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "nix-github-actions",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix2container": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils_3",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1730479402,
|
||||
"narHash": "sha256-79NLeNjpCa4mSasmFsE3QA6obURezF0TUO5Pm+1daog=",
|
||||
"owner": "nlewo",
|
||||
"repo": "nix2container",
|
||||
"rev": "5fb215a1564baa74ce04ad7f903d94ad6617e17a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nlewo",
|
||||
"repo": "nix2container",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix_2": {
|
||||
"inputs": {
|
||||
"flake-compat": [
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"flake-compat"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-regression": "nixpkgs-regression_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1712911606,
|
||||
"narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=",
|
||||
"owner": "domenkozar",
|
||||
"repo": "nix",
|
||||
"rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "domenkozar",
|
||||
"ref": "devenv-2.21",
|
||||
"repo": "nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix_3": {
|
||||
"inputs": {
|
||||
"flake-compat": [
|
||||
"devenv",
|
||||
"flake-compat"
|
||||
],
|
||||
"flake-parts": "flake-parts",
|
||||
"libgit2": "libgit2",
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"nixpkgs-23-11": "nixpkgs-23-11",
|
||||
"nixpkgs-regression": "nixpkgs-regression_3",
|
||||
"pre-commit-hooks": "pre-commit-hooks"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1727438425,
|
||||
"narHash": "sha256-X8ES7I1cfNhR9oKp06F6ir4Np70WGZU5sfCOuNBEwMg=",
|
||||
"owner": "domenkozar",
|
||||
"repo": "nix",
|
||||
"rev": "f6c5ae4c1b2e411e6b1e6a8181cc84363d6a7546",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "domenkozar",
|
||||
"ref": "devenv-2.24",
|
||||
"repo": "nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1692808169,
|
||||
"narHash": "sha256-x9Opq06rIiwdwGeK2Ykj69dNc2IvUH1fY55Wm7atwrE=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "9201b5ff357e781bf014d0330d18555695df7ba8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-23-11": {
|
||||
"locked": {
|
||||
"lastModified": 1717159533,
|
||||
"narHash": "sha256-oamiKNfr2MS6yH64rUn99mIZjc45nGJlj9eGth/3Xuw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "a62e6edd6d5e1fa0329b8653c801147986f8d446",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "a62e6edd6d5e1fa0329b8653c801147986f8d446",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"lastModified": 1730504152,
|
||||
"narHash": "sha256-lXvH/vOfb4aGYyvFmZK/HlsNsr/0CVWlwYvo2rxJk3s=",
|
||||
"type": "tarball",
|
||||
"url": "https://github.com/NixOS/nixpkgs/archive/cc2f28000298e1269cea6612cd06ec9979dd5d7f.tar.gz"
|
||||
},
|
||||
"original": {
|
||||
"type": "tarball",
|
||||
"url": "https://github.com/NixOS/nixpkgs/archive/cc2f28000298e1269cea6612cd06ec9979dd5d7f.tar.gz"
|
||||
}
|
||||
},
|
||||
"nixpkgs-regression": {
|
||||
"locked": {
|
||||
"lastModified": 1643052045,
|
||||
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-regression_2": {
|
||||
"locked": {
|
||||
"lastModified": 1643052045,
|
||||
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-regression_3": {
|
||||
"locked": {
|
||||
"lastModified": 1643052045,
|
||||
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
"locked": {
|
||||
"lastModified": 1720386169,
|
||||
"narHash": "sha256-NGKVY4PjzwAa4upkGtAMz1npHGoRzWotlSnVlqI40mo=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "194846768975b7ad2c4988bdb82572c00222c0d7",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-24.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1717432640,
|
||||
"narHash": "sha256-+f9c4/ZX5MWDOuB1rKoWj+lBNm0z0rs4CK47HBLxy1o=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "88269ab3044128b7c2f4c7d68448b2fb50456870",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "release-24.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_3": {
|
||||
"locked": {
|
||||
"lastModified": 1716977621,
|
||||
"narHash": "sha256-Q1UQzYcMJH4RscmpTkjlgqQDX5yi1tZL0O345Ri6vXQ=",
|
||||
"owner": "cachix",
|
||||
"repo": "devenv-nixpkgs",
|
||||
"rev": "4267e705586473d3e5c8d50299e71503f16a6fb6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"ref": "rolling",
|
||||
"repo": "devenv-nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_4": {
|
||||
"locked": {
|
||||
"lastModified": 1716977621,
|
||||
"narHash": "sha256-Q1UQzYcMJH4RscmpTkjlgqQDX5yi1tZL0O345Ri6vXQ=",
|
||||
"owner": "cachix",
|
||||
"repo": "devenv-nixpkgs",
|
||||
"rev": "4267e705586473d3e5c8d50299e71503f16a6fb6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"ref": "rolling",
|
||||
"repo": "devenv-nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"poetry2nix": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nix-github-actions": "nix-github-actions",
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1692876271,
|
||||
"narHash": "sha256-IXfZEkI0Mal5y1jr6IRWMqK8GW2/f28xJenZIPQqkY0=",
|
||||
"owner": "nix-community",
|
||||
"repo": "poetry2nix",
|
||||
"rev": "d5006be9c2c2417dafb2e2e5034d83fabd207ee3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "poetry2nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"pre-commit-hooks": {
|
||||
"inputs": {
|
||||
"flake-compat": [
|
||||
"devenv",
|
||||
"nix"
|
||||
],
|
||||
"flake-utils": "flake-utils_2",
|
||||
"gitignore": [
|
||||
"devenv",
|
||||
"nix"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"nix",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-stable": [
|
||||
"devenv",
|
||||
"nix",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1712897695,
|
||||
"narHash": "sha256-nMirxrGteNAl9sWiOhoN5tIHyjBbVi5e2tgZUgZlK3Y=",
|
||||
"owner": "cachix",
|
||||
"repo": "pre-commit-hooks.nix",
|
||||
"rev": "40e6053ecb65fcbf12863338a6dcefb3f55f1bf8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "pre-commit-hooks.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"pre-commit-hooks_2": {
|
||||
"inputs": {
|
||||
"flake-compat": [
|
||||
"devenv",
|
||||
"flake-compat"
|
||||
],
|
||||
"gitignore": "gitignore",
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-stable": "nixpkgs-stable"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1726745158,
|
||||
"narHash": "sha256-D5AegvGoEjt4rkKedmxlSEmC+nNLMBPWFxvmYnVLhjk=",
|
||||
"owner": "cachix",
|
||||
"repo": "pre-commit-hooks.nix",
|
||||
"rev": "4e743a6920eab45e8ba0fbe49dc459f1423a4b74",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "pre-commit-hooks.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"devenv": "devenv",
|
||||
"flake-parts": "flake-parts_2",
|
||||
"mk-shell-bin": "mk-shell-bin",
|
||||
"nix2container": "nix2container",
|
||||
"nixpkgs": "nixpkgs_4"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_2": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
56
flake.nix
56
flake.nix
|
@ -1,56 +0,0 @@
|
|||
{
|
||||
description = "Description for the project";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:cachix/devenv-nixpkgs/rolling";
|
||||
devenv.url = "github:cachix/devenv";
|
||||
nix2container.url = "github:nlewo/nix2container";
|
||||
nix2container.inputs.nixpkgs.follows = "nixpkgs";
|
||||
mk-shell-bin.url = "github:rrbutani/nix-mk-shell-bin";
|
||||
};
|
||||
|
||||
nixConfig = {
|
||||
extra-trusted-public-keys = "devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw=";
|
||||
extra-substituters = "https://devenv.cachix.org";
|
||||
};
|
||||
|
||||
outputs = inputs@{ self, flake-parts, ... }:
|
||||
let
|
||||
version = "1.2.0";
|
||||
rev = if (self ? rev) then self.rev else self.dirtyRev;
|
||||
in
|
||||
flake-parts.lib.mkFlake { inherit inputs; } {
|
||||
imports = [
|
||||
inputs.devenv.flakeModule
|
||||
];
|
||||
systems = [ "x86_64-linux" "i686-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ];
|
||||
|
||||
perSystem = { config, self', inputs', pkgs, system, ... }: {
|
||||
# Per-system attributes can be defined here. The self' and inputs'
|
||||
# module parameters provide easy access to attributes of the same
|
||||
# system.
|
||||
|
||||
# needed for devenv up
|
||||
packages.devenv-up = self'.devShells.default.config.procfileScript;
|
||||
|
||||
packages.default = pkgs.callPackage ./nix/package.nix { inherit rev version; };
|
||||
|
||||
devenv.shells.default = {
|
||||
name = "ytdl-web";
|
||||
|
||||
imports = [
|
||||
./nix/devenv.nix
|
||||
];
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
flake = {
|
||||
# The usual flake attributes can be defined here, including system-
|
||||
# agnostic ones like nixosModule and system-enumerating ones, although
|
||||
# those are more easily expressed in perSystem.
|
||||
|
||||
nixosModules.default = import ./nix/module.nix;
|
||||
};
|
||||
};
|
||||
}
|
3
go.mod
3
go.mod
|
@ -1,12 +1,13 @@
|
|||
module go.fifitido.net/ytdl-web
|
||||
|
||||
go 1.22
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/adrg/xdg v0.4.0
|
||||
github.com/dgraph-io/badger/v2 v2.2007.4
|
||||
github.com/go-chi/chi/v5 v5.0.10
|
||||
github.com/htfy96/reformism v0.0.0-20160819020323-e5bfca398e73
|
||||
github.com/samber/lo v1.38.1
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/spf13/viper v1.10.0
|
||||
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53
|
||||
|
|
3
go.sum
3
go.sum
|
@ -36,7 +36,6 @@ github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
|
|||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/htfy96/reformism v0.0.0-20160819020323-e5bfca398e73 h1:Shcv21tstWAyUkKxbn5bTARYej9sgEgFgTRxUPk1J8o=
|
||||
|
@ -72,6 +71,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
|
||||
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
|
|
13
main.go
13
main.go
|
@ -1,16 +1,11 @@
|
|||
/*
|
||||
Copyright © 2024 Evan Fiordeliso <evan.fiordeliso@gmail.com>
|
||||
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
|
||||
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"go.fifitido.net/ytdl-web/cmd"
|
||||
)
|
||||
import "go.fifitido.net/ytdl-web/cmd"
|
||||
|
||||
func main() {
|
||||
if err := cmd.Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
cmd.Execute()
|
||||
}
|
||||
|
|
229
nix/module.nix
229
nix/module.nix
|
@ -1,229 +0,0 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
inherit (lib.options) mkOption mkEnableOption mkPackageOption;
|
||||
inherit (lib.modules) mkIf;
|
||||
inherit (lib.types) str enum int path submodule nullOr bool;
|
||||
|
||||
cfg = config.services.ytdl-web;
|
||||
in
|
||||
{
|
||||
options.services.ytdl-web = {
|
||||
enable = mkEnableOption "ytdl-web";
|
||||
package = mkPackageOption pkgs "ytdl-web" { };
|
||||
|
||||
user = mkOption {
|
||||
type = str;
|
||||
default = "ytdl-web";
|
||||
description = ''
|
||||
The user account ytdl-web will run under.
|
||||
'';
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = str;
|
||||
default = "ytdl-web";
|
||||
description = ''
|
||||
The group ytdl-web will run under.
|
||||
'';
|
||||
};
|
||||
|
||||
appEnvironment = mkOption {
|
||||
type = enum [ "Development" "Staging" "Production" ];
|
||||
default = "Production";
|
||||
description = ''
|
||||
The application environment mode.
|
||||
'';
|
||||
};
|
||||
|
||||
ytdlPackage = mkPackageOption pkgs "yt-dlp" { };
|
||||
|
||||
port = mkOption {
|
||||
type = int;
|
||||
default = 8080;
|
||||
description = ''
|
||||
The tcp port for the web server to listen on.
|
||||
'';
|
||||
};
|
||||
|
||||
listen = mkOption {
|
||||
type = str;
|
||||
default = "0.0.0.0";
|
||||
example = "127.0.0.1";
|
||||
description = ''
|
||||
The address for the web server to listen on.
|
||||
'';
|
||||
};
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = bool;
|
||||
default = false;
|
||||
example = literalExpression "true";
|
||||
description = ''
|
||||
Open ports in the firewall for the ytdl-web server.
|
||||
'';
|
||||
};
|
||||
|
||||
basePath = mkOption {
|
||||
type = str;
|
||||
default = "/";
|
||||
example = "/ytdl-web";
|
||||
description = ''
|
||||
The base path that the web application is hosted under.
|
||||
Useful for reverse-proxies that proxy the app with a path prefix.
|
||||
'';
|
||||
};
|
||||
|
||||
cacheTTL = mkOption {
|
||||
type = str;
|
||||
default = "1h";
|
||||
example = "2m";
|
||||
description = ''
|
||||
How long to keep cached metadata for.
|
||||
|
||||
A duration string is a possibly signed sequence of decimal numbers,
|
||||
each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m".
|
||||
Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
|
||||
'';
|
||||
};
|
||||
|
||||
cacheDir = mkOption {
|
||||
type = path;
|
||||
default = "/var/cache/ytdl-web";
|
||||
example = "/tmp/ytdl-web";
|
||||
description = ''
|
||||
The directory containing the ytdl metadata cache.
|
||||
'';
|
||||
};
|
||||
|
||||
cookies = mkOption {
|
||||
type = submodule {
|
||||
options = {
|
||||
enable = mkEnableOption "ytdl cookies";
|
||||
|
||||
file = mkOption {
|
||||
type = nullOr path;
|
||||
default = null;
|
||||
example = "/etc/ytdl-web/cookies.txt";
|
||||
description = ''
|
||||
The file that contains the netscape formatted cookies.
|
||||
'';
|
||||
};
|
||||
|
||||
fromBrowser = mkOption {
|
||||
description = ''
|
||||
Settings for obtaining cookies from a local browser's cookie storage.
|
||||
'';
|
||||
|
||||
type = submodule {
|
||||
options = {
|
||||
browser = mkOption {
|
||||
type = nullOr (enum [ "brave" "chrome" "chromium" "edge" "firefox" "opera" "safari" "vivaldi" ]);
|
||||
default = null;
|
||||
description = ''
|
||||
The name of the browser to load cookies from.
|
||||
'';
|
||||
};
|
||||
|
||||
keyring = mkOption {
|
||||
type = nullOr (enum [ "basictext" "gnomekeyring" "kwallet" ]);
|
||||
default = null;
|
||||
description = ''
|
||||
The name of the keyring to use to decrypt cookies when using the chromium browser.
|
||||
'';
|
||||
};
|
||||
|
||||
profile = mkOption {
|
||||
type = nullOr str;
|
||||
default = null;
|
||||
description = ''
|
||||
The name of the browser profile to load the cookies from.
|
||||
'';
|
||||
};
|
||||
|
||||
container = mkOption {
|
||||
type = nullOr str;
|
||||
default = null;
|
||||
description = ''
|
||||
The container name to load the cookies from when using the firefox browser.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.cookies.file != null && cfg.cookies.fromBrowser.browser != null;
|
||||
message = "The `services.ytdl-web.cookies.file` and `services.ytdl-web.cookies.fromBrowser.browser` options are mutually exclusive.";
|
||||
}
|
||||
{
|
||||
assertion = cfg.cookies.fromBrowser.browser != null &&
|
||||
!(builtins.elem cfg.cookies.fromBrowser.browser [ "brave" "chrome" "chromium" "edge" "opera" "vivaldi" ]) &&
|
||||
cfg.cookies.fromBrowser.keyring != null;
|
||||
message = "The `services.ytdl-web.cookies.fromBrowser.keyring` only functions with a chromium-based browser.";
|
||||
}
|
||||
{
|
||||
assertion = cfg.cookies.fromBrowser.browser != null &&
|
||||
cfg.cookies.fromBrowser.browser != "firefox" &&
|
||||
cfg.cookies.fromBrowser.container != null;
|
||||
message = "The `services.ytdl-web.cookies.fromBrowser.container` only functions with the firefox browser.";
|
||||
}
|
||||
];
|
||||
|
||||
systemd.services.ytdl-web = {
|
||||
description = "ytdl-web";
|
||||
after = [ "networking.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
environment = {
|
||||
YTDL_ENV = cfg.appEnvironment;
|
||||
YTDL_BINARY_PATH = toString cfg.ytdlPackage;
|
||||
YTDL_HTTP_PORT = toString cfg.port;
|
||||
YTDL_HTTP_LISTEN = cfg.listen;
|
||||
YTDL_HTTP_BASEPATH = cfg.basePath;
|
||||
YTDL_CACHE_TTL = cfg.cacheTTL;
|
||||
YTDL_CACHE_DIRPATH = cfg.cacheDir;
|
||||
YTDL_COOKIES_ENABLED = if cfg.cookies.enable then "true" else "false";
|
||||
YTDL_COOKIES_FILEPATH = cfg.cookies.file;
|
||||
YTDL_COOKIES_FROMBROWSER_BROWSER = cfg.cookies.fromBrowser.browser;
|
||||
YTDL_COOKIES_FROMBROWSER_KEYRING = cfg.cookies.fromBrowser.keyring;
|
||||
YTDL_COOKIES_FROMBROWSER_PROFILE = cfg.cookies.fromBrowser.profile;
|
||||
YTDL_COOKIES_FROMBROWSER_CONTAINER = cfg.cookies.fromBrowser.container;
|
||||
};
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
ExecStart = "${lib.getExe cfg.package} serve";
|
||||
Restart = "on-failure";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${cfg.cacheDir} 0770 ${cfg.user} ${cfg.group} -"
|
||||
];
|
||||
|
||||
networking.firewall = mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [ cfg.port ];
|
||||
};
|
||||
|
||||
users = {
|
||||
users = mkIf (cfg.user == "ytdl-web") {
|
||||
ytdl-web = {
|
||||
isSystemUser = true;
|
||||
group = cfg.group;
|
||||
};
|
||||
};
|
||||
|
||||
groups = mkIf (cfg.group == "ytdl-web") {
|
||||
ytdl-web = { };
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
{ buildGoModule
|
||||
, installShellFiles
|
||||
, lib
|
||||
, rev
|
||||
, version
|
||||
, ...
|
||||
}:
|
||||
buildGoModule rec {
|
||||
pname = "ytdl-web";
|
||||
inherit version;
|
||||
|
||||
src = ./..;
|
||||
|
||||
vendorHash = "sha256-Rqh5tGcSey53e0Ln3u5agvOwRJ6/I1eUpzRylwtjhQo=";
|
||||
|
||||
ldflags = [
|
||||
"-s"
|
||||
"-w"
|
||||
"-X $VERSION_PKG.Version=${version}"
|
||||
"-X $VERSION_PKG.Build=${rev}"
|
||||
"-X $VERSION_PKG.BuildDate=1970-01-01T0:00:00+0000"
|
||||
"-X $VERSION_PKG.BuiltBy=nix"
|
||||
];
|
||||
|
||||
nativeBuildInputs = [ installShellFiles ];
|
||||
|
||||
postInstall = ''
|
||||
installShellCompletion --cmd ${pname} \
|
||||
--zsh <($out/bin/${pname} completion zsh) \
|
||||
--bash <($out/bin/${pname} completion bash) \
|
||||
--fish <($out/bin/${pname} completion fish)
|
||||
'';
|
||||
|
||||
meta = with lib; {
|
||||
description = "Yet another yt-dlp web frontend written in Go.";
|
||||
homepage = "https://git.fifitido.net/apps/ytdl-web";
|
||||
license = licenses.gpl3Only;
|
||||
mainProgram = "ytdl-web";
|
||||
};
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package ytdl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"os/exec"
|
||||
|
||||
"go.fifitido.net/ytdl-web/pkg/ytdl/metadata"
|
||||
)
|
||||
|
||||
func Exec(binary, url string, options ...Option) error {
|
||||
opts := Options{
|
||||
args: []string{url},
|
||||
stdin: nil,
|
||||
stdout: nil,
|
||||
stderr: nil,
|
||||
output: nil,
|
||||
}
|
||||
for _, opt := range options {
|
||||
if err := opt(&opts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
cmd := exec.Command(binary, opts.args...)
|
||||
|
||||
if opts.stdin != nil {
|
||||
cmd.Stdin = opts.stdin
|
||||
}
|
||||
|
||||
if opts.stdout != nil {
|
||||
cmd.Stdout = opts.stdout
|
||||
}
|
||||
|
||||
if opts.stderr != nil {
|
||||
cmd.Stderr = opts.stderr
|
||||
}
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf, bufOk := opts.stdout.(*bytes.Buffer)
|
||||
meta, metaOk := opts.output.(*metadata.Metadata)
|
||||
if bufOk && metaOk {
|
||||
if err := json.Unmarshal(buf.Bytes(), meta); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
package ytdl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"go.fifitido.net/ytdl-web/pkg/utils"
|
||||
"go.fifitido.net/ytdl-web/pkg/ytdl/metadata"
|
||||
"golang.org/x/exp/slog"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
args []string
|
||||
stdin io.Reader
|
||||
stdout io.Writer
|
||||
stderr io.Writer
|
||||
output interface{}
|
||||
}
|
||||
|
||||
type Option func(*Options) error
|
||||
|
||||
func WithFormat(format string) Option {
|
||||
return func(opts *Options) error {
|
||||
opts.args = append(opts.args, "--format", format)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithCookieFile(cookieFile string) Option {
|
||||
return func(opts *Options) error {
|
||||
opts.args = append(opts.args, "--cookies", cookieFile)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithDumpJson(meta *metadata.Metadata) Option {
|
||||
return func(opts *Options) error {
|
||||
opts.args = append(opts.args, "--dump-single-json")
|
||||
opts.stdout = new(bytes.Buffer)
|
||||
opts.output = meta
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithLoadJson(metadata *metadata.Metadata) Option {
|
||||
return func(opts *Options) error {
|
||||
opts.args = append(opts.args, "--load-info-json", "-")
|
||||
|
||||
json, err := json.Marshal(metadata)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
opts.stdin = bytes.NewBuffer(json)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithBrowserCookies(browser, keyring, profile, container string) Option {
|
||||
return func(opts *Options) error {
|
||||
var sb strings.Builder
|
||||
sb.WriteString(browser)
|
||||
|
||||
if keyring != "" {
|
||||
sb.WriteByte('+')
|
||||
sb.WriteString(keyring)
|
||||
}
|
||||
|
||||
if profile != "" {
|
||||
sb.WriteByte(':')
|
||||
sb.WriteString(profile)
|
||||
}
|
||||
|
||||
if container != "" {
|
||||
sb.WriteByte(':')
|
||||
sb.WriteByte(':')
|
||||
sb.WriteString(container)
|
||||
}
|
||||
|
||||
opts.args = append(opts.args, "--cookies-from-browser", sb.String())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithStreamOutput(output io.Writer) Option {
|
||||
return func(opts *Options) error {
|
||||
opts.args = append(opts.args, "--output", "-")
|
||||
opts.stdout = output
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithDebug() Option {
|
||||
return func(opts *Options) error {
|
||||
opts.stdout = utils.LoggerWriter(slog.With("module", "ytdl"), slog.LevelDebug)
|
||||
opts.stderr = utils.LoggerWriter(slog.With("module", "ytdl"), slog.LevelDebug)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithPlaylistIndex(index int) Option {
|
||||
return func(opts *Options) error {
|
||||
opts.args = append(opts.args, "--playlist-items", fmt.Sprint(index+1))
|
||||
return nil
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
package ytdl
|
||||
|
||||
type Error struct {
|
||||
stdout string
|
||||
stderr string
|
||||
child error
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return e.child.Error()
|
||||
}
|
||||
|
||||
func (e *Error) Stdout() string {
|
||||
return e.stdout
|
||||
}
|
||||
|
||||
func (e *Error) Stderr() string {
|
||||
return e.stderr
|
||||
}
|
103
pkg/ytdl/ytdl.go
103
pkg/ytdl/ytdl.go
|
@ -2,8 +2,6 @@ package ytdl
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
@ -29,7 +27,7 @@ type ytdlImpl struct {
|
|||
|
||||
func NewYtdl(cfg *config.Config, logger *slog.Logger, cache cache.MetadataCache) Ytdl {
|
||||
cmd := exec.Command(
|
||||
cfg.Ytdlp.BinaryPath,
|
||||
cfg.BinaryPath,
|
||||
"--version",
|
||||
)
|
||||
var out bytes.Buffer
|
||||
|
@ -44,44 +42,32 @@ func NewYtdl(cfg *config.Config, logger *slog.Logger, cache cache.MetadataCache)
|
|||
}
|
||||
}
|
||||
|
||||
func buildBrowserCookieString(browser, keyring, profile, container string) string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString(browser)
|
||||
func (y *ytdlImpl) baseOptions(url string) []Option {
|
||||
options := []Option{}
|
||||
|
||||
if keyring != "" {
|
||||
sb.WriteByte('+')
|
||||
sb.WriteString(keyring)
|
||||
metadata, err := y.cache.Get(url)
|
||||
if err == nil {
|
||||
options = append(options, WithLoadJson(metadata))
|
||||
}
|
||||
|
||||
if profile != "" {
|
||||
sb.WriteByte(':')
|
||||
sb.WriteString(profile)
|
||||
}
|
||||
|
||||
if container != "" {
|
||||
sb.WriteByte(':')
|
||||
sb.WriteByte(':')
|
||||
sb.WriteString(container)
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (y *ytdlImpl) appendCookieArgs(args []string) []string {
|
||||
if y.cfg.Cookies.Enabled {
|
||||
if y.cfg.Cookies.FromBrowser.Browser != "" {
|
||||
args = append(args, "--cookies-from-browser", buildBrowserCookieString(
|
||||
options = append(options, WithBrowserCookies(
|
||||
y.cfg.Cookies.FromBrowser.Browser,
|
||||
y.cfg.Cookies.FromBrowser.Keyring,
|
||||
y.cfg.Cookies.FromBrowser.Profile,
|
||||
y.cfg.Cookies.FromBrowser.Container,
|
||||
))
|
||||
} else {
|
||||
args = append(args, "--cookies", y.cfg.Cookies.FilePath)
|
||||
options = append(options, WithCookieFile(y.cfg.Cookies.FilePath))
|
||||
}
|
||||
}
|
||||
|
||||
return args
|
||||
if y.cfg.IsDevelopment() {
|
||||
options = append(options, WithDebug())
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
func (y *ytdlImpl) Version() string {
|
||||
|
@ -95,29 +81,14 @@ func (y *ytdlImpl) GetMetadata(url string) (*metadata.Metadata, error) {
|
|||
return meta, nil
|
||||
}
|
||||
|
||||
args := []string{
|
||||
url,
|
||||
"--dump-single-json",
|
||||
}
|
||||
|
||||
args = y.appendCookieArgs(args)
|
||||
|
||||
cmd := exec.Command(y.cfg.Ytdlp.BinaryPath, args...)
|
||||
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
attrs := []any{
|
||||
slog.String("url", url),
|
||||
slog.String("error", err.Error()),
|
||||
}
|
||||
|
||||
y.logger.Error("failed to get metadata", attrs...)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
meta = &metadata.Metadata{}
|
||||
if err := json.Unmarshal(out, meta); err != nil {
|
||||
y.logger.Error("failed to unmarshal metadata", slog.String("url", url), slog.String("error", err.Error()))
|
||||
options := append(
|
||||
y.baseOptions(url),
|
||||
WithDumpJson(meta),
|
||||
)
|
||||
|
||||
if err := Exec(y.cfg.BinaryPath, url, options...); err != nil {
|
||||
y.logger.Error("failed to get metadata", slog.String("url", url), slog.String("error", err.Error()))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -130,37 +101,17 @@ func (y *ytdlImpl) GetMetadata(url string) (*metadata.Metadata, error) {
|
|||
|
||||
// Download implements Ytdl
|
||||
func (y *ytdlImpl) Download(w io.Writer, url, format string, index int) error {
|
||||
args := []string{
|
||||
url,
|
||||
"--format", format,
|
||||
"--output", "-",
|
||||
}
|
||||
options := append(
|
||||
y.baseOptions(url),
|
||||
WithFormat(format),
|
||||
WithStreamOutput(w),
|
||||
)
|
||||
|
||||
if index >= 0 {
|
||||
args = append(args, "--playlist-ites", fmt.Sprint(index+1))
|
||||
options = append(options, WithPlaylistIndex(index))
|
||||
}
|
||||
|
||||
args = y.appendCookieArgs(args)
|
||||
|
||||
metadata, err := y.cache.Get(url)
|
||||
if err == nil {
|
||||
args = append(args, "--load-info-json", "-")
|
||||
}
|
||||
|
||||
cmd := exec.Command(y.cfg.Ytdlp.BinaryPath, args...)
|
||||
cmd.Stdout = w
|
||||
|
||||
if err == nil {
|
||||
json, err := json.Marshal(metadata)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Stdin = bytes.NewReader(json)
|
||||
}
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
y.logger.Error("failed to download", slog.String("url", url), slog.String("error", err.Error()))
|
||||
if err := Exec(y.cfg.BinaryPath, url, options...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue