2020-07-27 20:48:05 +02:00
|
|
|
package ytapi
|
|
|
|
|
|
|
|
import (
|
2020-07-27 21:51:03 +02:00
|
|
|
"bufio"
|
2020-07-27 20:48:05 +02:00
|
|
|
"net/http"
|
2020-07-27 21:51:03 +02:00
|
|
|
"regexp"
|
2020-07-27 20:48:05 +02:00
|
|
|
"sort"
|
2020-07-27 21:51:03 +02:00
|
|
|
"strconv"
|
2020-07-27 20:48:05 +02:00
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
2020-07-27 23:14:06 +02:00
|
|
|
"github.com/lbryio/ytsync/v5/downloader/ytdl"
|
|
|
|
|
2020-07-27 21:42:45 +02:00
|
|
|
"github.com/lbryio/ytsync/v5/downloader"
|
2020-07-27 20:48:05 +02:00
|
|
|
"github.com/lbryio/ytsync/v5/ip_manager"
|
|
|
|
"github.com/lbryio/ytsync/v5/sdk"
|
|
|
|
"github.com/lbryio/ytsync/v5/sources"
|
|
|
|
|
|
|
|
"github.com/lbryio/lbry.go/v2/extras/errors"
|
|
|
|
"github.com/lbryio/lbry.go/v2/extras/jsonrpc"
|
|
|
|
"github.com/lbryio/lbry.go/v2/extras/stop"
|
|
|
|
|
|
|
|
"github.com/aws/aws-sdk-go/aws"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"google.golang.org/api/googleapi/transport"
|
|
|
|
ytlib "google.golang.org/api/youtube/v3"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Video interface {
|
|
|
|
Size() *int64
|
|
|
|
ID() string
|
|
|
|
IDAndNum() string
|
|
|
|
PlaylistPosition() int
|
|
|
|
PublishedAt() time.Time
|
|
|
|
Sync(*jsonrpc.Client, sources.SyncParams, *sdk.SyncedVideo, bool, *sync.RWMutex) (*sources.SyncSummary, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
type byPublishedAt []Video
|
|
|
|
|
|
|
|
func (a byPublishedAt) Len() int { return len(a) }
|
|
|
|
func (a byPublishedAt) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
|
|
func (a byPublishedAt) Less(i, j int) bool { return a[i].PublishedAt().Before(a[j].PublishedAt()) }
|
|
|
|
|
|
|
|
type VideoParams struct {
|
|
|
|
VideoDir string
|
|
|
|
S3Config aws.Config
|
2020-07-29 03:34:08 +02:00
|
|
|
Stopper *stop.Group
|
2020-07-27 20:48:05 +02:00
|
|
|
IPPool *ip_manager.IPPool
|
|
|
|
}
|
|
|
|
|
|
|
|
var mostRecentlyFailedChannel string // TODO: fix this hack!
|
|
|
|
|
2020-07-29 06:12:23 +02:00
|
|
|
func GetVideosToSync(config *sdk.APIConfig, channelID string, syncedVideos map[string]sdk.SyncedVideo, quickSync bool, maxVideos int, videoParams VideoParams) ([]Video, error) {
|
2020-07-27 20:48:05 +02:00
|
|
|
|
|
|
|
var videos []Video
|
2020-07-27 21:57:19 +02:00
|
|
|
if quickSync {
|
|
|
|
maxVideos = 50
|
|
|
|
}
|
2020-07-29 03:34:08 +02:00
|
|
|
videoIDs, err := downloader.GetPlaylistVideoIDs(channelID, maxVideos, videoParams.Stopper.Ch())
|
2020-07-27 21:42:45 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Err(err)
|
|
|
|
}
|
2020-07-27 20:48:05 +02:00
|
|
|
|
2020-07-27 21:42:45 +02:00
|
|
|
log.Infof("Got info for %d videos from youtube downloader", len(videoIDs))
|
2020-07-27 20:48:05 +02:00
|
|
|
|
2020-07-27 21:42:45 +02:00
|
|
|
playlistMap := make(map[string]int64)
|
|
|
|
for i, videoID := range videoIDs {
|
|
|
|
playlistMap[videoID] = int64(i)
|
|
|
|
}
|
2020-07-27 20:48:05 +02:00
|
|
|
|
2020-07-27 21:42:45 +02:00
|
|
|
if len(videoIDs) < 1 {
|
|
|
|
if channelID == mostRecentlyFailedChannel {
|
|
|
|
return nil, errors.Err("playlist items not found")
|
2020-07-27 20:48:05 +02:00
|
|
|
}
|
2020-07-27 21:42:45 +02:00
|
|
|
mostRecentlyFailedChannel = channelID
|
|
|
|
}
|
2020-07-27 20:48:05 +02:00
|
|
|
|
2020-07-29 06:12:23 +02:00
|
|
|
vids, err := getVideos(config, videoIDs, videoParams.Stopper.Ch(), videoParams.IPPool)
|
2020-07-27 21:42:45 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-07-27 20:48:05 +02:00
|
|
|
|
2020-07-27 21:42:45 +02:00
|
|
|
for _, item := range vids {
|
2020-07-27 23:14:06 +02:00
|
|
|
positionInList := playlistMap[item.ID]
|
2020-07-29 03:34:08 +02:00
|
|
|
videoToAdd, err := sources.NewYoutubeVideo(videoParams.VideoDir, item, positionInList, videoParams.S3Config, videoParams.Stopper, videoParams.IPPool)
|
2020-07-27 23:14:06 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Err(err)
|
|
|
|
}
|
|
|
|
videos = append(videos, videoToAdd)
|
2020-07-27 20:48:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
for k, v := range syncedVideos {
|
|
|
|
if !v.Published {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if _, ok := playlistMap[k]; !ok {
|
2020-07-29 03:34:08 +02:00
|
|
|
videos = append(videos, sources.NewMockedVideo(videoParams.VideoDir, k, channelID, videoParams.S3Config, videoParams.Stopper, videoParams.IPPool))
|
2020-07-27 20:48:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Sort(byPublishedAt(videos))
|
|
|
|
|
|
|
|
return videos, nil
|
|
|
|
}
|
|
|
|
|
2020-07-27 21:51:03 +02:00
|
|
|
func CountVideosInChannel(channelID string) (int, error) {
|
|
|
|
res, err := http.Get("https://socialblade.com/youtube/channel/" + channelID)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
2020-07-27 20:48:05 +02:00
|
|
|
}
|
2020-07-27 21:51:03 +02:00
|
|
|
defer res.Body.Close()
|
2020-07-27 20:48:05 +02:00
|
|
|
|
2020-07-27 21:51:03 +02:00
|
|
|
var line string
|
|
|
|
|
|
|
|
scanner := bufio.NewScanner(res.Body)
|
|
|
|
for scanner.Scan() {
|
|
|
|
if strings.Contains(scanner.Text(), "youtube-stats-header-uploads") {
|
|
|
|
line = scanner.Text()
|
|
|
|
break
|
|
|
|
}
|
2020-07-27 20:48:05 +02:00
|
|
|
}
|
|
|
|
|
2020-07-27 21:51:03 +02:00
|
|
|
if err := scanner.Err(); err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
if line == "" {
|
|
|
|
return 0, errors.Err("upload count line not found")
|
2020-07-27 20:48:05 +02:00
|
|
|
}
|
|
|
|
|
2020-07-27 21:51:03 +02:00
|
|
|
matches := regexp.MustCompile(">([0-9]+)<").FindStringSubmatch(line)
|
|
|
|
if len(matches) != 2 {
|
|
|
|
return 0, errors.Err("upload count not found with regex")
|
|
|
|
}
|
|
|
|
|
|
|
|
num, err := strconv.Atoi(matches[1])
|
|
|
|
if err != nil {
|
|
|
|
return 0, errors.Err(err)
|
2020-07-27 20:48:05 +02:00
|
|
|
}
|
|
|
|
|
2020-07-27 21:51:03 +02:00
|
|
|
return num, nil
|
2020-07-27 20:48:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func ChannelInfo(apiKey, channelID string) (*ytlib.ChannelSnippet, *ytlib.ChannelBrandingSettings, error) {
|
2020-07-27 23:27:50 +02:00
|
|
|
return nil, nil, errors.Err("ChannelInfo doesn't work yet because we're focused on existing channels")
|
2020-07-29 03:34:08 +02:00
|
|
|
|
2020-07-27 20:48:05 +02:00
|
|
|
service, err := ytlib.New(&http.Client{Transport: &transport.APIKey{Key: apiKey}})
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, errors.Prefix("error creating YouTube service", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
response, err := service.Channels.List("snippet,brandingSettings").Id(channelID).Do()
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, errors.Prefix("error getting channel details", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(response.Items) < 1 {
|
|
|
|
return nil, nil, errors.Err("youtube channel not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
return response.Items[0].Snippet, response.Items[0].BrandingSettings, nil
|
|
|
|
}
|
|
|
|
|
2020-07-29 06:12:23 +02:00
|
|
|
func getVideos(config *sdk.APIConfig, videoIDs []string, stopChan stop.Chan, ipPool *ip_manager.IPPool) ([]*ytdl.YtdlVideo, error) {
|
2020-07-27 23:14:06 +02:00
|
|
|
var videos []*ytdl.YtdlVideo
|
|
|
|
for _, videoID := range videoIDs {
|
2020-07-29 03:34:08 +02:00
|
|
|
select {
|
|
|
|
case <-stopChan:
|
|
|
|
return videos, errors.Err("canceled by stopper")
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
|
|
|
|
//ip, err := ipPool.GetIP(videoID)
|
|
|
|
//if err != nil {
|
|
|
|
// return nil, err
|
|
|
|
//}
|
|
|
|
//video, err := downloader.GetVideoInformation(videoID, &net.TCPAddr{IP: net.ParseIP(ip)})
|
2020-07-29 06:12:23 +02:00
|
|
|
state, err := config.VideoState(videoID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Err(err)
|
|
|
|
}
|
|
|
|
if state == "published" {
|
|
|
|
continue
|
|
|
|
}
|
2020-07-30 17:13:19 +02:00
|
|
|
video, err := downloader.GetVideoInformation(config, videoID, stopChan, nil)
|
2020-07-27 23:14:06 +02:00
|
|
|
if err != nil {
|
2020-07-29 03:34:08 +02:00
|
|
|
//ipPool.ReleaseIP(ip)
|
2020-07-27 23:14:06 +02:00
|
|
|
return nil, errors.Err(err)
|
|
|
|
}
|
|
|
|
videos = append(videos, video)
|
2020-07-29 03:34:08 +02:00
|
|
|
//ipPool.ReleaseIP(ip)
|
2020-07-27 20:48:05 +02:00
|
|
|
}
|
2020-07-27 23:14:06 +02:00
|
|
|
return videos, nil
|
2020-07-27 20:48:05 +02:00
|
|
|
}
|