Skip to content
This repository has been archived by the owner on Jul 26, 2020. It is now read-only.

Commit

Permalink
Video Import Command (#15)
Browse files Browse the repository at this point in the history
Video Import Command

Co-authored-by: Dirk Kelly <github@dirkkelly.com>
  • Loading branch information
jmickey and dirkkelly committed May 16, 2019
2 parents c0beb96 + 838b732 commit a38bed5
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 5 deletions.
22 changes: 22 additions & 0 deletions cmd/importRoot.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package cmd

import (
"github.com/spf13/cobra"
)

// importRootCmd represents the importRoot command
var importRootCmd = &cobra.Command{
Use: "import",
Short: "Import resources, currently works with video",
Long: `Command to import resources. Not to be confused with the channel import command.
Currently the only valid resource for import is the video resource. However,
this command will later form the basis for importing all resources.`,
ValidArgs: []string{"video"},
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {},
}

func init() {
rootCmd.AddCommand(importRootCmd)
}
1 change: 1 addition & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ var Providers = map[string]map[string]interface{}{
"youtube": providers.LoadYoutube(),
}

// ProviderNames gets all available providers
func ProviderNames() []string {
keys := make([]string, 0, len(Providers))
for k := range Providers {
Expand Down
41 changes: 41 additions & 0 deletions cmd/video.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package cmd

import (
"log"
"os"

"github.com/breadtubetv/bake/providers"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

// videoCmd represents the video command
var videoCmd = &cobra.Command{
Use: "video",
Short: "Import a video by ID",
Long: `Import a YouTube video by ID and assign it to a creator.`,
Run: func(cmd *cobra.Command, args []string) {
err := providers.ImportVideo(id, creator, os.ExpandEnv(viper.GetString("projectRoot")))
if err != nil {
log.Fatalf("could not import video: %v", err)
}
},
}

var (
id string
creator string
provider string
)

func init() {
importRootCmd.AddCommand(videoCmd)

videoCmd.Flags().StringVar(&id, "id", "", "ID of the video, e.g. xspEtjnSfQA is the ID for https://www.youtube.com/watch?v=xspEtjnSfQA")
videoCmd.Flags().StringVarP(&creator, "creator", "c", "", "Creator slug for the imported video")
videoCmd.Flags().StringVarP(&provider, "provider", "p", "", "Video provider to import from - e.g. youtube")

videoCmd.MarkFlagRequired("id")
videoCmd.MarkFlagRequired("creator")
videoCmd.MarkFlagRequired("provider")
}
88 changes: 86 additions & 2 deletions providers/youtube.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"golang.org/x/net/context"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"gopkg.in/yaml.v2"

"google.golang.org/api/youtube/v3"
)
Expand All @@ -36,10 +37,12 @@ const accessJSON = `{
}
}`

// LoadYoutube initalises the Youtube service
func LoadYoutube() map[string]interface{} {
return map[string]interface{}{
"config": config,
"channel_import": importChannel,
"video_import": ImportVideo,
}
}

Expand Down Expand Up @@ -123,14 +126,17 @@ func fetchProfileImageURL(url *util.URL) (string, error) {
}

func saveImage(imgURL string, slug string, projectRoot string) error {
resp, _ := http.Get(imgURL)
resp, err := http.Get(imgURL)
if err != nil {
return fmt.Errorf("couldn't retreive image: %v", err)
}
defer resp.Body.Close()

filePath := fmt.Sprintf("%s/static/img/channels/%s.jpg", projectRoot, slug)
img, _ := os.Create(filePath)
defer img.Close()

_, err := io.Copy(img, resp.Body)
_, err = io.Copy(img, resp.Body)
if err != nil {
return fmt.Errorf("Error saving channel profile picture, please download manually.\nErr: %v", err.Error())
}
Expand Down Expand Up @@ -193,6 +199,84 @@ func importChannel(slug string, channelURL *util.URL, projectRoot string) {
}
}

// ImportVideo will import a YouTube video based on an ID and create
// a new file in the videos data folder for the specified creator
func ImportVideo(id, creator, projectRoot string) error {
channel, ok := util.LoadChannels(projectRoot + "/data/channels").Find(creator)
if !ok {
log.Fatalf("creator %v not found", creator)
}

creatorDir := fmt.Sprintf("%s/data/videos/%s", projectRoot, creator)
if _, err := os.Stat(creatorDir); os.IsNotExist(err) {
err := util.CreateChannelVideoFolder(channel, projectRoot)
if err != nil {
log.Fatalf("unable to create folder for %v: %v", creator, err)
}
}

vid, err := getVideo(id)
vid.Channel = creator
if err != nil {
return err
}

videoFile := fmt.Sprintf("%s/%s.yml", creatorDir, vid.ID)
f, err := os.Create(videoFile)
if err != nil {
return fmt.Errorf("could not create file for video '%s': %v", id, err)
}
defer f.Close()

data, err := yaml.Marshal(vid)
if err != nil {
return fmt.Errorf("couldn't marshal video data: %v", err)
}
f.Write(data)
f.Sync()
log.Printf("created video file %v", videoFile)

err = channel.GetChannelPage(projectRoot).AddVideo(id, projectRoot)
if err != nil {
return fmt.Errorf("couldn't update channel page: %v", err)
}

return nil
}

// Video represents the a YouTube video
type Video struct {
ID string `yaml:"id"`
Title string
Description string
Source string
Channel string
}

// GetVideo retreives video details from YouTube
func getVideo(videoID string) (*Video, error) {
client := getClient(youtube.YoutubeReadonlyScope)
yt, err := youtube.New(client)
if err != nil {
return nil, fmt.Errorf("error creating YouTube client: %v", err)
}

call := yt.Videos.List("snippet").Id(videoID)
resp, err := call.Do()
if err != nil {
return nil, fmt.Errorf("error calling the YouTube API: %v", err)
}

video := &Video{
ID: resp.Items[0].Id,
Title: resp.Items[0].Snippet.Title,
Description: resp.Items[0].Snippet.Description,
Source: "youtube",
}

return video, nil
}

const launchWebServer = true

func handleError(err error, message string) {
Expand Down
51 changes: 51 additions & 0 deletions util/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,29 @@ type ChannelPage struct {
Videos []string `yaml:",omitempty"`
}

// GetChannelPage returns the ChannelPage for the specifid channel
func (c *Channel) GetChannelPage(projectRoot string) *ChannelPage {
file := fmt.Sprintf("%s/content/%s.md", projectRoot, c.Slug)
data, err := ioutil.ReadFile(file)
if err != nil {
log.Print("couldn't open channel page")
return nil
}

d := strings.TrimPrefix(string(data), "---")
d = strings.TrimSuffix(d, "---")
data = []byte(d)

var page *ChannelPage
err = yaml.Unmarshal(data, &page)
if err != nil {
log.Print("couldn't unmarshal into channel page type")
return nil
}

return page
}

// CreateChannelPage takes the permalink for the channel
// and creates a .md file in the content directory.
func CreateChannelPage(channel *Channel, projectRoot string) error {
Expand Down Expand Up @@ -362,3 +385,31 @@ func CreateChannelPage(channel *Channel, projectRoot string) error {

return nil
}

// AddVideo adds a video to the channel page and saves it
func (cp *ChannelPage) AddVideo(id, projectRoot string) error {
cp.Videos = append(cp.Videos, id)
err := cp.save(projectRoot)
if err != nil {
return err
}
return nil
}

func (cp *ChannelPage) save(projectRoot string) error {
fileName := fmt.Sprintf("%s.md", cp.Channel)
dataDir := path.Join(projectRoot, "/content/")
pageBytes, err := yaml.Marshal(cp)
if err != nil {
return fmt.Errorf("Failed to marshal yaml for %s channel page. \nError: %s", cp.Channel, err.Error())
}

pageBytes = []byte(strings.Join([]string{"---\n", string(pageBytes), "---"}, ""))
log.Printf("Saving %s/%s", dataDir, fileName)
err = ioutil.WriteFile(path.Join(projectRoot, fmt.Sprintf("/content/%s.md", cp.Channel)), pageBytes, os.ModePerm)
if err != nil {
return fmt.Errorf("Failed to marshal yaml for %s channel page", cp.Channel)
}

return nil
}
4 changes: 1 addition & 3 deletions util/videos.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@ import (
"strings"
)

// CreateChannelVideoFolder creates an empty folder within the videos data folder
func CreateChannelVideoFolder(channel *Channel, projectRoot string) error {
folder := path.Join(projectRoot, fmt.Sprintf("/data/videos/%s", channel.Slug))

// Make a video directory with a .gitignore
videoFolder := os.Mkdir(folder, os.ModePerm)
// There was a suggestion to gitignore, but when importing I like to immediately start committing videos
os.OpenFile(path.Join(folder, "TODO.yml"), os.O_RDONLY|os.O_CREATE, 0666)

return videoFolder
}

Expand Down

0 comments on commit a38bed5

Please sign in to comment.