Skip to content

Commit

Permalink
Add recordingDate flag
Browse files Browse the repository at this point in the history
  • Loading branch information
porjo committed Nov 30, 2024
1 parent 56ec589 commit d6fd773
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 10 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ Usage:
suppress progress indicator
-ratelimit int
rate limit upload in Kbps. No limit by default
-recordingDate value
recording date e.g. 2024-11-23
-secrets string
Client Secrets configuration (default "client_secrets.json")
-sendFilename
Expand Down Expand Up @@ -139,7 +141,7 @@ Video title, description etc can specified via the command line flags or via a J
- all fields are optional
- use `\n` in the description to insert newlines
- times can be provided in one of two formats: `yyyy-mm-dd` (UTC) or `yyyy-mm-ddThh:mm:ss+zz:zz`
- any values supplied via command line will take precedence
- any values supplied via `-metaJSON` will take precedence over flags

## Credit

Expand Down
4 changes: 4 additions & 0 deletions cmd/youtubeuploader/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,11 @@ func main() {
var err error

var playlistIDs arrayFlags
var recordingDate yt.Date

flag.Var(&playlistIDs, "playlistID", "playlist ID to add the video to. Can be used multiple times")
flag.Var(&recordingDate, "recordingDate", "recording date e.g. 2024-11-23")

filename := flag.String("filename", "", "video filename. Can be a URL. Read from stdin with '-'")
thumbnail := flag.String("thumbnail", "", "thumbnail filename. Can be a URL")
caption := flag.String("caption", "", "caption filename. Can be a URL")
Expand Down Expand Up @@ -98,6 +101,7 @@ func main() {
NotifySubscribers: *notifySubscribers,
SendFileName: *sendFileName,
PlaylistIDs: playlistIDs,
RecordingDate: recordingDate,
}

config.Logger = utils.NewLogger(*debug)
Expand Down
15 changes: 15 additions & 0 deletions files.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ type Config struct {
Chunksize int
NotifySubscribers bool
SendFileName bool
RecordingDate Date

Logger utils.Logger
}
Expand Down Expand Up @@ -178,6 +179,10 @@ func LoadVideoMeta(config Config, video *youtube.Video) (*VideoMeta, error) {
video.Snippet.DefaultAudioLanguage = config.Language
}

if video.RecordingDetails.RecordingDate == "" && !config.RecordingDate.IsZero() {
video.RecordingDetails.RecordingDate = config.RecordingDate.UTC().Format(ytDateLayout)
}

// combine cli flag playistIDs and metaJSON playlistIDs. Remove any duplicates
playlistIDs := slices.Concat(config.PlaylistIDs, videoMeta.PlaylistIDs)
slices.Sort(playlistIDs)
Expand Down Expand Up @@ -261,6 +266,16 @@ func Open(filename string, mediaType MediaType) (io.ReadCloser, int, error) {
func (d *Date) UnmarshalJSON(b []byte) (err error) {
s := string(b)
s = s[1 : len(s)-1]
err = d.parse(s)
return
}

func (d *Date) Set(s string) (err error) {
err = d.parse(s)
return
}

func (d *Date) parse(s string) (err error) {
// support ISO 8601 date only, and date + time
if strings.ContainsAny(s, ":") {
d.Time, err = time.Parse(inputDatetimeLayout, s)
Expand Down
101 changes: 92 additions & 9 deletions test/upload_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ import (
"fmt"
"io"
"log"
"log/slog"
"mime"
"mime/multipart"
"net/http"
"net/http/httptest"
"net/url"
Expand Down Expand Up @@ -50,6 +53,10 @@ var (

config yt.Config
transport *mockTransport

recordingDate yt.Date

logger *slog.Logger
)

type mockTransport struct {
Expand All @@ -62,7 +69,7 @@ type mockReader struct {
}

func (m *mockTransport) RoundTrip(r *http.Request) (*http.Response, error) {
fmt.Printf("%s URL %s\n", r.Method, r.URL.String())
logger.Info("roundtrip", "method", r.Method, "URL", r.URL.String())
r.URL.Scheme = m.url.Scheme
r.URL.Host = m.url.Host

Expand All @@ -87,16 +94,26 @@ func (m *mockReader) Read(p []byte) (int, error) {

func TestMain(m *testing.M) {

logger = slog.Default()

testServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

// be sure to read the request body otherwise the client gets confused
_, err := io.Copy(io.Discard, r.Body)
l := logger.With("src", "httptest")

video, err := handleVideoPost(r, l)
if err != nil {
log.Printf("Error reading body: %v", err)
http.Error(w, "can't read body", http.StatusBadRequest)
return
http.Error(w, err.Error(), http.StatusBadRequest)
}

if video != nil {
recDateIn, err := time.Parse(time.RFC3339Nano, video.RecordingDetails.RecordingDate)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
}
if recDateIn.Equal(recordingDate.Time) {
http.Error(w, "Date didn't match", http.StatusBadRequest)
}
}
// log.Printf("Mock server: request body length %d", len(body))

w.Header().Set("Content-Type", "application/json")
switch r.Host {
Expand All @@ -110,7 +127,6 @@ func TestMain(m *testing.M) {
}
videoJ, err := json.Marshal(video)
if err != nil {
fmt.Printf("json marshall error %s\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
Expand All @@ -133,7 +149,6 @@ func TestMain(m *testing.M) {
}
playlistJ, err := json.Marshal(playlistResponse)
if err != nil {
fmt.Printf("json marshall error %s\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
Expand All @@ -158,6 +173,9 @@ func TestMain(m *testing.M) {
config.Logger = utils.NewLogger(false)
config.Filename = "test.mp4"
config.PlaylistIDs = []string{"xxxx", "yyyy"}
recordingDate = yt.Date{}
recordingDate.Time = time.Now()
config.RecordingDate = recordingDate

ret := m.Run()

Expand Down Expand Up @@ -198,3 +216,68 @@ func TestRateLimit(t *testing.T) {
}

}

func handleVideoPost(r *http.Request, l *slog.Logger) (*youtube.Video, error) {

if r.Method != http.MethodPost {
l.Info("not POST, skipping")
return nil, nil
}
// Parse the Content-Type header
contentType := r.Header.Get("Content-Type")
if contentType == "" {
return nil, fmt.Errorf("Missing Content-Type header")
}

// Parse the media type and boundary
mediaType, params, err := mime.ParseMediaType(contentType)
if err != nil {
return nil, err
}

if mediaType != "multipart/related" {
l.Info("not multipart, skipping")
return nil, nil
}

boundary, ok := params["boundary"]
if !ok {
return nil, fmt.Errorf("Missing boundary parameter")
}

// Parse the multipart form
mr := multipart.NewReader(r.Body, boundary)

video := &youtube.Video{}

// Iterate through the parts
for {
part, err := mr.NextPart()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}

contentType := part.Header.Get("Content-Type")
switch contentType {
case "application/json":
// Parse JSON part
err := json.NewDecoder(part).Decode(video)
if err != nil {
return nil, err
}
case "application/octet-stream":
// Read binary data part
_, err = io.Copy(io.Discard, part)
if err != nil {
return nil, err
}
default:
// Ignore other content types
}
}

return video, nil
}

0 comments on commit d6fd773

Please sign in to comment.