1
0
Fork 0
mirror of https://github.com/Chocobozzz/PeerTube.git synced 2025-10-03 09:49:20 +02:00

Redesign manage my videos

* Use a table to list my videos for a clearer overview and so it's
   easier to add bulk actions in the future
 * Use a "Manage" video page with a proper URL navigation
 * Add "Stats" and "Studio" in this "Manage" page
This commit is contained in:
Chocobozzz 2025-03-06 09:45:10 +01:00
parent f0f44e1704
commit b295dd5820
No known key found for this signature in database
GPG key ID: 583A612D890159BE
342 changed files with 9452 additions and 6376 deletions

View file

@ -23,10 +23,11 @@ import { AbstractCommand, OverrideCommandOptions } from '../shared/index.js'
import { sendRTMPStream, testFfmpegStreamError } from './live.js'
export class LiveCommand extends AbstractCommand {
get (options: OverrideCommandOptions & {
videoId: number | string
}) {
get (
options: OverrideCommandOptions & {
videoId: number | string
}
) {
const path = '/api/v1/videos/live'
return this.getRequestBody<LiveVideo>({
@ -40,9 +41,11 @@ export class LiveCommand extends AbstractCommand {
// ---------------------------------------------------------------------------
listSessions (options: OverrideCommandOptions & {
videoId: number | string
}) {
listSessions (
options: OverrideCommandOptions & {
videoId: number | string
}
) {
const path = `/api/v1/videos/live/${options.videoId}/sessions`
return this.getRequestBody<ResultList<LiveVideoSession>>({
@ -54,17 +57,21 @@ export class LiveCommand extends AbstractCommand {
})
}
async findLatestSession (options: OverrideCommandOptions & {
videoId: number | string
}) {
async findLatestSession (
options: OverrideCommandOptions & {
videoId: number | string
}
) {
const { data: sessions } = await this.listSessions(options)
return sessions[sessions.length - 1]
}
getReplaySession (options: OverrideCommandOptions & {
videoId: number | string
}) {
getReplaySession (
options: OverrideCommandOptions & {
videoId: number | string
}
) {
const path = `/api/v1/videos/${options.videoId}/live-session`
return this.getRequestBody<LiveVideoSession>({
@ -78,10 +85,12 @@ export class LiveCommand extends AbstractCommand {
// ---------------------------------------------------------------------------
update (options: OverrideCommandOptions & {
videoId: number | string
fields: LiveVideoUpdate
}) {
update (
options: OverrideCommandOptions & {
videoId: number | string
fields: LiveVideoUpdate
}
) {
const { videoId, fields } = options
const path = '/api/v1/videos/live'
@ -95,9 +104,11 @@ export class LiveCommand extends AbstractCommand {
})
}
async create (options: OverrideCommandOptions & {
fields: LiveVideoCreate
}) {
async create (
options: OverrideCommandOptions & {
fields: Omit<LiveVideoCreate, 'thumbnailfile' | 'previewfile'> & { thumbnailfile?: string | Blob, previewfile?: string | Blob }
}
) {
const { fields } = options
const path = '/api/v1/videos/live'
@ -118,13 +129,15 @@ export class LiveCommand extends AbstractCommand {
return body.video
}
async quickCreate (options: OverrideCommandOptions & {
saveReplay: boolean
permanentLive: boolean
name?: string
privacy?: VideoPrivacyType
videoPasswords?: string[]
}) {
async quickCreate (
options: OverrideCommandOptions & {
saveReplay: boolean
permanentLive: boolean
name?: string
privacy?: VideoPrivacyType
videoPasswords?: string[]
}
) {
const { name = 'live', saveReplay, permanentLive, privacy = VideoPrivacy.PUBLIC, videoPasswords } = options
const replaySettings = privacy === VideoPrivacy.PASSWORD_PROTECTED
@ -153,22 +166,26 @@ export class LiveCommand extends AbstractCommand {
// ---------------------------------------------------------------------------
async sendRTMPStreamInVideo (options: OverrideCommandOptions & {
videoId: number | string
fixtureName?: string
copyCodecs?: boolean
}) {
async sendRTMPStreamInVideo (
options: OverrideCommandOptions & {
videoId: number | string
fixtureName?: string
copyCodecs?: boolean
}
) {
const { videoId, fixtureName, copyCodecs } = options
const videoLive = await this.get({ videoId })
return sendRTMPStream({ rtmpBaseUrl: videoLive.rtmpUrl, streamKey: videoLive.streamKey, fixtureName, copyCodecs })
}
async runAndTestStreamError (options: OverrideCommandOptions & {
videoId: number | string
shouldHaveError: boolean
fixtureName?: string
}) {
async runAndTestStreamError (
options: OverrideCommandOptions & {
videoId: number | string
shouldHaveError: boolean
fixtureName?: string
}
) {
const command = await this.sendRTMPStreamInVideo(options)
return testFfmpegStreamError(command, options.shouldHaveError)
@ -176,35 +193,43 @@ export class LiveCommand extends AbstractCommand {
// ---------------------------------------------------------------------------
waitUntilPublished (options: OverrideCommandOptions & {
videoId: number | string
}) {
waitUntilPublished (
options: OverrideCommandOptions & {
videoId: number | string
}
) {
const { videoId } = options
return this.waitUntilState({ videoId, state: VideoState.PUBLISHED })
}
waitUntilWaiting (options: OverrideCommandOptions & {
videoId: number | string
}) {
waitUntilWaiting (
options: OverrideCommandOptions & {
videoId: number | string
}
) {
const { videoId } = options
return this.waitUntilState({ videoId, state: VideoState.WAITING_FOR_LIVE })
}
waitUntilEnded (options: OverrideCommandOptions & {
videoId: number | string
}) {
waitUntilEnded (
options: OverrideCommandOptions & {
videoId: number | string
}
) {
const { videoId } = options
return this.waitUntilState({ videoId, state: VideoState.LIVE_ENDED })
}
async waitUntilSegmentGeneration (options: OverrideCommandOptions & {
server: PeerTubeServer
videoUUID: string
playlistNumber: number
segment: number
objectStorage?: ObjectStorageCommand
objectStorageBaseUrl?: string
}) {
async waitUntilSegmentGeneration (
options: OverrideCommandOptions & {
server: PeerTubeServer
videoUUID: string
playlistNumber: number
segment: number
objectStorage?: ObjectStorageCommand
objectStorageBaseUrl?: string
}
) {
const {
server,
objectStorage,
@ -253,9 +278,11 @@ export class LiveCommand extends AbstractCommand {
}
}
async waitUntilReplacedByReplay (options: OverrideCommandOptions & {
videoId: number | string
}) {
async waitUntilReplacedByReplay (
options: OverrideCommandOptions & {
videoId: number | string
}
) {
let video: VideoDetails
do {
@ -267,12 +294,14 @@ export class LiveCommand extends AbstractCommand {
// ---------------------------------------------------------------------------
getSegmentFile (options: OverrideCommandOptions & {
videoUUID: string
playlistNumber: number
segment: number
objectStorage?: ObjectStorageCommand
}) {
getSegmentFile (
options: OverrideCommandOptions & {
videoUUID: string
playlistNumber: number
segment: number
objectStorage?: ObjectStorageCommand
}
) {
const { playlistNumber, segment, videoUUID, objectStorage } = options
const segmentName = `${playlistNumber}-00000${segment}.ts`
@ -291,11 +320,13 @@ export class LiveCommand extends AbstractCommand {
})
}
getPlaylistFile (options: OverrideCommandOptions & {
videoUUID: string
playlistName: string
objectStorage?: ObjectStorageCommand
}) {
getPlaylistFile (
options: OverrideCommandOptions & {
videoUUID: string
playlistName: string
objectStorage?: ObjectStorageCommand
}
) {
const { playlistName, videoUUID, objectStorage } = options
const baseUrl = objectStorage
@ -315,9 +346,11 @@ export class LiveCommand extends AbstractCommand {
// ---------------------------------------------------------------------------
async countPlaylists (options: OverrideCommandOptions & {
videoUUID: string
}) {
async countPlaylists (
options: OverrideCommandOptions & {
videoUUID: string
}
) {
const basePath = this.server.servers.buildDirectory('streaming-playlists')
const hlsPath = join(basePath, 'hls', options.videoUUID)
@ -326,10 +359,12 @@ export class LiveCommand extends AbstractCommand {
return files.filter(f => f.endsWith('.m3u8')).length
}
private async waitUntilState (options: OverrideCommandOptions & {
videoId: number | string
state: VideoStateType
}) {
private async waitUntilState (
options: OverrideCommandOptions & {
videoId: number | string
state: VideoStateType
}
) {
let video: VideoDetails
do {

View file

@ -1,10 +1,10 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/no-floating-promises */
import validator from 'validator'
import { getAllPrivacies, omit, pick, wait } from '@peertube/peertube-core-utils'
import {
HttpStatusCode,
HttpStatusCodeType, ResultList,
HttpStatusCodeType,
ResultList,
UserVideoRateType,
Video,
VideoCommentPolicy,
@ -20,6 +20,7 @@ import {
VideoTranscodingCreate
} from '@peertube/peertube-models'
import { buildAbsoluteFixturePath, buildUUID } from '@peertube/peertube-node-utils'
import validator from 'validator'
import { unwrapBody } from '../requests/index.js'
import { waitJobs } from '../server/jobs.js'
import { AbstractCommand, OverrideCommandOptions } from '../shared/index.js'
@ -31,7 +32,6 @@ export type VideoEdit = Partial<Omit<VideoCreate, 'thumbnailfile' | 'previewfile
}
export class VideosCommand extends AbstractCommand {
getCategories (options: OverrideCommandOptions = {}) {
const path = '/api/v1/videos/categories'
@ -82,9 +82,11 @@ export class VideosCommand extends AbstractCommand {
// ---------------------------------------------------------------------------
getFileMetadata (options: OverrideCommandOptions & {
url: string
}) {
getFileMetadata (
options: OverrideCommandOptions & {
url: string
}
) {
return unwrapBody<VideoFileMetadata>(this.getRawRequest({
...options,
@ -96,11 +98,13 @@ export class VideosCommand extends AbstractCommand {
// ---------------------------------------------------------------------------
rate (options: OverrideCommandOptions & {
id: number | string
rating: UserVideoRateType
videoPassword?: string
}) {
rate (
options: OverrideCommandOptions & {
id: number | string
rating: UserVideoRateType
videoPassword?: string
}
) {
const { id, rating, videoPassword } = options
const path = '/api/v1/videos/' + id + '/rate'
@ -117,9 +121,11 @@ export class VideosCommand extends AbstractCommand {
// ---------------------------------------------------------------------------
get (options: OverrideCommandOptions & {
id: number | string
}) {
get (
options: OverrideCommandOptions & {
id: number | string
}
) {
const path = '/api/v1/videos/' + options.id
return this.getRequestBody<VideoDetails>({
@ -131,9 +137,11 @@ export class VideosCommand extends AbstractCommand {
})
}
getWithToken (options: OverrideCommandOptions & {
id: number | string
}) {
getWithToken (
options: OverrideCommandOptions & {
id: number | string
}
) {
return this.get({
...options,
@ -141,15 +149,17 @@ export class VideosCommand extends AbstractCommand {
})
}
getWithPassword (options: OverrideCommandOptions & {
id: number | string
password?: string
}) {
getWithPassword (
options: OverrideCommandOptions & {
id: number | string
password?: string
}
) {
const path = '/api/v1/videos/' + options.id
return this.getRequestBody<VideoDetails>({
...options,
headers:{
headers: {
'x-peertube-video-password': options.password
},
path,
@ -158,9 +168,11 @@ export class VideosCommand extends AbstractCommand {
})
}
getSource (options: OverrideCommandOptions & {
id: number | string
}) {
getSource (
options: OverrideCommandOptions & {
id: number | string
}
) {
const path = '/api/v1/videos/' + options.id + '/source'
return this.getRequestBody<VideoSource>({
@ -172,9 +184,11 @@ export class VideosCommand extends AbstractCommand {
})
}
deleteSource (options: OverrideCommandOptions & {
id: number | string
}) {
deleteSource (
options: OverrideCommandOptions & {
id: number | string
}
) {
const path = '/api/v1/videos/' + options.id + '/source/file'
return this.deleteRequest({
@ -186,9 +200,11 @@ export class VideosCommand extends AbstractCommand {
})
}
async getId (options: OverrideCommandOptions & {
uuid: number | string
}) {
async getId (
options: OverrideCommandOptions & {
uuid: number | string
}
) {
const { uuid } = options
if (validator.default.isUUID('' + uuid) === false) return uuid as number
@ -198,9 +214,11 @@ export class VideosCommand extends AbstractCommand {
return id
}
async listFiles (options: OverrideCommandOptions & {
id: number | string
}) {
async listFiles (
options: OverrideCommandOptions & {
id: number | string
}
) {
const video = await this.get(options)
const files = video.files || []
@ -211,22 +229,14 @@ export class VideosCommand extends AbstractCommand {
// ---------------------------------------------------------------------------
listMyVideos (options: OverrideCommandOptions & {
start?: number
count?: number
sort?: string
search?: string
isLive?: boolean
channelId?: number
autoTagOneOf?: string[]
} = {}) {
listMyVideos (options: OverrideCommandOptions & VideosCommonQuery & { channelId?: number, channelNameOneOf?: string[] } = {}) {
const path = '/api/v1/users/me/videos'
return this.getRequestBody<ResultList<Video>>({
...options,
path,
query: pick(options, [ 'start', 'count', 'sort', 'search', 'isLive', 'channelId', 'autoTagOneOf' ]),
query: { ...this.buildListQuery(options), ...pick(options, [ 'channelId', 'channelNameOneOf' ]) },
implicitToken: true,
defaultExpectedStatus: HttpStatusCode.OK_200
})
@ -287,9 +297,11 @@ export class VideosCommand extends AbstractCommand {
})
}
listByAccount (options: OverrideCommandOptions & VideosCommonQuery & {
handle: string
}) {
listByAccount (
options: OverrideCommandOptions & VideosCommonQuery & {
handle: string
}
) {
const { handle, search } = options
const path = '/api/v1/accounts/' + handle + '/videos'
@ -303,9 +315,11 @@ export class VideosCommand extends AbstractCommand {
})
}
listByChannel (options: OverrideCommandOptions & VideosCommonQuery & {
handle: string
}) {
listByChannel (
options: OverrideCommandOptions & VideosCommonQuery & {
handle: string
}
) {
const { handle } = options
const path = '/api/v1/video-channels/' + handle + '/videos'
@ -321,17 +335,21 @@ export class VideosCommand extends AbstractCommand {
// ---------------------------------------------------------------------------
async find (options: OverrideCommandOptions & {
name: string
}) {
async find (
options: OverrideCommandOptions & {
name: string
}
) {
const { data } = await this.list(options)
return data.find(v => v.name === options.name)
}
async findFull (options: OverrideCommandOptions & {
name: string
}) {
async findFull (
options: OverrideCommandOptions & {
name: string
}
) {
const { uuid } = await this.find(options)
return this.get({ id: uuid })
@ -339,10 +357,12 @@ export class VideosCommand extends AbstractCommand {
// ---------------------------------------------------------------------------
update (options: OverrideCommandOptions & {
id: number | string
attributes?: VideoEdit
}) {
update (
options: OverrideCommandOptions & {
id: number | string
attributes?: VideoEdit
}
) {
const { id, attributes = {} } = options
const path = '/api/v1/videos/' + id
@ -376,9 +396,11 @@ export class VideosCommand extends AbstractCommand {
})
}
remove (options: OverrideCommandOptions & {
id: number | string
}) {
remove (
options: OverrideCommandOptions & {
id: number | string
}
) {
const path = '/api/v1/videos/' + options.id
return unwrapBody(this.deleteRequest({
@ -462,9 +484,11 @@ export class VideosCommand extends AbstractCommand {
return created
}
async buildLegacyUpload (options: OverrideCommandOptions & {
attributes: VideoEdit
}): Promise<VideoCreateResult> {
async buildLegacyUpload (
options: OverrideCommandOptions & {
attributes: VideoEdit
}
): Promise<VideoCreateResult> {
const path = '/api/v1/videos/upload'
return unwrapBody<{ video: VideoCreateResult }>(this.postUploadRequest({
@ -478,14 +502,16 @@ export class VideosCommand extends AbstractCommand {
})).then(body => body.video || body as any)
}
quickUpload (options: OverrideCommandOptions & {
name: string
nsfw?: boolean
privacy?: VideoPrivacyType
fixture?: string
videoPasswords?: string[]
channelId?: number
}) {
quickUpload (
options: OverrideCommandOptions & {
name: string
nsfw?: boolean
privacy?: VideoPrivacyType
fixture?: string
videoPasswords?: string[]
channelId?: number
}
) {
const attributes: VideoEdit = { name: options.name }
if (options.nsfw) attributes.nsfw = options.nsfw
if (options.privacy) attributes.privacy = options.privacy
@ -515,11 +541,13 @@ export class VideosCommand extends AbstractCommand {
// ---------------------------------------------------------------------------
replaceSourceFile (options: OverrideCommandOptions & {
videoId: number | string
fixture: string
completedExpectedStatus?: HttpStatusCodeType
}) {
replaceSourceFile (
options: OverrideCommandOptions & {
videoId: number | string
fixture: string
completedExpectedStatus?: HttpStatusCodeType
}
) {
return this.buildResumeUpload({
...options,
@ -530,9 +558,11 @@ export class VideosCommand extends AbstractCommand {
// ---------------------------------------------------------------------------
removeHLSPlaylist (options: OverrideCommandOptions & {
videoId: number | string
}) {
removeHLSPlaylist (
options: OverrideCommandOptions & {
videoId: number | string
}
) {
const path = '/api/v1/videos/' + options.videoId + '/hls'
return this.deleteRequest({
@ -544,10 +574,12 @@ export class VideosCommand extends AbstractCommand {
})
}
removeHLSFile (options: OverrideCommandOptions & {
videoId: number | string
fileId: number
}) {
removeHLSFile (
options: OverrideCommandOptions & {
videoId: number | string
fileId: number
}
) {
const path = '/api/v1/videos/' + options.videoId + '/hls/' + options.fileId
return this.deleteRequest({
@ -559,9 +591,11 @@ export class VideosCommand extends AbstractCommand {
})
}
removeAllWebVideoFiles (options: OverrideCommandOptions & {
videoId: number | string
}) {
removeAllWebVideoFiles (
options: OverrideCommandOptions & {
videoId: number | string
}
) {
const path = '/api/v1/videos/' + options.videoId + '/web-videos'
return this.deleteRequest({
@ -573,10 +607,12 @@ export class VideosCommand extends AbstractCommand {
})
}
removeWebVideoFile (options: OverrideCommandOptions & {
videoId: number | string
fileId: number
}) {
removeWebVideoFile (
options: OverrideCommandOptions & {
videoId: number | string
fileId: number
}
) {
const path = '/api/v1/videos/' + options.videoId + '/web-videos/' + options.fileId
return this.deleteRequest({
@ -588,9 +624,11 @@ export class VideosCommand extends AbstractCommand {
})
}
runTranscoding (options: OverrideCommandOptions & VideoTranscodingCreate & {
videoId: number | string
}) {
runTranscoding (
options: OverrideCommandOptions & VideoTranscodingCreate & {
videoId: number | string
}
) {
const path = '/api/v1/videos/' + options.videoId + '/transcoding'
return this.postBodyRequest({
@ -632,7 +670,7 @@ export class VideosCommand extends AbstractCommand {
}
buildUploadAttaches (attributes: VideoEdit, includeFixture: boolean) {
const attaches: { [ name: string ]: string } = {}
const attaches: { [name: string]: string } = {}
for (const key of [ 'thumbnailfile', 'previewfile' ]) {
if (attributes[key]) attaches[key] = buildAbsoluteFixturePath(attributes[key])
@ -666,11 +704,13 @@ export class VideosCommand extends AbstractCommand {
// ---------------------------------------------------------------------------
generateDownload (options: OverrideCommandOptions & {
videoId: number | string
videoFileIds: number[]
query?: Record<string, string>
}) {
generateDownload (
options: OverrideCommandOptions & {
videoId: number | string
videoFileIds: number[]
query?: Record<string, string>
}
) {
const { videoFileIds, videoId, query = {} } = options
const path = '/download/videos/generate/' + videoId