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'
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 { 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'
import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
import { VideoTagModel } from './video-tag'
import { VideoViewModel } from './video-view'
-import { UserModel } from '../account/user'
export enum ScopeNames {
AVAILABLE_FOR_LIST_IDS = 'AVAILABLE_FOR_LIST_IDS',
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',
[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
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,
include: subInclude
}
})
Tags: TagModel[]
+ @BelongsToMany(() => TrackerModel, {
+ foreignKey: 'videoId',
+ through: () => VideoTrackerModel,
+ onDelete: 'CASCADE'
+ })
+ Trackers: TrackerModel[]
+
@HasMany(() => ThumbnailModel, {
foreignKey: {
name: 'videoId',
// 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
return undefined
}
- static listLocal (): Promise<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) {
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()
user: options.user,
historyOfUser: options.historyOfUser,
trendingDays,
+ trendingAlgorithm,
search: options.search
}
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 ] }
]
'resolution',
'size',
'extname',
+ 'filename',
+ 'fileUrl',
+ 'torrentFilename',
+ 'torrentUrl',
'infoHash',
'fps',
'videoId',
'createdAt',
'updatedAt'
]
+ const buildOpts = { raw: true }
function buildActor (rowActor: any) {
const avatarModel = rowActor.Avatar.id !== null
- ? new AvatarModel(pick(rowActor.Avatar, avatarKeys))
+ ? new AvatarModel(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)
}
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()
+ getFormattedVideoFilesJSON (includeMagnet = true): VideoFile[] {
let files: VideoFile[] = []
if (Array.isArray(this.VideoFiles)) {
- const result = videoFilesModelToFormattedJSON(this, baseUrlHttp, baseUrlWs, this.VideoFiles)
+ const result = videoFilesModelToFormattedJSON(this, this.VideoFiles, includeMagnet)
files = files.concat(result)
}
for (const p of (this.VideoStreamingPlaylists || [])) {
- p.Video = this
-
- const result = videoFilesModelToFormattedJSON(p, baseUrlHttp, baseUrlWs, p.VideoFiles)
+ const result = videoFilesModelToFormattedJSON(this, p.VideoFiles, includeMagnet)
files = files.concat(result)
}
.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)
}
}