import * as validator from 'validator'
import { UserVideoHistoryModel } from '../account/user-video-history'
import { UserModel } from '../account/user'
+import { VideoImportModel } from './video-import'
// FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation
const indexes: Sequelize.DefineIndexesOptions[] = [
{ fields: [ 'createdAt' ] },
{ fields: [ 'publishedAt' ] },
{ fields: [ 'duration' ] },
- { fields: [ 'category' ] },
- { fields: [ 'licence' ] },
- { fields: [ 'nsfw' ] },
- { fields: [ 'language' ] },
- { fields: [ 'waitTranscoding' ] },
- { fields: [ 'state' ] },
- { fields: [ 'remote' ] },
{ fields: [ 'views' ] },
- { fields: [ 'likes' ] },
{ fields: [ 'channelId' ] },
+ {
+ fields: [ 'category' ], // We don't care videos with an unknown category
+ where: {
+ category: {
+ [Sequelize.Op.ne]: null
+ }
+ }
+ },
+ {
+ fields: [ 'licence' ], // We don't care videos with an unknown licence
+ where: {
+ licence: {
+ [Sequelize.Op.ne]: null
+ }
+ }
+ },
+ {
+ fields: [ 'language' ], // We don't care videos with an unknown language
+ where: {
+ language: {
+ [Sequelize.Op.ne]: null
+ }
+ }
+ },
+ {
+ fields: [ 'nsfw' ], // Most of the videos are not NSFW
+ where: {
+ nsfw: true
+ }
+ },
+ {
+ fields: [ 'remote' ], // Only index local videos
+ where: {
+ remote: false
+ }
+ },
{
fields: [ 'uuid' ],
unique: true
type AvailableForListIDsOptions = {
serverAccountId: number
- actorId: number
+ followerActorId: number
includeLocalVideos: boolean
filter?: VideoFilter
categoryOneOf?: number[]
accountId?: number
videoChannelId?: number
trendingDays?: number
- user?: UserModel
+ user?: UserModel,
+ historyOfUser?: UserModel
}
@Scopes({
query.include.push(videoChannelInclude)
}
- if (options.actorId) {
+ if (options.followerActorId) {
let localVideosReq = ''
if (options.includeLocalVideos === true) {
localVideosReq = ' UNION ALL ' +
}
// Force actorId to be a number to avoid SQL injections
- const actorIdNumber = parseInt(options.actorId.toString(), 10)
+ const actorIdNumber = parseInt(options.followerActorId.toString(), 10)
query.where[ 'id' ][ Sequelize.Op.and ].push({
[ Sequelize.Op.in ]: Sequelize.literal(
'(' +
query.subQuery = false
}
+ if (options.historyOfUser) {
+ query.include.push({
+ model: UserVideoHistoryModel,
+ required: true,
+ where: {
+ userId: options.historyOfUser.id
+ }
+ })
+
+ // Even if the relation is n:m, we know that a user only have 0..1 video history
+ // So we won't have multiple rows for the same video
+ // Without this, we would not be able to sort on "updatedAt" column of UserVideoHistoryModel
+ query.subQuery = false
+ }
+
return query
},
[ ScopeNames.WITH_ACCOUNT_DETAILS ]: {
})
VideoBlacklist: VideoBlacklistModel
+ @HasOne(() => VideoImportModel, {
+ foreignKey: {
+ name: 'videoId',
+ allowNull: true
+ },
+ onDelete: 'set null'
+ })
+ VideoImport: VideoImportModel
+
@HasMany(() => VideoCaptionModel, {
foreignKey: {
name: 'videoId',
filter?: VideoFilter,
accountId?: number,
videoChannelId?: number,
- actorId?: number
+ followerActorId?: number
trendingDays?: number,
- user?: UserModel
+ user?: UserModel,
+ historyOfUser?: UserModel
}, countVideos = true) {
if (options.filter && options.filter === 'all-local' && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) {
throw new Error('Try to filter all-local but no user has not the see all videos right')
const serverActor = await getServerActor()
- // actorId === null has a meaning, so just check undefined
- const actorId = options.actorId !== undefined ? options.actorId : serverActor.id
+ // followerActorId === null has a meaning, so just check undefined
+ const followerActorId = options.followerActorId !== undefined ? options.followerActorId : serverActor.id
const queryOptions = {
- actorId,
+ followerActorId,
serverAccountId: serverActor.Account.id,
nsfw: options.nsfw,
categoryOneOf: options.categoryOneOf,
videoChannelId: options.videoChannelId,
includeLocalVideos: options.includeLocalVideos,
user: options.user,
+ historyOfUser: options.historyOfUser,
trendingDays
}
const serverActor = await getServerActor()
const queryOptions = {
- actorId: serverActor.id,
+ followerActorId: serverActor.id,
serverAccountId: serverActor.Account.id,
includeLocalVideos: options.includeLocalVideos,
nsfw: options.nsfw,
})
}
+ static checkVideoHasInstanceFollow (videoId: number, followerActorId: number) {
+ // Instances only share videos
+ const query = 'SELECT 1 FROM "videoShare" ' +
+ 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' +
+ 'WHERE "actorFollow"."actorId" = $followerActorId AND "videoShare"."videoId" = $videoId ' +
+ 'LIMIT 1'
+
+ const options = {
+ type: Sequelize.QueryTypes.SELECT,
+ bind: { followerActorId, videoId },
+ raw: true
+ }
+
+ return VideoModel.sequelize.query(query, options)
+ .then(results => results.length === 1)
+ }
+
// threshold corresponds to how many video the field should have to be returned
static async getRandomFieldSamples (field: 'category' | 'channelId', threshold: number, count: number) {
const serverActor = await getServerActor()
- const actorId = serverActor.id
+ const followerActorId = serverActor.id
const scopeOptions: AvailableForListIDsOptions = {
serverAccountId: serverActor.Account.id,
- actorId,
+ followerActorId,
includeLocalVideos: true
}
}
const [ count, rowsId ] = await Promise.all([
- countVideos ? VideoModel.scope(countScope).count(countQuery) : Promise.resolve(undefined),
+ countVideos ? VideoModel.scope(countScope).count(countQuery) : Promise.resolve<number>(undefined),
VideoModel.scope(idsScope).findAll(query)
])
const ids = rowsId.map(r => r.id)
videoFile.infoHash = parsedTorrent.infoHash
}
+ getWatchStaticPath () {
+ return '/videos/watch/' + this.uuid
+ }
+
getEmbedStaticPath () {
return '/videos/embed/' + this.uuid
}
.catch(err => logger.warn('Cannot delete preview %s.', previewPath, { err }))
}
- removeFile (videoFile: VideoFileModel) {
- const filePath = join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile))
+ removeFile (videoFile: VideoFileModel, isRedundancy = false) {
+ const baseDir = isRedundancy ? CONFIG.STORAGE.REDUNDANCY_DIR : CONFIG.STORAGE.VIDEOS_DIR
+
+ const filePath = join(baseDir, this.getVideoFilename(videoFile))
return remove(filePath)
.catch(err => logger.warn('Cannot delete file %s.', filePath, { err }))
}
(now - updatedAtTime) > ACTIVITY_PUB.VIDEO_REFRESH_INTERVAL
}
+ setAsRefreshed () {
+ this.changed('updatedAt', true)
+
+ return this.save()
+ }
+
getBaseUrls () {
let baseUrlHttp
let baseUrlWs
return baseUrlHttp + STATIC_PATHS.WEBSEED + this.getVideoFilename(videoFile)
}
+ getVideoRedundancyUrl (videoFile: VideoFileModel, baseUrlHttp: string) {
+ return baseUrlHttp + STATIC_PATHS.REDUNDANCY + this.getVideoFilename(videoFile)
+ }
+
getVideoFileDownloadUrl (videoFile: VideoFileModel, baseUrlHttp: string) {
return baseUrlHttp + STATIC_DOWNLOAD_PATHS.VIDEOS + this.getVideoFilename(videoFile)
}