diff options
| author | Xe Iaso <me@xeiaso.net> | 2025-01-07 23:41:53 -0500 |
|---|---|---|
| committer | Xe Iaso <me@xeiaso.net> | 2025-01-11 16:46:28 -0500 |
| commit | 6bc2938856792f851b18fb3550fdb4c2c8040fce (patch) | |
| tree | e7a77aa35d9c94ae5ea7609c08f00b2b3ffca6eb /cmd/patchouli/ytdlp | |
| parent | c2187ae96bf081aba31a965d0b58e5934a42a93f (diff) | |
| download | x-6bc2938856792f851b18fb3550fdb4c2c8040fce.tar.xz x-6bc2938856792f851b18fb3550fdb4c2c8040fce.zip | |
kube/alrest: fix
Signed-off-by: Xe Iaso <me@xeiaso.net>
Diffstat (limited to 'cmd/patchouli/ytdlp')
| -rw-r--r-- | cmd/patchouli/ytdlp/ytdlp.go | 113 |
1 files changed, 113 insertions, 0 deletions
diff --git a/cmd/patchouli/ytdlp/ytdlp.go b/cmd/patchouli/ytdlp/ytdlp.go new file mode 100644 index 0000000..096ea2b --- /dev/null +++ b/cmd/patchouli/ytdlp/ytdlp.go @@ -0,0 +1,113 @@ +package ytdlp + +import ( + "bytes" + "context" + "database/sql/driver" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "time" + + "within.website/x/internal" +) + +const dateFormat = "20060102" + +type VideoMetadata struct { + ID string `json:"id"` + Title string `json:"title"` + Thumbnail string `json:"thumbnail"` + DurationString string `json:"duration_string"` + UploadDate Date `json:"upload_date"` + URL string `json:"url" gorm:"uniqueIndex"` +} + +type Date struct { + time.Time +} + +func (d *Date) MarshalJSON() ([]byte, error) { + result := d.Format(dateFormat) + + return []byte(fmt.Sprintf("%q", result)), nil +} + +func (d *Date) UnmarshalJSON(data []byte) error { + str := string(data) + str = str[1 : len(str)-1] + + parsedTime, err := time.Parse(dateFormat, str) + if err != nil { + return err + } + + d.Time = parsedTime + return nil +} + +// Value implements the driver.Valuer interface +func (d Date) Value() (driver.Value, error) { + return d.Format(dateFormat), nil +} + +// Scan implements the sql.Scanner interface +func (d *Date) Scan(value interface{}) error { + if value == nil { + *d = Date{Time: time.Time{}} + return nil + } + + switch v := value.(type) { + case time.Time: + *d = Date{Time: v} + case []byte: + parsedTime, err := time.Parse(dateFormat, string(v)) + if err != nil { + return err + } + *d = Date{Time: parsedTime} + case string: + parsedTime, err := time.Parse(dateFormat, v) + if err != nil { + return err + } + *d = Date{Time: parsedTime} + default: + return fmt.Errorf("cannot scan type %T into Date", value) + } + + return nil +} + +func Metadata(ctx context.Context, url string) (*VideoMetadata, error) { + result, err := internal.RunJSON[VideoMetadata](ctx, "yt-dlp", "--dump-json", url) + if err != nil { + return nil, err + } + + return &result, nil +} + +func Download(ctx context.Context, url, to string) error { + exePath, err := exec.LookPath("yt-dlp") + if err != nil { + return fmt.Errorf("can't find yt-dlp: %w", err) + } + + cmd := exec.CommandContext(ctx, exePath, "-o", filepath.Join(to, "%(id)s.%(ext)s"), "--write-info-json", url) + + var stdout, stderr bytes.Buffer + + cmd.Stdout = io.MultiWriter(&stdout, os.Stdout) + cmd.Stderr = io.MultiWriter(&stderr, os.Stderr) + + if err := cmd.Run(); err != nil { + // TODO(Xe): return stdout/err? + return fmt.Errorf("can't download %s: %w", url, err) + } + + return nil +} |
