mirror of
https://github.com/Chocobozzz/PeerTube.git
synced 2024-11-25 02:00:35 -06:00
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>
This commit is contained in:
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,
|
||||
|
34
server/initializers/migrations/0715-video-source.ts
Normal file
34
server/initializers/migrations/0715-video-source.ts
Normal file
@ -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'
|
||||
|
37
server/middlewares/validators/videos/video-source.ts
Normal file
37
server/middlewares/validators/videos/video-source.ts
Normal file
@ -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()
|
||||
}
|
||||
|
55
server/models/video/video-source.ts
Normal file
55
server/models/video/video-source.ts
Normal file
@ -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'
|
||||
|
44
server/tests/api/check-params/video-source.ts
Normal file
44
server/tests/api/check-params/video-source.ts
Normal file
@ -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'
|
||||
|
39
server/tests/api/videos/video-source.ts
Normal file
39
server/tests/api/videos/video-source.ts
Normal file
@ -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 ])
|
||||
})
|
||||
})
|
6
server/types/express.d.ts
vendored
6
server/types/express.d.ts
vendored
@ -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
|
||||
|
3
server/types/models/video/video-source.ts
Normal file
3
server/types/models/video/video-source.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { VideoSourceModel } from '@server/models/video/video-source'
|
||||
|
||||
export type MVideoSource = Omit<VideoSourceModel, 'Video'>
|
3
shared/models/videos/video-source.ts
Normal file
3
shared/models/videos/video-source.ts
Normal file
@ -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
Block a user