-
Notifications
You must be signed in to change notification settings - Fork 97
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Ian Bishop
committed
Mar 27, 2018
1 parent
f2c4770
commit 89a4376
Showing
5 changed files
with
321 additions
and
241 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,5 +5,6 @@ | |
*.mkv | ||
*.gz | ||
*.zip | ||
.*.swp | ||
vendor | ||
youtubeuploader |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
/* | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"io/ioutil" | ||
"log" | ||
"net/http" | ||
"os" | ||
"strconv" | ||
"strings" | ||
"time" | ||
|
||
"google.golang.org/api/youtube/v3" | ||
) | ||
|
||
const outputDateLayout = "2006-01-02T15:04:05.000Z" //ISO 8601 (YYYY-MM-DDThh:mm:ss.sssZ) | ||
const inputDateLayout = "2006-01-02" | ||
|
||
type Date struct { | ||
time.Time | ||
} | ||
|
||
func LoadVideoMeta(filename string, video *youtube.Video) (videoMeta VideoMeta) { | ||
// attempt to load from meta JSON, otherwise use values specified from command line flags | ||
if filename != "" { | ||
file, e := ioutil.ReadFile(filename) | ||
if e != nil { | ||
fmt.Printf("Could not read filename file '%s': %s\n", filename, e) | ||
fmt.Println("Will use command line flags instead") | ||
goto errJump | ||
} | ||
|
||
e = json.Unmarshal(file, &videoMeta) | ||
if e != nil { | ||
fmt.Printf("Could not read filename file '%s': %s\n", filename, e) | ||
fmt.Println("Will use command line flags instead") | ||
goto errJump | ||
} | ||
|
||
video.Snippet.Tags = videoMeta.Tags | ||
video.Snippet.Title = videoMeta.Title | ||
video.Snippet.Description = videoMeta.Description | ||
video.Snippet.CategoryId = videoMeta.CategoryId | ||
if videoMeta.PrivacyStatus != "" { | ||
video.Status.PrivacyStatus = videoMeta.PrivacyStatus | ||
} | ||
if videoMeta.Location != nil { | ||
video.RecordingDetails.Location = videoMeta.Location | ||
} | ||
if videoMeta.LocationDescription != "" { | ||
video.RecordingDetails.LocationDescription = videoMeta.LocationDescription | ||
} | ||
if !videoMeta.RecordingDate.IsZero() { | ||
video.RecordingDetails.RecordingDate = videoMeta.RecordingDate.Format(outputDateLayout) | ||
} | ||
if videoMeta.Language != "" { | ||
video.Snippet.DefaultLanguage = videoMeta.Language | ||
video.Snippet.DefaultAudioLanguage = videoMeta.Language | ||
} | ||
} | ||
errJump: | ||
|
||
if video.Status.PrivacyStatus == "" { | ||
video.Status = &youtube.VideoStatus{PrivacyStatus: *privacy} | ||
} | ||
if video.Snippet.Tags == nil && strings.Trim(*tags, "") != "" { | ||
video.Snippet.Tags = strings.Split(*tags, ",") | ||
} | ||
if video.Snippet.Title == "" { | ||
video.Snippet.Title = *title | ||
} | ||
if video.Snippet.Description == "" { | ||
video.Snippet.Description = *description | ||
} | ||
if video.Snippet.CategoryId == "" && *categoryId != "" { | ||
video.Snippet.CategoryId = *categoryId | ||
} | ||
|
||
return | ||
} | ||
|
||
func Open(filename string) (reader io.ReadCloser, filesize int64) { | ||
if strings.HasPrefix(filename, "http") { | ||
resp, err := http.Head(filename) | ||
if err != nil { | ||
log.Fatalf("Error opening %s: %s", filename, err) | ||
} | ||
lenStr := resp.Header.Get("content-length") | ||
if lenStr != "" { | ||
filesize, err = strconv.ParseInt(lenStr, 10, 64) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
} | ||
|
||
resp, err = http.Get(filename) | ||
if err != nil { | ||
log.Fatalf("Error opening %s: %s", filename, err) | ||
} | ||
if resp.ContentLength != 0 { | ||
filesize = resp.ContentLength | ||
} | ||
reader = resp.Body | ||
return | ||
} | ||
|
||
file, err := os.Open(filename) | ||
if err != nil { | ||
log.Fatalf("Error opening %s: %s", filename, err) | ||
} | ||
|
||
fileInfo, err := file.Stat() | ||
if err != nil { | ||
log.Fatalf("Error stat'ing file %s: %s", filename, err) | ||
} | ||
|
||
return file, fileInfo.Size() | ||
} | ||
|
||
func (d *Date) UnmarshalJSON(b []byte) (err error) { | ||
s := string(b) | ||
s = s[1 : len(s)-1] | ||
d.Time, err = time.Parse(inputDateLayout, s) | ||
return | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
/* | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package main | ||
|
||
import ( | ||
"fmt" | ||
"io/ioutil" | ||
"net/http" | ||
|
||
"github.com/porjo/go-flowrate/flowrate" | ||
"google.golang.org/api/youtube/v3" | ||
) | ||
|
||
type limitTransport struct { | ||
rt http.RoundTripper | ||
reader *flowrate.Reader | ||
filesize int64 | ||
} | ||
|
||
type VideoMeta struct { | ||
// snippet | ||
Title string `json:"title,omitempty"` | ||
Description string `json:"description,omitempty"` | ||
CategoryId string `json:"categoryId,omitempty"` | ||
Tags []string `json:"tags,omitempty"` | ||
|
||
// status | ||
PrivacyStatus string `json:"privacyStatus,omitempty"` | ||
Embeddable bool `json:"embeddable,omitempty"` | ||
License string `json:"license,omitempty"` | ||
PublicStatsViewable bool `json:"publicStatsViewable,omitempty"` | ||
PublishAt string `json:"publishAt,omitempty"` | ||
|
||
// recording details | ||
Location *youtube.GeoPoint `json:"location,omitempty"` | ||
LocationDescription string `json:"locationDescription, omitempty"` | ||
RecordingDate Date `json:"recordingDate, omitempty"` | ||
|
||
// single playistID retained for backwards compatibility | ||
PlaylistID string `json:"playlistId, omitempty"` | ||
PlaylistIDs []string `json:"playlistIds, omitempty"` | ||
|
||
// BCP-47 language code e.g. 'en','es' | ||
Language string `json:"language, omitempty"` | ||
} | ||
|
||
func (t *limitTransport) RoundTrip(r *http.Request) (res *http.Response, err error) { | ||
// FIXME need a better way to detect which roundtrip is the media upload | ||
if r.ContentLength > 1000 { | ||
var monitor *flowrate.Monitor | ||
|
||
if t.reader != nil { | ||
monitor = t.reader.Monitor | ||
} | ||
|
||
// kbit/s to B/s = 1000/8 = 125 | ||
t.reader = flowrate.NewReader(r.Body, int64(*rate*125)) | ||
|
||
if monitor != nil { | ||
// carry over stats to new limiter | ||
t.reader.Monitor = monitor | ||
} else { | ||
t.reader.Monitor.SetTransferSize(t.filesize) | ||
} | ||
r.Body = ioutil.NopCloser(t.reader) | ||
} | ||
|
||
return t.rt.RoundTrip(r) | ||
} | ||
|
||
func AddVideoToPlaylist(service *youtube.Service, playlistID, videoID string) (err error) { | ||
listCall := service.Playlists.List("snippet,contentDetails") | ||
listCall = listCall.Mine(true) | ||
response, err := listCall.Do() | ||
if err != nil { | ||
return fmt.Errorf("error retrieving playlists: %s", err) | ||
} | ||
|
||
var playlist *youtube.Playlist | ||
for _, pl := range response.Items { | ||
if pl.Id == playlistID { | ||
playlist = pl | ||
break | ||
} | ||
} | ||
|
||
// TODO: handle creation of playlist | ||
if playlist == nil { | ||
return fmt.Errorf("playlist ID '%s' doesn't exist", playlistID) | ||
} | ||
|
||
playlistItem := &youtube.PlaylistItem{} | ||
playlistItem.Snippet = &youtube.PlaylistItemSnippet{PlaylistId: playlist.Id} | ||
playlistItem.Snippet.ResourceId = &youtube.ResourceId{ | ||
VideoId: videoID, | ||
Kind: "youtube#video", | ||
} | ||
|
||
insertCall := service.PlaylistItems.Insert("snippet", playlistItem) | ||
_, err = insertCall.Do() | ||
if err != nil { | ||
return fmt.Errorf("error inserting video into playlist: %s", err) | ||
} | ||
|
||
fmt.Printf("Video added to playlist '%s' (%s)\n", playlist.Snippet.Title, playlist.Id) | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
/* | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package main | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
"time" | ||
) | ||
|
||
func Progress(quitChan chanChan, transport *limitTransport, filesize int64) { | ||
ticker := time.Tick(time.Second) | ||
var erase int | ||
for { | ||
select { | ||
case <-ticker: | ||
if transport.reader != nil { | ||
s := transport.reader.Monitor.Status() | ||
curRate := float32(s.CurRate) | ||
var status string | ||
if curRate >= 125000 { | ||
status = fmt.Sprintf("Progress: %8.2f Mbps, %d / %d (%s) ETA %8s", curRate/125000, s.Bytes, filesize, s.Progress, s.TimeRem) | ||
} else { | ||
status = fmt.Sprintf("Progress: %8.2f kbps, %d / %d (%s) ETA %8s", curRate/125, s.Bytes, filesize, s.Progress, s.TimeRem) | ||
} | ||
fmt.Printf("\r%s\r%s", strings.Repeat(" ", erase), status) | ||
erase = len(status) | ||
} | ||
case ch := <-quitChan: | ||
// final newline | ||
fmt.Println() | ||
close(ch) | ||
return | ||
} | ||
} | ||
} |
Oops, something went wrong.