From 2e401e8575decb1d491d0db48ca1ab1eba5b2a66 Mon Sep 17 00:00:00 2001 From: kontrollanten <6680299+kontrollanten@users.noreply.github.com> Date: Tue, 21 Jun 2022 15:31:25 +0200 Subject: [PATCH] store uploaded video filename (#4885) * store uploaded video filename closes #4731 * dont crash if videos channel exist * migration: use raw query * video source: fixes after code review * cleanup * bump migration * updates after code review * refactor: use checkUserCanManageVideo * videoSource: add openapi doc * test(check-params/video-source): fix timeout * Styling * Correctly set original filename as source Co-authored-by: Chocobozzz --- .../shared/video-edit.component.html | 15 +++++ .../shared/video-edit.component.ts | 2 + .../+video-edit/video-update.component.html | 1 + .../+video-edit/video-update.component.ts | 5 +- .../+video-edit/video-update.resolver.ts | 5 +- .../shared/shared-main/video/video.service.ts | 17 +++++- server/controllers/api/videos/index.ts | 13 +++++ server/controllers/api/videos/upload.ts | 7 +++ server/initializers/constants.ts | 2 +- server/initializers/database.ts | 2 + .../migrations/0715-video-source.ts | 34 ++++++++++++ server/middlewares/validators/videos/index.ts | 1 + .../validators/videos/video-source.ts | 37 +++++++++++++ .../middlewares/validators/videos/videos.ts | 2 +- server/models/video/video-source.ts | 55 +++++++++++++++++++ server/models/video/video.ts | 10 ++++ server/tests/api/check-params/index.ts | 11 ++-- server/tests/api/check-params/video-source.ts | 44 +++++++++++++++ server/tests/api/videos/index.ts | 1 + server/tests/api/videos/video-source.ts | 39 +++++++++++++ server/types/express.d.ts | 6 +- server/types/models/video/video-source.ts | 3 + shared/models/videos/video-source.ts | 3 + .../server-commands/videos/videos-command.ts | 15 +++++ support/doc/api/openapi.yaml | 20 +++++++ 25 files changed, 339 insertions(+), 11 deletions(-) create mode 100644 server/initializers/migrations/0715-video-source.ts create mode 100644 server/middlewares/validators/videos/video-source.ts create mode 100644 server/models/video/video-source.ts create mode 100644 server/tests/api/check-params/video-source.ts create mode 100644 server/tests/api/videos/video-source.ts create mode 100644 server/types/models/video/video-source.ts create mode 100644 shared/models/videos/video-source.ts diff --git a/client/src/app/+videos/+video-edit/shared/video-edit.component.html b/client/src/app/+videos/+video-edit/shared/video-edit.component.html index 595200c3b..650448a74 100644 --- a/client/src/app/+videos/+video-edit/shared/video-edit.component.html +++ b/client/src/app/+videos/+video-edit/shared/video-edit.component.html @@ -340,6 +340,21 @@
+ +
+ + + + + + Name of the uploaded file + + + + + +
+
diff --git a/client/src/app/+videos/+video-edit/shared/video-edit.component.ts b/client/src/app/+videos/+video-edit/shared/video-edit.component.ts index 16b964482..c74ef5731 100644 --- a/client/src/app/+videos/+video-edit/shared/video-edit.component.ts +++ b/client/src/app/+videos/+video-edit/shared/video-edit.component.ts @@ -37,6 +37,7 @@ import { I18nPrimengCalendarService } from './i18n-primeng-calendar.service' import { VideoCaptionAddModalComponent } from './video-caption-add-modal.component' import { VideoCaptionEditModalComponent } from './video-caption-edit-modal/video-caption-edit-modal.component' import { VideoEditType } from './video-edit.type' +import { VideoSource } from '@shared/models/videos/video-source' type VideoLanguages = VideoConstant & { group?: string } type PluginField = { @@ -61,6 +62,7 @@ export class VideoEditComponent implements OnInit, OnDestroy { @Input() forbidScheduledPublication = true @Input() videoCaptions: VideoCaptionWithPathEdit[] = [] + @Input() videoSource: VideoSource @Input() waitTranscodingEnabled = true @Input() type: VideoEditType diff --git a/client/src/app/+videos/+video-edit/video-update.component.html b/client/src/app/+videos/+video-edit/video-update.component.html index 6a32f1477..ffd125695 100644 --- a/client/src/app/+videos/+video-edit/video-update.component.html +++ b/client/src/app/+videos/+video-edit/video-update.component.html @@ -12,6 +12,7 @@ [videoCaptions]="videoCaptions" [waitTranscodingEnabled]="isWaitTranscodingEnabled()" type="update" (pluginFieldsAdded)="hydratePluginFieldsFromVideo()" [liveVideo]="liveVideo" [videoToUpdate]="videoDetails" + [videoSource]="videoSource" (formBuilt)="onFormBuilt()" > diff --git a/client/src/app/+videos/+video-edit/video-update.component.ts b/client/src/app/+videos/+video-edit/video-update.component.ts index 9c4998f2e..43e8ba3e5 100644 --- a/client/src/app/+videos/+video-edit/video-update.component.ts +++ b/client/src/app/+videos/+video-edit/video-update.component.ts @@ -10,6 +10,7 @@ import { LiveVideoService } from '@app/shared/shared-video-live' import { LoadingBarService } from '@ngx-loading-bar/core' import { LiveVideo, LiveVideoUpdate, VideoPrivacy } from '@shared/models' import { hydrateFormFromVideo } from './shared/video-edit-utils' +import { VideoSource } from '@shared/models/videos/video-source' @Component({ selector: 'my-videos-update', @@ -19,6 +20,7 @@ import { hydrateFormFromVideo } from './shared/video-edit-utils' export class VideoUpdateComponent extends FormReactive implements OnInit { video: VideoEdit videoDetails: VideoDetails + videoSource: VideoSource userVideoChannels: SelectChannelItem[] = [] videoCaptions: VideoCaptionEdit[] = [] liveVideo: LiveVideo @@ -46,13 +48,14 @@ export class VideoUpdateComponent extends FormReactive implements OnInit { this.buildForm({}) const { videoData } = this.route.snapshot.data - const { video, videoChannels, videoCaptions, liveVideo } = videoData + const { video, videoChannels, videoCaptions, videoSource, liveVideo } = videoData this.video = new VideoEdit(video) this.videoDetails = video this.userVideoChannels = videoChannels this.videoCaptions = videoCaptions + this.videoSource = videoSource this.liveVideo = liveVideo this.forbidScheduledPublication = this.video.privacy !== VideoPrivacy.PRIVATE diff --git a/client/src/app/+videos/+video-edit/video-update.resolver.ts b/client/src/app/+videos/+video-edit/video-update.resolver.ts index 82dae5c1c..db5017340 100644 --- a/client/src/app/+videos/+video-edit/video-update.resolver.ts +++ b/client/src/app/+videos/+video-edit/video-update.resolver.ts @@ -23,7 +23,8 @@ export class VideoUpdateResolver implements Resolve { return this.videoService.getVideo({ videoId: uuid }) .pipe( switchMap(video => forkJoin(this.buildVideoObservables(video))), - map(([ video, videoChannels, videoCaptions, liveVideo ]) => ({ video, videoChannels, videoCaptions, liveVideo })) + map(([ video, videoSource, videoChannels, videoCaptions, liveVideo ]) => + ({ video, videoChannels, videoCaptions, videoSource, liveVideo })) ) } @@ -33,6 +34,8 @@ export class VideoUpdateResolver implements Resolve { .loadCompleteDescription(video.descriptionPath) .pipe(map(description => Object.assign(video, { description }))), + this.videoService.getSource(video.id), + listUserChannelsForSelect(this.authService), this.videoCaptionService diff --git a/client/src/app/shared/shared-main/video/video.service.ts b/client/src/app/shared/shared-main/video/video.service.ts index 142367506..83bc4eeb6 100644 --- a/client/src/app/shared/shared-main/video/video.service.ts +++ b/client/src/app/shared/shared-main/video/video.service.ts @@ -1,5 +1,5 @@ import { SortMeta } from 'primeng/api' -import { from, Observable } from 'rxjs' +import { from, Observable, of } from 'rxjs' import { catchError, concatMap, map, switchMap, toArray } from 'rxjs/operators' import { HttpClient, HttpParams, HttpRequest } from '@angular/common/http' import { Injectable } from '@angular/core' @@ -24,6 +24,7 @@ import { VideoTranscodingCreate, VideoUpdate } from '@shared/models' +import { VideoSource } from '@shared/models/videos/video-source' import { environment } from '../../../../environments/environment' import { Account } from '../account/account.model' import { AccountService } from '../account/account.service' @@ -323,6 +324,20 @@ export class VideoService { ) } + getSource (videoId: number) { + return this.authHttp + .get<{ source: VideoSource }>(VideoService.BASE_VIDEO_URL + '/' + videoId + '/source') + .pipe( + catchError(err => { + if (err.status === 404) { + return of(undefined) + } + + this.restExtractor.handleError(err) + }) + ) + } + setVideoLike (id: number) { return this.setVideoRate(id, 'like') } diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index be233722c..d4e08293e 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts @@ -26,6 +26,7 @@ import { setDefaultVideosSort, videosCustomGetValidator, videosGetValidator, + videoSourceGetValidator, videosRemoveValidator, videosSortValidator } from '../../../middlewares' @@ -96,6 +97,14 @@ videosRouter.get('/:id/description', asyncMiddleware(videosGetValidator), asyncMiddleware(getVideoDescription) ) + +videosRouter.get('/:id/source', + openapiOperationDoc({ operationId: 'getVideoSource' }), + authenticate, + asyncMiddleware(videoSourceGetValidator), + getVideoSource +) + videosRouter.get('/:id', openapiOperationDoc({ operationId: 'getVideo' }), optionalAuthenticate, @@ -155,6 +164,10 @@ async function getVideoDescription (req: express.Request, res: express.Response) return res.json({ description }) } +function getVideoSource (req: express.Request, res: express.Response) { + return res.json(res.locals.videoSource.toFormattedJSON()) +} + async function listVideos (req: express.Request, res: express.Response) { const serverActor = await getServerActor() diff --git a/server/controllers/api/videos/upload.ts b/server/controllers/api/videos/upload.ts index 3afbedbb2..c5890691e 100644 --- a/server/controllers/api/videos/upload.ts +++ b/server/controllers/api/videos/upload.ts @@ -44,6 +44,7 @@ import { import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update' import { VideoModel } from '../../../models/video/video' import { VideoFileModel } from '../../../models/video/video-file' +import { VideoSourceModel } from '@server/models/video/video-source' const lTags = loggerTagsFactory('api', 'video') const auditLogger = auditLoggerFactory('videos') @@ -151,6 +152,7 @@ async function addVideo (options: { video.url = getLocalVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object const videoFile = await buildNewFile(videoPhysicalFile) + const originalFilename = videoPhysicalFile.originalname // Move physical file const destination = VideoPathManager.Instance.getFSVideoFileOutputPath(video, videoFile) @@ -181,6 +183,11 @@ async function addVideo (options: { video.VideoFiles = [ videoFile ] + await VideoSourceModel.create({ + filename: originalFilename, + videoId: video.id + }, { transaction: t }) + await setVideoTags({ video, tags: videoInfo.tags, transaction: t }) // Schedule an update in the future? diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index f54ce9506..0d7e7077d 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts @@ -24,7 +24,7 @@ import { CONFIG, registerConfigChangedHandler } from './config' // --------------------------------------------------------------------------- -const LAST_MIGRATION_VERSION = 710 +const LAST_MIGRATION_VERSION = 715 // --------------------------------------------------------------------------- diff --git a/server/initializers/database.ts b/server/initializers/database.ts index 3576f444c..09786a91f 100644 --- a/server/initializers/database.ts +++ b/server/initializers/database.ts @@ -49,6 +49,7 @@ import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-pla import { VideoTagModel } from '../models/video/video-tag' import { VideoViewModel } from '../models/view/video-view' import { CONFIG } from './config' +import { VideoSourceModel } from '@server/models/video/video-source' require('pg').defaults.parseInt8 = true // Avoid BIGINT to be converted to string @@ -126,6 +127,7 @@ async function initDatabaseModels (silent: boolean) { VideoChannelModel, VideoShareModel, VideoFileModel, + VideoSourceModel, VideoCaptionModel, VideoBlacklistModel, VideoTagModel, diff --git a/server/initializers/migrations/0715-video-source.ts b/server/initializers/migrations/0715-video-source.ts new file mode 100644 index 000000000..efcf77ebd --- /dev/null +++ b/server/initializers/migrations/0715-video-source.ts @@ -0,0 +1,34 @@ +import * as Sequelize from 'sequelize' + +async function up (utils: { + transaction: Sequelize.Transaction + queryInterface: Sequelize.QueryInterface + sequelize: Sequelize.Sequelize + db: any +}): Promise { + { + const query = ` + CREATE TABLE IF NOT EXISTS "videoSource" ( + "id" SERIAL , + "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, + "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, + "filename" VARCHAR(255) DEFAULT NULL, + "videoId" INTEGER + REFERENCES "video" ("id") + ON DELETE CASCADE + ON UPDATE CASCADE, + PRIMARY KEY ("id") + ); + ` + await utils.sequelize.query(query) + } +} + +function down (options) { + throw new Error('Not implemented.') +} + +export { + up, + down +} diff --git a/server/middlewares/validators/videos/index.ts b/server/middlewares/validators/videos/index.ts index bd2590bc5..1dd7b5d2e 100644 --- a/server/middlewares/validators/videos/index.ts +++ b/server/middlewares/validators/videos/index.ts @@ -9,6 +9,7 @@ export * from './video-ownership-changes' export * from './video-view' export * from './video-rates' export * from './video-shares' +export * from './video-source' export * from './video-stats' export * from './video-studio' export * from './video-transcoding' diff --git a/server/middlewares/validators/videos/video-source.ts b/server/middlewares/validators/videos/video-source.ts new file mode 100644 index 000000000..31a2f16b3 --- /dev/null +++ b/server/middlewares/validators/videos/video-source.ts @@ -0,0 +1,37 @@ +import express from 'express' +import { getVideoWithAttributes } from '@server/helpers/video' +import { VideoSourceModel } from '@server/models/video/video-source' +import { MVideoFullLight } from '@server/types/models' +import { HttpStatusCode, UserRight } from '@shared/models' +import { logger } from '../../../helpers/logger' +import { areValidationErrors, checkUserCanManageVideo, doesVideoExist, isValidVideoIdParam } from '../shared' + +const videoSourceGetValidator = [ + isValidVideoIdParam('id'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking videoSourceGet parameters', { parameters: req.params }) + + if (areValidationErrors(req, res)) return + if (!await doesVideoExist(req.params.id, res, 'for-api')) return + + const video = getVideoWithAttributes(res) as MVideoFullLight + + res.locals.videoSource = await VideoSourceModel.loadByVideoId(video.id) + if (!res.locals.videoSource) { + return res.fail({ + status: HttpStatusCode.NOT_FOUND_404, + message: 'Video source not found' + }) + } + + const user = res.locals.oauth.token.User + if (!checkUserCanManageVideo(user, video, UserRight.UPDATE_ANY_VIDEO, res)) return + + return next() + } +] + +export { + videoSourceGetValidator +} diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts index 0b6b8bfe5..c75c3640b 100644 --- a/server/middlewares/validators/videos/videos.ts +++ b/server/middlewares/validators/videos/videos.ts @@ -152,7 +152,7 @@ const videosAddResumableValidator = [ if (!await isVideoAccepted(req, res, file)) return cleanup() - res.locals.videoFileResumable = file + res.locals.videoFileResumable = { ...file, originalname: file.filename } return next() } diff --git a/server/models/video/video-source.ts b/server/models/video/video-source.ts new file mode 100644 index 000000000..e306b160d --- /dev/null +++ b/server/models/video/video-source.ts @@ -0,0 +1,55 @@ +import { Op } from 'sequelize' +import { + AllowNull, + BelongsTo, + Column, + CreatedAt, + ForeignKey, + Model, + Table, + UpdatedAt +} from 'sequelize-typescript' +import { AttributesOnly } from '@shared/typescript-utils' +import { VideoModel } from './video' + +@Table({ + tableName: 'videoSource', + indexes: [ + { + fields: [ 'videoId' ], + where: { + videoId: { + [Op.ne]: null + } + } + } + ] +}) +export class VideoSourceModel extends Model>> { + @CreatedAt + createdAt: Date + + @UpdatedAt + updatedAt: Date + + @AllowNull(false) + @Column + filename: string + + @ForeignKey(() => VideoModel) + @Column + videoId: number + + @BelongsTo(() => VideoModel) + Video: VideoModel + + static loadByVideoId (videoId) { + return VideoSourceModel.findOne({ where: { videoId } }) + } + + toFormattedJSON () { + return { + filename: this.filename + } + } +} diff --git a/server/models/video/video.ts b/server/models/video/video.ts index e6a8d3f95..08adbced6 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts @@ -136,6 +136,7 @@ import { VideoPlaylistElementModel } from './video-playlist-element' import { VideoShareModel } from './video-share' import { VideoStreamingPlaylistModel } from './video-streaming-playlist' import { VideoTagModel } from './video-tag' +import { VideoSourceModel } from './video-source' export enum ScopeNames { FOR_API = 'FOR_API', @@ -597,6 +598,15 @@ export class VideoModel extends Model>> { }) VideoPlaylistElements: VideoPlaylistElementModel[] + @HasOne(() => VideoSourceModel, { + foreignKey: { + name: 'videoId', + allowNull: true + }, + onDelete: 'CASCADE' + }) + VideoSource: VideoSourceModel + @HasMany(() => VideoAbuseModel, { foreignKey: { name: 'videoId', diff --git a/server/tests/api/check-params/index.ts b/server/tests/api/check-params/index.ts index 259d7e783..a27bc8509 100644 --- a/server/tests/api/check-params/index.ts +++ b/server/tests/api/check-params/index.ts @@ -3,14 +3,14 @@ import './accounts' import './blocklist' import './bulk' import './config' -import './custom-pages' import './contact-form' +import './custom-pages' import './debug' import './follows' import './jobs' +import './live' import './logs' import './my-user' -import './live' import './plugins' import './redundancy' import './search' @@ -25,12 +25,13 @@ import './video-blacklist' import './video-captions' import './video-channels' import './video-comments' -import './video-studio' +import './video-files' import './video-imports' import './video-playlists' -import './videos' +import './video-source' +import './video-studio' import './videos-common-filters' -import './video-files' import './videos-history' import './videos-overviews' +import './videos' import './views' diff --git a/server/tests/api/check-params/video-source.ts b/server/tests/api/check-params/video-source.ts new file mode 100644 index 000000000..ca324bb9d --- /dev/null +++ b/server/tests/api/check-params/video-source.ts @@ -0,0 +1,44 @@ +import { HttpStatusCode } from '@shared/models' +import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands' + +describe('Test video sources API validator', function () { + let server: PeerTubeServer = null + let uuid: string + let userToken: string + + before(async function () { + this.timeout(30000) + + server = await createSingleServer(1) + await setAccessTokensToServers([ server ]) + + const created = await server.videos.quickUpload({ name: 'video' }) + uuid = created.uuid + + userToken = await server.users.generateUserAndToken('user') + }) + + it('Should fail without a valid uuid', async function () { + await server.videos.getSource({ id: '4da6fde3-88f7-4d16-b119-108df563d0b0', expectedStatus: HttpStatusCode.NOT_FOUND_404 }) + }) + + it('Should receive 404 when passing a non existing video id', async function () { + await server.videos.getSource({ id: '4da6fde3-88f7-4d16-b119-108df5630b06', expectedStatus: HttpStatusCode.NOT_FOUND_404 }) + }) + + it('Should not get the source as unauthenticated', async function () { + await server.videos.getSource({ id: uuid, expectedStatus: HttpStatusCode.UNAUTHORIZED_401, token: null }) + }) + + it('Should not get the source with another user', async function () { + await server.videos.getSource({ id: uuid, expectedStatus: HttpStatusCode.FORBIDDEN_403, token: userToken }) + }) + + it('Should succeed with the correct parameters get the source as another user', async function () { + await server.videos.getSource({ id: uuid }) + }) + + after(async function () { + await cleanupTests([ server ]) + }) +}) diff --git a/server/tests/api/videos/index.ts b/server/tests/api/videos/index.ts index 27b119f30..a0b6b01cf 100644 --- a/server/tests/api/videos/index.ts +++ b/server/tests/api/videos/index.ts @@ -16,3 +16,4 @@ import './video-schedule-update' import './videos-common-filters' import './videos-history' import './videos-overview' +import './video-source' diff --git a/server/tests/api/videos/video-source.ts b/server/tests/api/videos/video-source.ts new file mode 100644 index 000000000..e34642300 --- /dev/null +++ b/server/tests/api/videos/video-source.ts @@ -0,0 +1,39 @@ +import 'mocha' +import * as chai from 'chai' +import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands' + +const expect = chai.expect + +describe('Test video source', () => { + let server: PeerTubeServer = null + const fixture = 'video_short.webm' + + before(async function () { + this.timeout(30000) + + server = await createSingleServer(1) + await setAccessTokensToServers([ server ]) + }) + + it('Should get the source filename with legacy upload', async function () { + this.timeout(30000) + + const { uuid } = await server.videos.upload({ attributes: { name: 'my video', fixture }, mode: 'legacy' }) + + const source = await server.videos.getSource({ id: uuid }) + expect(source.filename).to.equal(fixture) + }) + + it('Should get the source filename with resumable upload', async function () { + this.timeout(30000) + + const { uuid } = await server.videos.upload({ attributes: { name: 'my video', fixture }, mode: 'resumable' }) + + const source = await server.videos.getSource({ id: uuid }) + expect(source.filename).to.equal(fixture) + }) + + after(async function () { + await cleanupTests([ server ]) + }) +}) diff --git a/server/types/express.d.ts b/server/types/express.d.ts index 7cc13f21d..27e532c31 100644 --- a/server/types/express.d.ts +++ b/server/types/express.d.ts @@ -42,6 +42,7 @@ import { MVideoThumbnail } from './models' import { Writable } from 'stream' +import { MVideoSource } from './models/video/video-source' declare module 'express' { export interface Request { @@ -68,7 +69,7 @@ declare module 'express' { } | UploadFileForCheck[] // Upload file with a duration added by our middleware - export type VideoUploadFile = Pick & { + export type VideoUploadFile = Pick & { duration: number } @@ -85,6 +86,7 @@ declare module 'express' { duration: number path: string filename: string + originalname: string } // Extends Response with added functions and potential variables passed by middlewares @@ -123,6 +125,8 @@ declare module 'express' { videoShare?: MVideoShareActor + videoSource?: MVideoSource + videoFile?: MVideoFile videoFileResumable?: EnhancedUploadXFile diff --git a/server/types/models/video/video-source.ts b/server/types/models/video/video-source.ts new file mode 100644 index 000000000..0948f3b2e --- /dev/null +++ b/server/types/models/video/video-source.ts @@ -0,0 +1,3 @@ +import { VideoSourceModel } from '@server/models/video/video-source' + +export type MVideoSource = Omit diff --git a/shared/models/videos/video-source.ts b/shared/models/videos/video-source.ts new file mode 100644 index 000000000..57e54fc7f --- /dev/null +++ b/shared/models/videos/video-source.ts @@ -0,0 +1,3 @@ +export interface VideoSource { + filename: string +} diff --git a/shared/server-commands/videos/videos-command.ts b/shared/server-commands/videos/videos-command.ts index 1cceb58db..e952c9777 100644 --- a/shared/server-commands/videos/videos-command.ts +++ b/shared/server-commands/videos/videos-command.ts @@ -23,6 +23,7 @@ import { import { unwrapBody } from '../requests' import { waitJobs } from '../server' import { AbstractCommand, OverrideCommandOptions } from '../shared' +import { VideoSource } from '@shared/models/videos/video-source' export type VideoEdit = Partial> & { fixture?: string @@ -150,6 +151,20 @@ export class VideosCommand extends AbstractCommand { }) } + getSource (options: OverrideCommandOptions & { + id: number | string + }) { + const path = '/api/v1/videos/' + options.id + '/source' + + return this.getRequestBody({ + ...options, + + path, + implicitToken: true, + defaultExpectedStatus: HttpStatusCode.OK_200 + }) + } + async getId (options: OverrideCommandOptions & { uuid: number | string }) { diff --git a/support/doc/api/openapi.yaml b/support/doc/api/openapi.yaml index bd36c41cd..afd310c0b 100644 --- a/support/doc/api/openapi.yaml +++ b/support/doc/api/openapi.yaml @@ -1903,6 +1903,22 @@ paths: example: | **[Want to help to translate this video?](https://weblate.framasoft.org/projects/what-is-peertube-video/)**\r\n\r\n**Take back the control of your videos! [#JoinPeertube](https://joinpeertube.org)** + '/videos/{id}/source': + post: + summary: Get video source file metadata + operationId: getVideoSource + tags: + - Video + parameters: + - $ref: '#/components/parameters/idOrUUID' + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/VideoSource' + '/videos/{id}/views': post: summary: Notify user is watching a video @@ -6141,6 +6157,10 @@ components: $ref: '#/components/schemas/VideoConstantString-Language' captionPath: type: string + VideoSource: + properties: + filename: + type: string ActorImage: properties: path: