PeerTube/server/core/controllers/api/videos/upload.ts

189 lines
6.0 KiB
TypeScript
Raw Normal View History

import { ffprobePromise, getChaptersFromContainer } from '@peertube/peertube-ffmpeg'
import { HttpStatusCode, ThumbnailType, VideoCreate } from '@peertube/peertube-models'
import { uuidToShort } from '@peertube/peertube-node-utils'
import { getResumableUploadPath } from '@server/helpers/upload.js'
import { LocalVideoCreator } from '@server/lib/local-video-creator.js'
import { Redis } from '@server/lib/redis.js'
2024-02-21 07:32:47 +01:00
import { setupUploadResumableRoutes, uploadx } from '@server/lib/uploadx.js'
import { buildNextVideoState } from '@server/lib/video-state.js'
import { openapiOperationDoc } from '@server/middlewares/doc.js'
import express from 'express'
import { VideoAuditView, auditLoggerFactory, getAuditIdFromRes } from '../../../helpers/audit-logger.js'
import { createReqFiles } from '../../../helpers/express-utils.js'
import { logger, loggerTagsFactory } from '../../../helpers/logger.js'
import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../../initializers/constants.js'
import { Hooks } from '../../../lib/plugins/hooks.js'
2021-05-12 14:51:17 +02:00
import {
asyncMiddleware,
asyncRetryTransactionMiddleware,
authenticate,
videosAddLegacyValidator,
videosAddResumableInitValidator,
2022-01-18 09:29:46 +01:00
videosAddResumableValidator
} from '../../../middlewares/index.js'
2021-05-12 14:51:17 +02:00
const lTags = loggerTagsFactory('api', 'video')
const auditLogger = auditLoggerFactory('videos')
const uploadRouter = express.Router()
2021-05-12 14:51:17 +02:00
const reqVideoFileAdd = createReqFiles(
[ 'videofile', 'thumbnailfile', 'previewfile' ],
{ ...MIMETYPES.VIDEO.MIMETYPE_EXT, ...MIMETYPES.IMAGE.MIMETYPE_EXT }
2021-05-12 14:51:17 +02:00
)
const reqVideoFileAddResumable = createReqFiles(
[ 'thumbnailfile', 'previewfile' ],
MIMETYPES.IMAGE.MIMETYPE_EXT,
getResumableUploadPath()
2021-05-12 14:51:17 +02:00
)
uploadRouter.post('/upload',
openapiOperationDoc({ operationId: 'uploadLegacy' }),
2021-05-12 14:51:17 +02:00
authenticate,
reqVideoFileAdd,
asyncMiddleware(videosAddLegacyValidator),
asyncRetryTransactionMiddleware(addVideoLegacy)
)
2024-02-14 10:20:02 +01:00
setupUploadResumableRoutes({
routePath: '/upload-resumable',
router: uploadRouter,
2021-05-12 14:51:17 +02:00
2024-02-14 10:20:02 +01:00
uploadInitBeforeMiddlewares: [
openapiOperationDoc({ operationId: 'uploadResumableInit' }),
reqVideoFileAddResumable
],
2021-05-12 14:51:17 +02:00
2024-02-14 10:20:02 +01:00
uploadInitAfterMiddlewares: [ asyncMiddleware(videosAddResumableInitValidator) ],
uploadDeleteMiddlewares: [ asyncMiddleware(deleteUploadResumableCache) ],
uploadedMiddlewares: [
openapiOperationDoc({ operationId: 'uploadResumable' }),
asyncMiddleware(videosAddResumableValidator)
],
uploadedController: asyncMiddleware(addVideoResumable)
})
2021-05-12 14:51:17 +02:00
// ---------------------------------------------------------------------------
export {
uploadRouter
}
// ---------------------------------------------------------------------------
async function addVideoLegacy (req: express.Request, res: express.Response) {
2021-05-12 14:51:17 +02:00
// Uploading the video could be long
// Set timeout to 10 minutes, as Express's default is 2 minutes
req.setTimeout(1000 * 60 * 10, () => {
logger.error('Video upload has timed out.')
return res.fail({
status: HttpStatusCode.REQUEST_TIMEOUT_408,
message: 'Video upload has timed out.'
})
2021-05-12 14:51:17 +02:00
})
const videoPhysicalFile = req.files['videofile'][0]
const videoInfo: VideoCreate = req.body
const files = req.files
const response = await addVideo({ req, res, videoPhysicalFile, videoInfo, files })
return res.json(response)
2021-05-12 14:51:17 +02:00
}
async function addVideoResumable (req: express.Request, res: express.Response) {
2023-07-19 16:02:49 +02:00
const videoPhysicalFile = res.locals.uploadVideoFileResumable
2021-05-12 14:51:17 +02:00
const videoInfo = videoPhysicalFile.metadata
2023-06-06 11:14:13 +02:00
const files = { previewfile: videoInfo.previewfile, thumbnailfile: videoInfo.thumbnailfile }
2021-05-12 14:51:17 +02:00
const response = await addVideo({ req, res, videoPhysicalFile, videoInfo, files })
await Redis.Instance.deleteUploadSession(req.query.upload_id)
await uploadx.storage.delete(res.locals.uploadVideoFileResumable)
return res.json(response)
2021-05-12 14:51:17 +02:00
}
async function addVideo (options: {
req: express.Request
2021-05-12 14:51:17 +02:00
res: express.Response
2024-02-14 10:20:02 +01:00
videoPhysicalFile: express.VideoLegacyUploadFile
2021-05-12 14:51:17 +02:00
videoInfo: VideoCreate
files: express.UploadFiles
}) {
const { req, res, videoPhysicalFile, videoInfo, files } = options
2021-05-12 14:51:17 +02:00
const ffprobe = await ffprobePromise(videoPhysicalFile.path)
const containerChapters = await getChaptersFromContainer({
path: videoPhysicalFile.path,
maxTitleLength: CONSTRAINTS_FIELDS.VIDEO_CHAPTERS.TITLE.max,
ffprobe
})
2024-02-13 14:23:32 +01:00
logger.debug(`Got ${containerChapters.length} chapters from video "${videoInfo.name}" container`, { containerChapters, ...lTags() })
const thumbnails = [ { type: ThumbnailType.MINIATURE, field: 'thumbnailfile' }, { type: ThumbnailType.PREVIEW, field: 'previewfile' } ]
.filter(({ field }) => !!files?.[field]?.[0])
.map(({ type, field }) => ({
path: files[field][0].path,
type,
automaticallyGenerated: false,
keepOriginal: false
}))
const localVideoCreator = new LocalVideoCreator({
lTags,
videoFile: {
path: videoPhysicalFile.path,
probe: res.locals.ffprobe
},
2024-02-13 14:23:32 +01:00
user: res.locals.oauth.token.User,
channel: res.locals.videoChannel,
chapters: undefined,
fallbackChapters: {
fromDescription: true,
finalFallback: containerChapters
},
videoAttributes: {
...videoInfo,
duration: videoPhysicalFile.duration,
inputFilename: videoPhysicalFile.originalname,
2024-02-13 14:23:32 +01:00
state: buildNextVideoState(),
isLive: false
},
liveAttributes: undefined,
videoAttributeResultHook: 'filter:api.video.upload.video-attribute.result',
thumbnails
2021-05-12 14:51:17 +02:00
})
2024-02-13 14:23:32 +01:00
const { video } = await localVideoCreator.create()
2021-08-30 16:24:25 +02:00
2024-02-13 14:23:32 +01:00
auditLogger.create(getAuditIdFromRes(res), new VideoAuditView(video.toFormattedDetailsJSON()))
logger.info('Video with name %s and uuid %s created.', videoInfo.name, video.uuid, lTags(video.uuid))
2021-05-12 14:51:17 +02:00
2024-02-13 14:23:32 +01:00
Hooks.runAction('action:api.video.uploaded', { video, req, res })
2021-05-12 14:51:17 +02:00
return {
2021-05-12 14:51:17 +02:00
video: {
2024-02-13 14:23:32 +01:00
id: video.id,
shortUUID: uuidToShort(video.uuid),
uuid: video.uuid
2021-05-12 14:51:17 +02:00
}
}
2021-05-12 14:51:17 +02:00
}
async function deleteUploadResumableCache (req: express.Request, res: express.Response, next: express.NextFunction) {
await Redis.Instance.deleteUploadSession(req.query.upload_id)
return next()
}