Table,
UpdatedAt
} from 'sequelize-typescript'
-import { buildNSFWFilter } from '@server/helpers/express-utils'
import { getPrivaciesForFederation, isPrivacyForFederation, isStateForFederation } from '@server/helpers/video'
import { LiveManager } from '@server/lib/live/live-manager'
-import { removeHLSObjectStorage, removeWebTorrentObjectStorage } from '@server/lib/object-storage'
-import { getHLSDirectory, getHLSRedundancyDirectory } from '@server/lib/paths'
+import { removeHLSFileObjectStorage, removeHLSObjectStorage, removeWebTorrentObjectStorage } from '@server/lib/object-storage'
+import { tracer } from '@server/lib/opentelemetry/tracing'
+import { getHLSDirectory, getHLSRedundancyDirectory, getHlsResolutionPlaylistFilename } from '@server/lib/paths'
import { VideoPathManager } from '@server/lib/video-path-manager'
import { getServerActor } from '@server/models/application/application'
import { ModelCache } from '@server/models/model-cache'
import { UserModel } from '../user/user'
import { UserVideoHistoryModel } from '../user/user-video-history'
import { buildTrigramSearchIndex, buildWhereIdOrUUID, getVideoSort, isOutdated, throwIfNotValid } from '../utils'
+import { VideoViewModel } from '../view/video-view'
import {
videoFilesModelToFormattedJSON,
VideoFormattingJSONOptions,
import { VideoLiveModel } from './video-live'
import { VideoPlaylistElementModel } from './video-playlist-element'
import { VideoShareModel } from './video-share'
+import { VideoSourceModel } from './video-source'
import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
import { VideoTagModel } from './video-tag'
-import { VideoViewModel } from './video-view'
export enum ScopeNames {
FOR_API = 'FOR_API',
})
VideoPlaylistElements: VideoPlaylistElementModel[]
+ @HasOne(() => VideoSourceModel, {
+ foreignKey: {
+ name: 'videoId',
+ allowNull: true
+ },
+ onDelete: 'CASCADE'
+ })
+ VideoSource: VideoSourceModel
+
@HasMany(() => VideoAbuseModel, {
foreignKey: {
name: 'videoId',
// Remove physical files and torrents
instance.VideoFiles.forEach(file => {
- tasks.push(instance.removeWebTorrentFileAndTorrent(file))
+ tasks.push(instance.removeWebTorrentFile(file))
})
// Remove playlists file
logger.info('Stopping live of video %s after video deletion.', instance.uuid)
- LiveManager.Instance.stopSessionOf(instance.id)
+ LiveManager.Instance.stopSessionOf(instance.id, null)
}
@BeforeDestroy
const total = totalVideos + totalVideoShares
return {
data: rows,
- total: total
+ total
}
})
}
}) {
const { accountId, channelId, start, count, sort, search, isLive } = options
- function buildBaseQuery (): FindOptions {
+ function buildBaseQuery (forCount: boolean): FindOptions {
const where: WhereOptions = {}
if (search) {
order: getVideoSort(sort),
include: [
{
- model: VideoChannelModel,
+ model: forCount
+ ? VideoChannelModel.unscoped()
+ : VideoChannelModel,
required: true,
where: channelWhere,
include: [
{
- model: AccountModel,
+ model: forCount
+ ? AccountModel.unscoped()
+ : AccountModel,
where: {
id: accountId
},
return baseQuery
}
- const countQuery = buildBaseQuery()
- const findQuery = buildBaseQuery()
+ const countQuery = buildBaseQuery(true)
+ const findQuery = buildBaseQuery(false)
const findScopes: (string | ScopeOptions)[] = [
ScopeNames.WITH_SCHEDULED_UPDATE,
return VideoModel.getAvailableForApi(queryOptions)
}
- static countLocalLives () {
- const options = {
+ static countLives (options: {
+ remote: boolean
+ mode: 'published' | 'not-ended'
+ }) {
+ const query = {
where: {
- remote: false,
+ remote: options.remote,
isLive: true,
- state: {
- [Op.ne]: VideoState.LIVE_ENDED
- }
+ state: options.mode === 'not-ended'
+ ? { [Op.ne]: VideoState.LIVE_ENDED }
+ : { [Op.eq]: VideoState.PUBLISHED }
}
}
- return VideoModel.count(options)
+ return VideoModel.count(query)
}
static countVideosUploadedByUserSince (userId: number, since: Date) {
return queryBuilder.queryVideo({ url, transaction, type: 'account-blacklist-files' })
}
- static loadAndPopulateAccountAndServerAndTags (id: number | string, t?: Transaction, userId?: number): Promise<MVideoFullLight> {
+ static loadFull (id: number | string, t?: Transaction, userId?: number): Promise<MVideoFullLight> {
const queryBuilder = new VideoModelGetQueryBuilder(VideoModel.sequelize)
- return queryBuilder.queryVideo({ id, transaction: t, type: 'full-light', userId })
+ return queryBuilder.queryVideo({ id, transaction: t, type: 'full', userId })
}
static loadForGetAPI (parameters: {
}
static async getStats () {
- const totalLocalVideos = await VideoModel.count({
- where: {
- remote: false
- }
- })
+ const serverActor = await getServerActor()
let totalLocalVideoViews = await VideoModel.sum('views', {
where: {
// Sequelize could return null...
if (!totalLocalVideoViews) totalLocalVideoViews = 0
- const serverActor = await getServerActor()
-
- const { total: totalVideos } = await VideoModel.listForApi({
+ const baseOptions = {
start: 0,
count: 0,
sort: '-publishedAt',
- nsfw: buildNSFWFilter(),
+ nsfw: null,
displayOnlyForFollower: {
actorId: serverActor.id,
orLocalVideos: true
}
+ }
+
+ const { total: totalLocalVideos } = await VideoModel.listForApi({
+ ...baseOptions,
+
+ isLocal: true
})
+ const { total: totalVideos } = await VideoModel.listForApi(baseOptions)
+
return {
totalLocalVideos,
totalLocalVideoViews,
})
}
- static updateRatesOf (videoId: number, type: VideoRateType, t: Transaction) {
+ static updateRatesOf (videoId: number, type: VideoRateType, count: number, t: Transaction) {
+ const field = type === 'like'
+ ? 'likes'
+ : 'dislikes'
+
+ const rawQuery = `UPDATE "video" SET "${field}" = :count WHERE "video"."id" = :videoId`
+
+ return AccountVideoRateModel.sequelize.query(rawQuery, {
+ transaction: t,
+ replacements: { videoId, rateType: type, count },
+ type: QueryTypes.UPDATE
+ })
+ }
+
+ static syncLocalRates (videoId: number, type: VideoRateType, t: Transaction) {
const field = type === 'like'
? 'likes'
: 'dislikes'
options: BuildVideosListQueryOptions,
countVideos = true
): Promise<ResultList<VideoModel>> {
+ const span = tracer.startSpan('peertube.VideoModel.getAvailableForApi')
+
function getCount () {
if (countVideos !== true) return Promise.resolve(undefined)
const [ count, rows ] = await Promise.all([ getCount(), getModels() ])
+ span.end()
+
return {
data: rows,
total: count
}
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)
+ const files = this.getAllFiles()
+ const file = fun(files, file => file.resolution)
+ if (!file) return undefined
+ if (file.videoId) {
return Object.assign(file, { Video: this })
}
- // No webtorrent files, try with streaming playlist files
- if (Array.isArray(this.VideoStreamingPlaylists) && this.VideoStreamingPlaylists.length !== 0) {
+ if (file.videoStreamingPlaylistId) {
const streamingPlaylistWithVideo = Object.assign(this.VideoStreamingPlaylists[0], { Video: this })
- const file = fun(streamingPlaylistWithVideo.VideoFiles, file => file.resolution)
return Object.assign(file, { VideoStreamingPlaylist: streamingPlaylistWithVideo })
}
- return undefined
+ throw new Error('File is not associated to a video of a playlist')
}
getMaxQualityFile<T extends MVideoWithFile> (this: T): MVideoFileVideo | MVideoFileStreamingPlaylistVideo {
.concat(toAdd)
}
- removeWebTorrentFileAndTorrent (videoFile: MVideoFile, isRedundancy = false) {
+ removeWebTorrentFile (videoFile: MVideoFile, isRedundancy = false) {
const filePath = isRedundancy
? VideoPathManager.Instance.getFSRedundancyVideoFilePath(this, videoFile)
: VideoPathManager.Instance.getFSVideoFileOutputPath(this, videoFile)
}
}
+ async removeStreamingPlaylistVideoFile (streamingPlaylist: MStreamingPlaylist, videoFile: MVideoFile) {
+ const filePath = VideoPathManager.Instance.getFSHLSOutputPath(this, videoFile.filename)
+ await videoFile.removeTorrent()
+ await remove(filePath)
+
+ const resolutionFilename = getHlsResolutionPlaylistFilename(videoFile.filename)
+ await remove(VideoPathManager.Instance.getFSHLSOutputPath(this, resolutionFilename))
+
+ if (videoFile.storage === VideoStorage.OBJECT_STORAGE) {
+ await removeHLSFileObjectStorage(streamingPlaylist.withVideo(this), videoFile.filename)
+ await removeHLSFileObjectStorage(streamingPlaylist.withVideo(this), resolutionFilename)
+ }
+ }
+
+ async removeStreamingPlaylistFile (streamingPlaylist: MStreamingPlaylist, filename: string) {
+ const filePath = VideoPathManager.Instance.getFSHLSOutputPath(this, filename)
+ await remove(filePath)
+
+ if (streamingPlaylist.storage === VideoStorage.OBJECT_STORAGE) {
+ await removeHLSFileObjectStorage(streamingPlaylist.withVideo(this), filename)
+ }
+ }
+
isOutdated () {
if (this.isOwned()) return false
}
getBandwidthBits (this: MVideo, videoFile: MVideoFile) {
+ if (!this.duration) throw new Error(`Cannot get bandwidth bits because video ${this.url} has duration of 0`)
+
return Math.ceil((videoFile.size * 8) / this.duration)
}