import { VideoTorrentObject } from '../../../shared/models/activitypub/objects'
import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos'
import { VideoFilter } from '../../../shared/models/videos/video-query.type'
-import { createTorrentPromise, peertubeTruncate } from '../../helpers/core-utils'
+import { peertubeTruncate } from '../../helpers/core-utils'
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
import { isArray, isBooleanValid } from '../../helpers/custom-validators/misc'
import {
CONSTRAINTS_FIELDS,
HLS_REDUNDANCY_DIRECTORY,
HLS_STREAMING_PLAYLIST_DIRECTORY,
+ LAZY_STATIC_PATHS,
REMOTE_SCHEME,
STATIC_DOWNLOAD_PATHS,
STATIC_PATHS,
buildBlockedAccountSQL,
buildTrigramSearchIndex,
buildWhereIdOrUUID,
+ createSafeIn,
createSimilarityAttribute,
getVideoSort,
isOutdated,
} from '../utils'
import { TagModel } from './tag'
import { VideoAbuseModel } from './video-abuse'
-import { ScopeNames as VideoChannelScopeNames, VideoChannelModel } from './video-channel'
+import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel'
import { VideoCommentModel } from './video-comment'
import { VideoFileModel } from './video-file'
import { VideoShareModel } from './video-share'
videoModelToFormattedJSON
} from './video-format-utils'
import { UserVideoHistoryModel } from '../account/user-video-history'
-import { UserModel } from '../account/user'
import { VideoImportModel } from './video-import'
import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
import { VideoPlaylistElementModel } from './video-playlist-element'
import { CONFIG } from '../../initializers/config'
import { ThumbnailModel } from './thumbnail'
import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type'
+import { createTorrentPromise } from '../../helpers/webtorrent'
+import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type'
+import {
+ MChannel,
+ MChannelAccountDefault,
+ MChannelId,
+ MUserAccountId,
+ MUserId,
+ MVideoAccountLight,
+ MVideoAccountLightBlacklistAllFiles,
+ MVideoDetails,
+ MVideoForUser,
+ MVideoFullLight,
+ MVideoIdThumbnail,
+ MVideoThumbnail,
+ MVideoWithAllFiles, MVideoWithFile,
+ MVideoWithRights,
+ MVideoFormattable
+} from '../../typings/models'
+import { MVideoFile, MVideoFileRedundanciesOpt } from '../../typings/models/video/video-file'
+import { MThumbnail } from '../../typings/models/video/thumbnail'
// FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation
const indexes: (ModelIndexesOptions & { where?: WhereOptions })[] = [
WITH_FILES = 'WITH_FILES',
WITH_SCHEDULED_UPDATE = 'WITH_SCHEDULED_UPDATE',
WITH_BLACKLISTED = 'WITH_BLACKLISTED',
+ WITH_BLOCKLIST = 'WITH_BLOCKLIST',
WITH_USER_HISTORY = 'WITH_USER_HISTORY',
WITH_STREAMING_PLAYLISTS = 'WITH_STREAMING_PLAYLISTS',
WITH_USER_ID = 'WITH_USER_ID',
WITH_THUMBNAILS = 'WITH_THUMBNAILS'
}
-type ForAPIOptions = {
- ids: number[]
+export type ForAPIOptions = {
+ ids?: number[]
videoPlaylistId?: number
withFiles?: boolean
+
+ withAccountBlockerIds?: number[]
}
-type AvailableForListIDsOptions = {
+export type AvailableForListIDsOptions = {
serverAccountId: number
followerActorId: number
includeLocalVideos: boolean
+ attributesType?: 'none' | 'id' | 'all'
+
filter?: VideoFilter
categoryOneOf?: number[]
nsfw?: boolean
videoPlaylistId?: number
trendingDays?: number
- user?: UserModel,
- historyOfUser?: UserModel
+ user?: MUserAccountId
+ historyOfUser?: MUserId
+
+ baseWhere?: WhereOptions[]
}
@Scopes(() => ({
[ ScopeNames.FOR_API ]: (options: ForAPIOptions) => {
const query: FindOptions = {
- where: {
- id: {
- [ Op.in ]: options.ids // FIXME: sequelize ANY seems broken
- }
- },
include: [
{
- model: VideoChannelModel.scope({ method: [ VideoChannelScopeNames.SUMMARY, true ] }),
+ model: VideoChannelModel.scope({
+ method: [
+ VideoChannelScopeNames.SUMMARY, {
+ withAccount: true,
+ withAccountBlockerIds: options.withAccountBlockerIds
+ } as SummaryOptions
+ ]
+ }),
required: true
+ },
+ {
+ attributes: [ 'type', 'filename' ],
+ model: ThumbnailModel,
+ required: false
}
]
}
+ if (options.ids) {
+ query.where = {
+ id: {
+ [ Op.in ]: options.ids // FIXME: sequelize ANY seems broken
+ }
+ }
+ }
+
if (options.withFiles === true) {
query.include.push({
model: VideoFileModel.unscoped(),
return query
},
[ ScopeNames.AVAILABLE_FOR_LIST_IDS ]: (options: AvailableForListIDsOptions) => {
+ const whereAnd = options.baseWhere ? options.baseWhere : []
+
const query: FindOptions = {
raw: true,
- attributes: [ 'id' ],
- where: {
- id: {
- [ Op.and ]: [
- {
- [ Op.notIn ]: Sequelize.literal(
- '(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")'
- )
- }
- ]
- },
+ include: []
+ }
+
+ const attributesType = options.attributesType || 'id'
+
+ if (attributesType === 'id') query.attributes = [ 'id' ]
+ else if (attributesType === 'none') query.attributes = [ ]
+
+ whereAnd.push({
+ id: {
+ [ Op.notIn ]: Sequelize.literal(
+ '(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")'
+ )
+ }
+ })
+
+ if (options.serverAccountId) {
+ whereAnd.push({
channelId: {
[ Op.notIn ]: Sequelize.literal(
'(' +
')'
)
}
- },
- include: []
+ })
}
// Only list public/published videos
]
}
- Object.assign(query.where, privacyWhere)
+ whereAnd.push(privacyWhere)
}
if (options.videoPlaylistId) {
// Force actorId to be a number to avoid SQL injections
const actorIdNumber = parseInt(options.followerActorId.toString(), 10)
- query.where[ 'id' ][ Op.and ].push({
- [ Op.in ]: Sequelize.literal(
- '(' +
- 'SELECT "videoShare"."videoId" AS "id" FROM "videoShare" ' +
- 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' +
- 'WHERE "actorFollow"."actorId" = ' + actorIdNumber +
- ' UNION ALL ' +
- 'SELECT "video"."id" AS "id" FROM "video" ' +
- 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' +
- 'INNER JOIN "account" ON "account"."id" = "videoChannel"."accountId" ' +
- 'INNER JOIN "actor" ON "account"."actorId" = "actor"."id" ' +
- 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "actor"."id" ' +
- 'WHERE "actorFollow"."actorId" = ' + actorIdNumber +
- localVideosReq +
- ')'
- )
+ whereAnd.push({
+ id: {
+ [ Op.in ]: Sequelize.literal(
+ '(' +
+ 'SELECT "videoShare"."videoId" AS "id" FROM "videoShare" ' +
+ 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' +
+ 'WHERE "actorFollow"."actorId" = ' + actorIdNumber +
+ ' UNION ALL ' +
+ 'SELECT "video"."id" AS "id" FROM "video" ' +
+ 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' +
+ 'INNER JOIN "account" ON "account"."id" = "videoChannel"."accountId" ' +
+ 'INNER JOIN "actor" ON "account"."actorId" = "actor"."id" ' +
+ 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "actor"."id" ' +
+ 'WHERE "actorFollow"."actorId" = ' + actorIdNumber +
+ localVideosReq +
+ ')'
+ )
+ }
})
}
if (options.withFiles === true) {
- query.where[ 'id' ][ Op.and ].push({
- [ Op.in ]: Sequelize.literal(
- '(SELECT "videoId" FROM "videoFile")'
- )
+ whereAnd.push({
+ id: {
+ [ Op.in ]: Sequelize.literal(
+ '(SELECT "videoId" FROM "videoFile")'
+ )
+ }
})
}
// FIXME: issues with sequelize count when making a join on n:m relation, so we just make a IN()
if (options.tagsAllOf || options.tagsOneOf) {
- const createTagsIn = (tags: string[]) => {
- return tags.map(t => VideoModel.sequelize.escape(t))
- .join(', ')
- }
-
if (options.tagsOneOf) {
- query.where[ 'id' ][ Op.and ].push({
- [ Op.in ]: Sequelize.literal(
- '(' +
- 'SELECT "videoId" FROM "videoTag" ' +
- 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' +
- 'WHERE "tag"."name" IN (' + createTagsIn(options.tagsOneOf) + ')' +
- ')'
- )
+ whereAnd.push({
+ id: {
+ [ Op.in ]: Sequelize.literal(
+ '(' +
+ 'SELECT "videoId" FROM "videoTag" ' +
+ 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' +
+ 'WHERE "tag"."name" IN (' + createSafeIn(VideoModel, options.tagsOneOf) + ')' +
+ ')'
+ )
+ }
})
}
if (options.tagsAllOf) {
- query.where[ 'id' ][ Op.and ].push({
- [ Op.in ]: Sequelize.literal(
- '(' +
- 'SELECT "videoId" FROM "videoTag" ' +
- 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' +
- 'WHERE "tag"."name" IN (' + createTagsIn(options.tagsAllOf) + ')' +
- 'GROUP BY "videoTag"."videoId" HAVING COUNT(*) = ' + options.tagsAllOf.length +
- ')'
- )
+ whereAnd.push({
+ id: {
+ [ Op.in ]: Sequelize.literal(
+ '(' +
+ 'SELECT "videoId" FROM "videoTag" ' +
+ 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' +
+ 'WHERE "tag"."name" IN (' + createSafeIn(VideoModel, options.tagsAllOf) + ')' +
+ 'GROUP BY "videoTag"."videoId" HAVING COUNT(*) = ' + options.tagsAllOf.length +
+ ')'
+ )
+ }
})
}
}
if (options.nsfw === true || options.nsfw === false) {
- query.where[ 'nsfw' ] = options.nsfw
+ whereAnd.push({ nsfw: options.nsfw })
}
if (options.categoryOneOf) {
- query.where[ 'category' ] = {
- [ Op.or ]: options.categoryOneOf
- }
+ whereAnd.push({
+ category: {
+ [ Op.or ]: options.categoryOneOf
+ }
+ })
}
if (options.licenceOneOf) {
- query.where[ 'licence' ] = {
- [ Op.or ]: options.licenceOneOf
- }
+ whereAnd.push({
+ licence: {
+ [ Op.or ]: options.licenceOneOf
+ }
+ })
}
if (options.languageOneOf) {
- query.where[ 'language' ] = {
- [ Op.or ]: options.languageOneOf
+ let videoLanguages = options.languageOneOf
+ if (options.languageOneOf.find(l => l === '_unknown')) {
+ videoLanguages = videoLanguages.concat([ null ])
}
+
+ whereAnd.push({
+ [Op.or]: [
+ {
+ language: {
+ [ Op.or ]: videoLanguages
+ }
+ },
+ {
+ id: {
+ [ Op.in ]: Sequelize.literal(
+ '(' +
+ 'SELECT "videoId" FROM "videoCaption" ' +
+ 'WHERE "language" IN (' + createSafeIn(VideoModel, options.languageOneOf) + ') ' +
+ ')'
+ )
+ }
+ }
+ ]
+ })
}
if (options.trendingDays) {
query.subQuery = false
}
+ query.where = {
+ [ Op.and ]: whereAnd
+ }
+
return query
+ },
+ [ScopeNames.WITH_BLOCKLIST]: {
+
},
[ ScopeNames.WITH_THUMBNAILS ]: {
include: [
[ ScopeNames.WITH_BLACKLISTED ]: {
include: [
{
- attributes: [ 'id', 'reason' ],
+ attributes: [ 'id', 'reason', 'unfederated' ],
model: VideoBlacklistModel,
required: false
}
@HasMany(() => VideoPlaylistElementModel, {
foreignKey: {
name: 'videoId',
- allowNull: false
+ allowNull: true
},
- onDelete: 'cascade'
+ onDelete: 'set null'
})
VideoPlaylistElements: VideoPlaylistElementModel[]
VideoCaptions: VideoCaptionModel[]
@BeforeDestroy
- static async sendDelete (instance: VideoModel, options) {
+ static async sendDelete (instance: MVideoAccountLight, options) {
if (instance.isOwned()) {
if (!instance.VideoChannel) {
instance.VideoChannel = await instance.$get('VideoChannel', {
include: [
- {
- model: AccountModel,
- include: [ ActorModel ]
- }
+ ActorModel,
+ AccountModel
],
transaction: options.transaction
- }) as VideoChannelModel
+ }) as MChannelAccountDefault
}
return sendDeleteVideo(instance, options.transaction)
return undefined
}
- static listLocal () {
+ static listLocal (): Bluebird<MVideoWithAllFiles[]> {
const query = {
where: {
remote: false
})
}
- static listUserVideosForApi (accountId: number, start: number, count: number, sort: string, withFiles = false) {
+ static listUserVideosForApi (accountId: number, start: number, count: number, sort: string) {
function buildBaseQuery (): FindOptions {
return {
offset: start,
const countQuery = buildBaseQuery()
const findQuery = buildBaseQuery()
- findQuery.include.push({
- model: ScheduleVideoUpdateModel,
- required: false
- })
-
- findQuery.include.push({
- model: VideoBlacklistModel,
- required: false
- })
-
- if (withFiles === true) {
- findQuery.include.push({
- model: VideoFileModel.unscoped(),
- required: true
- })
- }
+ const findScopes = [
+ ScopeNames.WITH_SCHEDULED_UPDATE,
+ ScopeNames.WITH_BLACKLISTED,
+ ScopeNames.WITH_THUMBNAILS
+ ]
return Promise.all([
VideoModel.count(countQuery),
- VideoModel.findAll(findQuery)
+ VideoModel.scope(findScopes).findAll<MVideoForUser>(findQuery)
]).then(([ count, rows ]) => {
return {
data: rows,
followerActorId?: number
videoPlaylistId?: number,
trendingDays?: number,
- user?: UserModel,
- historyOfUser?: UserModel
+ user?: MUserAccountId,
+ historyOfUser?: MUserId
}, 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 query: FindOptions = {
+ const query: FindOptions & { where?: null } = {
offset: options.start,
limit: options.count,
order: getVideoSort(options.sort)
tagsAllOf?: string[]
durationMin?: number // seconds
durationMax?: number // seconds
- user?: UserModel,
+ user?: MUserAccountId,
filter?: VideoFilter
}) {
const whereAnd = []
)
}
- const query: FindOptions = {
+ const query = {
attributes: {
include: attributesInclude
},
offset: options.start,
limit: options.count,
- order: getVideoSort(options.sort),
- where: {
- [ Op.and ]: whereAnd
- }
+ order: getVideoSort(options.sort)
}
const serverActor = await getServerActor()
tagsOneOf: options.tagsOneOf,
tagsAllOf: options.tagsAllOf,
user: options.user,
- filter: options.filter
+ filter: options.filter,
+ baseWhere: whereAnd
}
return VideoModel.getAvailableForApi(query, queryOptions)
}
- static load (id: number | string, t?: Transaction) {
+ static load (id: number | string, t?: Transaction): Bluebird<MVideoThumbnail> {
const where = buildWhereIdOrUUID(id)
const options = {
where,
return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options)
}
- static loadWithRights (id: number | string, t?: Transaction) {
+ static loadWithRights (id: number | string, t?: Transaction): Bluebird<MVideoWithRights> {
const where = buildWhereIdOrUUID(id)
const options = {
where,
]).findOne(options)
}
- static loadOnlyId (id: number | string, t?: Transaction) {
+ static loadOnlyId (id: number | string, t?: Transaction): Bluebird<MVideoIdThumbnail> {
const where = buildWhereIdOrUUID(id)
const options = {
return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options)
}
- static loadWithFiles (id: number, t?: Transaction, logging?: boolean) {
+ static loadWithFiles (id: number | string, t?: Transaction, logging?: boolean): Bluebird<MVideoWithAllFiles> {
+ const where = buildWhereIdOrUUID(id)
+
+ const query = {
+ where,
+ transaction: t,
+ logging
+ }
+
return VideoModel.scope([
ScopeNames.WITH_FILES,
ScopeNames.WITH_STREAMING_PLAYLISTS,
ScopeNames.WITH_THUMBNAILS
- ]).findByPk(id, { transaction: t, logging })
+ ]).findOne(query)
}
- static loadByUUIDWithFile (uuid: string) {
+ static loadByUUID (uuid: string): Bluebird<MVideoThumbnail> {
const options = {
where: {
uuid
return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options)
}
- static loadByUrl (url: string, transaction?: Transaction) {
+ static loadByUrl (url: string, transaction?: Transaction): Bluebird<MVideoThumbnail> {
const query: FindOptions = {
where: {
url
return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(query)
}
- static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction) {
+ static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction): Bluebird<MVideoAccountLightBlacklistAllFiles> {
const query: FindOptions = {
where: {
url
ScopeNames.WITH_ACCOUNT_DETAILS,
ScopeNames.WITH_FILES,
ScopeNames.WITH_STREAMING_PLAYLISTS,
- ScopeNames.WITH_THUMBNAILS
+ ScopeNames.WITH_THUMBNAILS,
+ ScopeNames.WITH_BLACKLISTED
]).findOne(query)
}
- static loadAndPopulateAccountAndServerAndTags (id: number | string, t?: Transaction, userId?: number) {
+ static loadAndPopulateAccountAndServerAndTags (id: number | string, t?: Transaction, userId?: number): Bluebird<MVideoFullLight> {
const where = buildWhereIdOrUUID(id)
const options = {
.findOne(options)
}
- static loadForGetAPI (id: number | string, t?: Transaction, userId?: number) {
+ static loadForGetAPI (parameters: {
+ id: number | string,
+ t?: Transaction,
+ userId?: number
+ }): Bluebird<MVideoDetails> {
+ const { id, t, userId } = parameters
const where = buildWhereIdOrUUID(id)
const options = {
.then(results => results.length === 1)
}
+ static bulkUpdateSupportField (videoChannel: MChannel, t: Transaction) {
+ const options = {
+ where: {
+ channelId: videoChannel.id
+ },
+ transaction: t
+ }
+
+ return VideoModel.update({ support: videoChannel.support }, options)
+ }
+
+ static getAllIdsFromChannel (videoChannel: MChannelId): Bluebird<number[]> {
+ const query = {
+ attributes: [ 'id' ],
+ where: {
+ channelId: videoChannel.id
+ }
+ }
+
+ return VideoModel.findAll(query)
+ .then(videos => videos.map(v => v.id))
+ }
+
// 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 scopeOptions: AvailableForListIDsOptions = {
serverAccountId: serverActor.Account.id,
followerActorId,
- includeLocalVideos: true
+ includeLocalVideos: true,
+ attributesType: 'none' // Don't break aggregation
}
const query: FindOptions = {
}
private static async getAvailableForApi (
- query: FindOptions,
+ query: FindOptions & { where?: null }, // Forbid where field in query
options: AvailableForListIDsOptions,
countVideos = true
) {
]
}
- const [ count, rowsId ] = await Promise.all([
- countVideos ? VideoModel.scope(countScope).count(countQuery) : Promise.resolve<number>(undefined),
- VideoModel.scope(idsScope).findAll(query)
+ const [ count, ids ] = await Promise.all([
+ countVideos
+ ? VideoModel.scope(countScope).count(countQuery)
+ : Promise.resolve<number>(undefined),
+
+ VideoModel.scope(idsScope)
+ .findAll(query)
+ .then(rows => rows.map(r => r.id))
])
- const ids = rowsId.map(r => r.id)
if (ids.length === 0) return { data: [], total: count }
]
}
- const apiScope: (string | ScopeOptions)[] = [ ScopeNames.WITH_THUMBNAILS ]
+ const apiScope: (string | ScopeOptions)[] = []
if (options.user) {
apiScope.push({ method: [ ScopeNames.WITH_USER_HISTORY, options.user.id ] })
return VIDEO_STATES[ id ] || 'Unknown'
}
- getOriginalFile () {
+ isBlacklisted () {
+ return !!this.VideoBlacklist
+ }
+
+ isBlocked () {
+ return (this.VideoChannel.Account.Actor.Server && this.VideoChannel.Account.Actor.Server.isBlocked()) ||
+ this.VideoChannel.Account.isBlocked()
+ }
+
+ getOriginalFile <T extends MVideoWithFile> (this: T) {
if (Array.isArray(this.VideoFiles) === false) return undefined
// The original file is the file that have the higher resolution
return maxBy(this.VideoFiles, file => file.resolution)
}
- async addAndSaveThumbnail (thumbnail: ThumbnailModel, transaction: Transaction) {
+ getFile <T extends MVideoWithFile> (this: T, resolution: number) {
+ if (Array.isArray(this.VideoFiles) === false) return undefined
+
+ return this.VideoFiles.find(f => f.resolution === resolution)
+ }
+
+ async addAndSaveThumbnail (thumbnail: MThumbnail, transaction: Transaction) {
thumbnail.videoId = this.id
const savedThumbnail = await thumbnail.save({ transaction })
this.Thumbnails.push(savedThumbnail)
}
- getVideoFilename (videoFile: VideoFileModel) {
+ getVideoFilename (videoFile: MVideoFile) {
return this.uuid + '-' + videoFile.resolution + videoFile.extname
}
return this.Thumbnails.find(t => t.type === ThumbnailType.PREVIEW)
}
- getTorrentFileName (videoFile: VideoFileModel) {
+ getTorrentFileName (videoFile: MVideoFile) {
const extension = '.torrent'
return this.uuid + '-' + videoFile.resolution + extension
}
return this.remote === false
}
- getTorrentFilePath (videoFile: VideoFileModel) {
+ getTorrentFilePath (videoFile: MVideoFile) {
return join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile))
}
- getVideoFilePath (videoFile: VideoFileModel) {
+ getVideoFilePath (videoFile: MVideoFile) {
return join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile))
}
- async createTorrentAndSetInfoHash (videoFile: VideoFileModel) {
+ async createTorrentAndSetInfoHash (videoFile: MVideoFile) {
const options = {
// Keep the extname, it's used by the client to stream the file inside a web browser
name: `${this.name} ${videoFile.resolution}p${videoFile.extname}`,
if (!preview) return null
// We use a local cache, so specify our cache endpoint instead of potential remote URL
- return join(STATIC_PATHS.PREVIEWS, preview.filename)
+ return join(LAZY_STATIC_PATHS.PREVIEWS, preview.filename)
}
- toFormattedJSON (options?: VideoFormattingJSONOptions): Video {
+ toFormattedJSON <T extends MVideoFormattable> (this: T, options?: VideoFormattingJSONOptions): Video {
return videoModelToFormattedJSON(this, options)
}
return `/api/${API_VERSION}/videos/${this.uuid}/description`
}
- removeFile (videoFile: VideoFileModel, isRedundancy = false) {
+ getHLSPlaylist () {
+ if (!this.VideoStreamingPlaylists) return undefined
+
+ return this.VideoStreamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS)
+ }
+
+ removeFile (videoFile: MVideoFile, isRedundancy = false) {
const baseDir = isRedundancy ? CONFIG.STORAGE.REDUNDANCY_DIR : CONFIG.STORAGE.VIDEOS_DIR
const filePath = join(baseDir, this.getVideoFilename(videoFile))
.catch(err => logger.warn('Cannot delete file %s.', filePath, { err }))
}
- removeTorrent (videoFile: VideoFileModel) {
+ removeTorrent (videoFile: MVideoFile) {
const torrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile))
return remove(torrentPath)
.catch(err => logger.warn('Cannot delete torrent %s.', torrentPath, { err }))
return { baseUrlHttp, baseUrlWs }
}
- generateMagnetUri (videoFile: VideoFileModel, baseUrlHttp: string, baseUrlWs: string) {
+ generateMagnetUri (videoFile: MVideoFileRedundanciesOpt, baseUrlHttp: string, baseUrlWs: string) {
const xs = this.getTorrentUrl(videoFile, baseUrlHttp)
const announce = this.getTrackerUrls(baseUrlHttp, baseUrlWs)
let urlList = [ this.getVideoFileUrl(videoFile, baseUrlHttp) ]
return [ baseUrlWs + '/tracker/socket', baseUrlHttp + '/tracker/announce' ]
}
- getTorrentUrl (videoFile: VideoFileModel, baseUrlHttp: string) {
+ getTorrentUrl (videoFile: MVideoFile, baseUrlHttp: string) {
return baseUrlHttp + STATIC_PATHS.TORRENTS + this.getTorrentFileName(videoFile)
}
- getTorrentDownloadUrl (videoFile: VideoFileModel, baseUrlHttp: string) {
+ getTorrentDownloadUrl (videoFile: MVideoFile, baseUrlHttp: string) {
return baseUrlHttp + STATIC_DOWNLOAD_PATHS.TORRENTS + this.getTorrentFileName(videoFile)
}
- getVideoFileUrl (videoFile: VideoFileModel, baseUrlHttp: string) {
+ getVideoFileUrl (videoFile: MVideoFile, baseUrlHttp: string) {
return baseUrlHttp + STATIC_PATHS.WEBSEED + this.getVideoFilename(videoFile)
}
- getVideoRedundancyUrl (videoFile: VideoFileModel, baseUrlHttp: string) {
+ getVideoRedundancyUrl (videoFile: MVideoFile, baseUrlHttp: string) {
return baseUrlHttp + STATIC_PATHS.REDUNDANCY + this.getVideoFilename(videoFile)
}
- getVideoFileDownloadUrl (videoFile: VideoFileModel, baseUrlHttp: string) {
+ getVideoFileDownloadUrl (videoFile: MVideoFile, baseUrlHttp: string) {
return baseUrlHttp + STATIC_DOWNLOAD_PATHS.VIDEOS + this.getVideoFilename(videoFile)
}
- getBandwidthBits (videoFile: VideoFileModel) {
+ getBandwidthBits (videoFile: MVideoFile) {
return Math.ceil((videoFile.size * 8) / this.duration)
}
}