Skip to content

Commit 0474b59

Browse files
authored
Merge pull request #196 from porjo/recdate
Add recordingDate flag
2 parents 56ec589 + 186cb8e commit 0474b59

File tree

5 files changed

+115
-13
lines changed

5 files changed

+115
-13
lines changed

.github/workflows/gotest.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@ name: gotests
33
# Run on any push except tag push
44
# See: https://stackoverflow.com/a/71879890/202311
55
on:
6-
push:
7-
branches:
8-
- '**'
6+
pull_request:
97

108
jobs:
119
lint:

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ Usage:
9797
suppress progress indicator
9898
-ratelimit int
9999
rate limit upload in Kbps. No limit by default
100+
-recordingDate value
101+
recording date e.g. 2024-11-23
100102
-secrets string
101103
Client Secrets configuration (default "client_secrets.json")
102104
-sendFilename
@@ -139,7 +141,7 @@ Video title, description etc can specified via the command line flags or via a J
139141
- all fields are optional
140142
- use `\n` in the description to insert newlines
141143
- times can be provided in one of two formats: `yyyy-mm-dd` (UTC) or `yyyy-mm-ddThh:mm:ss+zz:zz`
142-
- any values supplied via command line will take precedence
144+
- any values supplied via `-metaJSON` will take precedence over flags
143145

144146
## Credit
145147

cmd/youtubeuploader/main.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,11 @@ func main() {
5353
var err error
5454

5555
var playlistIDs arrayFlags
56+
var recordingDate yt.Date
5657

5758
flag.Var(&playlistIDs, "playlistID", "playlist ID to add the video to. Can be used multiple times")
59+
flag.Var(&recordingDate, "recordingDate", "recording date e.g. 2024-11-23")
60+
5861
filename := flag.String("filename", "", "video filename. Can be a URL. Read from stdin with '-'")
5962
thumbnail := flag.String("thumbnail", "", "thumbnail filename. Can be a URL")
6063
caption := flag.String("caption", "", "caption filename. Can be a URL")
@@ -98,6 +101,7 @@ func main() {
98101
NotifySubscribers: *notifySubscribers,
99102
SendFileName: *sendFileName,
100103
PlaylistIDs: playlistIDs,
104+
RecordingDate: recordingDate,
101105
}
102106

103107
config.Logger = utils.NewLogger(*debug)

files.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ type Config struct {
6161
Chunksize int
6262
NotifySubscribers bool
6363
SendFileName bool
64+
RecordingDate Date
6465

6566
Logger utils.Logger
6667
}
@@ -178,6 +179,10 @@ func LoadVideoMeta(config Config, video *youtube.Video) (*VideoMeta, error) {
178179
video.Snippet.DefaultAudioLanguage = config.Language
179180
}
180181

182+
if video.RecordingDetails.RecordingDate == "" && !config.RecordingDate.IsZero() {
183+
video.RecordingDetails.RecordingDate = config.RecordingDate.UTC().Format(ytDateLayout)
184+
}
185+
181186
// combine cli flag playistIDs and metaJSON playlistIDs. Remove any duplicates
182187
playlistIDs := slices.Concat(config.PlaylistIDs, videoMeta.PlaylistIDs)
183188
slices.Sort(playlistIDs)
@@ -261,6 +266,16 @@ func Open(filename string, mediaType MediaType) (io.ReadCloser, int, error) {
261266
func (d *Date) UnmarshalJSON(b []byte) (err error) {
262267
s := string(b)
263268
s = s[1 : len(s)-1]
269+
err = d.parse(s)
270+
return
271+
}
272+
273+
func (d *Date) Set(s string) (err error) {
274+
err = d.parse(s)
275+
return
276+
}
277+
278+
func (d *Date) parse(s string) (err error) {
264279
// support ISO 8601 date only, and date + time
265280
if strings.ContainsAny(s, ":") {
266281
d.Time, err = time.Parse(inputDatetimeLayout, s)

test/upload_test.go

Lines changed: 92 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ import (
2020
"fmt"
2121
"io"
2222
"log"
23+
"log/slog"
24+
"mime"
25+
"mime/multipart"
2326
"net/http"
2427
"net/http/httptest"
2528
"net/url"
@@ -50,6 +53,10 @@ var (
5053

5154
config yt.Config
5255
transport *mockTransport
56+
57+
recordingDate yt.Date
58+
59+
logger *slog.Logger
5360
)
5461

5562
type mockTransport struct {
@@ -62,7 +69,7 @@ type mockReader struct {
6269
}
6370

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

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

8895
func TestMain(m *testing.M) {
8996

97+
logger = slog.Default()
98+
9099
testServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
91100

92-
// be sure to read the request body otherwise the client gets confused
93-
_, err := io.Copy(io.Discard, r.Body)
101+
l := logger.With("src", "httptest")
102+
103+
video, err := handleVideoPost(r, l)
94104
if err != nil {
95-
log.Printf("Error reading body: %v", err)
96-
http.Error(w, "can't read body", http.StatusBadRequest)
97-
return
105+
http.Error(w, err.Error(), http.StatusBadRequest)
106+
}
107+
108+
if video != nil {
109+
recDateIn, err := time.Parse(time.RFC3339Nano, video.RecordingDetails.RecordingDate)
110+
if err != nil {
111+
http.Error(w, err.Error(), http.StatusBadRequest)
112+
}
113+
if recDateIn.Equal(recordingDate.Time) {
114+
http.Error(w, "Date didn't match", http.StatusBadRequest)
115+
}
98116
}
99-
// log.Printf("Mock server: request body length %d", len(body))
100117

101118
w.Header().Set("Content-Type", "application/json")
102119
switch r.Host {
@@ -110,7 +127,6 @@ func TestMain(m *testing.M) {
110127
}
111128
videoJ, err := json.Marshal(video)
112129
if err != nil {
113-
fmt.Printf("json marshall error %s\n", err)
114130
http.Error(w, err.Error(), http.StatusInternalServerError)
115131
return
116132
}
@@ -133,7 +149,6 @@ func TestMain(m *testing.M) {
133149
}
134150
playlistJ, err := json.Marshal(playlistResponse)
135151
if err != nil {
136-
fmt.Printf("json marshall error %s\n", err)
137152
http.Error(w, err.Error(), http.StatusInternalServerError)
138153
return
139154
}
@@ -158,6 +173,9 @@ func TestMain(m *testing.M) {
158173
config.Logger = utils.NewLogger(false)
159174
config.Filename = "test.mp4"
160175
config.PlaylistIDs = []string{"xxxx", "yyyy"}
176+
recordingDate = yt.Date{}
177+
recordingDate.Time = time.Now()
178+
config.RecordingDate = recordingDate
161179

162180
ret := m.Run()
163181

@@ -198,3 +216,68 @@ func TestRateLimit(t *testing.T) {
198216
}
199217

200218
}
219+
220+
func handleVideoPost(r *http.Request, l *slog.Logger) (*youtube.Video, error) {
221+
222+
if r.Method != http.MethodPost {
223+
l.Info("not POST, skipping")
224+
return nil, nil
225+
}
226+
// Parse the Content-Type header
227+
contentType := r.Header.Get("Content-Type")
228+
if contentType == "" {
229+
return nil, fmt.Errorf("Missing Content-Type header")
230+
}
231+
232+
// Parse the media type and boundary
233+
mediaType, params, err := mime.ParseMediaType(contentType)
234+
if err != nil {
235+
return nil, err
236+
}
237+
238+
if mediaType != "multipart/related" {
239+
l.Info("not multipart, skipping")
240+
return nil, nil
241+
}
242+
243+
boundary, ok := params["boundary"]
244+
if !ok {
245+
return nil, fmt.Errorf("Missing boundary parameter")
246+
}
247+
248+
// Parse the multipart form
249+
mr := multipart.NewReader(r.Body, boundary)
250+
251+
video := &youtube.Video{}
252+
253+
// Iterate through the parts
254+
for {
255+
part, err := mr.NextPart()
256+
if err == io.EOF {
257+
break
258+
}
259+
if err != nil {
260+
return nil, err
261+
}
262+
263+
contentType := part.Header.Get("Content-Type")
264+
switch contentType {
265+
case "application/json":
266+
// Parse JSON part
267+
err := json.NewDecoder(part).Decode(video)
268+
if err != nil {
269+
return nil, err
270+
}
271+
case "application/octet-stream":
272+
// Read binary data part
273+
_, err = io.Copy(io.Discard, part)
274+
if err != nil {
275+
return nil, err
276+
}
277+
default:
278+
// Ignore other content types
279+
}
280+
}
281+
282+
return video, nil
283+
}

0 commit comments

Comments
 (0)