mirror of
https://github.com/Chocobozzz/PeerTube.git
synced 2025-10-05 02:39:33 +02:00
Separate HLS audio and video streams
Allows: * The HLS player to propose an "Audio only" resolution * The live to output an "Audio only" resolution * The live to ingest and output an "Audio only" stream This feature is under a config for VOD videos and is enabled by default for lives In the future we can imagine: * To propose multiple audio streams for a specific video * To ingest an audio only VOD and just output an audio only "video" (the player would play the audio file and PeerTube would not generate additional resolutions) This commit introduce a new way to download videos: * Add "/download/videos/generate/:videoId" endpoint where PeerTube can mux an audio only and a video only file to a mp4 container * The download client modal introduces a new default panel where the user can choose resolutions it wants to download
This commit is contained in:
parent
e77ba2dfbc
commit
816f346a60
186 changed files with 5748 additions and 2807 deletions
|
@ -1,11 +1,11 @@
|
|||
import express from 'express'
|
||||
import { param } from 'express-validator'
|
||||
import { HttpStatusCode, VideoResolution } from '@peertube/peertube-models'
|
||||
import { isIdValid } from '@server/helpers/custom-validators/misc.js'
|
||||
import { MVideo } from '@server/types/models/index.js'
|
||||
import { HttpStatusCode } from '@peertube/peertube-models'
|
||||
import express from 'express'
|
||||
import { param } from 'express-validator'
|
||||
import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from '../shared/index.js'
|
||||
|
||||
const videoFilesDeleteWebVideoValidator = [
|
||||
export const videoFilesDeleteWebVideoValidator = [
|
||||
isValidVideoIdParam('id'),
|
||||
|
||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
|
@ -34,7 +34,7 @@ const videoFilesDeleteWebVideoValidator = [
|
|||
}
|
||||
]
|
||||
|
||||
const videoFilesDeleteWebVideoFileValidator = [
|
||||
export const videoFilesDeleteWebVideoFileValidator = [
|
||||
isValidVideoIdParam('id'),
|
||||
|
||||
param('videoFileId')
|
||||
|
@ -69,7 +69,7 @@ const videoFilesDeleteWebVideoFileValidator = [
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const videoFilesDeleteHLSValidator = [
|
||||
export const videoFilesDeleteHLSValidator = [
|
||||
isValidVideoIdParam('id'),
|
||||
|
||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
|
@ -98,7 +98,7 @@ const videoFilesDeleteHLSValidator = [
|
|||
}
|
||||
]
|
||||
|
||||
const videoFilesDeleteHLSFileValidator = [
|
||||
export const videoFilesDeleteHLSFileValidator = [
|
||||
isValidVideoIdParam('id'),
|
||||
|
||||
param('videoFileId')
|
||||
|
@ -112,15 +112,19 @@ const videoFilesDeleteHLSFileValidator = [
|
|||
|
||||
if (!checkLocalVideo(video, res)) return
|
||||
|
||||
if (!video.getHLSPlaylist()) {
|
||||
const hls = video.getHLSPlaylist()
|
||||
|
||||
if (!hls) {
|
||||
return res.fail({
|
||||
status: HttpStatusCode.BAD_REQUEST_400,
|
||||
message: 'This video does not have HLS files'
|
||||
})
|
||||
}
|
||||
|
||||
const hlsFiles = video.getHLSPlaylist().VideoFiles
|
||||
if (!hlsFiles.find(f => f.id === +req.params.videoFileId)) {
|
||||
const hlsFiles = hls.VideoFiles
|
||||
const file = hlsFiles.find(f => f.id === +req.params.videoFileId)
|
||||
|
||||
if (!file) {
|
||||
return res.fail({
|
||||
status: HttpStatusCode.NOT_FOUND_404,
|
||||
message: 'This HLS playlist does not have this file id'
|
||||
|
@ -135,18 +139,19 @@ const videoFilesDeleteHLSFileValidator = [
|
|||
})
|
||||
}
|
||||
|
||||
if (hls.hasAudioAndVideoSplitted() && file.resolution === VideoResolution.H_NOVIDEO) {
|
||||
return res.fail({
|
||||
status: HttpStatusCode.BAD_REQUEST_400,
|
||||
message: 'Cannot delete audio file of HLS playlist with splitted audio/video. Delete all the videos first'
|
||||
})
|
||||
}
|
||||
|
||||
return next()
|
||||
}
|
||||
]
|
||||
|
||||
export {
|
||||
videoFilesDeleteWebVideoValidator,
|
||||
videoFilesDeleteWebVideoFileValidator,
|
||||
|
||||
videoFilesDeleteHLSValidator,
|
||||
videoFilesDeleteHLSFileValidator
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Private
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function checkLocalVideo (video: MVideo, res: express.Response) {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import express from 'express'
|
||||
import { param } from 'express-validator'
|
||||
import { HttpStatusCode, UserRight, VideoChangeOwnershipAccept, VideoChangeOwnershipStatus, VideoState } from '@peertube/peertube-models'
|
||||
import { isIdValid } from '@server/helpers/custom-validators/misc.js'
|
||||
import { checkUserCanTerminateOwnershipChange } from '@server/helpers/custom-validators/video-ownership.js'
|
||||
import { AccountModel } from '@server/models/account/account.js'
|
||||
import { MVideoWithAllFiles } from '@server/types/models/index.js'
|
||||
import { HttpStatusCode, UserRight, VideoChangeOwnershipAccept, VideoChangeOwnershipStatus, VideoState } from '@peertube/peertube-models'
|
||||
import express from 'express'
|
||||
import { param } from 'express-validator'
|
||||
import {
|
||||
areValidationErrors,
|
||||
checkUserCanManageVideo,
|
||||
|
@ -15,7 +15,7 @@ import {
|
|||
isValidVideoIdParam
|
||||
} from '../shared/index.js'
|
||||
|
||||
const videosChangeOwnershipValidator = [
|
||||
export const videosChangeOwnershipValidator = [
|
||||
isValidVideoIdParam('videoId'),
|
||||
|
||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
|
@ -36,7 +36,7 @@ const videosChangeOwnershipValidator = [
|
|||
}
|
||||
]
|
||||
|
||||
const videosTerminateChangeOwnershipValidator = [
|
||||
export const videosTerminateChangeOwnershipValidator = [
|
||||
param('id')
|
||||
.custom(isIdValid),
|
||||
|
||||
|
@ -61,7 +61,7 @@ const videosTerminateChangeOwnershipValidator = [
|
|||
}
|
||||
]
|
||||
|
||||
const videosAcceptChangeOwnershipValidator = [
|
||||
export const videosAcceptChangeOwnershipValidator = [
|
||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
const body = req.body as VideoChangeOwnershipAccept
|
||||
if (!await doesVideoChannelOfAccountExist(body.channelId, res.locals.oauth.token.User, res)) return
|
||||
|
@ -76,12 +76,8 @@ const videosAcceptChangeOwnershipValidator = [
|
|||
}
|
||||
]
|
||||
|
||||
export {
|
||||
videosChangeOwnershipValidator,
|
||||
videosTerminateChangeOwnershipValidator,
|
||||
videosAcceptChangeOwnershipValidator
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Private
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function checkCanAccept (video: MVideoWithAllFiles, res: express.Response): Promise<boolean> {
|
||||
|
@ -101,7 +97,7 @@ async function checkCanAccept (video: MVideoWithAllFiles, res: express.Response)
|
|||
|
||||
const user = res.locals.oauth.token.User
|
||||
|
||||
if (!await checkUserQuota(user, video.getMaxQualityFile().size, res)) return false
|
||||
if (!await checkUserQuota(user, video.getMaxQualityBytes(), res)) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -9,11 +9,14 @@ import express from 'express'
|
|||
import { ValidationChain, body, param, query } from 'express-validator'
|
||||
import {
|
||||
exists,
|
||||
hasArrayLength,
|
||||
isBooleanValid,
|
||||
isDateValid,
|
||||
isFileValid,
|
||||
isIdValid,
|
||||
isNotEmptyIntArray,
|
||||
toBooleanOrNull,
|
||||
toIntArray,
|
||||
toIntOrNull,
|
||||
toValueOrNull
|
||||
} from '../../../helpers/custom-validators/misc.js'
|
||||
|
@ -52,8 +55,9 @@ import {
|
|||
isValidVideoPasswordHeader
|
||||
} from '../shared/index.js'
|
||||
import { addDurationToVideoFileIfNeeded, commonVideoFileChecks, isVideoFileAccepted } from './shared/index.js'
|
||||
import { VideoLoadType } from '@server/lib/model-loaders/video.js'
|
||||
|
||||
const videosAddLegacyValidator = getCommonVideoEditAttributes().concat([
|
||||
export const videosAddLegacyValidator = getCommonVideoEditAttributes().concat([
|
||||
body('videofile')
|
||||
.custom((_, { req }) => isFileValid({ files: req.files, field: 'videofile', mimeTypeRegex: null, maxSize: null }))
|
||||
.withMessage('Should have a file'),
|
||||
|
@ -92,7 +96,7 @@ const videosAddLegacyValidator = getCommonVideoEditAttributes().concat([
|
|||
/**
|
||||
* Gets called after the last PUT request
|
||||
*/
|
||||
const videosAddResumableValidator = [
|
||||
export const videosAddResumableValidator = [
|
||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
const user = res.locals.oauth.token.User
|
||||
const file = buildUploadXFile(req.body as express.CustomUploadXFile<express.UploadNewVideoXFileMetadata>)
|
||||
|
@ -130,7 +134,7 @@ const videosAddResumableValidator = [
|
|||
* see https://github.com/kukhariev/node-uploadx/blob/dc9fb4a8ac5a6f481902588e93062f591ec6ef03/packages/core/src/handlers/base-handler.ts
|
||||
*
|
||||
*/
|
||||
const videosAddResumableInitValidator = getCommonVideoEditAttributes().concat([
|
||||
export const videosAddResumableInitValidator = getCommonVideoEditAttributes().concat([
|
||||
body('filename')
|
||||
.custom(isVideoSourceFilenameValid),
|
||||
body('name')
|
||||
|
@ -175,7 +179,7 @@ const videosAddResumableInitValidator = getCommonVideoEditAttributes().concat([
|
|||
}
|
||||
])
|
||||
|
||||
const videosUpdateValidator = getCommonVideoEditAttributes().concat([
|
||||
export const videosUpdateValidator = getCommonVideoEditAttributes().concat([
|
||||
isValidVideoIdParam('id'),
|
||||
|
||||
body('name')
|
||||
|
@ -215,7 +219,7 @@ const videosUpdateValidator = getCommonVideoEditAttributes().concat([
|
|||
}
|
||||
])
|
||||
|
||||
async function checkVideoFollowConstraints (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
export async function checkVideoFollowConstraints (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
const video = getVideoWithAttributes(res)
|
||||
|
||||
// Anybody can watch local videos
|
||||
|
@ -244,7 +248,8 @@ async function checkVideoFollowConstraints (req: express.Request, res: express.R
|
|||
})
|
||||
}
|
||||
|
||||
const videosCustomGetValidator = (fetchType: 'for-api' | 'all' | 'only-video-and-blacklist' | 'unsafe-only-immutable-attributes') => {
|
||||
type FetchType = Extract<VideoLoadType, 'for-api' | 'all' | 'only-video-and-blacklist' | 'unsafe-only-immutable-attributes'>
|
||||
export const videosCustomGetValidator = (fetchType: FetchType) => {
|
||||
return [
|
||||
isValidVideoIdParam('id'),
|
||||
|
||||
|
@ -266,9 +271,9 @@ const videosCustomGetValidator = (fetchType: 'for-api' | 'all' | 'only-video-and
|
|||
]
|
||||
}
|
||||
|
||||
const videosGetValidator = videosCustomGetValidator('all')
|
||||
export const videosGetValidator = videosCustomGetValidator('all')
|
||||
|
||||
const videoFileMetadataGetValidator = getCommonVideoEditAttributes().concat([
|
||||
export const videoFileMetadataGetValidator = getCommonVideoEditAttributes().concat([
|
||||
isValidVideoIdParam('id'),
|
||||
|
||||
param('videoFileId')
|
||||
|
@ -282,7 +287,7 @@ const videoFileMetadataGetValidator = getCommonVideoEditAttributes().concat([
|
|||
}
|
||||
])
|
||||
|
||||
const videosDownloadValidator = [
|
||||
export const videosDownloadValidator = [
|
||||
isValidVideoIdParam('id'),
|
||||
|
||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
|
@ -297,7 +302,20 @@ const videosDownloadValidator = [
|
|||
}
|
||||
]
|
||||
|
||||
const videosRemoveValidator = [
|
||||
export const videosGenerateDownloadValidator = [
|
||||
query('videoFileIds')
|
||||
.customSanitizer(toIntArray)
|
||||
.custom(isNotEmptyIntArray)
|
||||
.custom(v => hasArrayLength(v, { max: 2 })),
|
||||
|
||||
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
if (areValidationErrors(req, res)) return
|
||||
|
||||
return next()
|
||||
}
|
||||
]
|
||||
|
||||
export const videosRemoveValidator = [
|
||||
isValidVideoIdParam('id'),
|
||||
|
||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
|
@ -311,7 +329,7 @@ const videosRemoveValidator = [
|
|||
}
|
||||
]
|
||||
|
||||
const videosOverviewValidator = [
|
||||
export const videosOverviewValidator = [
|
||||
query('page')
|
||||
.optional()
|
||||
.isInt({ min: 1, max: OVERVIEWS.VIDEOS.SAMPLES_COUNT }),
|
||||
|
@ -323,7 +341,7 @@ const videosOverviewValidator = [
|
|||
}
|
||||
]
|
||||
|
||||
function getCommonVideoEditAttributes () {
|
||||
export function getCommonVideoEditAttributes () {
|
||||
return [
|
||||
body('thumbnailfile')
|
||||
.custom((value, { req }) => isVideoImageValid(req.files, 'thumbnailfile')).withMessage(
|
||||
|
@ -406,7 +424,7 @@ function getCommonVideoEditAttributes () {
|
|||
] as (ValidationChain | ExpressPromiseHandler)[]
|
||||
}
|
||||
|
||||
const commonVideosFiltersValidator = [
|
||||
export const commonVideosFiltersValidator = [
|
||||
query('categoryOneOf')
|
||||
.optional()
|
||||
.customSanitizer(arrayify)
|
||||
|
@ -508,23 +526,7 @@ const commonVideosFiltersValidator = [
|
|||
]
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
checkVideoFollowConstraints,
|
||||
commonVideosFiltersValidator,
|
||||
getCommonVideoEditAttributes,
|
||||
videoFileMetadataGetValidator,
|
||||
videosAddLegacyValidator,
|
||||
videosAddResumableInitValidator,
|
||||
videosAddResumableValidator,
|
||||
videosCustomGetValidator,
|
||||
videosDownloadValidator,
|
||||
videosGetValidator,
|
||||
videosOverviewValidator,
|
||||
videosRemoveValidator,
|
||||
videosUpdateValidator
|
||||
}
|
||||
|
||||
// Private
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function areErrorsInScheduleUpdate (req: express.Request, res: express.Response) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue