var fs = require ( 'fs-extra' )
var path = require ( 'path' )
var utils = require ( './utils' )
const { uuid } = require ( 'uuidv4' ) ;
const config _api = require ( './config' ) ;
var logger = null ;
var db = null ;
var users _db = null ;
function setDB ( input _db , input _users _db ) { db = input _db ; users _db = input _users _db }
function setLogger ( input _logger ) { logger = input _logger ; }
exports . initialize = ( input _db , input _users _db , input _logger ) => {
setDB ( input _db , input _users _db ) ;
setLogger ( input _logger ) ;
}
exports . registerFileDB = ( file _path , type , multiUserMode = null , sub = null , customPath = null , category = null , cropFileSettings = null ) => {
let db _path = null ;
const file _id = utils . removeFileExtension ( file _path ) ;
const file _object = generateFileObject ( file _id , type , customPath || multiUserMode && multiUserMode . file _path , sub ) ;
if ( ! file _object ) {
logger . error ( ` Could not find associated JSON file for ${ type } file ${ file _id } ` ) ;
return false ;
}
utils . fixVideoMetadataPerms ( file _id , type , multiUserMode && multiUserMode . file _path ) ;
// add thumbnail path
file _object [ 'thumbnailPath' ] = utils . getDownloadedThumbnail ( file _id , type , customPath || multiUserMode && multiUserMode . file _path ) ;
// if category exists, only include essential info
if ( category ) file _object [ 'category' ] = { name : category [ 'name' ] , uid : category [ 'uid' ] } ;
// modify duration
if ( cropFileSettings ) {
file _object [ 'duration' ] = ( cropFileSettings . cropFileEnd || file _object . duration ) - cropFileSettings . cropFileStart ;
}
if ( ! sub ) {
if ( multiUserMode ) {
const user _uid = multiUserMode . user ;
db _path = users _db . get ( 'users' ) . find ( { uid : user _uid } ) . get ( ` files ` ) ;
} else {
db _path = db . get ( ` files ` ) ;
}
} else {
if ( multiUserMode ) {
const user _uid = multiUserMode . user ;
db _path = users _db . get ( 'users' ) . find ( { uid : user _uid } ) . get ( 'subscriptions' ) . find ( { id : sub . id } ) . get ( 'videos' ) ;
} else {
db _path = db . get ( 'subscriptions' ) . find ( { id : sub . id } ) . get ( 'videos' ) ;
}
}
const file _uid = registerFileDBManual ( db _path , file _object ) ;
// remove metadata JSON if needed
if ( ! config _api . getConfigItem ( 'ytdl_include_metadata' ) ) {
utils . deleteJSONFile ( file _id , type , multiUserMode && multiUserMode . file _path )
}
return file _uid ;
}
function registerFileDBManual ( db _path , file _object ) {
// add additional info
file _object [ 'uid' ] = uuid ( ) ;
file _object [ 'registered' ] = Date . now ( ) ;
path _object = path . parse ( file _object [ 'path' ] ) ;
file _object [ 'path' ] = path . format ( path _object ) ;
// remove duplicate(s)
db _path . remove ( { path : file _object [ 'path' ] } ) . write ( ) ;
// add new file to db
db _path . push ( file _object ) . write ( ) ;
return file _object [ 'uid' ] ;
}
function generateFileObject ( id , type , customPath = null , sub = null ) {
if ( ! customPath && sub ) {
customPath = getAppendedBasePathSub ( sub , config _api . getConfigItem ( 'ytdl_subscriptions_base_path' ) ) ;
}
var jsonobj = ( type === 'audio' ) ? utils . getJSONMp3 ( id , customPath , true ) : utils . getJSONMp4 ( id , customPath , true ) ;
if ( ! jsonobj ) {
return null ;
}
const ext = ( type === 'audio' ) ? '.mp3' : '.mp4'
const file _path = utils . getTrueFileName ( jsonobj [ '_filename' ] , type ) ; // path.join(type === 'audio' ? audioFolderPath : videoFolderPath, id + ext);
// console.
var stats = fs . statSync ( path . join ( _ _dirname , file _path ) ) ;
var title = jsonobj . title ;
var url = jsonobj . webpage _url ;
var uploader = jsonobj . uploader ;
var upload _date = jsonobj . upload _date ;
upload _date = upload _date ? ` ${ upload _date . substring ( 0 , 4 ) } - ${ upload _date . substring ( 4 , 6 ) } - ${ upload _date . substring ( 6 , 8 ) } ` : 'N/A' ;
var size = stats . size ;
var thumbnail = jsonobj . thumbnail ;
var duration = jsonobj . duration ;
var isaudio = type === 'audio' ;
var description = jsonobj . description ;
var file _obj = new utils . File ( id , title , thumbnail , isaudio , duration , url , uploader , size , file _path , upload _date , description , jsonobj . view _count , jsonobj . height , jsonobj . abr ) ;
return file _obj ;
}
function getAppendedBasePathSub ( sub , base _path ) {
return path . join ( base _path , ( sub . isPlaylist ? 'playlists/' : 'channels/' ) , sub . name ) ;
}
exports . getFileDirectoriesAndDBs = ( ) => {
let dirs _to _check = [ ] ;
let subscriptions _to _check = [ ] ;
const subscriptions _base _path = config _api . getConfigItem ( 'ytdl_subscriptions_base_path' ) ; // only for single-user mode
const multi _user _mode = config _api . getConfigItem ( 'ytdl_multi_user_mode' ) ;
const usersFileFolder = config _api . getConfigItem ( 'ytdl_users_base_path' ) ;
const subscriptions _enabled = config _api . getConfigItem ( 'ytdl_allow_subscriptions' ) ;
if ( multi _user _mode ) {
let users = users _db . get ( 'users' ) . value ( ) ;
for ( let i = 0 ; i < users . length ; i ++ ) {
const user = users [ i ] ;
if ( subscriptions _enabled ) subscriptions _to _check = subscriptions _to _check . concat ( users [ i ] [ 'subscriptions' ] ) ;
// add user's audio dir to check list
dirs _to _check . push ( {
basePath : path . join ( usersFileFolder , user . uid , 'audio' ) ,
dbPath : users _db . get ( 'users' ) . find ( { uid : user . uid } ) . get ( 'files' ) ,
type : 'audio'
} ) ;
// add user's video dir to check list
dirs _to _check . push ( {
basePath : path . join ( usersFileFolder , user . uid , 'video' ) ,
dbPath : users _db . get ( 'users' ) . find ( { uid : user . uid } ) . get ( 'files' ) ,
type : 'video'
} ) ;
}
} else {
const audioFolderPath = config _api . getConfigItem ( 'ytdl_audio_folder_path' ) ;
const videoFolderPath = config _api . getConfigItem ( 'ytdl_video_folder_path' ) ;
const subscriptions = db . get ( 'subscriptions' ) . value ( ) ;
if ( subscriptions _enabled && subscriptions ) subscriptions _to _check = subscriptions _to _check . concat ( subscriptions ) ;
// add audio dir to check list
dirs _to _check . push ( {
basePath : audioFolderPath ,
dbPath : db . get ( 'files' ) ,
type : 'audio'
} ) ;
// add video dir to check list
dirs _to _check . push ( {
basePath : videoFolderPath ,
dbPath : db . get ( 'files' ) ,
type : 'video'
} ) ;
}
// add subscriptions to check list
for ( let i = 0 ; i < subscriptions _to _check . length ; i ++ ) {
let subscription _to _check = subscriptions _to _check [ i ] ;
if ( ! subscription _to _check . name ) {
// TODO: Remove subscription as it'll never complete
continue ;
}
dirs _to _check . push ( {
basePath : multi _user _mode ? path . join ( usersFileFolder , subscription _to _check . user _uid , 'subscriptions' , subscription _to _check . isPlaylist ? 'playlists/' : 'channels/' , subscription _to _check . name )
: path . join ( subscriptions _base _path , subscription _to _check . isPlaylist ? 'playlists/' : 'channels/' , subscription _to _check . name ) ,
dbPath : multi _user _mode ? users _db . get ( 'users' ) . find ( { uid : subscription _to _check . user _uid } ) . get ( 'subscriptions' ) . find ( { id : subscription _to _check . id } ) . get ( 'videos' )
: db . get ( 'subscriptions' ) . find ( { id : subscription _to _check . id } ) . get ( 'videos' ) ,
type : subscription _to _check . type
} ) ;
}
return dirs _to _check ;
}
exports . importUnregisteredFiles = async ( ) => {
const dirs _to _check = exports . getFileDirectoriesAndDBs ( ) ;
// run through check list and check each file to see if it's missing from the db
for ( const dir _to _check of dirs _to _check ) {
// recursively get all files in dir's path
const files = await utils . getDownloadedFilesByType ( dir _to _check . basePath , dir _to _check . type ) ;
files . forEach ( file => {
// check if file exists in db, if not add it
const file _is _registered = ! ! ( dir _to _check . dbPath . find ( { id : file . id } ) . value ( ) )
if ( ! file _is _registered ) {
// add additional info
registerFileDBManual ( dir _to _check . dbPath , file ) ;
logger . verbose ( ` Added discovered file to the database: ${ file . id } ` ) ;
}
} ) ;
}
}
exports . preimportUnregisteredSubscriptionFile = async ( sub , appendedBasePath ) => {
const preimported _file _paths = [ ] ;
let dbPath = null ;
if ( sub . user _uid )
dbPath = users _db . get ( 'users' ) . find ( { uid : sub . user _uid } ) . get ( 'subscriptions' ) . find ( { id : sub . id } ) . get ( 'videos' ) ;
else
dbPath = db . get ( 'subscriptions' ) . find ( { id : sub . id } ) . get ( 'videos' ) ;
const files = await utils . getDownloadedFilesByType ( appendedBasePath , sub . type ) ;
files . forEach ( file => {
// check if file exists in db, if not add it
const file _is _registered = ! ! ( dbPath . find ( { id : file . id } ) . value ( ) )
if ( ! file _is _registered ) {
// add additional info
registerFileDBManual ( dbPath , file ) ;
preimported _file _paths . push ( file [ 'path' ] ) ;
logger . verbose ( ` Preemptively added subscription file to the database: ${ file . id } ` ) ;
}
} ) ;
return preimported _file _paths ;
}
exports . getPlaylist = async ( playlist _id , user _uid = null , require _sharing = false ) => {
let playlist = null
if ( user _uid ) {
playlist = users _db . get ( 'users' ) . find ( { uid : user _uid } ) . get ( ` playlists ` ) . find ( { id : playlist _id } ) . value ( ) ;
// prevent unauthorized users from accessing the file info
if ( require _sharing && ! playlist [ 'sharingEnabled' ] ) return null ;
} else {
playlist = db . get ( ` playlists ` ) . find ( { id : playlist _id } ) . value ( ) ;
}
// converts playlists to new UID-based schema
if ( playlist && playlist [ 'fileNames' ] && ! playlist [ 'uids' ] ) {
playlist [ 'uids' ] = [ ] ;
logger . verbose ( ` Converting playlist ${ playlist [ 'name' ] } to new UID-based schema. ` ) ;
for ( let i = 0 ; i < playlist [ 'fileNames' ] . length ; i ++ ) {
const fileName = playlist [ 'fileNames' ] [ i ] ;
const uid = exports . getVideoUIDByID ( fileName , user _uid ) ;
if ( uid ) playlist [ 'uids' ] . push ( uid ) ;
else logger . warn ( ` Failed to convert file with name ${ fileName } to its UID while converting playlist ${ playlist [ 'name' ] } to the new UID-based schema. The original file is likely missing/deleted and it will be skipped. ` ) ;
}
exports . updatePlaylist ( playlist , user _uid ) ;
}
return playlist ;
}
exports . updatePlaylist = ( playlist , user _uid = null ) => {
let playlistID = playlist . id ;
let db _loc = null ;
if ( user _uid ) {
db _loc = users _db . get ( 'users' ) . find ( { uid : user _uid } ) . get ( ` playlists ` ) . find ( { id : playlistID } ) ;
} else {
db _loc = db . get ( ` playlists ` ) . find ( { id : playlistID } ) ;
}
db _loc . assign ( playlist ) . write ( ) ;
return true ;
}
// Video ID is basically just the file name without the base path and file extension - this method helps us get away from that
exports . getVideoUIDByID = ( file _id , uuid = null ) => {
const base _db _path = uuid ? users _db . get ( 'users' ) . find ( { uid : uuid } ) : db ;
const file _obj = base _db _path . get ( 'files' ) . find ( { id : file _id } ) . value ( ) ;
return file _obj ? file _obj [ 'uid' ] : null ;
}
exports . getVideo = async ( file _uid , uuid , sub _id ) => {
const base _db _path = uuid ? users _db . get ( 'users' ) . find ( { uid : uuid } ) : db ;
const sub _db _path = sub _id ? base _db _path . get ( 'subscriptions' ) . find ( { id : sub _id } ) . get ( 'videos' ) : base _db _path . get ( 'files' ) ;
return sub _db _path . find ( { uid : file _uid } ) . value ( ) ;
}
exports . setVideoProperty = async ( file _uid , assignment _obj , uuid , sub _id ) => {
const base _db _path = uuid ? users _db . get ( 'users' ) . find ( { uid : uuid } ) : db ;
const sub _db _path = sub _id ? base _db _path . get ( 'subscriptions' ) . find ( { id : sub _id } ) . get ( 'videos' ) : base _db _path . get ( 'files' ) ;
const file _db _path = sub _db _path . find ( { uid : file _uid } ) ;
if ( ! ( file _db _path . value ( ) ) ) {
logger . error ( ` Failed to find file with uid ${ file _uid } ` ) ;
}
sub _db _path . find ( { uid : file _uid } ) . assign ( assignment _obj ) . write ( ) ;
}