mirror of https://github.com/Chocobozzz/PeerTube
				
				
				
			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 <me@florianbigard.com>pull/5074/head
							parent
							
								
									dec4952155
								
							
						
					
					
						commit
						2e401e8575
					
				|  | @ -340,6 +340,21 @@ | |||
|           </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> | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ | |||
|       [videoCaptions]="videoCaptions" [waitTranscodingEnabled]="isWaitTranscodingEnabled()" | ||||
|       type="update" (pluginFieldsAdded)="hydratePluginFieldsFromVideo()" | ||||
|       [liveVideo]="liveVideo" [videoToUpdate]="videoDetails" | ||||
|       [videoSource]="videoSource" | ||||
| 
 | ||||
|       (formBuilt)="onFormBuilt()" | ||||
|     ></my-video-edit> | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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') | ||||
|   } | ||||
|  |  | |||
|  | @ -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() | ||||
| 
 | ||||
|  |  | |||
|  | @ -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?
 | ||||
|  |  | |||
|  | @ -24,7 +24,7 @@ import { CONFIG, registerConfigChangedHandler } from './config' | |||
| 
 | ||||
| // ---------------------------------------------------------------------------
 | ||||
| 
 | ||||
| const LAST_MIGRATION_VERSION = 710 | ||||
| const LAST_MIGRATION_VERSION = 715 | ||||
| 
 | ||||
| // ---------------------------------------------------------------------------
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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, | ||||
|  |  | |||
|  | @ -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 | ||||
| } | ||||
|  | @ -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' | ||||
|  |  | |||
|  | @ -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 | ||||
| } | ||||
|  | @ -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() | ||||
|   } | ||||
|  |  | |||
|  | @ -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 | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | @ -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', | ||||
|  |  | |||
|  | @ -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' | ||||
|  |  | |||
|  | @ -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 ]) | ||||
|   }) | ||||
| }) | ||||
|  | @ -16,3 +16,4 @@ import './video-schedule-update' | |||
| import './videos-common-filters' | ||||
| import './videos-history' | ||||
| import './videos-overview' | ||||
| import './video-source' | ||||
|  |  | |||
|  | @ -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 ]) | ||||
|   }) | ||||
| }) | ||||
|  | @ -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 | ||||
|  |  | |||
|  | @ -0,0 +1,3 @@ | |||
| import { VideoSourceModel } from '@server/models/video/video-source' | ||||
| 
 | ||||
| export type MVideoSource = Omit<VideoSourceModel, 'Video'> | ||||
|  | @ -0,0 +1,3 @@ | |||
| export interface VideoSource { | ||||
|   filename: string | ||||
| } | ||||
|  | @ -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 | ||||
|   }) { | ||||
|  |  | |||
|  | @ -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: | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 kontrollanten
						kontrollanten