feat: generate metadata for shows and episodes
All checks were successful
build / build (push) Successful in 2m2s
All checks were successful
build / build (push) Successful in 2m2s
This commit is contained in:
parent
7af5900b04
commit
dda892750d
|
|
@ -11,6 +11,8 @@ out_dir = "./vids" # path to archive vids
|
||||||
[provider]
|
[provider]
|
||||||
[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
|
||||||
|
output_path_template = "" # 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 = 1 # throttle yt request
|
||||||
range = "1:5:1" # [START][:STOP][:STEP]
|
range = "1:5:1" # [START][:STOP][:STEP]
|
||||||
|
|
@ -42,6 +44,9 @@ opml_file = "./opml.xml" # the opml file to use
|
||||||
|
|
||||||
## Cookies
|
## Cookies
|
||||||
|
|
||||||
|
> [!CRITICAL]
|
||||||
|
> Your account **MAY** be banned when using cookies !
|
||||||
|
|
||||||
E.g. from Chromium:
|
E.g. from Chromium:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ type Provider struct {
|
||||||
Cookies bool
|
Cookies bool
|
||||||
Cookies_file string
|
Cookies_file string
|
||||||
Opml_file string
|
Opml_file string
|
||||||
|
Quality string
|
||||||
|
Output_path_template string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
|
@ -37,7 +39,7 @@ func Load(filepath string) (Config, error) {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Loaded config:")
|
log.Println("Loaded config:")
|
||||||
log.Printf("%+v\n", cfg)
|
log.Printf("%+v\n", cfg)
|
||||||
|
|
||||||
return cfg, err
|
return cfg, err
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,11 @@ package dl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
@ -20,10 +23,9 @@ type Download struct {
|
||||||
DryRun bool
|
DryRun bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func Get(d Download, p config.Provider) {
|
func Youtube(d Download, p config.Provider) {
|
||||||
output := filepath.Join("%(channel)s-%(title)s-%(id)s.%(ext)s")
|
archive := filepath.Join(d.OutDir, "archive.txt")
|
||||||
archive := filepath.Join(d.OutDir, d.Name, "archive.txt")
|
outdir := d.OutDir
|
||||||
outdir := filepath.Join(d.OutDir, d.Name)
|
|
||||||
|
|
||||||
curl := strings.TrimPrefix(d.Url, "/feed/")
|
curl := strings.TrimPrefix(d.Url, "/feed/")
|
||||||
furl, err := url.JoinPath(p.Url, curl, "videos")
|
furl, err := url.JoinPath(p.Url, curl, "videos")
|
||||||
|
|
@ -43,22 +45,26 @@ func Get(d Download, p config.Provider) {
|
||||||
"--sleep-interval", throttle,
|
"--sleep-interval", throttle,
|
||||||
"--sleep-subtitles", throttle,
|
"--sleep-subtitles", throttle,
|
||||||
"--sleep-requests", throttle,
|
"--sleep-requests", throttle,
|
||||||
|
"--format-sort", p.Quality,
|
||||||
"--prefer-free-formats",
|
"--prefer-free-formats",
|
||||||
"--write-subs",
|
"--write-subs",
|
||||||
"--no-write-automatic-subs",
|
"--no-write-automatic-subs",
|
||||||
"--sub-langs", "en",
|
"--sub-langs", "en",
|
||||||
"--paths", outdir,
|
"--paths", outdir,
|
||||||
"--output", output,
|
"--output", p.Output_path_template,
|
||||||
"--download-archive", archive,
|
"--download-archive", archive,
|
||||||
"--break-on-existing",
|
"--break-on-existing",
|
||||||
"--playlist-items", p.Range,
|
"--playlist-items", p.Range,
|
||||||
"--restrict-filenames",
|
"--restrict-filenames",
|
||||||
"--embed-metadata",
|
"--embed-metadata",
|
||||||
|
"--write-thumbnail",
|
||||||
|
"--write-info-json",
|
||||||
|
"--convert-thumbnails", "jpg",
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.DryRun == true {
|
if d.DryRun == true {
|
||||||
args = append(args, "--simulate")
|
args = append(args, "--simulate")
|
||||||
log.Printf("/!\\ DRY RUN ENABLED /!\\")
|
log.Println("/!\\ DRY RUN ENABLED /!\\")
|
||||||
} else {
|
} else {
|
||||||
args = append(args, "--no-simulate")
|
args = append(args, "--no-simulate")
|
||||||
}
|
}
|
||||||
|
|
@ -83,7 +89,7 @@ func Get(d Download, p config.Provider) {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[%s] running yt-dlp for: %s", d.Name, d.Url)
|
log.Printf("[%s] running yt-dlp for: %s\n", d.OutDir, d.Url)
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(2)
|
wg.Add(2)
|
||||||
|
|
@ -92,7 +98,7 @@ func Get(d Download, p config.Provider) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
scanner := bufio.NewScanner(stdout)
|
scanner := bufio.NewScanner(stdout)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
log.Printf("[%s] %s\n", d.Name, scanner.Text())
|
log.Printf("[%s] %s\n", d.OutDir, scanner.Text())
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
@ -100,7 +106,7 @@ func Get(d Download, p config.Provider) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
scanner := bufio.NewScanner(stderr)
|
scanner := bufio.NewScanner(stderr)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
log.Printf("[%s] %s\n", d.Name, scanner.Text())
|
log.Printf("[%s] %s\n", d.OutDir, scanner.Text())
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
@ -116,3 +122,30 @@ func Get(d Download, p config.Provider) {
|
||||||
log.Printf("Error: %s\n", err)
|
log.Printf("Error: %s\n", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Fetch(d Download) {
|
||||||
|
// Create output directory if it doesn't exist
|
||||||
|
if err := os.MkdirAll(d.OutDir, 0755); err != nil {
|
||||||
|
}
|
||||||
|
|
||||||
|
outputPath := filepath.Join(d.OutDir, d.Name)
|
||||||
|
|
||||||
|
out, err := os.Create(outputPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to create '%s'\n", outputPath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
resp, err := http.Get(d.Url)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to download '%s'\n", d.Url)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(out, resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to write file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
128
internal/metadata/metadata.go
Normal file
128
internal/metadata/metadata.go
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
package metadata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.meatbag.se/varl/subsyt/internal/dl"
|
||||||
|
"git.meatbag.se/varl/subsyt/internal/models"
|
||||||
|
"git.meatbag.se/varl/subsyt/internal/nfo"
|
||||||
|
)
|
||||||
|
|
||||||
|
func findFiles(scanPath string, ext string) ([]string, error) {
|
||||||
|
var result []string
|
||||||
|
|
||||||
|
err := filepath.Walk(scanPath, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !info.IsDir() && strings.HasSuffix(path, ext) {
|
||||||
|
result = append(result, path)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error walking directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Generate(outDir string, title string, dryRun bool) {
|
||||||
|
showDir := filepath.Join(outDir, title)
|
||||||
|
log.Printf("Writing NFO's for %s\n", showDir)
|
||||||
|
|
||||||
|
if dryRun {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
infojsons, err := findFiles(showDir, ".info.json")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
show := regexp.MustCompile("s(NA)")
|
||||||
|
season := regexp.MustCompile(`s\d\d\d\d`)
|
||||||
|
|
||||||
|
for index, path := range infojsons {
|
||||||
|
log.Println(index, path)
|
||||||
|
switch {
|
||||||
|
case show.MatchString(path):
|
||||||
|
show := models.LoadShow(path)
|
||||||
|
nfo.WriteShowInfo(show, filepath.Join(showDir, "tvshow.nfo"))
|
||||||
|
showBanner(show, showDir)
|
||||||
|
case season.MatchString(path):
|
||||||
|
ep := models.LoadEpisode(path)
|
||||||
|
nfo.WriteEpisodeNFO(ep, path)
|
||||||
|
default:
|
||||||
|
log.Printf("no match for '%s'\n", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Remove(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
images, err := findFiles(showDir, ".jpg")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for index, path := range images {
|
||||||
|
log.Println(index, path)
|
||||||
|
switch {
|
||||||
|
case show.MatchString(path):
|
||||||
|
showPoster(path, showDir)
|
||||||
|
case season.MatchString(path):
|
||||||
|
episodeImage(path)
|
||||||
|
default:
|
||||||
|
log.Printf("no match for '%s'\n", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
del := filepath.Join(showDir, "sNA")
|
||||||
|
log.Printf("removing '%s'\n", del)
|
||||||
|
err = os.RemoveAll(del)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("failed to remove", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func episodeImage(path string) {
|
||||||
|
if strings.Contains(path, "-thumb") {
|
||||||
|
log.Printf("thumbnail detected '%s'\n", path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
thumb := strings.Replace(path, ".jpg", "-thumb.jpg", 1)
|
||||||
|
log.Printf("renaming thumbnail from '%s' to '%s'\n", path, thumb)
|
||||||
|
err := os.Rename(path, thumb)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to rename '%s' to '%s\n'", path, thumb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func showPoster(path string, show_dir string) {
|
||||||
|
poster := filepath.Join(show_dir, "poster.jpg")
|
||||||
|
log.Printf("renaming show image from '%s' to '%s'\n", path, poster)
|
||||||
|
err := os.Rename(path, poster)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to rename '%s' to '%s\n'", path, poster)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func showBanner(show models.Show, showDir string) {
|
||||||
|
for index, thumb := range show.Thumbnails {
|
||||||
|
log.Println(index, thumb)
|
||||||
|
if thumb.Id == "banner_uncropped" {
|
||||||
|
log.Println("found banner candidate")
|
||||||
|
dl.Fetch(dl.Download{
|
||||||
|
Url: thumb.Url,
|
||||||
|
OutDir: showDir,
|
||||||
|
Name: "banner.jpg",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
73
internal/models/episode.go
Normal file
73
internal/models/episode.go
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Target Episode XML structure:
|
||||||
|
//
|
||||||
|
// <?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||||
|
// <episodedetails>
|
||||||
|
// <title>#{safe(metadata["title"])}</title>
|
||||||
|
// <showtitle>#{safe(metadata["uploader"])}</showtitle>
|
||||||
|
// <uniqueid type="youtube" default="true">#{safe(metadata["id"])}</uniqueid>
|
||||||
|
// <plot>#{safe(metadata["description"])}</plot>
|
||||||
|
// <aired>#{safe(upload_date)}</aired>
|
||||||
|
// <season>#{safe(season)}</season>
|
||||||
|
// <episode>#{episode}</episode>
|
||||||
|
// <genre>YouTube</genre>
|
||||||
|
// </episodedetails>
|
||||||
|
|
||||||
|
type Episode struct {
|
||||||
|
XMLName xml.Name `xml:"episodedetails"`
|
||||||
|
Title string `json:"title" xml:"title"`
|
||||||
|
ShowTitle string `json:"channel" xml:"showtitle"`
|
||||||
|
Id string `json:"id" xml:"-"`
|
||||||
|
UniqueId UniqueId `json:"-" xml:"uniqueid"`
|
||||||
|
Plot string `json:"description" xml:"plot"`
|
||||||
|
UploadDate string `json:"upload_date" xml:"-"`
|
||||||
|
Aired string `json:"-" xml:"aired"`
|
||||||
|
Season string `json:"-" xml:"season"`
|
||||||
|
Episode string `json:"-" xml:"episode"`
|
||||||
|
Genre string `json:"-" xml:"genre"`
|
||||||
|
InfoPath string `json:"-" xml:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadEpisode(info_path string) Episode {
|
||||||
|
info, err := os.ReadFile(info_path)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
episode := Episode{}
|
||||||
|
|
||||||
|
err = json.Unmarshal(info, &episode)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed, err := time.Parse("20060102", episode.UploadDate)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
episode.Aired = parsed.String()
|
||||||
|
episode.Season = fmt.Sprintf("s%d", parsed.Year())
|
||||||
|
episode.Episode = fmt.Sprintf("e%02d%02d", parsed.Month(), parsed.Day())
|
||||||
|
|
||||||
|
// these fields cannot be inferred
|
||||||
|
episode.Genre = "YouTube"
|
||||||
|
episode.UniqueId = UniqueId{
|
||||||
|
Default: "true",
|
||||||
|
Type: "youtube",
|
||||||
|
Text: episode.Id,
|
||||||
|
}
|
||||||
|
|
||||||
|
episode.InfoPath = info_path
|
||||||
|
|
||||||
|
return episode
|
||||||
|
}
|
||||||
58
internal/models/show.go
Normal file
58
internal/models/show.go
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/xml"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Target Show XML structure:
|
||||||
|
// <?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||||
|
// <tvshow>
|
||||||
|
// <title>#{safe(metadata["title"]}/title>
|
||||||
|
// <plot>#{safe(metadata["description"]}</plot>
|
||||||
|
// <uniqueid type="youtube" default="true">#{safe(metadata["id"])}</uniqueid>
|
||||||
|
// <genre>YouTube</genre>
|
||||||
|
// </tvshow>
|
||||||
|
|
||||||
|
type Show struct {
|
||||||
|
XMLName xml.Name `xml:"tvshow"`
|
||||||
|
Title string `json:"channel" xml:"title"`
|
||||||
|
Id string `json:"channel_id" xml:"-"`
|
||||||
|
UniqueId UniqueId `json:"-" xml:"uniqueid"`
|
||||||
|
Plot string `json:"description" xml:"plot"`
|
||||||
|
Genre string `json:"-" xml:"genre"`
|
||||||
|
InfoPath string `json:"-" xml:"-"`
|
||||||
|
Thumbnails []Thumbnail `json:"thumbnails" xml:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Thumbnail struct {
|
||||||
|
Url string `json:"url" xml:"-"`
|
||||||
|
Id string `json:"id" xml:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadShow(info_path string) Show {
|
||||||
|
info, err := os.ReadFile(info_path)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
show := Show{}
|
||||||
|
|
||||||
|
err = json.Unmarshal(info, &show)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// these fields cannot be inferred
|
||||||
|
show.Genre = "YouTube"
|
||||||
|
show.UniqueId = UniqueId{
|
||||||
|
Default: "true",
|
||||||
|
Type: "youtube",
|
||||||
|
Text: show.Id,
|
||||||
|
}
|
||||||
|
|
||||||
|
show.InfoPath = info_path
|
||||||
|
|
||||||
|
return show
|
||||||
|
}
|
||||||
7
internal/models/uniqueid.go
Normal file
7
internal/models/uniqueid.go
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
type UniqueId struct {
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Type string `xml:"type,attr"`
|
||||||
|
Default string `xml:"default,attr"`
|
||||||
|
}
|
||||||
38
internal/nfo/nfo.go
Normal file
38
internal/nfo/nfo.go
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
package nfo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.meatbag.se/varl/subsyt/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func WriteEpisodeNFO(ep models.Episode, info_path string) {
|
||||||
|
out_path := strings.Replace(info_path, ".info.json", ".nfo", 1)
|
||||||
|
|
||||||
|
log.Printf("writing info from '%s' to '%s'\n", info_path, out_path)
|
||||||
|
|
||||||
|
xmlData, err := xml.MarshalIndent(ep, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
complete := xml.Header + string(xmlData)
|
||||||
|
log.Printf("%s", complete)
|
||||||
|
os.WriteFile(out_path, xmlData, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteShowInfo(show models.Show, out_path string) {
|
||||||
|
log.Printf("writing info from '%s' to '%s'\n", show, out_path)
|
||||||
|
|
||||||
|
xmlData, err := xml.MarshalIndent(show, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
complete := xml.Header + string(xmlData)
|
||||||
|
log.Printf("%s", complete)
|
||||||
|
os.WriteFile(out_path, xmlData, 0644)
|
||||||
|
}
|
||||||
11
main.go
11
main.go
|
|
@ -3,9 +3,11 @@ package main
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"git.meatbag.se/varl/subsyt/internal/config"
|
"git.meatbag.se/varl/subsyt/internal/config"
|
||||||
"git.meatbag.se/varl/subsyt/internal/dl"
|
"git.meatbag.se/varl/subsyt/internal/dl"
|
||||||
|
"git.meatbag.se/varl/subsyt/internal/metadata"
|
||||||
"git.meatbag.se/varl/subsyt/internal/opml"
|
"git.meatbag.se/varl/subsyt/internal/opml"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -29,15 +31,16 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, outlines := range opml.Body.Outline {
|
for _, outlines := range opml.Body.Outline {
|
||||||
log.Printf("Archiving videos from OPML: %s", outlines.Title)
|
log.Printf("Archiving videos from OPML: %s\n", outlines.Title)
|
||||||
|
|
||||||
for _, outline := range outlines.Outlines {
|
for _, outline := range outlines.Outlines {
|
||||||
dl.Get(dl.Download{
|
dl.Youtube(dl.Download{
|
||||||
Url: outline.XmlUrl,
|
Url: outline.XmlUrl,
|
||||||
Name: outline.Title,
|
OutDir: filepath.Join(cfg.Out_dir, outline.Title),
|
||||||
OutDir: cfg.Out_dir,
|
|
||||||
DryRun: cfg.Dry_run,
|
DryRun: cfg.Dry_run,
|
||||||
}, provider)
|
}, provider)
|
||||||
|
|
||||||
|
metadata.Generate(cfg.Out_dir, outline.Title, cfg.Dry_run)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue