feat: support opml from youtube
All checks were successful
build / build (push) Successful in 1m55s

This commit is contained in:
Viktor Varland 2025-04-08 12:32:12 +02:00
parent 4dc4c380c4
commit 3c2ec61635
Signed by: varl
GPG key ID: 7459F0B410115EE8
5 changed files with 60 additions and 20 deletions

View file

@ -12,13 +12,12 @@ out_dir = "./vids" # path to archive vids
[provider.youtube] [provider.youtube]
cmd = "./yt-dlp" # path to yt-dlp binary cmd = "./yt-dlp" # path to yt-dlp binary
quality = "res:1080" # set the preferred quality quality = "res:1080" # set the preferred quality
output_path_template = "" # yt-dlp output template output_path_template = "s%(upload_date>%Y)s/%(channel)s.s%(upload_date>%Y)Se%(upload_date>%m%d)S.%(title)s.%(id)s-1080p.%(ext)s" # yt-dlp output template
url = "https://www.youtube.com" # full yt url url = "https://www.youtube.com" # full yt url
throttle = 1 # throttle yt request throttle = 5 # throttle yt request, 5s works well
range = "1:5:1" # downloads videos in range 1-5: [START][:STOP][:STEP] range = "1:5:1" # downloads videos in range 1-5: [START][:STOP][:STEP]
after_date = "20250326" # not in use after_date = "20250326" # only download videos after date
cookies = false # control use of cookies file cookies_file = "./cookies.txt" # pass user cookies to yt, blank to disable
cookies_file = "./cookies.txt" # pass user cookies to yt
opml_file = "./opml.xml" # the opml file to use opml_file = "./opml.xml" # the opml file to use
``` ```
@ -29,14 +28,14 @@ opml_file = "./opml.xml" # the opml file to use
<opml version="1.1"> <opml version="1.1">
<body> <body>
<outline ...> <outline ...>
<outline .../> <outline text="" title="" xmlUrl="" .../>
<outline .../> <outline text="" title="" xmlUrl="" .../>
<outline .../> <outline text="" title="" xmlUrl="" .../>
</outline> </outline>
<outline ...> <outline ...>
<outline .../> <outline text="" title="" xmlUrl="" .../>
<outline .../> <outline text="" title="" xmlUrl="" .../>
<outline .../> <outline text="" title="" xmlUrl="" .../>
</outline> </outline>
</body> </body>
</opml> </opml>
@ -59,6 +58,38 @@ E.g. from Chromium:
yt-dlp --cookies-from-browser chromium --cookies cookies.txt yt-dlp --cookies-from-browser chromium --cookies cookies.txt
``` ```
## Scheduling
### systemd
`~/.config/systemd/user/subsyt-archival.service`
```
[Unit]
Description=subsyt archival of yt subscribtions
[Service]
Type=oneshot
ExecStart=/home/varl/yt/yt-dlp -U
ExecStart=/home/varl/yt/subsyt
WorkingDirectory=/home/varl/yt
```
`~/.config/systemd/user/subsyt-archival.timer`
```
[Unit]
Description=subsyt archival on boot and daily
[Timer]
OnCalendar=*-*-* 4:00:00
Persistent=true
AccuracySec=1us
RandomizedDelaySec=30
[Install]
WantedBy=timers.target
```
## Container ## Container
``` ```
@ -97,3 +128,7 @@ podman run --rm \
│   │   └── Technology_Connextras.s2025e0331.An_unplanned_trip_from_Chicago_to_Milwaukee_in_an_electric_car.3GUQdrpduo0-1080p.webm │   │   └── Technology_Connextras.s2025e0331.An_unplanned_trip_from_Chicago_to_Milwaukee_in_an_electric_car.3GUQdrpduo0-1080p.webm
│   └── tvshow.nfo │   └── tvshow.nfo
``` ```
## Generate OPML
E.g. https://github.com/jeb5/YouTube-Subscriptions-RSS

View file

@ -13,7 +13,6 @@ type Provider struct {
Range string Range string
After_date string After_date string
Cmd string Cmd string
Cookies bool
Cookies_file string Cookies_file string
Opml_file string Opml_file string
Quality string Quality string

View file

@ -10,7 +10,6 @@ import (
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings"
"sync" "sync"
"git.meatbag.se/varl/subsyt/internal/config" "git.meatbag.se/varl/subsyt/internal/config"
@ -27,16 +26,22 @@ func Youtube(d Download, p config.Provider) {
archive := filepath.Join(d.OutDir, "archive.txt") archive := filepath.Join(d.OutDir, "archive.txt")
outdir := d.OutDir outdir := d.OutDir
curl := strings.TrimPrefix(d.Url, "/feed/") opmlUrl, err := url.Parse(d.Url)
furl, err := url.JoinPath(p.Url, curl, "videos")
if err != nil { if err != nil {
panic(err) panic(err)
} }
q := opmlUrl.Query()
cid := q.Get("channel_id")
if cid == "" {
log.Fatal("no channel !")
}
fullUrl, err := url.Parse(furl) fullUrl, err := url.Parse(p.Url)
if err != nil { if err != nil {
panic(err) panic(err)
} }
channelUrl := fullUrl.JoinPath("channel", cid, "videos")
throttle := strconv.Itoa(p.Throttle) throttle := strconv.Itoa(p.Throttle)
@ -70,7 +75,7 @@ func Youtube(d Download, p config.Provider) {
args = append(args, "--no-simulate") args = append(args, "--no-simulate")
} }
if p.Cookies == true { if p.Cookies_file != "" {
args = append(args, "--cookies") args = append(args, "--cookies")
args = append(args, p.Cookies_file) args = append(args, p.Cookies_file)
} else { } else {
@ -82,7 +87,7 @@ func Youtube(d Download, p config.Provider) {
args = append(args, p.After_date) args = append(args, p.After_date)
} }
args = append(args, fullUrl.String()) args = append(args, channelUrl.String())
cmd := exec.Command(p.Cmd, args...) cmd := exec.Command(p.Cmd, args...)
stdout, err := cmd.StdoutPipe() stdout, err := cmd.StdoutPipe()
@ -95,7 +100,7 @@ func Youtube(d Download, p config.Provider) {
log.Fatal(err) log.Fatal(err)
} }
log.Printf("[%s] running yt-dlp for: %s\n", d.OutDir, d.Url) log.Printf("[%s] running yt-dlp with args: %v\n", d.OutDir, args)
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(2) wg.Add(2)

View file

@ -25,7 +25,7 @@ func WriteEpisodeNFO(ep models.Episode, info_path string) {
} }
func WriteShowInfo(show models.Show, out_path string) { func WriteShowInfo(show models.Show, out_path string) {
log.Printf("writing info from '%s' to '%s'\n", show, out_path) log.Printf("writing info from '%v' to '%s'\n", show, out_path)
xmlData, err := xml.MarshalIndent(show, "", " ") xmlData, err := xml.MarshalIndent(show, "", " ")
if err != nil { if err != nil {

View file

@ -2,6 +2,7 @@ with (import <nixpkgs> {});
mkShell { mkShell {
buildInputs = [ buildInputs = [
go_1_24
yt-dlp yt-dlp
]; ];