@ -273,10 +273,7 @@ async function getVideosForSub(sub, user_uid = null) {
else
basePath = config _api . getConfigItem ( 'ytdl_subscriptions_base_path' ) ;
const useArchive = config _api . getConfigItem ( 'ytdl_use_youtubedl_archive' ) ;
let appendedBasePath = null
appendedBasePath = getAppendedBasePath ( sub , basePath ) ;
let appendedBasePath = getAppendedBasePath ( sub , basePath ) ;
let multiUserMode = null ;
if ( user _uid ) {
@ -286,14 +283,87 @@ async function getVideosForSub(sub, user_uid = null) {
}
}
const ext = ( sub . type && sub . type === 'audio' ) ? '.mp3' : '.mp4'
const downloadConfig = await generateArgsForSubscription ( sub , user _uid ) ;
// get videos
logger . verbose ( 'Subscription: getting videos for subscription ' + sub . name ) ;
return new Promise ( resolve => {
youtubedl . exec ( sub . url , downloadConfig , { } , async function ( err , output ) {
logger . verbose ( 'Subscription: finished check for ' + sub . name ) ;
if ( err && ! output ) {
logger . error ( err . stderr ? err . stderr : err . message ) ;
if ( err . stderr . includes ( 'This video is unavailable' ) ) {
logger . info ( 'An error was encountered with at least one video, backup method will be used.' )
try {
const outputs = err . stdout . split ( /\r\n|\r|\n/ ) ;
for ( let i = 0 ; i < outputs . length ; i ++ ) {
const output = JSON . parse ( outputs [ i ] ) ;
handleOutputJSON ( sub , sub _db , output , i === 0 , multiUserMode )
if ( err . stderr . includes ( output [ 'id' ] ) && archive _path ) {
// we found a video that errored! add it to the archive to prevent future errors
if ( sub . archive ) {
archive _dir = sub . archive ;
archive _path = path . join ( archive _dir , 'archive.txt' )
fs . appendFileSync ( archive _path , output [ 'id' ] ) ;
}
}
}
} catch ( e ) {
logger . error ( 'Backup method failed. See error below:' ) ;
logger . error ( e ) ;
}
}
resolve ( false ) ;
} else if ( output ) {
if ( output . length === 0 || ( output . length === 1 && output [ 0 ] === '' ) ) {
logger . verbose ( 'No additional videos to download for ' + sub . name ) ;
resolve ( true ) ;
}
for ( let i = 0 ; i < output . length ; i ++ ) {
let output _json = null ;
try {
output _json = JSON . parse ( output [ i ] ) ;
} catch ( e ) {
output _json = null ;
}
if ( ! output _json ) {
continue ;
}
const reset _videos = i === 0 ;
handleOutputJSON ( sub , sub _db , output _json , multiUserMode , reset _videos ) ;
await setFreshUploads ( sub , user _uid ) ;
checkVideosForFreshUploads ( sub , user _uid ) ;
}
resolve ( true ) ;
}
} ) ;
} , err => {
logger . error ( err ) ;
} ) ;
}
async function generateArgsForSubscription ( sub , user _uid , redownload = false , desired _path = null ) {
// get basePath
let basePath = null ;
if ( user _uid )
basePath = path . join ( config _api . getConfigItem ( 'ytdl_users_base_path' ) , user _uid , 'subscriptions' ) ;
else
basePath = config _api . getConfigItem ( 'ytdl_subscriptions_base_path' ) ;
const useArchive = config _api . getConfigItem ( 'ytdl_use_youtubedl_archive' ) ;
let appendedBasePath = getAppendedBasePath ( sub , basePath ) ;
let fullOutput = ` ${ appendedBasePath } /%(title)s.%(ext)s ` ;
if ( sub . custom _output ) {
if ( desired _path ) {
fullOutput = ` ${ desired _path } .%(ext)s ` ;
} else if ( sub . custom _output ) {
fullOutput = ` ${ appendedBasePath } / ${ sub . custom _output } .%(ext)s ` ;
}
let downloadConfig = [ '-o' , fullOutput , '-ciw' , '--write-info-json' , '--print-json' ] ;
let downloadConfig = [ '-o' , fullOutput , ! redownload ? '-ciw ' : '-ci ', '--write-info-json' , '--print-json' ] ;
let qualityPath = null ;
if ( sub . type && sub . type === 'audio' ) {
@ -320,7 +390,7 @@ async function getVideosForSub(sub, user_uid = null) {
let archive _dir = null ;
let archive _path = null ;
if ( useArchive ) {
if ( useArchive && ! redownload ) {
if ( sub . archive ) {
archive _dir = sub . archive ;
archive _path = path . join ( archive _dir , 'archive.txt' )
@ -350,60 +420,7 @@ async function getVideosForSub(sub, user_uid = null) {
downloadConfig . push ( '--write-thumbnail' ) ;
}
// get videos
logger . verbose ( 'Subscription: getting videos for subscription ' + sub . name ) ;
return new Promise ( resolve => {
youtubedl . exec ( sub . url , downloadConfig , { } , function ( err , output ) {
logger . verbose ( 'Subscription: finished check for ' + sub . name ) ;
if ( err && ! output ) {
logger . error ( err . stderr ? err . stderr : err . message ) ;
if ( err . stderr . includes ( 'This video is unavailable' ) ) {
logger . info ( 'An error was encountered with at least one video, backup method will be used.' )
try {
const outputs = err . stdout . split ( /\r\n|\r|\n/ ) ;
for ( let i = 0 ; i < outputs . length ; i ++ ) {
const output = JSON . parse ( outputs [ i ] ) ;
handleOutputJSON ( sub , sub _db , output , i === 0 , multiUserMode )
if ( err . stderr . includes ( output [ 'id' ] ) && archive _path ) {
// we found a video that errored! add it to the archive to prevent future errors
fs . appendFileSync ( archive _path , output [ 'id' ] ) ;
}
}
} catch ( e ) {
logger . error ( 'Backup method failed. See error below:' ) ;
logger . error ( e ) ;
}
}
resolve ( false ) ;
} else if ( output ) {
if ( output . length === 0 || ( output . length === 1 && output [ 0 ] === '' ) ) {
logger . verbose ( 'No additional videos to download for ' + sub . name ) ;
resolve ( true ) ;
}
for ( let i = 0 ; i < output . length ; i ++ ) {
let output _json = null ;
try {
output _json = JSON . parse ( output [ i ] ) ;
} catch ( e ) {
output _json = null ;
}
if ( ! output _json ) {
continue ;
}
const reset _videos = i === 0 ;
handleOutputJSON ( sub , sub _db , output _json , multiUserMode , reset _videos ) ;
// TODO: Potentially store downloaded files in db?
}
resolve ( true ) ;
}
} ) ;
} , err => {
logger . error ( err ) ;
} ) ;
return downloadConfig ;
}
function handleOutputJSON ( sub , sub _db , output _json , multiUserMode = null , reset _videos = false ) {
@ -418,6 +435,14 @@ function handleOutputJSON(sub, sub_db, output_json, multiUserMode = null, reset_
// add to db
sub _db . get ( 'videos' ) . push ( output _json ) . write ( ) ;
} else {
path _object = path . parse ( output _json [ '_filename' ] ) ;
const path _string = path . format ( path _object ) ;
if ( sub _db . get ( 'videos' ) . find ( { path : path _string } ) . value ( ) ) {
// file already exists in DB, return early to avoid reseting the download date
return ;
}
db _api . registerFileDB ( path . basename ( output _json [ '_filename' ] ) , sub . type , multiUserMode , sub ) ;
const url = output _json [ 'webpage_url' ] ;
if ( sub . type === 'video' && url . includes ( 'twitch.tv/videos/' ) && url . split ( 'twitch.tv/videos/' ) . length > 1
@ -468,6 +493,53 @@ function subExists(subID, user_uid = null) {
return ! ! db . get ( 'subscriptions' ) . find ( { id : subID } ) . value ( ) ;
}
async function setFreshUploads ( sub , user _uid ) {
const current _date = new Date ( ) . toISOString ( ) . split ( 'T' ) [ 0 ] . replace ( /-/g , '' ) ;
sub . videos . forEach ( async video => {
if ( current _date === video [ 'upload_date' ] . replace ( /-/g , '' ) ) {
// set upload as fresh
const video _uid = video [ 'uid' ] ;
await db _api . setVideoProperty ( video _uid , { 'fresh_upload' : true } , user _uid , sub [ 'id' ] ) ;
}
} ) ;
}
async function checkVideosForFreshUploads ( sub , user _uid ) {
const current _date = new Date ( ) . toISOString ( ) . split ( 'T' ) [ 0 ] . replace ( /-/g , '' ) ;
sub . videos . forEach ( async video => {
if ( video [ 'fresh_upload' ] && current _date > video [ 'upload_date' ] . replace ( /-/g , '' ) ) {
checkVideoIfBetterExists ( video , sub , user _uid )
}
} ) ;
}
async function checkVideoIfBetterExists ( file _obj , sub , user _uid ) {
const new _path = file _obj [ 'path' ] . substring ( 0 , file _obj [ 'path' ] . length - 4 ) ;
const downloadConfig = generateArgsForSubscription ( sub , user _uid , true , new _path ) ;
logger . verbose ( ` Checking if a better version of the fresh upload ${ file _obj [ 'id' ] } exists. ` ) ;
// simulate a download to verify that a better version exists
youtubedl . getInfo ( file _obj [ 'url' ] , downloadConfig , ( err , output ) => {
if ( err ) {
// video is not available anymore for whatever reason
} else if ( output ) {
console . log ( output ) ;
const metric _to _compare = sub . type === 'audio' ? 'abr' : 'height' ;
if ( output [ metric _to _compare ] > file _obj [ metric _to _compare ] ) {
// download new video as the simulated one is better
youtubedl . exec ( file _obj [ 'url' ] , downloadConfig , async ( err , output ) => {
if ( err ) {
logger . verbose ( ` Failed to download better version of video ${ file _obj [ 'id' ] } ` ) ;
} else if ( output ) {
logger . verbose ( ` Successfully upgraded video ${ file _obj [ 'id' ] } 's ${ metric _to _compare } from ${ file _obj [ metric _to _compare ] } to ${ output [ metric _to _compare ] } ` ) ;
await db _api . setVideoProperty ( file _obj [ 'uid' ] , { [ metric _to _compare ] : output [ metric _to _compare ] } , user _uid , sub [ 'id' ] ) ;
}
} ) ;
}
}
} ) ;
await db _api . setVideoProperty ( file _obj [ 'uid' ] , { 'fresh_upload' : false } , user _uid , sub [ 'id' ] ) ;
}
// helper functions
function getAppendedBasePath ( sub , base _path ) {