Process video torrents in order
Prevent update before video torrent generation for example
This commit is contained in:
parent
8366491890
commit
f012319a64
|
@ -1,12 +1,12 @@
|
||||||
import express from 'express'
|
import express from 'express'
|
||||||
import { Transaction } from 'sequelize/types'
|
import { Transaction } from 'sequelize/types'
|
||||||
import { updateTorrentMetadata } from '@server/helpers/webtorrent'
|
|
||||||
import { changeVideoChannelShare } from '@server/lib/activitypub/share'
|
import { changeVideoChannelShare } from '@server/lib/activitypub/share'
|
||||||
|
import { JobQueue } from '@server/lib/job-queue'
|
||||||
import { buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video'
|
import { buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video'
|
||||||
import { openapiOperationDoc } from '@server/middlewares/doc'
|
import { openapiOperationDoc } from '@server/middlewares/doc'
|
||||||
import { FilteredModelAttributes } from '@server/types'
|
import { FilteredModelAttributes } from '@server/types'
|
||||||
import { MVideoFullLight } from '@server/types/models'
|
import { MVideoFullLight } from '@server/types/models'
|
||||||
import { HttpStatusCode, VideoUpdate } from '@shared/models'
|
import { HttpStatusCode, ManageVideoTorrentPayload, VideoUpdate } from '@shared/models'
|
||||||
import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
|
import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
|
||||||
import { resetSequelizeInstance } from '../../../helpers/database-utils'
|
import { resetSequelizeInstance } from '../../../helpers/database-utils'
|
||||||
import { createReqFiles } from '../../../helpers/express-utils'
|
import { createReqFiles } from '../../../helpers/express-utils'
|
||||||
|
@ -139,15 +139,13 @@ async function updateVideo (req: express.Request, res: express.Response) {
|
||||||
return { videoInstanceUpdated, isNewVideo }
|
return { videoInstanceUpdated, isNewVideo }
|
||||||
})
|
})
|
||||||
|
|
||||||
if (videoInstanceUpdated.isLive !== true && videoInfoToUpdate.name) {
|
const refreshedVideo = await updateTorrentsMetadataIfNeeded(videoInstanceUpdated, videoInfoToUpdate)
|
||||||
await updateTorrentsMetadata(videoInstanceUpdated)
|
|
||||||
}
|
|
||||||
|
|
||||||
await sequelizeTypescript.transaction(t => federateVideoIfNeeded(videoInstanceUpdated, isNewVideo, t))
|
await sequelizeTypescript.transaction(t => federateVideoIfNeeded(refreshedVideo, isNewVideo, t))
|
||||||
|
|
||||||
if (wasConfidentialVideo) Notifier.Instance.notifyOnNewVideoIfNeeded(videoInstanceUpdated)
|
if (wasConfidentialVideo) Notifier.Instance.notifyOnNewVideoIfNeeded(refreshedVideo)
|
||||||
|
|
||||||
Hooks.runAction('action:api.video.updated', { video: videoInstanceUpdated, body: req.body, req, res })
|
Hooks.runAction('action:api.video.updated', { video: refreshedVideo, body: req.body, req, res })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Force fields we want to update
|
// Force fields we want to update
|
||||||
// If the transaction is retried, sequelize will think the object has not changed
|
// If the transaction is retried, sequelize will think the object has not changed
|
||||||
|
@ -194,19 +192,25 @@ function updateSchedule (videoInstance: MVideoFullLight, videoInfoToUpdate: Vide
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateTorrentsMetadata (video: MVideoFullLight) {
|
async function updateTorrentsMetadataIfNeeded (video: MVideoFullLight, videoInfoToUpdate: VideoUpdate) {
|
||||||
for (const file of (video.VideoFiles || [])) {
|
if (video.isLive || !videoInfoToUpdate.name) return video
|
||||||
await updateTorrentMetadata(video, file)
|
|
||||||
|
|
||||||
await file.save()
|
for (const file of (video.VideoFiles || [])) {
|
||||||
|
const payload: ManageVideoTorrentPayload = { action: 'update-metadata', videoId: video.id, videoFileId: file.id }
|
||||||
|
|
||||||
|
const job = await JobQueue.Instance.createJobWithPromise({ type: 'manage-video-torrent', payload })
|
||||||
|
await job.finished()
|
||||||
}
|
}
|
||||||
|
|
||||||
const hls = video.getHLSPlaylist()
|
const hls = video.getHLSPlaylist()
|
||||||
if (!hls) return
|
|
||||||
|
|
||||||
for (const file of (hls.VideoFiles || [])) {
|
for (const file of (hls?.VideoFiles || [])) {
|
||||||
await updateTorrentMetadata(hls, file)
|
const payload: ManageVideoTorrentPayload = { action: 'update-metadata', streamingPlaylistId: hls.id, videoFileId: file.id }
|
||||||
|
|
||||||
await file.save()
|
const job = await JobQueue.Instance.createJobWithPromise({ type: 'manage-video-torrent', payload })
|
||||||
|
await job.finished()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Refresh video since files have changed
|
||||||
|
return VideoModel.loadAndPopulateAccountAndServerAndTags(video.id)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,8 @@ import express from 'express'
|
||||||
import { move } from 'fs-extra'
|
import { move } from 'fs-extra'
|
||||||
import { basename } from 'path'
|
import { basename } from 'path'
|
||||||
import { getResumableUploadPath } from '@server/helpers/upload'
|
import { getResumableUploadPath } from '@server/helpers/upload'
|
||||||
import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent'
|
|
||||||
import { getLocalVideoActivityPubUrl } from '@server/lib/activitypub/url'
|
import { getLocalVideoActivityPubUrl } from '@server/lib/activitypub/url'
|
||||||
|
import { JobQueue } from '@server/lib/job-queue'
|
||||||
import { generateWebTorrentVideoFilename } from '@server/lib/paths'
|
import { generateWebTorrentVideoFilename } from '@server/lib/paths'
|
||||||
import { Redis } from '@server/lib/redis'
|
import { Redis } from '@server/lib/redis'
|
||||||
import { uploadx } from '@server/lib/uploadx'
|
import { uploadx } from '@server/lib/uploadx'
|
||||||
|
@ -17,10 +17,10 @@ import {
|
||||||
import { VideoPathManager } from '@server/lib/video-path-manager'
|
import { VideoPathManager } from '@server/lib/video-path-manager'
|
||||||
import { buildNextVideoState } from '@server/lib/video-state'
|
import { buildNextVideoState } from '@server/lib/video-state'
|
||||||
import { openapiOperationDoc } from '@server/middlewares/doc'
|
import { openapiOperationDoc } from '@server/middlewares/doc'
|
||||||
import { MVideo, MVideoFile, MVideoFullLight } from '@server/types/models'
|
import { MVideoFile, MVideoFullLight } from '@server/types/models'
|
||||||
import { getLowercaseExtension } from '@shared/core-utils'
|
import { getLowercaseExtension } from '@shared/core-utils'
|
||||||
import { isAudioFile, uuidToShort } from '@shared/extra-utils'
|
import { isAudioFile, uuidToShort } from '@shared/extra-utils'
|
||||||
import { HttpStatusCode, VideoCreate, VideoResolution, VideoState } from '@shared/models'
|
import { HttpStatusCode, ManageVideoTorrentPayload, VideoCreate, VideoResolution, VideoState } from '@shared/models'
|
||||||
import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
|
import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
|
||||||
import { retryTransactionWrapper } from '../../../helpers/database-utils'
|
import { retryTransactionWrapper } from '../../../helpers/database-utils'
|
||||||
import { createReqFiles } from '../../../helpers/express-utils'
|
import { createReqFiles } from '../../../helpers/express-utils'
|
||||||
|
@ -209,17 +209,22 @@ async function addVideo (options: {
|
||||||
// Channel has a new content, set as updated
|
// Channel has a new content, set as updated
|
||||||
await videoCreated.VideoChannel.setAsUpdated()
|
await videoCreated.VideoChannel.setAsUpdated()
|
||||||
|
|
||||||
createTorrentFederate(video, videoFile)
|
createTorrentFederate(videoCreated, videoFile)
|
||||||
.then(() => {
|
.catch(err => {
|
||||||
if (video.state === VideoState.TO_MOVE_TO_EXTERNAL_STORAGE) {
|
logger.error('Cannot create torrent or federate video for %s.', videoCreated.uuid, { err, ...lTags(videoCreated.uuid) })
|
||||||
return addMoveToObjectStorageJob(video)
|
|
||||||
|
return videoCreated
|
||||||
|
}).then(refreshedVideo => {
|
||||||
|
if (!refreshedVideo) return
|
||||||
|
|
||||||
|
if (refreshedVideo.state === VideoState.TO_MOVE_TO_EXTERNAL_STORAGE) {
|
||||||
|
return addMoveToObjectStorageJob(refreshedVideo)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (video.state === VideoState.TO_TRANSCODE) {
|
if (refreshedVideo.state === VideoState.TO_TRANSCODE) {
|
||||||
return addOptimizeOrMergeAudioJob(videoCreated, videoFile, user)
|
return addOptimizeOrMergeAudioJob(refreshedVideo, videoFile, user)
|
||||||
}
|
}
|
||||||
})
|
}).catch(err => logger.error('Cannot add optimize/merge audio job for %s.', videoCreated.uuid, { err, ...lTags(videoCreated.uuid) }))
|
||||||
.catch(err => logger.error('Cannot add optimize/merge audio job for %s.', videoCreated.uuid, { err, ...lTags(videoCreated.uuid) }))
|
|
||||||
|
|
||||||
Hooks.runAction('action:api.video.uploaded', { video: videoCreated, req, res })
|
Hooks.runAction('action:api.video.uploaded', { video: videoCreated, req, res })
|
||||||
|
|
||||||
|
@ -254,36 +259,23 @@ async function buildNewFile (videoPhysicalFile: express.VideoUploadFile) {
|
||||||
return videoFile
|
return videoFile
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createTorrentAndSetInfoHashAsync (video: MVideo, fileArg: MVideoFile) {
|
async function createTorrentFederate (video: MVideoFullLight, videoFile: MVideoFile) {
|
||||||
await createTorrentAndSetInfoHash(video, fileArg)
|
const payload: ManageVideoTorrentPayload = { videoId: video.id, videoFileId: videoFile.id, action: 'create' }
|
||||||
|
|
||||||
// Refresh videoFile because the createTorrentAndSetInfoHash could be long
|
const job = await JobQueue.Instance.createJobWithPromise({ type: 'manage-video-torrent', payload })
|
||||||
const refreshedFile = await VideoFileModel.loadWithVideo(fileArg.id)
|
await job.finished()
|
||||||
// File does not exist anymore, remove the generated torrent
|
|
||||||
if (!refreshedFile) return fileArg.removeTorrent()
|
|
||||||
|
|
||||||
refreshedFile.infoHash = fileArg.infoHash
|
const refreshedVideo = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.id)
|
||||||
refreshedFile.torrentFilename = fileArg.torrentFilename
|
if (!refreshedVideo) return
|
||||||
|
|
||||||
return refreshedFile.save()
|
// Only federate and notify after the torrent creation
|
||||||
}
|
Notifier.Instance.notifyOnNewVideoIfNeeded(refreshedVideo)
|
||||||
|
|
||||||
function createTorrentFederate (video: MVideoFullLight, videoFile: MVideoFile) {
|
await retryTransactionWrapper(() => {
|
||||||
// Create the torrent file in async way because it could be long
|
return sequelizeTypescript.transaction(t => federateVideoIfNeeded(refreshedVideo, true, t))
|
||||||
return createTorrentAndSetInfoHashAsync(video, videoFile)
|
})
|
||||||
.catch(err => logger.error('Cannot create torrent file for video %s', video.url, { err, ...lTags(video.uuid) }))
|
|
||||||
.then(() => VideoModel.loadAndPopulateAccountAndServerAndTags(video.id))
|
|
||||||
.then(refreshedVideo => {
|
|
||||||
if (!refreshedVideo) return
|
|
||||||
|
|
||||||
// Only federate and notify after the torrent creation
|
return refreshedVideo
|
||||||
Notifier.Instance.notifyOnNewVideoIfNeeded(refreshedVideo)
|
|
||||||
|
|
||||||
return retryTransactionWrapper(() => {
|
|
||||||
return sequelizeTypescript.transaction(t => federateVideoIfNeeded(refreshedVideo, true, t))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(err => logger.error('Cannot federate or notify video creation %s', video.url, { err, ...lTags(video.uuid) }))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteUploadResumableCache (req: express.Request, res: express.Response, next: express.NextFunction) {
|
async function deleteUploadResumableCache (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
|
|
|
@ -153,6 +153,7 @@ const JOB_ATTEMPTS: { [id in JobType]: number } = {
|
||||||
'video-redundancy': 1,
|
'video-redundancy': 1,
|
||||||
'video-live-ending': 1,
|
'video-live-ending': 1,
|
||||||
'video-edition': 1,
|
'video-edition': 1,
|
||||||
|
'manage-video-torrent': 1,
|
||||||
'move-to-object-storage': 3
|
'move-to-object-storage': 3
|
||||||
}
|
}
|
||||||
// Excluded keys are jobs that can be configured by admins
|
// Excluded keys are jobs that can be configured by admins
|
||||||
|
@ -170,6 +171,7 @@ const JOB_CONCURRENCY: { [id in Exclude<JobType, 'video-transcoding' | 'video-im
|
||||||
'video-redundancy': 1,
|
'video-redundancy': 1,
|
||||||
'video-live-ending': 10,
|
'video-live-ending': 10,
|
||||||
'video-edition': 1,
|
'video-edition': 1,
|
||||||
|
'manage-video-torrent': 1,
|
||||||
'move-to-object-storage': 1
|
'move-to-object-storage': 1
|
||||||
}
|
}
|
||||||
const JOB_TTL: { [id in JobType]: number } = {
|
const JOB_TTL: { [id in JobType]: number } = {
|
||||||
|
@ -188,6 +190,7 @@ const JOB_TTL: { [id in JobType]: number } = {
|
||||||
'activitypub-refresher': 60000 * 10, // 10 minutes
|
'activitypub-refresher': 60000 * 10, // 10 minutes
|
||||||
'video-redundancy': 1000 * 3600 * 3, // 3 hours
|
'video-redundancy': 1000 * 3600 * 3, // 3 hours
|
||||||
'video-live-ending': 1000 * 60 * 10, // 10 minutes
|
'video-live-ending': 1000 * 60 * 10, // 10 minutes
|
||||||
|
'manage-video-torrent': 1000 * 3600 * 3, // 3 hours
|
||||||
'move-to-object-storage': 1000 * 60 * 60 * 3 // 3 hours
|
'move-to-object-storage': 1000 * 60 * 60 * 3 // 3 hours
|
||||||
}
|
}
|
||||||
const REPEAT_JOBS: { [ id in JobType ]?: EveryRepeatOptions | CronRepeatOptions } = {
|
const REPEAT_JOBS: { [ id in JobType ]?: EveryRepeatOptions | CronRepeatOptions } = {
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
import { Job } from 'bull'
|
||||||
|
import { createTorrentAndSetInfoHash, updateTorrentMetadata } from '@server/helpers/webtorrent'
|
||||||
|
import { VideoModel } from '@server/models/video/video'
|
||||||
|
import { VideoFileModel } from '@server/models/video/video-file'
|
||||||
|
import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist'
|
||||||
|
import { ManageVideoTorrentPayload } from '@shared/models'
|
||||||
|
import { logger } from '../../../helpers/logger'
|
||||||
|
|
||||||
|
async function processManageVideoTorrent (job: Job) {
|
||||||
|
const payload = job.data as ManageVideoTorrentPayload
|
||||||
|
logger.info('Processing torrent in job %d.', job.id)
|
||||||
|
|
||||||
|
if (payload.action === 'create') return doCreateAction(payload)
|
||||||
|
if (payload.action === 'update-metadata') return doUpdateMetadataAction(payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export {
|
||||||
|
processManageVideoTorrent
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
async function doCreateAction (payload: ManageVideoTorrentPayload & { action: 'create' }) {
|
||||||
|
const [ video, file ] = await Promise.all([
|
||||||
|
loadVideoOrLog(payload.videoId),
|
||||||
|
loadFileOrLog(payload.videoFileId)
|
||||||
|
])
|
||||||
|
|
||||||
|
await createTorrentAndSetInfoHash(video, file)
|
||||||
|
|
||||||
|
// Refresh videoFile because the createTorrentAndSetInfoHash could be long
|
||||||
|
const refreshedFile = await VideoFileModel.loadWithVideo(file.id)
|
||||||
|
// File does not exist anymore, remove the generated torrent
|
||||||
|
if (!refreshedFile) return file.removeTorrent()
|
||||||
|
|
||||||
|
refreshedFile.infoHash = file.infoHash
|
||||||
|
refreshedFile.torrentFilename = file.torrentFilename
|
||||||
|
|
||||||
|
return refreshedFile.save()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function doUpdateMetadataAction (payload: ManageVideoTorrentPayload & { action: 'update-metadata' }) {
|
||||||
|
const [ video, streamingPlaylist, file ] = await Promise.all([
|
||||||
|
loadVideoOrLog(payload.videoId),
|
||||||
|
loadStreamingPlaylistOrLog(payload.streamingPlaylistId),
|
||||||
|
loadFileOrLog(payload.videoFileId)
|
||||||
|
])
|
||||||
|
|
||||||
|
await updateTorrentMetadata(video || streamingPlaylist, file)
|
||||||
|
|
||||||
|
await file.save()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadVideoOrLog (videoId: number) {
|
||||||
|
if (!videoId) return undefined
|
||||||
|
|
||||||
|
const video = await VideoModel.load(videoId)
|
||||||
|
if (!video) {
|
||||||
|
logger.debug('Do not process torrent for video %d: does not exist anymore.', videoId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return video
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadStreamingPlaylistOrLog (streamingPlaylistId: number) {
|
||||||
|
if (!streamingPlaylistId) return undefined
|
||||||
|
|
||||||
|
const streamingPlaylist = await VideoStreamingPlaylistModel.loadWithVideo(streamingPlaylistId)
|
||||||
|
if (!streamingPlaylist) {
|
||||||
|
logger.debug('Do not process torrent for streaming playlist %d: does not exist anymore.', streamingPlaylistId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return streamingPlaylist
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadFileOrLog (videoFileId: number) {
|
||||||
|
if (!videoFileId) return undefined
|
||||||
|
|
||||||
|
const file = await VideoFileModel.loadWithVideo(videoFileId)
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
logger.debug('Do not process torrent for file %d: does not exist anymore.', videoFileId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return file
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ import {
|
||||||
EmailPayload,
|
EmailPayload,
|
||||||
JobState,
|
JobState,
|
||||||
JobType,
|
JobType,
|
||||||
|
ManageVideoTorrentPayload,
|
||||||
MoveObjectStoragePayload,
|
MoveObjectStoragePayload,
|
||||||
RefreshPayload,
|
RefreshPayload,
|
||||||
VideoEditionPayload,
|
VideoEditionPayload,
|
||||||
|
@ -31,6 +32,7 @@ import { processActivityPubHttpUnicast } from './handlers/activitypub-http-unica
|
||||||
import { refreshAPObject } from './handlers/activitypub-refresher'
|
import { refreshAPObject } from './handlers/activitypub-refresher'
|
||||||
import { processActorKeys } from './handlers/actor-keys'
|
import { processActorKeys } from './handlers/actor-keys'
|
||||||
import { processEmail } from './handlers/email'
|
import { processEmail } from './handlers/email'
|
||||||
|
import { processManageVideoTorrent } from './handlers/manage-video-torrent'
|
||||||
import { processMoveToObjectStorage } from './handlers/move-to-object-storage'
|
import { processMoveToObjectStorage } from './handlers/move-to-object-storage'
|
||||||
import { processVideoEdition } from './handlers/video-edition'
|
import { processVideoEdition } from './handlers/video-edition'
|
||||||
import { processVideoFileImport } from './handlers/video-file-import'
|
import { processVideoFileImport } from './handlers/video-file-import'
|
||||||
|
@ -56,6 +58,7 @@ type CreateJobArgument =
|
||||||
{ type: 'video-redundancy', payload: VideoRedundancyPayload } |
|
{ type: 'video-redundancy', payload: VideoRedundancyPayload } |
|
||||||
{ type: 'delete-resumable-upload-meta-file', payload: DeleteResumableUploadMetaFilePayload } |
|
{ type: 'delete-resumable-upload-meta-file', payload: DeleteResumableUploadMetaFilePayload } |
|
||||||
{ type: 'video-edition', payload: VideoEditionPayload } |
|
{ type: 'video-edition', payload: VideoEditionPayload } |
|
||||||
|
{ type: 'manage-video-torrent', payload: ManageVideoTorrentPayload } |
|
||||||
{ type: 'move-to-object-storage', payload: MoveObjectStoragePayload }
|
{ type: 'move-to-object-storage', payload: MoveObjectStoragePayload }
|
||||||
|
|
||||||
export type CreateJobOptions = {
|
export type CreateJobOptions = {
|
||||||
|
@ -79,6 +82,7 @@ const handlers: { [id in JobType]: (job: Job) => Promise<any> } = {
|
||||||
'actor-keys': processActorKeys,
|
'actor-keys': processActorKeys,
|
||||||
'video-redundancy': processVideoRedundancy,
|
'video-redundancy': processVideoRedundancy,
|
||||||
'move-to-object-storage': processMoveToObjectStorage,
|
'move-to-object-storage': processMoveToObjectStorage,
|
||||||
|
'manage-video-torrent': processManageVideoTorrent,
|
||||||
'video-edition': processVideoEdition
|
'video-edition': processVideoEdition
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,6 +102,7 @@ const jobTypes: JobType[] = [
|
||||||
'actor-keys',
|
'actor-keys',
|
||||||
'video-live-ending',
|
'video-live-ending',
|
||||||
'move-to-object-storage',
|
'move-to-object-storage',
|
||||||
|
'manage-video-torrent',
|
||||||
'video-edition'
|
'video-edition'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -185,7 +190,7 @@ class JobQueue {
|
||||||
}
|
}
|
||||||
|
|
||||||
createJobWithPromise (obj: CreateJobArgument, options: CreateJobOptions = {}) {
|
createJobWithPromise (obj: CreateJobArgument, options: CreateJobOptions = {}) {
|
||||||
const queue = this.queues[obj.type]
|
const queue: Queue = this.queues[obj.type]
|
||||||
if (queue === undefined) {
|
if (queue === undefined) {
|
||||||
logger.error('Unknown queue %s: cannot create job.', obj.type)
|
logger.error('Unknown queue %s: cannot create job.', obj.type)
|
||||||
return
|
return
|
||||||
|
|
|
@ -1683,6 +1683,24 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
|
||||||
return peertubeTruncate(this.description, { length: maxLength })
|
return peertubeTruncate(this.description, { length: maxLength })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAllFiles () {
|
||||||
|
let files: MVideoFile[] = []
|
||||||
|
|
||||||
|
if (Array.isArray(this.VideoFiles)) {
|
||||||
|
files = files.concat(this.VideoFiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(this.VideoStreamingPlaylists)) {
|
||||||
|
for (const p of this.VideoStreamingPlaylists) {
|
||||||
|
if (Array.isArray(p.VideoFiles)) {
|
||||||
|
files = files.concat(p.VideoFiles)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
|
||||||
probeMaxQualityFile () {
|
probeMaxQualityFile () {
|
||||||
const file = this.getMaxQualityFile()
|
const file = this.getMaxQualityFile()
|
||||||
const videoOrPlaylist = file.getVideoOrStreamingPlaylist()
|
const videoOrPlaylist = file.getVideoOrStreamingPlaylist()
|
||||||
|
|
|
@ -20,6 +20,7 @@ export type JobType =
|
||||||
| 'video-redundancy'
|
| 'video-redundancy'
|
||||||
| 'video-live-ending'
|
| 'video-live-ending'
|
||||||
| 'actor-keys'
|
| 'actor-keys'
|
||||||
|
| 'manage-video-torrent'
|
||||||
| 'move-to-object-storage'
|
| 'move-to-object-storage'
|
||||||
| 'video-edition'
|
| 'video-edition'
|
||||||
|
|
||||||
|
@ -96,6 +97,20 @@ export type VideoRedundancyPayload = {
|
||||||
videoId: number
|
videoId: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ManageVideoTorrentPayload =
|
||||||
|
{
|
||||||
|
action: 'create'
|
||||||
|
videoId: number
|
||||||
|
videoFileId: number
|
||||||
|
} | {
|
||||||
|
action: 'update-metadata'
|
||||||
|
|
||||||
|
videoId?: number
|
||||||
|
streamingPlaylistId?: number
|
||||||
|
|
||||||
|
videoFileId: number
|
||||||
|
}
|
||||||
|
|
||||||
// Video transcoding payloads
|
// Video transcoding payloads
|
||||||
|
|
||||||
interface BaseTranscodingPayload {
|
interface BaseTranscodingPayload {
|
||||||
|
|
Loading…
Reference in New Issue