import { remove } from 'fs-extra'
import { maxBy, minBy, pick } from 'lodash'
import { join } from 'path'
-import { FindOptions, IncludeOptions, Op, QueryTypes, ScopeOptions, Sequelize, Transaction, WhereOptions } from 'sequelize'
+import { FindOptions, Includeable, IncludeOptions, Op, QueryTypes, ScopeOptions, Sequelize, Transaction, WhereOptions } from 'sequelize'
import {
AllowNull,
BeforeDestroy,
Table,
UpdatedAt
} from 'sequelize-typescript'
+import { v4 as uuidv4 } from 'uuid'
import { buildNSFWFilter } from '@server/helpers/express-utils'
import { getPrivaciesForFederation, isPrivacyForFederation, isStateForFederation } from '@server/helpers/video'
import { LiveManager } from '@server/lib/live-manager'
-import { getHLSDirectory, getTorrentFileName, getTorrentFilePath, getVideoFilename, getVideoFilePath } from '@server/lib/video-paths'
+import { getHLSDirectory, getVideoFilePath } from '@server/lib/video-paths'
import { getServerActor } from '@server/models/application/application'
import { ModelCache } from '@server/models/model-cache'
import { VideoFile } from '@shared/models/videos/video-file.model'
import { ResultList, UserRight, VideoPrivacy, VideoState } from '../../../shared'
import { VideoObject } from '../../../shared/models/activitypub/objects'
-import { Video, VideoDetails } from '../../../shared/models/videos'
+import { Video, VideoDetails, VideoRateType } from '../../../shared/models/videos'
import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type'
import { VideoFilter } from '../../../shared/models/videos/video-query.type'
import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type'
isVideoStateValid,
isVideoSupportValid
} from '../../helpers/custom-validators/videos'
-import { getVideoFileResolution } from '../../helpers/ffmpeg-utils'
+import { getVideoFileResolution } from '../../helpers/ffprobe-utils'
import { logger } from '../../helpers/logger'
import { CONFIG } from '../../initializers/config'
import {
API_VERSION,
CONSTRAINTS_FIELDS,
LAZY_STATIC_PATHS,
- REMOTE_SCHEME,
- STATIC_DOWNLOAD_PATHS,
STATIC_PATHS,
VIDEO_CATEGORIES,
VIDEO_LANGUAGES,
MStreamingPlaylistFilesVideo,
MUserAccountId,
MUserId,
+ MVideo,
MVideoAccountLight,
MVideoAccountLightBlacklistAllFiles,
MVideoAP,
MVideoWithRights
} from '../../types/models'
import { MThumbnail } from '../../types/models/video/thumbnail'
-import { MVideoFile, MVideoFileRedundanciesOpt, MVideoFileStreamingPlaylistVideo } from '../../types/models/video/video-file'
+import { MVideoFile, MVideoFileStreamingPlaylistVideo } from '../../types/models/video/video-file'
import { VideoAbuseModel } from '../abuse/video-abuse'
import { AccountModel } from '../account/account'
import { AccountVideoRateModel } from '../account/account-video-rate'
+import { ActorImageModel } from '../account/actor-image'
+import { UserModel } from '../account/user'
import { UserVideoHistoryModel } from '../account/user-video-history'
import { ActorModel } from '../activitypub/actor'
-import { AvatarModel } from '../avatar/avatar'
import { VideoRedundancyModel } from '../redundancy/video-redundancy'
import { ServerModel } from '../server/server'
+import { TrackerModel } from '../server/tracker'
+import { VideoTrackerModel } from '../server/video-tracker'
import { buildTrigramSearchIndex, buildWhereIdOrUUID, getVideoSort, isOutdated, throwIfNotValid } from '../utils'
import { ScheduleVideoUpdateModel } from './schedule-video-update'
import { TagModel } from './tag'
FOR_API = 'FOR_API',
WITH_ACCOUNT_DETAILS = 'WITH_ACCOUNT_DETAILS',
WITH_TAGS = 'WITH_TAGS',
+ WITH_TRACKERS = 'WITH_TRACKERS',
WITH_WEBTORRENT_FILES = 'WITH_WEBTORRENT_FILES',
WITH_SCHEDULED_UPDATE = 'WITH_SCHEDULED_UPDATE',
WITH_BLACKLISTED = 'WITH_BLACKLISTED',
videoPlaylistId?: number
- withFiles?: boolean
-
withAccountBlockerIds?: number[]
}
attributes: [ 'id', 'url', 'uuid', 'remote' ]
},
[ScopeNames.FOR_API]: (options: ForAPIOptions) => {
- const query: FindOptions = {
- include: [
- {
- model: VideoChannelModel.scope({
- method: [
- VideoChannelScopeNames.SUMMARY, {
- withAccount: true,
- withAccountBlockerIds: options.withAccountBlockerIds
- } as SummaryOptions
- ]
- }),
- required: true
- },
- {
- attributes: [ 'type', 'filename' ],
- model: ThumbnailModel,
- required: false
- }
- ]
- }
+ const include: Includeable[] = [
+ {
+ model: VideoChannelModel.scope({
+ method: [
+ VideoChannelScopeNames.SUMMARY, {
+ withAccount: true,
+ withAccountBlockerIds: options.withAccountBlockerIds
+ } as SummaryOptions
+ ]
+ }),
+ required: true
+ },
+ {
+ attributes: [ 'type', 'filename' ],
+ model: ThumbnailModel,
+ required: false
+ }
+ ]
+
+ const query: FindOptions = {}
if (options.ids) {
query.where = {
}
}
- if (options.withFiles === true) {
- query.include.push({
- model: VideoFileModel,
- required: true
- })
- }
-
if (options.videoPlaylistId) {
- query.include.push({
+ include.push({
model: VideoPlaylistElementModel.unscoped(),
required: true,
where: {
})
}
+ query.include = include
+
return query
},
[ScopeNames.WITH_THUMBNAILS]: {
required: false
},
{
- model: AvatarModel.unscoped(),
+ model: ActorImageModel.unscoped(),
+ as: 'Avatar',
required: false
}
]
required: false
},
{
- model: AvatarModel.unscoped(),
+ model: ActorImageModel.unscoped(),
+ as: 'Avatar',
required: false
}
]
[ScopeNames.WITH_TAGS]: {
include: [ TagModel ]
},
+ [ScopeNames.WITH_TRACKERS]: {
+ include: [
+ {
+ attributes: [ 'id', 'url' ],
+ model: TrackerModel
+ }
+ ]
+ },
[ScopeNames.WITH_BLACKLISTED]: {
include: [
{
include: [
{
model: VideoFileModel,
- separate: true, // We may have multiple files, having multiple redundancies so let's separate this join
+ separate: true,
required: false,
include: subInclude
}
include: [
{
model: VideoStreamingPlaylistModel.unscoped(),
- separate: true, // We may have multiple streaming playlists, having multiple redundancies so let's separate this join
required: false,
+ separate: true,
include: subInclude
}
]
]
},
{ fields: [ 'duration' ] },
- { fields: [ 'views' ] },
+ {
+ fields: [
+ { name: 'views', order: 'DESC' },
+ { name: 'id', order: 'ASC' }
+ ]
+ },
{ fields: [ 'channelId' ] },
{
fields: [ 'originallyPublishedAt' ],
}
]
})
-export class VideoModel extends Model<VideoModel> {
+export class VideoModel extends Model {
@AllowNull(false)
@Default(DataType.UUIDV4)
})
Tags: TagModel[]
+ @BelongsToMany(() => TrackerModel, {
+ foreignKey: 'videoId',
+ through: () => VideoTrackerModel,
+ onDelete: 'CASCADE'
+ })
+ Trackers: TrackerModel[]
+
@HasMany(() => ThumbnailModel, {
foreignKey: {
name: 'videoId',
@BeforeDestroy
static async sendDelete (instance: MVideoAccountLight, options) {
- if (instance.isOwned()) {
- if (!instance.VideoChannel) {
- instance.VideoChannel = await instance.$get('VideoChannel', {
- include: [
- ActorModel,
- AccountModel
- ],
- transaction: options.transaction
- }) as MChannelAccountDefault
- }
+ if (!instance.isOwned()) return undefined
- return sendDeleteVideo(instance, options.transaction)
+ // Lazy load channels
+ if (!instance.VideoChannel) {
+ instance.VideoChannel = await instance.$get('VideoChannel', {
+ include: [
+ ActorModel,
+ AccountModel
+ ],
+ transaction: options.transaction
+ }) as MChannelAccountDefault
}
- return undefined
+ return sendDeleteVideo(instance, options.transaction)
}
@BeforeDestroy
// Remove physical files and torrents
instance.VideoFiles.forEach(file => {
tasks.push(instance.removeFile(file))
- tasks.push(instance.removeTorrent(file))
+ tasks.push(file.removeTorrent())
})
// Remove playlists file
logger.info('Saving video abuses details of video %s.', instance.url)
+ if (!instance.Trackers) instance.Trackers = await instance.$get('Trackers', { transaction: options.transaction })
const details = instance.toFormattedDetailsJSON()
for (const abuse of instance.VideoAbuses) {
return undefined
}
- static listLocal (): Bluebird<MVideoWithAllFiles[]> {
+ static listLocal (): Promise<MVideo[]> {
const query = {
where: {
remote: false
}
}
- return VideoModel.scope([
- ScopeNames.WITH_WEBTORRENT_FILES,
- ScopeNames.WITH_STREAMING_PLAYLISTS,
- ScopeNames.WITH_THUMBNAILS
- ]).findAll(query)
+ return VideoModel.findAll(query)
}
static listAllAndSharedByActorForOutbox (actorId: number, start: number, count: number) {
})
}
- static listUserVideosForApi (
- accountId: number,
- start: number,
- count: number,
- sort: string,
+ static async listPublishedLiveIds () {
+ const options = {
+ attributes: [ 'id' ],
+ where: {
+ isLive: true,
+ state: VideoState.PUBLISHED
+ }
+ }
+
+ const result = await VideoModel.findAll(options)
+
+ return result.map(v => v.id)
+ }
+
+ static listUserVideosForApi (options: {
+ accountId: number
+ start: number
+ count: number
+ sort: string
search?: string
- ) {
+ }) {
+ const { accountId, start, count, sort, search } = options
+
function buildBaseQuery (): FindOptions {
let baseQuery = {
offset: start,
user?: MUserAccountId
historyOfUser?: MUserId
countVideos?: boolean
+ search?: string
}) {
- if (options.filter && options.filter === 'all-local' && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) {
+ if ((options.filter === 'all-local' || options.filter === 'all') && !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 trendingDays = options.sort.endsWith('trending')
? CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS
: undefined
+ let trendingAlgorithm
+ if (options.sort.endsWith('hot')) trendingAlgorithm = 'hot'
+ if (options.sort.endsWith('best')) trendingAlgorithm = 'best'
const serverActor = await getServerActor()
includeLocalVideos: options.includeLocalVideos,
user: options.user,
historyOfUser: options.historyOfUser,
- trendingDays
+ trendingDays,
+ trendingAlgorithm,
+ search: options.search
}
return VideoModel.getAvailableForApi(queryOptions, options.countVideos)
const options = {
where: {
remote: false,
- isLive: true
+ isLive: true,
+ state: {
+ [Op.ne]: VideoState.LIVE_ENDED
+ }
}
}
return VideoModel.count(options)
}
+ static countVideosUploadedByUserSince (userId: number, since: Date) {
+ const options = {
+ include: [
+ {
+ model: VideoChannelModel.unscoped(),
+ required: true,
+ include: [
+ {
+ model: AccountModel.unscoped(),
+ required: true,
+ include: [
+ {
+ model: UserModel.unscoped(),
+ required: true,
+ where: {
+ id: userId
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ where: {
+ createdAt: {
+ [Op.gte]: since
+ }
+ }
+ }
+
+ return VideoModel.unscoped().count(options)
+ }
+
static countLivesOfAccount (accountId: number) {
const options = {
where: {
remote: false,
- isLive: true
+ isLive: true,
+ state: {
+ [Op.ne]: VideoState.LIVE_ENDED
+ }
},
include: [
{
return VideoModel.count(options)
}
- static load (id: number | string, t?: Transaction): Bluebird<MVideoThumbnail> {
+ static load (id: number | string, t?: Transaction): Promise<MVideoThumbnail> {
const where = buildWhereIdOrUUID(id)
const options = {
where,
return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options)
}
- static loadWithBlacklist (id: number | string, t?: Transaction): Bluebird<MVideoThumbnailBlacklist> {
+ static loadWithBlacklist (id: number | string, t?: Transaction): Promise<MVideoThumbnailBlacklist> {
const where = buildWhereIdOrUUID(id)
const options = {
where,
]).findOne(options)
}
- static loadImmutableAttributes (id: number | string, t?: Transaction): Bluebird<MVideoImmutable> {
+ static loadImmutableAttributes (id: number | string, t?: Transaction): Promise<MVideoImmutable> {
const fun = () => {
const query = {
where: buildWhereIdOrUUID(id),
})
}
- static loadWithRights (id: number | string, t?: Transaction): Bluebird<MVideoWithRights> {
+ static loadWithRights (id: number | string, t?: Transaction): Promise<MVideoWithRights> {
const where = buildWhereIdOrUUID(id)
const options = {
where,
return VideoModel.scope([
ScopeNames.WITH_BLACKLISTED,
- ScopeNames.WITH_USER_ID,
- ScopeNames.WITH_THUMBNAILS
+ ScopeNames.WITH_USER_ID
]).findOne(options)
}
- static loadOnlyId (id: number | string, t?: Transaction): Bluebird<MVideoIdThumbnail> {
+ static loadOnlyId (id: number | string, t?: Transaction): Promise<MVideoIdThumbnail> {
const where = buildWhereIdOrUUID(id)
const options = {
return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options)
}
- static loadWithFiles (id: number | string, t?: Transaction, logging?: boolean): Bluebird<MVideoWithAllFiles> {
+ static loadWithFiles (id: number | string, t?: Transaction, logging?: boolean): Promise<MVideoWithAllFiles> {
const where = buildWhereIdOrUUID(id)
const query = {
]).findOne(query)
}
- static loadByUUID (uuid: string): Bluebird<MVideoThumbnail> {
+ static loadByUUID (uuid: string): Promise<MVideoThumbnail> {
const options = {
where: {
uuid
return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options)
}
- static loadByUrl (url: string, transaction?: Transaction): Bluebird<MVideoThumbnail> {
+ static loadByUrl (url: string, transaction?: Transaction): Promise<MVideoThumbnail> {
const query: FindOptions = {
where: {
url
return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(query)
}
- static loadByUrlImmutableAttributes (url: string, transaction?: Transaction): Bluebird<MVideoImmutable> {
+ static loadByUrlImmutableAttributes (url: string, transaction?: Transaction): Promise<MVideoImmutable> {
const fun = () => {
const query: FindOptions = {
where: {
})
}
- static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction): Bluebird<MVideoAccountLightBlacklistAllFiles> {
+ static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction): Promise<MVideoAccountLightBlacklistAllFiles> {
const query: FindOptions = {
where: {
url
]).findOne(query)
}
- static loadAndPopulateAccountAndServerAndTags (id: number | string, t?: Transaction, userId?: number): Bluebird<MVideoFullLight> {
+ static loadAndPopulateAccountAndServerAndTags (id: number | string, t?: Transaction, userId?: number): Promise<MVideoFullLight> {
const where = buildWhereIdOrUUID(id)
const options = {
id: number | string
t?: Transaction
userId?: number
- }): Bluebird<MVideoDetails> {
+ }): Promise<MVideoDetails> {
const { id, t, userId } = parameters
const where = buildWhereIdOrUUID(id)
ScopeNames.WITH_SCHEDULED_UPDATE,
ScopeNames.WITH_THUMBNAILS,
ScopeNames.WITH_LIVE,
+ ScopeNames.WITH_TRACKERS,
{ method: [ ScopeNames.WITH_WEBTORRENT_FILES, true ] },
{ method: [ ScopeNames.WITH_STREAMING_PLAYLISTS, true ] }
]
})
}
+ static updateRatesOf (videoId: number, type: VideoRateType, t: Transaction) {
+ const field = type === 'like'
+ ? 'likes'
+ : 'dislikes'
+
+ const rawQuery = `UPDATE "video" SET "${field}" = ` +
+ '(' +
+ 'SELECT COUNT(id) FROM "accountVideoRate" WHERE "accountVideoRate"."videoId" = "video"."id" AND type = :rateType' +
+ ') ' +
+ 'WHERE "video"."id" = :videoId'
+
+ return AccountVideoRateModel.sequelize.query(rawQuery, {
+ transaction: t,
+ replacements: { videoId, rateType: type },
+ type: QueryTypes.UPDATE
+ })
+ }
+
static checkVideoHasInstanceFollow (videoId: number, followerActorId: number) {
// Instances only share videos
const query = 'SELECT 1 FROM "videoShare" ' +
return VideoModel.update({ support: videoChannel.support }, options)
}
- static getAllIdsFromChannel (videoChannel: MChannelId): Bluebird<number[]> {
+ static getAllIdsFromChannel (videoChannel: MChannelId): Promise<number[]> {
const query = {
attributes: [ 'id' ],
where: {
const avatarKeys = [ 'id', 'filename', 'fileUrl', 'onDisk', 'createdAt', 'updatedAt' ]
const actorKeys = [ 'id', 'preferredUsername', 'url', 'serverId', 'avatarId' ]
const serverKeys = [ 'id', 'host' ]
- const videoFileKeys = [ 'id', 'createdAt', 'updatedAt', 'resolution', 'size', 'extname', 'infoHash', 'fps', 'videoId' ]
- const videoStreamingPlaylistKeys = [ 'id' ]
+ const videoFileKeys = [
+ 'id',
+ 'createdAt',
+ 'updatedAt',
+ 'resolution',
+ 'size',
+ 'extname',
+ 'filename',
+ 'fileUrl',
+ 'torrentFilename',
+ 'torrentUrl',
+ 'infoHash',
+ 'fps',
+ 'videoId',
+ 'videoStreamingPlaylistId'
+ ]
+ const videoStreamingPlaylistKeys = [ 'id', 'type', 'playlistUrl' ]
const videoKeys = [
'id',
'uuid',
'likes',
'dislikes',
'remote',
+ 'isLive',
'url',
'commentsEnabled',
'downloadEnabled',
'createdAt',
'updatedAt'
]
+ const buildOpts = { raw: true }
function buildActor (rowActor: any) {
const avatarModel = rowActor.Avatar.id !== null
- ? new AvatarModel(pick(rowActor.Avatar, avatarKeys))
+ ? new ActorImageModel(pick(rowActor.Avatar, avatarKeys), buildOpts)
: null
const serverModel = rowActor.Server.id !== null
- ? new ServerModel(pick(rowActor.Server, serverKeys))
+ ? new ServerModel(pick(rowActor.Server, serverKeys), buildOpts)
: null
- const actorModel = new ActorModel(pick(rowActor, actorKeys))
+ const actorModel = new ActorModel(pick(rowActor, actorKeys), buildOpts)
actorModel.Avatar = avatarModel
actorModel.Server = serverModel
if (!videosMemo[row.id]) {
// Build Channel
const channel = row.VideoChannel
- const channelModel = new VideoChannelModel(pick(channel, [ 'id', 'name', 'description', 'actorId' ]))
+ const channelModel = new VideoChannelModel(pick(channel, [ 'id', 'name', 'description', 'actorId' ]), buildOpts)
channelModel.Actor = buildActor(channel.Actor)
const account = row.VideoChannel.Account
- const accountModel = new AccountModel(pick(account, [ 'id', 'name' ]))
+ const accountModel = new AccountModel(pick(account, [ 'id', 'name' ]), buildOpts)
accountModel.Actor = buildActor(account.Actor)
channelModel.Account = accountModel
- const videoModel = new VideoModel(pick(row, videoKeys))
+ const videoModel = new VideoModel(pick(row, videoKeys), buildOpts)
videoModel.VideoChannel = channelModel
videoModel.UserVideoHistories = []
const videoModel = videosMemo[row.id]
if (row.userVideoHistory?.id && !historyDone.has(row.userVideoHistory.id)) {
- const historyModel = new UserVideoHistoryModel(pick(row.userVideoHistory, [ 'id', 'currentTime' ]))
+ const historyModel = new UserVideoHistoryModel(pick(row.userVideoHistory, [ 'id', 'currentTime' ]), buildOpts)
videoModel.UserVideoHistories.push(historyModel)
historyDone.add(row.userVideoHistory.id)
}
if (row.Thumbnails?.id && !thumbnailsDone.has(row.Thumbnails.id)) {
- const thumbnailModel = new ThumbnailModel(pick(row.Thumbnails, [ 'id', 'type', 'filename' ]))
+ const thumbnailModel = new ThumbnailModel(pick(row.Thumbnails, [ 'id', 'type', 'filename' ]), buildOpts)
videoModel.Thumbnails.push(thumbnailModel)
thumbnailsDone.add(row.Thumbnails.id)
}
if (row.VideoFiles?.id && !videoFilesDone.has(row.VideoFiles.id)) {
- const videoFileModel = new VideoFileModel(pick(row.VideoFiles, videoFileKeys))
+ const videoFileModel = new VideoFileModel(pick(row.VideoFiles, videoFileKeys), buildOpts)
videoModel.VideoFiles.push(videoFileModel)
videoFilesDone.add(row.VideoFiles.id)
}
if (row.VideoStreamingPlaylists?.id && !videoStreamingPlaylistMemo[row.VideoStreamingPlaylists.id]) {
- const streamingPlaylist = new VideoStreamingPlaylistModel(pick(row.VideoStreamingPlaylists, videoStreamingPlaylistKeys))
+ const streamingPlaylist = new VideoStreamingPlaylistModel(pick(row.VideoStreamingPlaylists, videoStreamingPlaylistKeys), buildOpts)
streamingPlaylist.VideoFiles = []
videoModel.VideoStreamingPlaylists.push(streamingPlaylist)
if (row.VideoStreamingPlaylists?.VideoFiles?.id && !videoFilesDone.has(row.VideoStreamingPlaylists.VideoFiles.id)) {
const streamingPlaylist = videoStreamingPlaylistMemo[row.VideoStreamingPlaylists.id]
- const videoFileModel = new VideoFileModel(pick(row.VideoStreamingPlaylists.VideoFiles, videoFileKeys))
+ const videoFileModel = new VideoFileModel(pick(row.VideoStreamingPlaylists.VideoFiles, videoFileKeys), buildOpts)
streamingPlaylist.VideoFiles.push(videoFileModel)
videoFilesDone.add(row.VideoStreamingPlaylists.VideoFiles.id)
}
getQualityFileBy<T extends MVideoWithFile> (this: T, fun: (files: MVideoFile[], it: (file: MVideoFile) => number) => MVideoFile) {
+ // We first transcode to WebTorrent format, so try this array first
if (Array.isArray(this.VideoFiles) && this.VideoFiles.length !== 0) {
const file = fun(this.VideoFiles, file => file.resolution)
return Object.assign(file, { Video: this })
}
+ hasWebTorrentFiles () {
+ return Array.isArray(this.VideoFiles) === true && this.VideoFiles.length !== 0
+ }
+
async addAndSaveThumbnail (thumbnail: MThumbnail, transaction: Transaction) {
thumbnail.videoId = this.id
}
generateThumbnailName () {
- return this.uuid + '.jpg'
+ return uuidv4() + '.jpg'
}
getMiniature () {
}
generatePreviewName () {
- return this.uuid + '.jpg'
+ return uuidv4() + '.jpg'
}
hasPreview () {
return videoModelToFormattedDetailsJSON(this)
}
- getFormattedVideoFilesJSON (): VideoFile[] {
- const { baseUrlHttp, baseUrlWs } = this.getBaseUrls()
- let files: MVideoFileRedundanciesOpt[] = []
+ getFormattedVideoFilesJSON (includeMagnet = true): VideoFile[] {
+ let files: VideoFile[] = []
if (Array.isArray(this.VideoFiles)) {
- files = files.concat(this.VideoFiles)
+ const result = videoFilesModelToFormattedJSON(this, this.VideoFiles, includeMagnet)
+ files = files.concat(result)
}
for (const p of (this.VideoStreamingPlaylists || [])) {
- files = files.concat(p.VideoFiles || [])
+ const result = videoFilesModelToFormattedJSON(this, p.VideoFiles, includeMagnet)
+ files = files.concat(result)
}
- return videoFilesModelToFormattedJSON(this, baseUrlHttp, baseUrlWs, files)
+ return files
}
toActivityPubObject (this: MVideoAP): VideoObject {
.catch(err => logger.warn('Cannot delete file %s.', filePath, { err }))
}
- removeTorrent (videoFile: MVideoFile) {
- const torrentPath = getTorrentFilePath(this, videoFile)
- return remove(torrentPath)
- .catch(err => logger.warn('Cannot delete torrent %s.', torrentPath, { err }))
- }
-
async removeStreamingPlaylistFiles (streamingPlaylist: MStreamingPlaylist, isRedundancy = false) {
const directoryPath = getHLSDirectory(this, isRedundancy)
// Remove physical files and torrents
await Promise.all(
- streamingPlaylistWithFiles.VideoFiles.map(file => streamingPlaylistWithFiles.removeTorrent(file))
+ streamingPlaylistWithFiles.VideoFiles.map(file => file.removeTorrent())
)
}
}
return false
}
- getBaseUrls () {
- if (this.isOwned()) {
- return {
- baseUrlHttp: WEBSERVER.URL,
- baseUrlWs: WEBSERVER.WS + '://' + WEBSERVER.HOSTNAME + ':' + WEBSERVER.PORT
- }
- }
-
- return {
- baseUrlHttp: REMOTE_SCHEME.HTTP + '://' + this.VideoChannel.Account.Actor.Server.host,
- baseUrlWs: REMOTE_SCHEME.WS + '://' + this.VideoChannel.Account.Actor.Server.host
- }
- }
-
- getTrackerUrls (baseUrlHttp: string, baseUrlWs: string) {
- return [ baseUrlWs + '/tracker/socket', baseUrlHttp + '/tracker/announce' ]
- }
-
- getTorrentUrl (videoFile: MVideoFile, baseUrlHttp: string) {
- return baseUrlHttp + STATIC_PATHS.TORRENTS + getTorrentFileName(this, videoFile)
- }
-
- getTorrentDownloadUrl (videoFile: MVideoFile, baseUrlHttp: string) {
- return baseUrlHttp + STATIC_DOWNLOAD_PATHS.TORRENTS + getTorrentFileName(this, videoFile)
- }
-
- getVideoFileUrl (videoFile: MVideoFile, baseUrlHttp: string) {
- return baseUrlHttp + STATIC_PATHS.WEBSEED + getVideoFilename(this, videoFile)
- }
-
- getVideoFileMetadataUrl (videoFile: MVideoFile, baseUrlHttp: string) {
- const path = '/api/v1/videos/'
-
- return this.isOwned()
- ? baseUrlHttp + path + this.uuid + '/metadata/' + videoFile.id
- : videoFile.metadataUrl
- }
-
- getVideoRedundancyUrl (videoFile: MVideoFile, baseUrlHttp: string) {
- return baseUrlHttp + STATIC_PATHS.REDUNDANCY + getVideoFilename(this, videoFile)
+ getBandwidthBits (videoFile: MVideoFile) {
+ return Math.ceil((videoFile.size * 8) / this.duration)
}
- getVideoFileDownloadUrl (videoFile: MVideoFile, baseUrlHttp: string) {
- return baseUrlHttp + STATIC_DOWNLOAD_PATHS.VIDEOS + getVideoFilename(this, videoFile)
- }
+ getTrackerUrls () {
+ if (this.isOwned()) {
+ return [
+ WEBSERVER.URL + '/tracker/announce',
+ WEBSERVER.WS + '://' + WEBSERVER.HOSTNAME + ':' + WEBSERVER.PORT + '/tracker/socket'
+ ]
+ }
- getBandwidthBits (videoFile: MVideoFile) {
- return Math.ceil((videoFile.size * 8) / this.duration)
+ return this.Trackers.map(t => t.url)
}
}