aboutsummaryrefslogtreecommitdiff
path: root/cmd/patchouli/ytdlp
diff options
context:
space:
mode:
authorXe Iaso <me@xeiaso.net>2025-01-07 23:41:53 -0500
committerXe Iaso <me@xeiaso.net>2025-01-11 16:46:28 -0500
commit6bc2938856792f851b18fb3550fdb4c2c8040fce (patch)
treee7a77aa35d9c94ae5ea7609c08f00b2b3ffca6eb /cmd/patchouli/ytdlp
parentc2187ae96bf081aba31a965d0b58e5934a42a93f (diff)
downloadx-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.go113
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
+}