Add video file size info in admin videos list
This commit is contained in:
parent
2760b454a7
commit
3c10840fa9
|
@ -37,6 +37,7 @@
|
||||||
<th style="width: 60px;"></th>
|
<th style="width: 60px;"></th>
|
||||||
<th i18n>Video</th>
|
<th i18n>Video</th>
|
||||||
<th i18n>Info</th>
|
<th i18n>Info</th>
|
||||||
|
<th i18n>Files</th>
|
||||||
<th style="width: 150px;" i18n pSortableColumn="publishedAt">Published <p-sortIcon field="publishedAt"></p-sortIcon></th>
|
<th style="width: 150px;" i18n pSortableColumn="publishedAt">Published <p-sortIcon field="publishedAt"></p-sortIcon></th>
|
||||||
</tr>
|
</tr>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
@ -63,8 +64,8 @@
|
||||||
<my-video-cell [video]="video"></my-video-cell>
|
<my-video-cell [video]="video"></my-video-cell>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="badges">
|
<td>
|
||||||
<span [ngClass]="getPrivacyBadgeClass(video.privacy.id)" class="badge" i18n>{{ video.privacy.label }}</span>
|
<span [ngClass]="getPrivacyBadgeClass(video.privacy.id)" class="badge">{{ video.privacy.label }}</span>
|
||||||
|
|
||||||
<span *ngIf="video.nsfw" class="badge badge-red" i18n>NSFW</span>
|
<span *ngIf="video.nsfw" class="badge badge-red" i18n>NSFW</span>
|
||||||
|
|
||||||
|
@ -76,6 +77,13 @@
|
||||||
<span *ngIf="isVideoBlocked(video)" class="badge badge-red" i18n>Blocked</span>
|
<span *ngIf="isVideoBlocked(video)" class="badge badge-red" i18n>Blocked</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<span *ngIf="isHLS(video)" class="badge badge-blue">HLS</span>
|
||||||
|
<span *ngIf="isWebTorrent(video)" class="badge badge-blue">WebTorrent</span>
|
||||||
|
|
||||||
|
<span *ngIf="!video.remote">{{ getFilesSize(video) | bytes: 1 }}</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
{{ video.publishedAt | date: 'short' }}
|
{{ video.publishedAt | date: 'short' }}
|
||||||
</td>
|
</td>
|
||||||
|
@ -85,8 +93,30 @@
|
||||||
|
|
||||||
<ng-template pTemplate="rowexpansion" let-video>
|
<ng-template pTemplate="rowexpansion" let-video>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="50">
|
<td class="video-info expand-cell" colspan="7">
|
||||||
<my-embed [video]="video"></my-embed>
|
<div>
|
||||||
|
<div *ngIf="isWebTorrent(video)">
|
||||||
|
WebTorrent:
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li *ngFor="let file of video.files">
|
||||||
|
{{ file.resolution.label }}: {{ file.size | bytes: 1 }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="isHLS(video)">
|
||||||
|
HLS:
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li *ngFor="let file of video.streamingPlaylists[0].files">
|
||||||
|
{{ file.resolution.label }}: {{ file.size | bytes: 1 }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<my-embed class="ml-auto" [video]="video"></my-embed>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
my-embed {
|
my-embed {
|
||||||
display: block;
|
display: block;
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
|
width: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.badge {
|
.badge {
|
||||||
|
@ -10,3 +11,7 @@ my-embed {
|
||||||
|
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.video-info > div {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { Component, OnInit } from '@angular/core'
|
||||||
import { ActivatedRoute, Router } from '@angular/router'
|
import { ActivatedRoute, Router } from '@angular/router'
|
||||||
import { AuthService, ConfirmService, Notifier, RestPagination, RestTable } from '@app/core'
|
import { AuthService, ConfirmService, Notifier, RestPagination, RestTable } from '@app/core'
|
||||||
import { DropdownAction, Video, VideoService } from '@app/shared/shared-main'
|
import { DropdownAction, Video, VideoService } from '@app/shared/shared-main'
|
||||||
import { UserRight, VideoPrivacy, VideoState } from '@shared/models'
|
import { UserRight, VideoPrivacy, VideoState, VideoStreamingPlaylistType } from '@shared/models'
|
||||||
import { AdvancedInputFilter } from '@app/shared/shared-forms'
|
import { AdvancedInputFilter } from '@app/shared/shared-forms'
|
||||||
import { VideoActionsDisplayType } from '@app/shared/shared-video-miniature'
|
import { VideoActionsDisplayType } from '@app/shared/shared-video-miniature'
|
||||||
|
|
||||||
|
@ -114,6 +114,24 @@ export class VideoListComponent extends RestTable implements OnInit {
|
||||||
return video.blacklisted
|
return video.blacklisted
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isHLS (video: Video) {
|
||||||
|
return video.streamingPlaylists.some(p => p.type === VideoStreamingPlaylistType.HLS)
|
||||||
|
}
|
||||||
|
|
||||||
|
isWebTorrent (video: Video) {
|
||||||
|
return video.files.length !== 0
|
||||||
|
}
|
||||||
|
|
||||||
|
getFilesSize (video: Video) {
|
||||||
|
let files = video.files
|
||||||
|
|
||||||
|
if (this.isHLS(video)) {
|
||||||
|
files = files.concat(video.streamingPlaylists[0].files)
|
||||||
|
}
|
||||||
|
|
||||||
|
return files.reduce((p, f) => p += f.size, 0)
|
||||||
|
}
|
||||||
|
|
||||||
protected reloadData () {
|
protected reloadData () {
|
||||||
this.selectedVideos = []
|
this.selectedVideos = []
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,6 @@ export class VideoDetails extends Video implements VideoDetailsServerModel {
|
||||||
support: string
|
support: string
|
||||||
channel: VideoChannel
|
channel: VideoChannel
|
||||||
tags: string[]
|
tags: string[]
|
||||||
files: VideoFile[]
|
|
||||||
account: Account
|
account: Account
|
||||||
commentsEnabled: boolean
|
commentsEnabled: boolean
|
||||||
downloadEnabled: boolean
|
downloadEnabled: boolean
|
||||||
|
@ -28,13 +27,13 @@ export class VideoDetails extends Video implements VideoDetailsServerModel {
|
||||||
|
|
||||||
trackerUrls: string[]
|
trackerUrls: string[]
|
||||||
|
|
||||||
|
files: VideoFile[]
|
||||||
streamingPlaylists: VideoStreamingPlaylist[]
|
streamingPlaylists: VideoStreamingPlaylist[]
|
||||||
|
|
||||||
constructor (hash: VideoDetailsServerModel, translations = {}) {
|
constructor (hash: VideoDetailsServerModel, translations = {}) {
|
||||||
super(hash, translations)
|
super(hash, translations)
|
||||||
|
|
||||||
this.descriptionPath = hash.descriptionPath
|
this.descriptionPath = hash.descriptionPath
|
||||||
this.files = hash.files
|
|
||||||
this.channel = new VideoChannel(hash.channel)
|
this.channel = new VideoChannel(hash.channel)
|
||||||
this.account = new Account(hash.account)
|
this.account = new Account(hash.account)
|
||||||
this.tags = hash.tags
|
this.tags = hash.tags
|
||||||
|
@ -43,7 +42,6 @@ export class VideoDetails extends Video implements VideoDetailsServerModel {
|
||||||
this.downloadEnabled = hash.downloadEnabled
|
this.downloadEnabled = hash.downloadEnabled
|
||||||
|
|
||||||
this.trackerUrls = hash.trackerUrls
|
this.trackerUrls = hash.trackerUrls
|
||||||
this.streamingPlaylists = hash.streamingPlaylists
|
|
||||||
|
|
||||||
this.buildLikeAndDislikePercents()
|
this.buildLikeAndDislikePercents()
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,11 @@ import {
|
||||||
UserRight,
|
UserRight,
|
||||||
Video as VideoServerModel,
|
Video as VideoServerModel,
|
||||||
VideoConstant,
|
VideoConstant,
|
||||||
|
VideoFile,
|
||||||
VideoPrivacy,
|
VideoPrivacy,
|
||||||
VideoScheduleUpdate,
|
VideoScheduleUpdate,
|
||||||
VideoState
|
VideoState,
|
||||||
|
VideoStreamingPlaylist
|
||||||
} from '@shared/models'
|
} from '@shared/models'
|
||||||
|
|
||||||
export class Video implements VideoServerModel {
|
export class Video implements VideoServerModel {
|
||||||
|
@ -96,6 +98,9 @@ export class Video implements VideoServerModel {
|
||||||
|
|
||||||
pluginData?: any
|
pluginData?: any
|
||||||
|
|
||||||
|
streamingPlaylists?: VideoStreamingPlaylist[]
|
||||||
|
files?: VideoFile[]
|
||||||
|
|
||||||
static buildWatchUrl (video: Partial<Pick<Video, 'uuid' | 'shortUUID'>>) {
|
static buildWatchUrl (video: Partial<Pick<Video, 'uuid' | 'shortUUID'>>) {
|
||||||
return buildVideoWatchPath({ shortUUID: video.shortUUID || video.uuid })
|
return buildVideoWatchPath({ shortUUID: video.shortUUID || video.uuid })
|
||||||
}
|
}
|
||||||
|
@ -172,6 +177,9 @@ export class Video implements VideoServerModel {
|
||||||
this.blockedOwner = hash.blockedOwner
|
this.blockedOwner = hash.blockedOwner
|
||||||
this.blockedServer = hash.blockedServer
|
this.blockedServer = hash.blockedServer
|
||||||
|
|
||||||
|
this.streamingPlaylists = hash.streamingPlaylists
|
||||||
|
this.files = hash.files
|
||||||
|
|
||||||
this.userHistory = hash.userHistory
|
this.userHistory = hash.userHistory
|
||||||
|
|
||||||
this.originInstanceHost = this.account.host
|
this.originInstanceHost = this.account.host
|
||||||
|
|
|
@ -208,7 +208,11 @@ export class VideoService {
|
||||||
): Observable<ResultList<Video>> {
|
): Observable<ResultList<Video>> {
|
||||||
const { pagination, search } = parameters
|
const { pagination, search } = parameters
|
||||||
|
|
||||||
const include = VideoInclude.BLACKLISTED | VideoInclude.BLOCKED_OWNER | VideoInclude.HIDDEN_PRIVACY | VideoInclude.NOT_PUBLISHED_STATE
|
const include = VideoInclude.BLACKLISTED |
|
||||||
|
VideoInclude.BLOCKED_OWNER |
|
||||||
|
VideoInclude.HIDDEN_PRIVACY |
|
||||||
|
VideoInclude.NOT_PUBLISHED_STATE |
|
||||||
|
VideoInclude.FILES
|
||||||
|
|
||||||
let params = new HttpParams()
|
let params = new HttpParams()
|
||||||
params = this.buildCommonVideosParams({ params, include, ...parameters })
|
params = this.buildCommonVideosParams({ params, include, ...parameters })
|
||||||
|
|
|
@ -189,7 +189,6 @@ async function listAccountVideos (req: express.Request, res: express.Response) {
|
||||||
|
|
||||||
displayOnlyForFollower,
|
displayOnlyForFollower,
|
||||||
nsfw: buildNSFWFilter(res, query.nsfw),
|
nsfw: buildNSFWFilter(res, query.nsfw),
|
||||||
withFiles: false,
|
|
||||||
accountId: account.id,
|
accountId: account.id,
|
||||||
user: res.locals.oauth ? res.locals.oauth.token.User : undefined,
|
user: res.locals.oauth ? res.locals.oauth.token.User : undefined,
|
||||||
countVideos
|
countVideos
|
||||||
|
|
|
@ -122,7 +122,6 @@ async function getVideos (
|
||||||
},
|
},
|
||||||
nsfw: buildNSFWFilter(res),
|
nsfw: buildNSFWFilter(res),
|
||||||
user: res.locals.oauth ? res.locals.oauth.token.User : undefined,
|
user: res.locals.oauth ? res.locals.oauth.token.User : undefined,
|
||||||
withFiles: false,
|
|
||||||
countVideos: false,
|
countVideos: false,
|
||||||
|
|
||||||
...where
|
...where
|
||||||
|
|
|
@ -181,7 +181,6 @@ async function getUserSubscriptionVideos (req: express.Request, res: express.Res
|
||||||
orLocalVideos: false
|
orLocalVideos: false
|
||||||
},
|
},
|
||||||
nsfw: buildNSFWFilter(res, query.nsfw),
|
nsfw: buildNSFWFilter(res, query.nsfw),
|
||||||
withFiles: false,
|
|
||||||
user,
|
user,
|
||||||
countVideos
|
countVideos
|
||||||
})
|
})
|
||||||
|
|
|
@ -347,7 +347,6 @@ async function listVideoChannelVideos (req: express.Request, res: express.Respon
|
||||||
|
|
||||||
displayOnlyForFollower,
|
displayOnlyForFollower,
|
||||||
nsfw: buildNSFWFilter(res, query.nsfw),
|
nsfw: buildNSFWFilter(res, query.nsfw),
|
||||||
withFiles: false,
|
|
||||||
videoChannelId: videoChannelInstance.id,
|
videoChannelId: videoChannelInstance.id,
|
||||||
user: res.locals.oauth ? res.locals.oauth.token.User : undefined,
|
user: res.locals.oauth ? res.locals.oauth.token.User : undefined,
|
||||||
countVideos
|
countVideos
|
||||||
|
|
|
@ -225,7 +225,6 @@ async function listVideos (req: express.Request, res: express.Response) {
|
||||||
orLocalVideos: true
|
orLocalVideos: true
|
||||||
},
|
},
|
||||||
nsfw: buildNSFWFilter(res, query.nsfw),
|
nsfw: buildNSFWFilter(res, query.nsfw),
|
||||||
withFiles: false,
|
|
||||||
user: res.locals.oauth ? res.locals.oauth.token.User : undefined,
|
user: res.locals.oauth ? res.locals.oauth.token.User : undefined,
|
||||||
countVideos
|
countVideos
|
||||||
}, 'filter:api.videos.list.params')
|
}, 'filter:api.videos.list.params')
|
||||||
|
|
|
@ -76,7 +76,6 @@ async function getSitemapLocalVideoUrls () {
|
||||||
},
|
},
|
||||||
isLocal: true,
|
isLocal: true,
|
||||||
nsfw: buildNSFWFilter(),
|
nsfw: buildNSFWFilter(),
|
||||||
withFiles: false,
|
|
||||||
countVideos: false
|
countVideos: false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import express from 'express'
|
||||||
import Feed from 'pfeed'
|
import Feed from 'pfeed'
|
||||||
import { getServerActor } from '@server/models/application/application'
|
import { getServerActor } from '@server/models/application/application'
|
||||||
import { getCategoryLabel } from '@server/models/video/formatter/video-format-utils'
|
import { getCategoryLabel } from '@server/models/video/formatter/video-format-utils'
|
||||||
|
import { VideoInclude } from '@shared/models'
|
||||||
import { buildNSFWFilter } from '../helpers/express-utils'
|
import { buildNSFWFilter } from '../helpers/express-utils'
|
||||||
import { CONFIG } from '../initializers/config'
|
import { CONFIG } from '../initializers/config'
|
||||||
import { FEEDS, PREVIEWS_SIZE, ROUTE_CACHE_LIFETIME, WEBSERVER } from '../initializers/constants'
|
import { FEEDS, PREVIEWS_SIZE, ROUTE_CACHE_LIFETIME, WEBSERVER } from '../initializers/constants'
|
||||||
|
@ -171,8 +172,8 @@ async function generateVideoFeed (req: express.Request, res: express.Response) {
|
||||||
},
|
},
|
||||||
nsfw,
|
nsfw,
|
||||||
isLocal: req.query.isLocal,
|
isLocal: req.query.isLocal,
|
||||||
include: req.query.include,
|
include: req.query.include | VideoInclude.FILES,
|
||||||
withFiles: true,
|
hasFiles: true,
|
||||||
countVideos: false,
|
countVideos: false,
|
||||||
...options
|
...options
|
||||||
})
|
})
|
||||||
|
@ -204,9 +205,10 @@ async function generateVideoFeedForSubscriptions (req: express.Request, res: exp
|
||||||
nsfw,
|
nsfw,
|
||||||
|
|
||||||
isLocal: req.query.isLocal,
|
isLocal: req.query.isLocal,
|
||||||
include: req.query.include,
|
|
||||||
|
|
||||||
withFiles: true,
|
hasFiles: true,
|
||||||
|
include: req.query.include | VideoInclude.FILES,
|
||||||
|
|
||||||
countVideos: false,
|
countVideos: false,
|
||||||
|
|
||||||
displayOnlyForFollower: {
|
displayOnlyForFollower: {
|
||||||
|
|
|
@ -99,7 +99,7 @@ export type SummaryOptions = {
|
||||||
queryInclude.push({
|
queryInclude.push({
|
||||||
attributes: [ 'id' ],
|
attributes: [ 'id' ],
|
||||||
model: AccountBlocklistModel.unscoped(),
|
model: AccountBlocklistModel.unscoped(),
|
||||||
as: 'BlockedAccounts',
|
as: 'BlockedBy',
|
||||||
required: false,
|
required: false,
|
||||||
where: {
|
where: {
|
||||||
accountId: {
|
accountId: {
|
||||||
|
|
|
@ -70,7 +70,6 @@ export class UserVideoHistoryModel extends Model<Partial<AttributesOnly<UserVide
|
||||||
actorId: serverActor.id,
|
actorId: serverActor.id,
|
||||||
orLocalVideos: true
|
orLocalVideos: true
|
||||||
},
|
},
|
||||||
withFiles: false,
|
|
||||||
user,
|
user,
|
||||||
historyOfUser: user
|
historyOfUser: user
|
||||||
})
|
})
|
||||||
|
|
|
@ -42,6 +42,7 @@ export type VideoFormattingJSONOptions = {
|
||||||
waitTranscoding?: boolean
|
waitTranscoding?: boolean
|
||||||
scheduledUpdate?: boolean
|
scheduledUpdate?: boolean
|
||||||
blacklistInfo?: boolean
|
blacklistInfo?: boolean
|
||||||
|
files?: boolean
|
||||||
blockedOwner?: boolean
|
blockedOwner?: boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,6 +56,7 @@ function guessAdditionalAttributesFromQuery (query: VideosCommonQueryAfterSaniti
|
||||||
waitTranscoding: !!(query.include & VideoInclude.NOT_PUBLISHED_STATE),
|
waitTranscoding: !!(query.include & VideoInclude.NOT_PUBLISHED_STATE),
|
||||||
scheduledUpdate: !!(query.include & VideoInclude.NOT_PUBLISHED_STATE),
|
scheduledUpdate: !!(query.include & VideoInclude.NOT_PUBLISHED_STATE),
|
||||||
blacklistInfo: !!(query.include & VideoInclude.BLACKLISTED),
|
blacklistInfo: !!(query.include & VideoInclude.BLACKLISTED),
|
||||||
|
files: !!(query.include & VideoInclude.FILES),
|
||||||
blockedOwner: !!(query.include & VideoInclude.BLOCKED_OWNER)
|
blockedOwner: !!(query.include & VideoInclude.BLOCKED_OWNER)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,22 +152,26 @@ function videoModelToFormattedJSON (video: MVideoFormattable, options: VideoForm
|
||||||
videoObject.blockedServer = !!(server?.isBlocked())
|
videoObject.blockedServer = !!(server?.isBlocked())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (add?.files === true) {
|
||||||
|
videoObject.streamingPlaylists = streamingPlaylistsModelToFormattedJSON(video, video.VideoStreamingPlaylists)
|
||||||
|
videoObject.files = videoFilesModelToFormattedJSON(video, video.VideoFiles)
|
||||||
|
}
|
||||||
|
|
||||||
return videoObject
|
return videoObject
|
||||||
}
|
}
|
||||||
|
|
||||||
function videoModelToFormattedDetailsJSON (video: MVideoFormattableDetails): VideoDetails {
|
function videoModelToFormattedDetailsJSON (video: MVideoFormattableDetails): VideoDetails {
|
||||||
const formattedJson = video.toFormattedJSON({
|
const videoJSON = video.toFormattedJSON({
|
||||||
additionalAttributes: {
|
additionalAttributes: {
|
||||||
scheduledUpdate: true,
|
scheduledUpdate: true,
|
||||||
blacklistInfo: true
|
blacklistInfo: true,
|
||||||
|
files: true
|
||||||
}
|
}
|
||||||
})
|
}) as Video & Required<Pick<Video, 'files' | 'streamingPlaylists'>>
|
||||||
|
|
||||||
const tags = video.Tags ? video.Tags.map(t => t.name) : []
|
const tags = video.Tags ? video.Tags.map(t => t.name) : []
|
||||||
|
|
||||||
const streamingPlaylists = streamingPlaylistsModelToFormattedJSON(video, video.VideoStreamingPlaylists)
|
const detailsJSON = {
|
||||||
|
|
||||||
const detailsJson = {
|
|
||||||
support: video.support,
|
support: video.support,
|
||||||
descriptionPath: video.getDescriptionAPIPath(),
|
descriptionPath: video.getDescriptionAPIPath(),
|
||||||
channel: video.VideoChannel.toFormattedJSON(),
|
channel: video.VideoChannel.toFormattedJSON(),
|
||||||
|
@ -179,20 +185,14 @@ function videoModelToFormattedDetailsJSON (video: MVideoFormattableDetails): Vid
|
||||||
label: getStateLabel(video.state)
|
label: getStateLabel(video.state)
|
||||||
},
|
},
|
||||||
|
|
||||||
trackerUrls: video.getTrackerUrls(),
|
trackerUrls: video.getTrackerUrls()
|
||||||
|
|
||||||
files: [],
|
|
||||||
streamingPlaylists
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format and sort video files
|
return Object.assign(videoJSON, detailsJSON)
|
||||||
detailsJson.files = videoFilesModelToFormattedJSON(video, video.VideoFiles)
|
|
||||||
|
|
||||||
return Object.assign(formattedJson, detailsJson)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function streamingPlaylistsModelToFormattedJSON (
|
function streamingPlaylistsModelToFormattedJSON (
|
||||||
video: MVideoFormattableDetails,
|
video: MVideoFormattable,
|
||||||
playlists: MStreamingPlaylistRedundanciesOpt[]
|
playlists: MStreamingPlaylistRedundanciesOpt[]
|
||||||
): VideoStreamingPlaylist[] {
|
): VideoStreamingPlaylist[] {
|
||||||
if (isArray(playlists) === false) return []
|
if (isArray(playlists) === false) return []
|
||||||
|
@ -223,7 +223,7 @@ function sortByResolutionDesc (fileA: MVideoFile, fileB: MVideoFile) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function videoFilesModelToFormattedJSON (
|
function videoFilesModelToFormattedJSON (
|
||||||
video: MVideoFormattableDetails,
|
video: MVideoFormattable,
|
||||||
videoFiles: MVideoFileRedundanciesOpt[],
|
videoFiles: MVideoFileRedundanciesOpt[],
|
||||||
includeMagnet = true
|
includeMagnet = true
|
||||||
): VideoFile[] {
|
): VideoFile[] {
|
||||||
|
|
|
@ -32,7 +32,7 @@ export type BuildVideoGetQueryOptions = {
|
||||||
logging?: boolean
|
logging?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export class VideosModelGetQueryBuilder {
|
export class VideoModelGetQueryBuilder {
|
||||||
videoQueryBuilder: VideosModelGetQuerySubBuilder
|
videoQueryBuilder: VideosModelGetQuerySubBuilder
|
||||||
webtorrentFilesQueryBuilder: VideoFileQueryBuilder
|
webtorrentFilesQueryBuilder: VideoFileQueryBuilder
|
||||||
streamingPlaylistFilesQueryBuilder: VideoFileQueryBuilder
|
streamingPlaylistFilesQueryBuilder: VideoFileQueryBuilder
|
||||||
|
@ -53,11 +53,11 @@ export class VideosModelGetQueryBuilder {
|
||||||
const [ videoRows, webtorrentFilesRows, streamingPlaylistFilesRows ] = await Promise.all([
|
const [ videoRows, webtorrentFilesRows, streamingPlaylistFilesRows ] = await Promise.all([
|
||||||
this.videoQueryBuilder.queryVideos(options),
|
this.videoQueryBuilder.queryVideos(options),
|
||||||
|
|
||||||
VideosModelGetQueryBuilder.videoFilesInclude.has(options.type)
|
VideoModelGetQueryBuilder.videoFilesInclude.has(options.type)
|
||||||
? this.webtorrentFilesQueryBuilder.queryWebTorrentVideos(options)
|
? this.webtorrentFilesQueryBuilder.queryWebTorrentVideos(options)
|
||||||
: Promise.resolve(undefined),
|
: Promise.resolve(undefined),
|
||||||
|
|
||||||
VideosModelGetQueryBuilder.videoFilesInclude.has(options.type)
|
VideoModelGetQueryBuilder.videoFilesInclude.has(options.type)
|
||||||
? this.streamingPlaylistFilesQueryBuilder.queryStreamingPlaylistVideos(options)
|
? this.streamingPlaylistFilesQueryBuilder.queryStreamingPlaylistVideos(options)
|
||||||
: Promise.resolve(undefined)
|
: Promise.resolve(undefined)
|
||||||
])
|
])
|
||||||
|
|
|
@ -43,7 +43,7 @@ export type BuildVideosListQueryOptions = {
|
||||||
|
|
||||||
uuids?: string[]
|
uuids?: string[]
|
||||||
|
|
||||||
withFiles?: boolean
|
hasFiles?: boolean
|
||||||
|
|
||||||
accountId?: number
|
accountId?: number
|
||||||
videoChannelId?: number
|
videoChannelId?: number
|
||||||
|
@ -165,7 +165,7 @@ export class VideosIdListQueryBuilder extends AbstractVideosQueryBuilder {
|
||||||
this.whereFollowerActorId(options.displayOnlyForFollower)
|
this.whereFollowerActorId(options.displayOnlyForFollower)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.withFiles === true) {
|
if (options.hasFiles === true) {
|
||||||
this.whereFileExists()
|
this.whereFileExists()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ export class VideosModelListQueryBuilder extends AbstractVideosModelQueryBuilder
|
||||||
this.includeAccounts()
|
this.includeAccounts()
|
||||||
this.includeThumbnails()
|
this.includeThumbnails()
|
||||||
|
|
||||||
if (options.withFiles) {
|
if (options.include & VideoInclude.FILES) {
|
||||||
this.includeWebtorrentFiles()
|
this.includeWebtorrentFiles()
|
||||||
this.includeStreamingPlaylistFiles()
|
this.includeStreamingPlaylistFiles()
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,7 +105,7 @@ import {
|
||||||
videoModelToFormattedJSON
|
videoModelToFormattedJSON
|
||||||
} from './formatter/video-format-utils'
|
} from './formatter/video-format-utils'
|
||||||
import { ScheduleVideoUpdateModel } from './schedule-video-update'
|
import { ScheduleVideoUpdateModel } from './schedule-video-update'
|
||||||
import { VideosModelGetQueryBuilder } from './sql/video-model-get-query-builder'
|
import { VideoModelGetQueryBuilder } from './sql/video-model-get-query-builder'
|
||||||
import { BuildVideosListQueryOptions, DisplayOnlyForFollowerOptions, VideosIdListQueryBuilder } from './sql/videos-id-list-query-builder'
|
import { BuildVideosListQueryOptions, DisplayOnlyForFollowerOptions, VideosIdListQueryBuilder } from './sql/videos-id-list-query-builder'
|
||||||
import { VideosModelListQueryBuilder } from './sql/videos-model-list-query-builder'
|
import { VideosModelListQueryBuilder } from './sql/videos-model-list-query-builder'
|
||||||
import { TagModel } from './tag'
|
import { TagModel } from './tag'
|
||||||
|
@ -1029,7 +1029,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
|
||||||
isLocal?: boolean
|
isLocal?: boolean
|
||||||
include?: VideoInclude
|
include?: VideoInclude
|
||||||
|
|
||||||
withFiles: boolean
|
hasFiles?: boolean // default false
|
||||||
|
|
||||||
categoryOneOf?: number[]
|
categoryOneOf?: number[]
|
||||||
licenceOneOf?: number[]
|
licenceOneOf?: number[]
|
||||||
|
@ -1053,7 +1053,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
|
||||||
|
|
||||||
search?: string
|
search?: string
|
||||||
}) {
|
}) {
|
||||||
if (options.include && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) {
|
if (VideoModel.isPrivateInclude(options.include) && !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')
|
throw new Error('Try to filter all-local but no user has not the see all videos right')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1082,7 +1082,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
|
||||||
'isLocal',
|
'isLocal',
|
||||||
'include',
|
'include',
|
||||||
'displayOnlyForFollower',
|
'displayOnlyForFollower',
|
||||||
'withFiles',
|
'hasFiles',
|
||||||
'accountId',
|
'accountId',
|
||||||
'videoChannelId',
|
'videoChannelId',
|
||||||
'videoPlaylistId',
|
'videoPlaylistId',
|
||||||
|
@ -1229,13 +1229,13 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
static load (id: number | string, transaction?: Transaction): Promise<MVideoThumbnail> {
|
static load (id: number | string, transaction?: Transaction): Promise<MVideoThumbnail> {
|
||||||
const queryBuilder = new VideosModelGetQueryBuilder(VideoModel.sequelize)
|
const queryBuilder = new VideoModelGetQueryBuilder(VideoModel.sequelize)
|
||||||
|
|
||||||
return queryBuilder.queryVideo({ id, transaction, type: 'thumbnails' })
|
return queryBuilder.queryVideo({ id, transaction, type: 'thumbnails' })
|
||||||
}
|
}
|
||||||
|
|
||||||
static loadWithBlacklist (id: number | string, transaction?: Transaction): Promise<MVideoThumbnailBlacklist> {
|
static loadWithBlacklist (id: number | string, transaction?: Transaction): Promise<MVideoThumbnailBlacklist> {
|
||||||
const queryBuilder = new VideosModelGetQueryBuilder(VideoModel.sequelize)
|
const queryBuilder = new VideoModelGetQueryBuilder(VideoModel.sequelize)
|
||||||
|
|
||||||
return queryBuilder.queryVideo({ id, transaction, type: 'thumbnails-blacklist' })
|
return queryBuilder.queryVideo({ id, transaction, type: 'thumbnails-blacklist' })
|
||||||
}
|
}
|
||||||
|
@ -1279,31 +1279,31 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
static loadOnlyId (id: number | string, transaction?: Transaction): Promise<MVideoId> {
|
static loadOnlyId (id: number | string, transaction?: Transaction): Promise<MVideoId> {
|
||||||
const queryBuilder = new VideosModelGetQueryBuilder(VideoModel.sequelize)
|
const queryBuilder = new VideoModelGetQueryBuilder(VideoModel.sequelize)
|
||||||
|
|
||||||
return queryBuilder.queryVideo({ id, transaction, type: 'id' })
|
return queryBuilder.queryVideo({ id, transaction, type: 'id' })
|
||||||
}
|
}
|
||||||
|
|
||||||
static loadWithFiles (id: number | string, transaction?: Transaction, logging?: boolean): Promise<MVideoWithAllFiles> {
|
static loadWithFiles (id: number | string, transaction?: Transaction, logging?: boolean): Promise<MVideoWithAllFiles> {
|
||||||
const queryBuilder = new VideosModelGetQueryBuilder(VideoModel.sequelize)
|
const queryBuilder = new VideoModelGetQueryBuilder(VideoModel.sequelize)
|
||||||
|
|
||||||
return queryBuilder.queryVideo({ id, transaction, type: 'all-files', logging })
|
return queryBuilder.queryVideo({ id, transaction, type: 'all-files', logging })
|
||||||
}
|
}
|
||||||
|
|
||||||
static loadByUrl (url: string, transaction?: Transaction): Promise<MVideoThumbnail> {
|
static loadByUrl (url: string, transaction?: Transaction): Promise<MVideoThumbnail> {
|
||||||
const queryBuilder = new VideosModelGetQueryBuilder(VideoModel.sequelize)
|
const queryBuilder = new VideoModelGetQueryBuilder(VideoModel.sequelize)
|
||||||
|
|
||||||
return queryBuilder.queryVideo({ url, transaction, type: 'thumbnails' })
|
return queryBuilder.queryVideo({ url, transaction, type: 'thumbnails' })
|
||||||
}
|
}
|
||||||
|
|
||||||
static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction): Promise<MVideoAccountLightBlacklistAllFiles> {
|
static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction): Promise<MVideoAccountLightBlacklistAllFiles> {
|
||||||
const queryBuilder = new VideosModelGetQueryBuilder(VideoModel.sequelize)
|
const queryBuilder = new VideoModelGetQueryBuilder(VideoModel.sequelize)
|
||||||
|
|
||||||
return queryBuilder.queryVideo({ url, transaction, type: 'account-blacklist-files' })
|
return queryBuilder.queryVideo({ url, transaction, type: 'account-blacklist-files' })
|
||||||
}
|
}
|
||||||
|
|
||||||
static loadAndPopulateAccountAndServerAndTags (id: number | string, t?: Transaction, userId?: number): Promise<MVideoFullLight> {
|
static loadAndPopulateAccountAndServerAndTags (id: number | string, t?: Transaction, userId?: number): Promise<MVideoFullLight> {
|
||||||
const queryBuilder = new VideosModelGetQueryBuilder(VideoModel.sequelize)
|
const queryBuilder = new VideoModelGetQueryBuilder(VideoModel.sequelize)
|
||||||
|
|
||||||
return queryBuilder.queryVideo({ id, transaction: t, type: 'full-light', userId })
|
return queryBuilder.queryVideo({ id, transaction: t, type: 'full-light', userId })
|
||||||
}
|
}
|
||||||
|
@ -1314,7 +1314,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
|
||||||
userId?: number
|
userId?: number
|
||||||
}): Promise<MVideoDetails> {
|
}): Promise<MVideoDetails> {
|
||||||
const { id, transaction, userId } = parameters
|
const { id, transaction, userId } = parameters
|
||||||
const queryBuilder = new VideosModelGetQueryBuilder(VideoModel.sequelize)
|
const queryBuilder = new VideoModelGetQueryBuilder(VideoModel.sequelize)
|
||||||
|
|
||||||
return queryBuilder.queryVideo({ id, transaction, type: 'api', userId })
|
return queryBuilder.queryVideo({ id, transaction, type: 'api', userId })
|
||||||
}
|
}
|
||||||
|
@ -1345,8 +1345,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
|
||||||
displayOnlyForFollower: {
|
displayOnlyForFollower: {
|
||||||
actorId: serverActor.id,
|
actorId: serverActor.id,
|
||||||
orLocalVideos: true
|
orLocalVideos: true
|
||||||
},
|
}
|
||||||
withFiles: false
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -1490,6 +1489,13 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static isPrivateInclude (include: VideoInclude) {
|
||||||
|
return include & VideoInclude.BLACKLISTED ||
|
||||||
|
include & VideoInclude.BLOCKED_OWNER ||
|
||||||
|
include & VideoInclude.HIDDEN_PRIVACY ||
|
||||||
|
include & VideoInclude.NOT_PUBLISHED_STATE
|
||||||
|
}
|
||||||
|
|
||||||
isBlacklisted () {
|
isBlacklisted () {
|
||||||
return !!this.VideoBlacklist
|
return !!this.VideoBlacklist
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import {
|
||||||
setDefaultVideoChannel,
|
setDefaultVideoChannel,
|
||||||
waitJobs
|
waitJobs
|
||||||
} from '@shared/extra-utils'
|
} from '@shared/extra-utils'
|
||||||
import { HttpStatusCode, UserRole, Video, VideoInclude, VideoPrivacy } from '@shared/models'
|
import { HttpStatusCode, UserRole, Video, VideoDetails, VideoInclude, VideoPrivacy } from '@shared/models'
|
||||||
|
|
||||||
describe('Test videos filter', function () {
|
describe('Test videos filter', function () {
|
||||||
let servers: PeerTubeServer[]
|
let servers: PeerTubeServer[]
|
||||||
|
@ -365,6 +365,32 @@ describe('Test videos filter', function () {
|
||||||
await servers[0].blocklist.removeFromServerBlocklist({ server: servers[1].host })
|
await servers[0].blocklist.removeFromServerBlocklist({ server: servers[1].host })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Should include video files', async function () {
|
||||||
|
for (const path of paths) {
|
||||||
|
{
|
||||||
|
const videos = await listVideos({ server: servers[0], path })
|
||||||
|
|
||||||
|
for (const video of videos) {
|
||||||
|
const videoWithFiles = video as VideoDetails
|
||||||
|
|
||||||
|
expect(videoWithFiles.files).to.not.exist
|
||||||
|
expect(videoWithFiles.streamingPlaylists).to.not.exist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const videos = await listVideos({ server: servers[0], path, include: VideoInclude.FILES })
|
||||||
|
|
||||||
|
for (const video of videos) {
|
||||||
|
const videoWithFiles = video as VideoDetails
|
||||||
|
|
||||||
|
expect(videoWithFiles.files).to.exist
|
||||||
|
expect(videoWithFiles.files).to.have.length.at.least(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
it('Should filter by tags and category', async function () {
|
it('Should filter by tags and category', async function () {
|
||||||
await servers[0].videos.upload({ attributes: { name: 'tag filter', tags: [ 'tag1', 'tag2' ] } })
|
await servers[0].videos.upload({ attributes: { name: 'tag filter', tags: [ 'tag1', 'tag2' ] } })
|
||||||
await servers[0].videos.upload({ attributes: { name: 'tag filter with category', tags: [ 'tag3' ], category: 4 } })
|
await servers[0].videos.upload({ attributes: { name: 'tag filter with category', tags: [ 'tag3' ], category: 4 } })
|
||||||
|
|
|
@ -23,7 +23,7 @@ type Use<K extends keyof AccountModel, M> = PickWith<AccountModel, K, M>
|
||||||
|
|
||||||
export type MAccount =
|
export type MAccount =
|
||||||
Omit<AccountModel, 'Actor' | 'User' | 'Application' | 'VideoChannels' | 'VideoPlaylists' |
|
Omit<AccountModel, 'Actor' | 'User' | 'Application' | 'VideoChannels' | 'VideoPlaylists' |
|
||||||
'VideoComments' | 'BlockedAccounts'>
|
'VideoComments' | 'BlockedBy'>
|
||||||
|
|
||||||
// ############################################################################
|
// ############################################################################
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ export type MAccountSummary =
|
||||||
|
|
||||||
export type MAccountSummaryBlocks =
|
export type MAccountSummaryBlocks =
|
||||||
MAccountSummary &
|
MAccountSummary &
|
||||||
Use<'BlockedByAccounts', MAccountBlocklistId[]>
|
Use<'BlockedBy', MAccountBlocklistId[]>
|
||||||
|
|
||||||
export type MAccountAPI =
|
export type MAccountAPI =
|
||||||
MAccount &
|
MAccount &
|
||||||
|
|
|
@ -15,7 +15,7 @@ export type MServerRedundancyAllowed = Pick<MServer, 'redundancyAllowed'>
|
||||||
|
|
||||||
export type MServerHostBlocks =
|
export type MServerHostBlocks =
|
||||||
MServerHost &
|
MServerHost &
|
||||||
Use<'BlockedByAccounts', MAccountBlocklistId[]>
|
Use<'BlockedBy', MAccountBlocklistId[]>
|
||||||
|
|
||||||
// ############################################################################
|
// ############################################################################
|
||||||
|
|
||||||
|
|
|
@ -210,7 +210,9 @@ export type MVideoFormattable =
|
||||||
PickWithOpt<VideoModel, 'UserVideoHistories', MUserVideoHistoryTime[]> &
|
PickWithOpt<VideoModel, 'UserVideoHistories', MUserVideoHistoryTime[]> &
|
||||||
Use<'VideoChannel', MChannelAccountSummaryFormattable> &
|
Use<'VideoChannel', MChannelAccountSummaryFormattable> &
|
||||||
PickWithOpt<VideoModel, 'ScheduleVideoUpdate', Pick<MScheduleVideoUpdate, 'updateAt' | 'privacy'>> &
|
PickWithOpt<VideoModel, 'ScheduleVideoUpdate', Pick<MScheduleVideoUpdate, 'updateAt' | 'privacy'>> &
|
||||||
PickWithOpt<VideoModel, 'VideoBlacklist', Pick<MVideoBlacklist, 'reason'>>
|
PickWithOpt<VideoModel, 'VideoBlacklist', Pick<MVideoBlacklist, 'reason'>> &
|
||||||
|
PickWithOpt<VideoModel, 'VideoStreamingPlaylists', MStreamingPlaylistFiles[]> &
|
||||||
|
PickWithOpt<VideoModel, 'VideoFiles', MVideoFile[]>
|
||||||
|
|
||||||
export type MVideoFormattableDetails =
|
export type MVideoFormattableDetails =
|
||||||
MVideoFormattable &
|
MVideoFormattable &
|
||||||
|
|
|
@ -3,5 +3,6 @@ export const enum VideoInclude {
|
||||||
NOT_PUBLISHED_STATE = 1 << 0,
|
NOT_PUBLISHED_STATE = 1 << 0,
|
||||||
HIDDEN_PRIVACY = 1 << 1,
|
HIDDEN_PRIVACY = 1 << 1,
|
||||||
BLACKLISTED = 1 << 2,
|
BLACKLISTED = 1 << 2,
|
||||||
BLOCKED_OWNER = 1 << 3
|
BLOCKED_OWNER = 1 << 3,
|
||||||
|
FILES = 1 << 4
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,9 @@ export interface Video {
|
||||||
|
|
||||||
blockedOwner?: boolean
|
blockedOwner?: boolean
|
||||||
blockedServer?: boolean
|
blockedServer?: boolean
|
||||||
|
|
||||||
|
files?: VideoFile[]
|
||||||
|
streamingPlaylists?: VideoStreamingPlaylist[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VideoDetails extends Video {
|
export interface VideoDetails extends Video {
|
||||||
|
@ -70,7 +73,6 @@ export interface VideoDetails extends Video {
|
||||||
channel: VideoChannel
|
channel: VideoChannel
|
||||||
account: Account
|
account: Account
|
||||||
tags: string[]
|
tags: string[]
|
||||||
files: VideoFile[]
|
|
||||||
commentsEnabled: boolean
|
commentsEnabled: boolean
|
||||||
downloadEnabled: boolean
|
downloadEnabled: boolean
|
||||||
|
|
||||||
|
@ -80,5 +82,6 @@ export interface VideoDetails extends Video {
|
||||||
|
|
||||||
trackerUrls: string[]
|
trackerUrls: string[]
|
||||||
|
|
||||||
|
files: VideoFile[]
|
||||||
streamingPlaylists: VideoStreamingPlaylist[]
|
streamingPlaylists: VideoStreamingPlaylist[]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue