]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
store uploaded video filename (#4885)
authorkontrollanten <6680299+kontrollanten@users.noreply.github.com>
Tue, 21 Jun 2022 13:31:25 +0000 (15:31 +0200)
committerGitHub <noreply@github.com>
Tue, 21 Jun 2022 13:31:25 +0000 (15:31 +0200)
* 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 <me@florianbigard.com>
25 files changed:
client/src/app/+videos/+video-edit/shared/video-edit.component.html
client/src/app/+videos/+video-edit/shared/video-edit.component.ts
client/src/app/+videos/+video-edit/video-update.component.html
client/src/app/+videos/+video-edit/video-update.component.ts
client/src/app/+videos/+video-edit/video-update.resolver.ts
client/src/app/shared/shared-main/video/video.service.ts
server/controllers/api/videos/index.ts
server/controllers/api/videos/upload.ts
server/initializers/constants.ts
server/initializers/database.ts
server/initializers/migrations/0715-video-source.ts [new file with mode: 0644]
server/middlewares/validators/videos/index.ts
server/middlewares/validators/videos/video-source.ts [new file with mode: 0644]
server/middlewares/validators/videos/videos.ts
server/models/video/video-source.ts [new file with mode: 0644]
server/models/video/video.ts
server/tests/api/check-params/index.ts
server/tests/api/check-params/video-source.ts [new file with mode: 0644]
server/tests/api/videos/index.ts
server/tests/api/videos/video-source.ts [new file with mode: 0644]
server/types/express.d.ts
server/types/models/video/video-source.ts [new file with mode: 0644]
shared/models/videos/video-source.ts [new file with mode: 0644]
shared/server-commands/videos/videos-command.ts
support/doc/api/openapi.yaml

index 595200c3be5e5cf1ebbe13da5c2a640bb99a7dda..650448a74165e6c3548d21a87635d5b9deb9b601 100644 (file)
           </div>
 
           <div class="col-md-12 col-xl-4">
+
+            <div *ngIf="videoSource" class="form-group">
+              <label i18n for="filename">Filename</label>
+
+              <my-help>
+                <ng-template ptTemplate="preHtml">
+                  <ng-container i18n>
+                    Name of the uploaded file
+                  </ng-container>
+                </ng-template>
+              </my-help>
+
+              <input type="text" [disabled]="true" id="filename" class="form-control" [value]="videoSource.filename" />
+            </div>
+
             <div class="form-group originally-published-at">
               <label i18n for="originallyPublishedAt">Original publication date</label>
               <my-help>
index 16b964482a7d72360995dd88a57b1df25fe9d1a2..c74ef5731605aa311ead9b0cc6ac6066de5318c1 100644 (file)
@@ -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<string> & { 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
index 6a32f1477995632015b5028f9774d24f301c7e00..ffd125695b00d4aecb007dcec32d446e2d0c66f6 100644 (file)
@@ -12,6 +12,7 @@
       [videoCaptions]="videoCaptions" [waitTranscodingEnabled]="isWaitTranscodingEnabled()"
       type="update" (pluginFieldsAdded)="hydratePluginFieldsFromVideo()"
       [liveVideo]="liveVideo" [videoToUpdate]="videoDetails"
+      [videoSource]="videoSource"
 
       (formBuilt)="onFormBuilt()"
     ></my-video-edit>
index 9c4998f2e75b3eafcc54b8f528a23b22b827c4db..43e8ba3e566c41b38b4cb5ed674d95ae400746ad 100644 (file)
@@ -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
index 82dae5c1cef83d36ae3d51613855579aef2afe05..db5017340cc548800ad0abe15a206d3a6282c0c6 100644 (file)
@@ -23,7 +23,8 @@ export class VideoUpdateResolver implements Resolve<any> {
     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<any> {
         .loadCompleteDescription(video.descriptionPath)
         .pipe(map(description => Object.assign(video, { description }))),
 
+      this.videoService.getSource(video.id),
+
       listUserChannelsForSelect(this.authService),
 
       this.videoCaptionService
index 1423675068de65ff1633abf291aaab2cd123f3cf..83bc4eeb6a4e551d452d38dad667497c14e1b363 100644 (file)
@@ -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')
   }
index be233722c1d020643f4279dcec663a84ba34055b..d4e08293e85f597975d8294442d1c3eb8442fe8b 100644 (file)
@@ -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()
 
index 3afbedbb2ecd5811851eb33858d30d707fb97e9b..c5890691e9062f1e41b908c3046997ea5824a3f7 100644 (file)
@@ -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?
index f54ce95065fc7d69311ae37bdd21ee3b1a28a5d9..0d7e7077d7b97b198830884fe7cf12e1ae6533f7 100644 (file)
@@ -24,7 +24,7 @@ import { CONFIG, registerConfigChangedHandler } from './config'
 
 // ---------------------------------------------------------------------------
 
-const LAST_MIGRATION_VERSION = 710
+const LAST_MIGRATION_VERSION = 715
 
 // ---------------------------------------------------------------------------
 
index 3576f444cf761b5e20296a422ec8d11b302fa33b..09786a91f7e6a64af63475268e3dcbbf072bc2a6 100644 (file)
@@ -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 (file)
index 0000000..efcf77e
--- /dev/null
@@ -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<void> {
+  {
+    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
+}
index bd2590bc5477f32e7d02c2dd2983c1f3a29bf6e1..1dd7b5d2e7aca4a693a697832479b0ae318a24d7 100644 (file)
@@ -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 (file)
index 0000000..31a2f16
--- /dev/null
@@ -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
+}
index 0b6b8bfe51b1d1f1c0015c728b62845c503a7090..c75c3640bef2ca2244b1dc6f0f51b833b1c5194a 100644 (file)
@@ -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 (file)
index 0000000..e306b16
--- /dev/null
@@ -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<Partial<AttributesOnly<VideoSourceModel>>> {
+  @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
+    }
+  }
+}
index e6a8d3f9550475a3f7a13aa249eeca31194658b0..08adbced6c31ed9db7014d76e8ce72eb31a15360 100644 (file)
@@ -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<Partial<AttributesOnly<VideoModel>>> {
   })
   VideoPlaylistElements: VideoPlaylistElementModel[]
 
+  @HasOne(() => VideoSourceModel, {
+    foreignKey: {
+      name: 'videoId',
+      allowNull: true
+    },
+    onDelete: 'CASCADE'
+  })
+  VideoSource: VideoSourceModel
+
   @HasMany(() => VideoAbuseModel, {
     foreignKey: {
       name: 'videoId',
index 259d7e7834e25e4dad8b5a9c7cca2ee7c8910d46..a27bc85091aaf3891711308f2071e82e9f4c1c10 100644 (file)
@@ -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 (file)
index 0000000..ca324bb
--- /dev/null
@@ -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 ])
+  })
+})
index 27b119f30b0eae351feeecf64ef7115909d0d719..a0b6b01cf30737df7a8bb81b3fc5183e911b60ca 100644 (file)
@@ -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 (file)
index 0000000..e346423
--- /dev/null
@@ -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 ])
+  })
+})
index 7cc13f21d40506b1c7217de3ea64aabd2bdc234c..27e532c313b4dd161cf38e4d044891e5c2170eac 100644 (file)
@@ -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<Express.Multer.File, 'path' | 'filename' | 'size'> & {
+  export type VideoUploadFile = Pick<Express.Multer.File, 'path' | 'filename' | 'size', 'originalname'> & {
     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 (file)
index 0000000..0948f3b
--- /dev/null
@@ -0,0 +1,3 @@
+import { VideoSourceModel } from '@server/models/video/video-source'
+
+export type MVideoSource = Omit<VideoSourceModel, 'Video'>
diff --git a/shared/models/videos/video-source.ts b/shared/models/videos/video-source.ts
new file mode 100644 (file)
index 0000000..57e54fc
--- /dev/null
@@ -0,0 +1,3 @@
+export interface VideoSource {
+  filename: string
+}
index 1cceb58dbd3b259d6ceb264ccbbbc14afb4b5186..e952c9777d0187f502864cd1ace1dfc249578c23 100644 (file)
@@ -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<Omit<VideoCreate, 'thumbnailfile' | 'previewfile'>> & {
   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<VideoSource>({
+      ...options,
+
+      path,
+      implicitToken: true,
+      defaultExpectedStatus: HttpStatusCode.OK_200
+    })
+  }
+
   async getId (options: OverrideCommandOptions & {
     uuid: number | string
   }) {
index bd36c41cdfc6e1ede1dbb564be334d81193797f5..afd310c0b8e2d2f3a097ad3a18a307ac297423fb 100644 (file)
@@ -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: