2017-12-28 18:14:33 +01:00
package sources
import (
2019-05-03 05:11:52 +02:00
"fmt"
2019-05-28 18:33:42 +02:00
"io/ioutil"
2019-04-19 03:22:51 +02:00
"math"
2017-12-28 18:14:33 +01:00
"os"
2019-05-28 18:33:42 +02:00
"os/exec"
2019-05-28 22:40:31 +02:00
"path/filepath"
2017-12-28 18:14:33 +01:00
"regexp"
"strconv"
"strings"
2019-06-12 03:17:59 +02:00
"sync"
2018-09-18 21:20:34 +02:00
"time"
2018-08-23 00:28:31 +02:00
2019-10-10 16:50:33 +02:00
"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/lbryio/lbry.go/v2/extras/util"
2020-05-19 23:13:01 +02:00
"github.com/lbryio/ytsync/timing"
2020-04-21 16:26:34 +02:00
logUtils "github.com/lbryio/ytsync/util"
2019-05-06 21:56:56 +02:00
2019-10-21 15:44:24 +02:00
"github.com/lbryio/ytsync/ip_manager"
2018-10-08 22:19:17 +02:00
"github.com/lbryio/ytsync/namer"
2019-05-06 21:56:56 +02:00
"github.com/lbryio/ytsync/sdk"
2019-10-21 15:44:24 +02:00
"github.com/lbryio/ytsync/tags_manager"
2019-05-06 21:56:56 +02:00
"github.com/lbryio/ytsync/thumbs"
2017-12-28 18:14:33 +01:00
2019-08-02 03:43:41 +02:00
duration "github.com/ChannelMeter/iso8601duration"
2019-05-03 05:11:52 +02:00
"github.com/aws/aws-sdk-go/aws"
2019-06-06 02:16:07 +02:00
"github.com/shopspring/decimal"
2017-12-28 18:14:33 +01:00
log "github.com/sirupsen/logrus"
"google.golang.org/api/youtube/v3"
)
type YoutubeVideo struct {
id string
title string
description string
playlistPosition int64
2018-08-14 17:09:23 +02:00
size * int64
2018-09-18 23:28:25 +02:00
maxVideoSize int64
2019-01-03 19:55:27 +01:00
maxVideoLength float64
2017-12-28 18:14:33 +01:00
publishedAt time . Time
dir string
2019-04-19 03:22:51 +02:00
youtubeInfo * youtube . Video
2019-05-31 23:15:22 +02:00
youtubeChannelID string
2019-04-19 03:22:51 +02:00
tags [ ] string
2019-05-03 05:11:52 +02:00
awsConfig aws . Config
2019-05-07 16:01:11 +02:00
thumbnailURL string
2019-05-07 21:15:43 +02:00
lbryChannelID string
2019-05-31 16:38:31 +02:00
mocked bool
2019-06-12 03:17:59 +02:00
walletLock * sync . RWMutex
2019-07-12 21:32:49 +02:00
stopGroup * stop . Group
2019-12-10 23:02:56 +01:00
pool * ip_manager . IPPool
2019-05-03 05:11:52 +02:00
}
var youtubeCategories = map [ string ] string {
2019-06-06 02:16:07 +02:00
"1" : "film & animation" ,
"2" : "autos & vehicles" ,
"10" : "music" ,
"15" : "pets & animals" ,
"17" : "sports" ,
"18" : "short movies" ,
"19" : "travel & events" ,
"20" : "gaming" ,
"21" : "videoblogging" ,
"22" : "people & blogs" ,
"23" : "comedy" ,
"24" : "entertainment" ,
"25" : "news & politics" ,
"26" : "howto & style" ,
"27" : "education" ,
"28" : "science & technology" ,
"29" : "nonprofits & activism" ,
"30" : "movies" ,
"31" : "anime/animation" ,
"32" : "action/adventure" ,
"33" : "classics" ,
"34" : "comedy" ,
"35" : "documentary" ,
"36" : "drama" ,
"37" : "family" ,
"38" : "foreign" ,
"39" : "horror" ,
"40" : "sci-fi/fantasy" ,
"41" : "thriller" ,
"42" : "shorts" ,
"43" : "shows" ,
"44" : "trailers" ,
2017-12-28 18:14:33 +01:00
}
2019-12-10 23:02:56 +01:00
func NewYoutubeVideo ( directory string , videoData * youtube . Video , playlistPosition int64 , awsConfig aws . Config , stopGroup * stop . Group , pool * ip_manager . IPPool ) * YoutubeVideo {
2019-04-19 03:22:51 +02:00
publishedAt , _ := time . Parse ( time . RFC3339Nano , videoData . Snippet . PublishedAt ) // ignore parse errors
2018-08-17 16:05:54 +02:00
return & YoutubeVideo {
2019-04-19 03:22:51 +02:00
id : videoData . Id ,
title : videoData . Snippet . Title ,
description : videoData . Snippet . Description ,
playlistPosition : playlistPosition ,
2017-12-28 18:14:33 +01:00
publishedAt : publishedAt ,
dir : directory ,
2019-04-19 03:22:51 +02:00
youtubeInfo : videoData ,
2019-05-03 05:11:52 +02:00
awsConfig : awsConfig ,
2019-05-31 16:38:31 +02:00
mocked : false ,
2019-05-31 23:15:22 +02:00
youtubeChannelID : videoData . Snippet . ChannelId ,
2019-07-12 21:32:49 +02:00
stopGroup : stopGroup ,
2019-12-10 23:02:56 +01:00
pool : pool ,
2019-05-31 16:38:31 +02:00
}
}
2019-12-10 23:02:56 +01:00
func NewMockedVideo ( directory string , videoID string , youtubeChannelID string , awsConfig aws . Config , stopGroup * stop . Group , pool * ip_manager . IPPool ) * YoutubeVideo {
2019-05-31 16:38:31 +02:00
return & YoutubeVideo {
id : videoID ,
playlistPosition : 0 ,
dir : directory ,
awsConfig : awsConfig ,
mocked : true ,
2019-05-31 23:15:22 +02:00
youtubeChannelID : youtubeChannelID ,
2019-07-12 21:32:49 +02:00
stopGroup : stopGroup ,
2019-12-10 23:02:56 +01:00
pool : pool ,
2017-12-28 18:14:33 +01:00
}
}
2018-08-17 16:05:54 +02:00
func ( v * YoutubeVideo ) ID ( ) string {
2017-12-28 18:14:33 +01:00
return v . id
}
2018-08-17 16:05:54 +02:00
func ( v * YoutubeVideo ) PlaylistPosition ( ) int {
2018-04-25 20:56:26 +02:00
return int ( v . playlistPosition )
}
2018-08-17 16:05:54 +02:00
func ( v * YoutubeVideo ) IDAndNum ( ) string {
2017-12-28 18:14:33 +01:00
return v . ID ( ) + " (" + strconv . Itoa ( int ( v . playlistPosition ) ) + " in channel)"
}
2018-08-17 16:05:54 +02:00
func ( v * YoutubeVideo ) PublishedAt ( ) time . Time {
2019-05-31 16:38:31 +02:00
if v . mocked {
return time . Unix ( 0 , 0 )
}
2017-12-28 18:14:33 +01:00
return v . publishedAt
}
2018-10-09 21:57:07 +02:00
func ( v * YoutubeVideo ) getFullPath ( ) string {
2018-05-05 13:22:33 +02:00
maxLen := 30
2017-12-28 18:14:33 +01:00
reg := regexp . MustCompile ( ` [^a-zA-Z0-9]+ ` )
chunks := strings . Split ( strings . ToLower ( strings . Trim ( reg . ReplaceAllString ( v . title , "-" ) , "-" ) ) , "-" )
name := chunks [ 0 ]
if len ( name ) > maxLen {
2018-08-10 14:41:21 +02:00
name = name [ : maxLen ]
2017-12-28 18:14:33 +01:00
}
for _ , chunk := range chunks [ 1 : ] {
tmpName := name + "-" + chunk
if len ( tmpName ) > maxLen {
if len ( name ) < 20 {
name = tmpName [ : maxLen ]
}
break
}
name = tmpName
}
2018-06-04 16:35:35 +02:00
if len ( name ) < 1 {
name = v . id
}
2018-07-24 02:01:35 +02:00
return v . videoDir ( ) + "/" + name + ".mp4"
2017-12-28 18:14:33 +01:00
}
2018-08-17 16:05:54 +02:00
func ( v * YoutubeVideo ) getAbbrevDescription ( ) string {
2019-08-02 16:01:33 +02:00
maxLength := 2800
2017-12-28 18:14:33 +01:00
description := strings . TrimSpace ( v . description )
2019-05-07 21:15:43 +02:00
additionalDescription := "\nhttps://www.youtube.com/watch?v=" + v . id
khanAcademyClaimID := "5fc52291980268b82413ca4c0ace1b8d749f3ffb"
if v . lbryChannelID == khanAcademyClaimID {
additionalDescription = additionalDescription + "\nNote: All Khan Academy content is available for free at (www.khanacademy.org)"
}
2019-08-02 16:01:33 +02:00
if len ( description ) > maxLength {
description = description [ : maxLength ]
2019-06-13 19:33:58 +02:00
}
2019-08-02 16:01:33 +02:00
return description + "\n..." + additionalDescription
2017-12-28 18:14:33 +01:00
}
2019-12-14 14:58:04 +01:00
func ( v * YoutubeVideo ) download ( ) error {
2020-05-19 23:13:01 +02:00
start := time . Now ( )
defer func ( start time . Time ) {
timing . TimedComponent ( "download" ) . Add ( time . Since ( start ) )
} ( start )
2020-04-29 16:15:22 +02:00
if v . youtubeInfo . Snippet . LiveBroadcastContent != "none" {
return errors . Err ( "video is a live stream and hasn't completed yet" )
}
2019-07-11 19:14:15 +02:00
videoPath := v . getFullPath ( )
2019-08-11 04:50:43 +02:00
err := os . Mkdir ( v . videoDir ( ) , 0777 )
2019-07-11 19:14:15 +02:00
if err != nil && ! strings . Contains ( err . Error ( ) , "file exists" ) {
return errors . Wrap ( err , 0 )
}
_ , err = os . Stat ( videoPath )
if err != nil && ! os . IsNotExist ( err ) {
2019-07-11 16:22:58 +02:00
return errors . Err ( err )
2019-07-11 19:14:15 +02:00
} else if err == nil {
log . Debugln ( v . id + " already exists at " + videoPath )
return nil
2019-07-11 16:22:58 +02:00
}
2019-07-22 02:09:18 +02:00
qualities := [ ] string {
2019-08-05 20:08:46 +02:00
"1080" ,
2019-07-22 02:09:18 +02:00
"720" ,
"480" ,
"320" ,
}
2019-07-11 19:14:15 +02:00
ytdlArgs := [ ] string {
2019-05-28 18:33:42 +02:00
"--no-progress" ,
2019-07-22 02:51:13 +02:00
"-o" + strings . TrimSuffix ( v . getFullPath ( ) , ".mp4" ) ,
2019-06-04 22:21:40 +02:00
"--merge-output-format" ,
2019-07-22 02:27:14 +02:00
"mp4" ,
2020-04-21 20:56:14 +02:00
"--rm-cache-dir" ,
2019-07-26 00:15:14 +02:00
"--postprocessor-args" ,
2019-07-26 00:33:05 +02:00
"-movflags faststart" ,
2019-07-22 02:09:18 +02:00
"--abort-on-unavailable-fragment" ,
"--fragment-retries" ,
"0" ,
2020-01-03 15:59:05 +01:00
"--user-agent" ,
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36" ,
"--cookies" ,
"cookies.txt" ,
2019-07-11 19:14:15 +02:00
}
2019-07-26 00:22:56 +02:00
if v . maxVideoSize > 0 {
ytdlArgs = append ( ytdlArgs ,
"--max-filesize" ,
fmt . Sprintf ( "%dM" , v . maxVideoSize ) ,
)
}
if v . maxVideoLength > 0 {
ytdlArgs = append ( ytdlArgs ,
"--match-filter" ,
fmt . Sprintf ( "duration <= %d" , int ( math . Round ( v . maxVideoLength * 3600 ) ) ) ,
)
}
2019-12-10 23:02:56 +01:00
2019-12-20 18:49:33 +01:00
var sourceAddress string
for {
sourceAddress , err = v . pool . GetIP ( v . id )
if err != nil {
if errors . Is ( err , ip_manager . ErrAllThrottled ) {
2019-07-12 21:32:49 +02:00
select {
case <- v . stopGroup . Ch ( ) :
return errors . Err ( "interrupted by user" )
default :
2019-12-20 18:49:33 +01:00
time . Sleep ( ip_manager . IPCooldownPeriod )
continue
2019-07-13 12:15:19 +02:00
}
2019-12-20 18:49:33 +01:00
} else {
return err
2019-07-12 21:32:49 +02:00
}
}
2019-12-20 18:49:33 +01:00
break
2019-07-12 20:42:44 +02:00
}
2019-12-10 23:02:56 +01:00
defer v . pool . ReleaseIP ( sourceAddress )
2019-12-20 18:49:33 +01:00
2019-12-10 23:02:56 +01:00
ytdlArgs = append ( ytdlArgs ,
"--source-address" ,
sourceAddress ,
"https://www.youtube.com/watch?v=" + v . ID ( ) ,
)
2019-12-20 18:49:33 +01:00
for i , quality := range qualities {
argsWithFilters := append ( ytdlArgs , "-fbestvideo[ext=mp4][height<=" + quality + "]+bestaudio[ext!=webm]" )
cmd := exec . Command ( "youtube-dl" , argsWithFilters ... )
log . Printf ( "Running command youtube-dl %s" , strings . Join ( argsWithFilters , " " ) )
2019-07-11 19:14:15 +02:00
2019-12-20 18:49:33 +01:00
stderr , err := cmd . StderrPipe ( )
if err != nil {
return errors . Err ( err )
}
stdout , err := cmd . StdoutPipe ( )
if err != nil {
return errors . Err ( err )
}
2019-07-11 19:14:15 +02:00
2019-12-20 18:49:33 +01:00
if err := cmd . Start ( ) ; err != nil {
return errors . Err ( err )
}
2019-07-11 19:14:15 +02:00
2019-12-20 18:49:33 +01:00
errorLog , _ := ioutil . ReadAll ( stderr )
outLog , _ := ioutil . ReadAll ( stdout )
err = cmd . Wait ( )
if err != nil {
if strings . Contains ( err . Error ( ) , "exit status 1" ) {
if strings . Contains ( string ( errorLog ) , "HTTP Error 429" ) || strings . Contains ( string ( errorLog ) , "returned non-zero exit status 8" ) {
v . pool . SetThrottled ( sourceAddress )
} else if strings . Contains ( string ( errorLog ) , "giving up after 0 fragment retries" ) {
if i == ( len ( qualities ) - 1 ) {
return errors . Err ( string ( errorLog ) )
}
continue //this bypasses the yt throttling IP redistribution... TODO: don't
}
return errors . Err ( string ( errorLog ) )
2019-07-12 21:32:49 +02:00
}
2019-12-20 18:49:33 +01:00
return errors . Err ( err )
2019-07-11 19:14:15 +02:00
}
2019-12-20 18:49:33 +01:00
log . Debugln ( string ( outLog ) )
2019-07-11 19:14:15 +02:00
2019-12-20 18:49:33 +01:00
if strings . Contains ( string ( outLog ) , "does not pass filter duration" ) {
_ = v . delete ( "does not pass filter duration" )
return errors . Err ( "video is too long to process" )
}
if strings . Contains ( string ( outLog ) , "File is larger than max-filesize" ) {
_ = v . delete ( "File is larger than max-filesize" )
return errors . Err ( "the video is too big to sync, skipping for now" )
}
if string ( errorLog ) != "" {
log . Printf ( "Command finished with error: %v" , errors . Err ( string ( errorLog ) ) )
_ = v . delete ( "due to error" )
return errors . Err ( string ( errorLog ) )
}
fi , err := os . Stat ( v . getFullPath ( ) )
if err != nil {
return errors . Err ( err )
}
err = os . Chmod ( v . getFullPath ( ) , 0777 )
if err != nil {
return errors . Err ( err )
}
videoSize := fi . Size ( )
v . size = & videoSize
break
2019-08-11 04:50:43 +02:00
}
2019-05-28 18:33:42 +02:00
return nil
}
2018-08-17 16:05:54 +02:00
func ( v * YoutubeVideo ) videoDir ( ) string {
2018-07-24 02:01:35 +02:00
return v . dir + "/" + v . id
}
2019-05-28 18:33:42 +02:00
func ( v * YoutubeVideo ) getDownloadedPath ( ) ( string , error ) {
files , err := ioutil . ReadDir ( v . videoDir ( ) )
2019-05-28 22:40:31 +02:00
log . Infoln ( v . videoDir ( ) )
2019-05-28 18:33:42 +02:00
if err != nil {
err = errors . Prefix ( "list error" , err )
log . Errorln ( err )
return "" , err
}
for _ , f := range files {
if f . IsDir ( ) {
continue
}
2019-05-28 22:40:31 +02:00
if strings . Contains ( v . getFullPath ( ) , strings . TrimSuffix ( f . Name ( ) , filepath . Ext ( f . Name ( ) ) ) ) {
2019-05-28 18:33:42 +02:00
return v . videoDir ( ) + "/" + f . Name ( ) , nil
}
}
return "" , errors . Err ( "could not find any downloaded videos" )
2018-07-24 02:01:35 +02:00
2019-05-28 18:33:42 +02:00
}
2019-08-02 03:43:41 +02:00
func ( v * YoutubeVideo ) delete ( reason string ) error {
2019-05-28 18:33:42 +02:00
videoPath , err := v . getDownloadedPath ( )
if err != nil {
log . Errorln ( err )
return err
}
err = os . Remove ( videoPath )
2019-08-02 03:43:41 +02:00
log . Debugf ( "%s deleted from disk for '%s' (%s)" , v . id , reason , videoPath )
2019-05-28 18:33:42 +02:00
2018-04-25 20:56:26 +02:00
if err != nil {
2019-05-28 18:33:42 +02:00
err = errors . Prefix ( "delete error" , err )
log . Errorln ( err )
2018-04-25 20:56:26 +02:00
return err
}
2019-05-28 18:33:42 +02:00
2018-04-25 20:56:26 +02:00
return nil
}
2019-05-07 16:01:11 +02:00
func ( v * YoutubeVideo ) triggerThumbnailSave ( ) ( err error ) {
thumbnail := thumbs . GetBestThumbnail ( v . youtubeInfo . Snippet . Thumbnails )
v . thumbnailURL , err = thumbs . MirrorThumbnail ( thumbnail . Url , v . ID ( ) , v . awsConfig )
return err
2017-12-28 18:14:33 +01:00
}
2019-06-06 02:16:07 +02:00
func ( v * YoutubeVideo ) publish ( daemon * jsonrpc . Client , params SyncParams ) ( * SyncSummary , error ) {
2020-05-19 23:13:01 +02:00
start := time . Now ( )
defer func ( start time . Time ) {
timing . TimedComponent ( "publish" ) . Add ( time . Since ( start ) )
} ( start )
2019-05-07 21:15:43 +02:00
languages , locations , tags := v . getMetadata ( )
2019-06-06 02:16:07 +02:00
var fee * jsonrpc . Fee
if params . Fee != nil {
feeAmount , err := decimal . NewFromString ( params . Fee . Amount )
if err != nil {
return nil , errors . Err ( err )
}
fee = & jsonrpc . Fee {
FeeAddress : & params . Fee . Address ,
FeeAmount : feeAmount ,
FeeCurrency : jsonrpc . Currency ( params . Fee . Currency ) ,
}
}
2019-04-19 03:22:51 +02:00
options := jsonrpc . StreamCreateOptions {
ClaimCreateOptions : jsonrpc . ClaimCreateOptions {
2019-06-04 22:21:40 +02:00
Title : & v . title ,
Description : util . PtrToString ( v . getAbbrevDescription ( ) ) ,
2019-06-06 02:16:07 +02:00
ClaimAddress : & params . ClaimAddress ,
2019-04-19 03:22:51 +02:00
Languages : languages ,
2019-05-07 16:01:11 +02:00
ThumbnailURL : & v . thumbnailURL ,
2019-05-07 21:15:43 +02:00
Tags : tags ,
Locations : locations ,
2020-01-12 04:01:40 +01:00
FundingAccountIDs : [ ] string {
params . DefaultAccount ,
} ,
2019-01-30 13:42:23 +01:00
} ,
2019-06-06 02:16:07 +02:00
Fee : fee ,
2019-05-31 18:02:55 +02:00
License : util . PtrToString ( "Copyrighted (contact publisher)" ) ,
2019-05-06 21:56:56 +02:00
ReleaseTime : util . PtrToInt64 ( v . publishedAt . Unix ( ) ) ,
2019-05-07 21:15:43 +02:00
ChannelID : & v . lbryChannelID ,
2017-12-28 18:14:33 +01:00
}
2019-05-28 18:33:42 +02:00
downloadPath , err := v . getDownloadedPath ( )
if err != nil {
return nil , err
}
2019-06-12 03:17:59 +02:00
return publishAndRetryExistingNames ( daemon , v . title , downloadPath , params . Amount , options , params . Namer , v . walletLock )
2017-12-28 18:14:33 +01:00
}
2018-08-17 16:05:54 +02:00
func ( v * YoutubeVideo ) Size ( ) * int64 {
2018-08-14 17:09:23 +02:00
return v . size
}
2019-05-03 05:11:52 +02:00
type SyncParams struct {
ClaimAddress string
Amount float64
ChannelID string
MaxVideoSize int
Namer * namer . Namer
MaxVideoLength float64
2019-06-06 02:16:07 +02:00
Fee * sdk . Fee
2020-01-12 04:01:40 +01:00
DefaultAccount string
2019-05-03 05:11:52 +02:00
}
2019-06-12 03:17:59 +02:00
func ( v * YoutubeVideo ) Sync ( daemon * jsonrpc . Client , params SyncParams , existingVideoData * sdk . SyncedVideo , reprocess bool , walletLock * sync . RWMutex ) ( * SyncSummary , error ) {
2019-07-12 01:04:45 +02:00
v . maxVideoSize = int64 ( params . MaxVideoSize )
2019-05-03 05:11:52 +02:00
v . maxVideoLength = params . MaxVideoLength
2019-05-07 21:15:43 +02:00
v . lbryChannelID = params . ChannelID
2019-06-12 03:35:21 +02:00
v . walletLock = walletLock
2019-05-07 22:07:44 +02:00
if reprocess && existingVideoData != nil && existingVideoData . Published {
2019-05-06 21:56:56 +02:00
summary , err := v . reprocess ( daemon , params , existingVideoData )
2019-06-10 21:59:42 +02:00
return summary , errors . Prefix ( "upgrade failed" , err )
2019-05-03 05:11:52 +02:00
}
2019-05-06 21:56:56 +02:00
return v . downloadAndPublish ( daemon , params )
}
func ( v * YoutubeVideo ) downloadAndPublish ( daemon * jsonrpc . Client , params SyncParams ) ( * SyncSummary , error ) {
2019-12-14 14:58:04 +01:00
var err error
2020-03-12 19:22:23 +01:00
videoDuration , err := duration . FromString ( v . youtubeInfo . ContentDetails . Duration )
if err != nil {
return nil , errors . Err ( err )
}
if videoDuration . ToDuration ( ) > time . Duration ( v . maxVideoLength * 60 ) * time . Minute {
2020-04-19 02:38:31 +02:00
log . Infof ( "%s is %s long and the limit is %s" , v . id , videoDuration . ToDuration ( ) . String ( ) , ( time . Duration ( v . maxVideoLength * 60 ) * time . Minute ) . String ( ) )
2020-04-21 16:26:34 +02:00
logUtils . SendErrorToSlack ( "%s is %s long and the limit is %s" , v . id , videoDuration . ToDuration ( ) . String ( ) , ( time . Duration ( v . maxVideoLength * 60 ) * time . Minute ) . String ( ) )
2020-03-12 19:22:23 +01:00
return nil , errors . Err ( "video is too long to process" )
}
2019-12-14 14:58:04 +01:00
for {
err = v . download ( )
if err != nil && strings . Contains ( err . Error ( ) , "HTTP Error 429" ) {
continue
} else if err != nil {
2019-07-11 19:14:15 +02:00
return nil , errors . Prefix ( "download error" , err )
}
2019-12-14 14:58:04 +01:00
break
2017-12-28 18:14:33 +01:00
}
2019-12-14 14:58:04 +01:00
2017-12-28 18:14:33 +01:00
log . Debugln ( "Downloaded " + v . id )
2018-02-13 18:47:05 +01:00
err = v . triggerThumbnailSave ( )
2017-12-28 18:14:33 +01:00
if err != nil {
2018-07-21 01:56:36 +02:00
return nil , errors . Prefix ( "thumbnail error" , err )
2017-12-28 18:14:33 +01:00
}
log . Debugln ( "Created thumbnail for " + v . id )
2019-06-06 02:16:07 +02:00
summary , err := v . publish ( daemon , params )
2018-06-06 23:47:28 +02:00
//delete the video in all cases (and ignore the error)
2019-08-02 03:43:41 +02:00
_ = v . delete ( "finished download and publish" )
2018-04-25 20:56:26 +02:00
2018-10-03 02:51:42 +02:00
return summary , errors . Prefix ( "publish error" , err )
2017-12-28 18:14:33 +01:00
}
2019-05-03 05:11:52 +02:00
2019-05-07 21:15:43 +02:00
func ( v * YoutubeVideo ) getMetadata ( ) ( languages [ ] string , locations [ ] jsonrpc . Location , tags [ ] string ) {
languages = nil
2019-05-31 23:15:22 +02:00
locations = nil
tags = nil
if ! v . mocked {
if v . youtubeInfo . Snippet . DefaultLanguage != "" {
2019-12-18 18:22:15 +01:00
if v . youtubeInfo . Snippet . DefaultLanguage == "iw" {
v . youtubeInfo . Snippet . DefaultLanguage = "he"
}
2019-05-31 23:15:22 +02:00
languages = [ ] string { v . youtubeInfo . Snippet . DefaultLanguage }
}
if v . youtubeInfo . RecordingDetails != nil && v . youtubeInfo . RecordingDetails . Location != nil {
locations = [ ] jsonrpc . Location { {
Latitude : util . PtrToString ( fmt . Sprintf ( "%.7f" , v . youtubeInfo . RecordingDetails . Location . Latitude ) ) ,
Longitude : util . PtrToString ( fmt . Sprintf ( "%.7f" , v . youtubeInfo . RecordingDetails . Location . Longitude ) ) ,
} }
}
tags = v . youtubeInfo . Snippet . Tags
}
2019-10-21 15:44:24 +02:00
tags , err := tags_manager . SanitizeTags ( tags , v . youtubeChannelID )
2019-05-31 23:15:22 +02:00
if err != nil {
log . Errorln ( err . Error ( ) )
}
if ! v . mocked {
tags = append ( tags , youtubeCategories [ v . youtubeInfo . Snippet . CategoryId ] )
2019-05-07 21:15:43 +02:00
}
return languages , locations , tags
}
2019-05-06 21:56:56 +02:00
func ( v * YoutubeVideo ) reprocess ( daemon * jsonrpc . Client , params SyncParams , existingVideoData * sdk . SyncedVideo ) ( * SyncSummary , error ) {
2019-12-08 17:48:24 +01:00
c , err := daemon . ClaimSearch ( nil , & existingVideoData . ClaimID , nil , nil , 1 , 20 )
2019-05-03 05:11:52 +02:00
if err != nil {
return nil , errors . Err ( err )
}
if len ( c . Claims ) == 0 {
return nil , errors . Err ( "cannot reprocess: no claim found for this video" )
} else if len ( c . Claims ) > 1 {
return nil , errors . Err ( "cannot reprocess: too many claims. claimID: %s" , existingVideoData . ClaimID )
}
currentClaim := c . Claims [ 0 ]
2019-05-07 21:15:43 +02:00
languages , locations , tags := v . getMetadata ( )
2019-05-03 05:11:52 +02:00
thumbnailURL := ""
if currentClaim . Value . GetThumbnail ( ) == nil {
2019-05-31 16:38:31 +02:00
if v . mocked {
return nil , errors . Err ( "could not find thumbnail for mocked video" )
}
2019-05-03 05:11:52 +02:00
thumbnail := thumbs . GetBestThumbnail ( v . youtubeInfo . Snippet . Thumbnails )
thumbnailURL , err = thumbs . MirrorThumbnail ( thumbnail . Url , v . ID ( ) , v . awsConfig )
} else {
thumbnailURL = thumbs . ThumbnailEndpoint + v . ID ( )
}
2019-05-06 21:56:56 +02:00
videoSize , err := currentClaim . GetStreamSizeByMagic ( )
if err != nil {
if existingVideoData . Size > 0 {
videoSize = uint64 ( existingVideoData . Size )
} else {
log . Infof ( "%s: the video must be republished as we can't get the right size" , v . ID ( ) )
2019-08-30 19:35:04 +02:00
if ! v . mocked {
_ , err = daemon . StreamAbandon ( currentClaim . Txid , currentClaim . Nout , nil , true )
if err != nil {
return nil , errors . Err ( err )
}
return v . downloadAndPublish ( daemon , params )
}
return nil , errors . Err ( "the video must be republished as we can't get the right size but it doesn't exist on youtube anymore" )
2019-05-06 21:56:56 +02:00
}
}
2019-06-06 23:25:31 +02:00
v . size = util . PtrToInt64 ( int64 ( videoSize ) )
2019-06-06 02:16:07 +02:00
var fee * jsonrpc . Fee
if params . Fee != nil {
feeAmount , err := decimal . NewFromString ( params . Fee . Amount )
if err != nil {
return nil , errors . Err ( err )
}
fee = & jsonrpc . Fee {
FeeAddress : & params . Fee . Address ,
FeeAmount : feeAmount ,
FeeCurrency : jsonrpc . Currency ( params . Fee . Currency ) ,
}
}
streamCreateOptions := & jsonrpc . StreamCreateOptions {
ClaimCreateOptions : jsonrpc . ClaimCreateOptions {
Tags : tags ,
ThumbnailURL : & thumbnailURL ,
Languages : languages ,
Locations : locations ,
2020-01-12 04:01:40 +01:00
FundingAccountIDs : [ ] string {
params . DefaultAccount ,
} ,
2019-06-06 02:16:07 +02:00
} ,
Author : util . PtrToString ( "" ) ,
License : util . PtrToString ( "Copyrighted (contact publisher)" ) ,
ChannelID : & v . lbryChannelID ,
Height : util . PtrToUint ( 720 ) ,
Width : util . PtrToUint ( 1280 ) ,
Fee : fee ,
}
2019-05-07 21:15:43 +02:00
2019-06-12 03:17:59 +02:00
v . walletLock . RLock ( )
defer v . walletLock . RUnlock ( )
2019-05-31 16:38:31 +02:00
if v . mocked {
2020-05-19 23:13:01 +02:00
start := time . Now ( )
2019-05-31 16:38:31 +02:00
pr , err := daemon . StreamUpdate ( existingVideoData . ClaimID , jsonrpc . StreamUpdateOptions {
2019-06-06 02:16:07 +02:00
StreamCreateOptions : streamCreateOptions ,
FileSize : & videoSize ,
2019-05-31 16:38:31 +02:00
} )
2020-05-19 23:13:01 +02:00
timing . TimedComponent ( "StreamUpdate" ) . Add ( time . Since ( start ) )
2019-05-31 16:38:31 +02:00
if err != nil {
return nil , err
}
return & SyncSummary {
ClaimID : pr . Outputs [ 0 ] . ClaimID ,
ClaimName : pr . Outputs [ 0 ] . Name ,
} , nil
}
2019-06-01 01:46:16 +02:00
videoDuration , err := duration . FromString ( v . youtubeInfo . ContentDetails . Duration )
if err != nil {
return nil , errors . Err ( err )
}
2019-06-06 02:16:07 +02:00
streamCreateOptions . ClaimCreateOptions . Title = & v . title
streamCreateOptions . ClaimCreateOptions . Description = util . PtrToString ( v . getAbbrevDescription ( ) )
streamCreateOptions . Duration = util . PtrToUint64 ( uint64 ( math . Ceil ( videoDuration . ToDuration ( ) . Seconds ( ) ) ) )
streamCreateOptions . ReleaseTime = util . PtrToInt64 ( v . publishedAt . Unix ( ) )
2020-05-19 23:13:01 +02:00
start := time . Now ( )
2019-05-07 16:01:11 +02:00
pr , err := daemon . StreamUpdate ( existingVideoData . ClaimID , jsonrpc . StreamUpdateOptions {
2019-06-06 02:16:07 +02:00
ClearLanguages : util . PtrToBool ( true ) ,
ClearLocations : util . PtrToBool ( true ) ,
ClearTags : util . PtrToBool ( true ) ,
StreamCreateOptions : streamCreateOptions ,
FileSize : & videoSize ,
2019-05-03 05:11:52 +02:00
} )
2020-05-19 23:13:01 +02:00
timing . TimedComponent ( "StreamUpdate" ) . Add ( time . Since ( start ) )
2019-05-07 16:01:11 +02:00
if err != nil {
return nil , err
}
return & SyncSummary {
2019-05-08 23:12:13 +02:00
ClaimID : pr . Outputs [ 0 ] . ClaimID ,
ClaimName : pr . Outputs [ 0 ] . Name ,
2019-05-07 16:01:11 +02:00
} , nil
2019-05-03 05:11:52 +02:00
}