Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add recordingDate flag #196

Merged
merged 2 commits into from
Dec 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .github/workflows/gotest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ name: gotests
# Run on any push except tag push
# See: https://stackoverflow.com/a/71879890/202311
on:
push:
branches:
- '**'
pull_request:

jobs:
lint:
Expand Down
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
}