Implement video transcoding on server side
This commit is contained in:
parent
f0adb2701c
commit
40298b0254
19 changed files with 344 additions and 128 deletions
|
@ -41,7 +41,14 @@ user:
|
||||||
video_quota: -1
|
video_quota: -1
|
||||||
|
|
||||||
# If enabled, the video will be transcoded to mp4 (x264) with "faststart" flag
|
# If enabled, the video will be transcoded to mp4 (x264) with "faststart" flag
|
||||||
# Uses a lot of CPU!
|
# In addition, if some resolutions are enabled the mp4 video file will be transcoded to these new resolutions.
|
||||||
|
# Uses a lot of CPU and increases storage!
|
||||||
transcoding:
|
transcoding:
|
||||||
enabled: false
|
enabled: false
|
||||||
threads: 2
|
threads: 2
|
||||||
|
resolutions: # Only created if the original video has a higher resolution
|
||||||
|
240p: true
|
||||||
|
360p: true
|
||||||
|
480p: true
|
||||||
|
720p: true
|
||||||
|
1080p: true
|
||||||
|
|
|
@ -39,13 +39,12 @@ import {
|
||||||
getFormattedObjects,
|
getFormattedObjects,
|
||||||
renamePromise
|
renamePromise
|
||||||
} from '../../../helpers'
|
} from '../../../helpers'
|
||||||
import { TagInstance } from '../../../models'
|
import { TagInstance, VideoInstance } from '../../../models'
|
||||||
import { VideoCreate, VideoUpdate } from '../../../../shared'
|
import { VideoCreate, VideoUpdate, VideoResolution } from '../../../../shared'
|
||||||
|
|
||||||
import { abuseVideoRouter } from './abuse'
|
import { abuseVideoRouter } from './abuse'
|
||||||
import { blacklistRouter } from './blacklist'
|
import { blacklistRouter } from './blacklist'
|
||||||
import { rateVideoRouter } from './rate'
|
import { rateVideoRouter } from './rate'
|
||||||
import { VideoInstance } from '../../../models/video/video-interface'
|
|
||||||
|
|
||||||
const videosRouter = express.Router()
|
const videosRouter = express.Router()
|
||||||
|
|
||||||
|
@ -195,7 +194,7 @@ function addVideo (req: express.Request, res: express.Response, videoPhysicalFil
|
||||||
.then(({ author, tagInstances, video }) => {
|
.then(({ author, tagInstances, video }) => {
|
||||||
const videoFileData = {
|
const videoFileData = {
|
||||||
extname: extname(videoPhysicalFile.filename),
|
extname: extname(videoPhysicalFile.filename),
|
||||||
resolution: 0, // TODO: improve readability,
|
resolution: VideoResolution.ORIGINAL,
|
||||||
size: videoPhysicalFile.size
|
size: videoPhysicalFile.size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,7 +229,7 @@ function addVideo (req: express.Request, res: express.Response, videoPhysicalFil
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
JobScheduler.Instance.createJob(t, 'videoTranscoder', dataInput)
|
JobScheduler.Instance.createJob(t, 'videoFileOptimizer', dataInput)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,9 @@ import {
|
||||||
rename,
|
rename,
|
||||||
unlink,
|
unlink,
|
||||||
writeFile,
|
writeFile,
|
||||||
access
|
access,
|
||||||
|
stat,
|
||||||
|
Stats
|
||||||
} from 'fs'
|
} from 'fs'
|
||||||
import * as mkdirp from 'mkdirp'
|
import * as mkdirp from 'mkdirp'
|
||||||
import * as bcrypt from 'bcrypt'
|
import * as bcrypt from 'bcrypt'
|
||||||
|
@ -92,6 +94,7 @@ const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt)
|
||||||
const bcryptHashPromise = promisify2<any, string|number, string>(bcrypt.hash)
|
const bcryptHashPromise = promisify2<any, string|number, string>(bcrypt.hash)
|
||||||
const createTorrentPromise = promisify2<string, any, any>(createTorrent)
|
const createTorrentPromise = promisify2<string, any, any>(createTorrent)
|
||||||
const rimrafPromise = promisify1WithVoid<string>(rimraf)
|
const rimrafPromise = promisify1WithVoid<string>(rimraf)
|
||||||
|
const statPromise = promisify1<string, Stats>(stat)
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -115,5 +118,6 @@ export {
|
||||||
bcryptGenSaltPromise,
|
bcryptGenSaltPromise,
|
||||||
bcryptHashPromise,
|
bcryptHashPromise,
|
||||||
createTorrentPromise,
|
createTorrentPromise,
|
||||||
rimrafPromise
|
rimrafPromise,
|
||||||
|
statPromise
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import * as Promise from 'bluebird'
|
||||||
import { pseudoRandomBytesPromise } from './core-utils'
|
import { pseudoRandomBytesPromise } from './core-utils'
|
||||||
import { CONFIG, database as db } from '../initializers'
|
import { CONFIG, database as db } from '../initializers'
|
||||||
import { ResultList } from '../../shared'
|
import { ResultList } from '../../shared'
|
||||||
|
import { VideoResolution } from '../../shared/models/videos/video-resolution.enum'
|
||||||
|
|
||||||
function badRequest (req: express.Request, res: express.Response, next: express.NextFunction) {
|
function badRequest (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
res.type('json').status(400).end()
|
res.type('json').status(400).end()
|
||||||
|
@ -13,11 +14,11 @@ function generateRandomString (size: number) {
|
||||||
return pseudoRandomBytesPromise(size).then(raw => raw.toString('hex'))
|
return pseudoRandomBytesPromise(size).then(raw => raw.toString('hex'))
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FormatableToJSON {
|
interface FormattableToJSON {
|
||||||
toFormattedJSON ()
|
toFormattedJSON ()
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFormattedObjects<U, T extends FormatableToJSON> (objects: T[], objectsTotal: number) {
|
function getFormattedObjects<U, T extends FormattableToJSON> (objects: T[], objectsTotal: number) {
|
||||||
const formattedObjects: U[] = []
|
const formattedObjects: U[] = []
|
||||||
|
|
||||||
objects.forEach(object => {
|
objects.forEach(object => {
|
||||||
|
@ -47,6 +48,27 @@ function isSignupAllowed () {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function computeResolutionsToTranscode (videoFileHeight: number) {
|
||||||
|
const resolutionsEnabled: number[] = []
|
||||||
|
const configResolutions = CONFIG.TRANSCODING.RESOLUTIONS
|
||||||
|
|
||||||
|
const resolutions = [
|
||||||
|
VideoResolution.H_240P,
|
||||||
|
VideoResolution.H_360P,
|
||||||
|
VideoResolution.H_480P,
|
||||||
|
VideoResolution.H_720P,
|
||||||
|
VideoResolution.H_1080P
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const resolution of resolutions) {
|
||||||
|
if (configResolutions[resolution.toString()] === true && videoFileHeight >= resolution) {
|
||||||
|
resolutionsEnabled.push(resolution)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolutionsEnabled
|
||||||
|
}
|
||||||
|
|
||||||
type SortType = { sortModel: any, sortValue: string }
|
type SortType = { sortModel: any, sortValue: string }
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
@ -56,5 +78,6 @@ export {
|
||||||
generateRandomString,
|
generateRandomString,
|
||||||
getFormattedObjects,
|
getFormattedObjects,
|
||||||
isSignupAllowed,
|
isSignupAllowed,
|
||||||
|
computeResolutionsToTranscode,
|
||||||
SortType
|
SortType
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,8 @@ import {
|
||||||
RequestEndpoint,
|
RequestEndpoint,
|
||||||
RequestVideoEventType,
|
RequestVideoEventType,
|
||||||
RequestVideoQaduType,
|
RequestVideoQaduType,
|
||||||
JobState
|
JobState,
|
||||||
|
VideoResolution
|
||||||
} from '../../shared/models'
|
} from '../../shared/models'
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
@ -85,7 +86,14 @@ const CONFIG = {
|
||||||
},
|
},
|
||||||
TRANSCODING: {
|
TRANSCODING: {
|
||||||
ENABLED: config.get<boolean>('transcoding.enabled'),
|
ENABLED: config.get<boolean>('transcoding.enabled'),
|
||||||
THREADS: config.get<number>('transcoding.threads')
|
THREADS: config.get<number>('transcoding.threads'),
|
||||||
|
RESOLUTIONS: {
|
||||||
|
'240' : config.get<boolean>('transcoding.resolutions.240p'),
|
||||||
|
'360': config.get<boolean>('transcoding.resolutions.360p'),
|
||||||
|
'480': config.get<boolean>('transcoding.resolutions.480p'),
|
||||||
|
'720': config.get<boolean>('transcoding.resolutions.720p'),
|
||||||
|
'1080': config.get<boolean>('transcoding.resolutions.1080p')
|
||||||
|
}
|
||||||
},
|
},
|
||||||
CACHE: {
|
CACHE: {
|
||||||
PREVIEWS: {
|
PREVIEWS: {
|
||||||
|
@ -144,7 +152,7 @@ const VIDEO_CATEGORIES = {
|
||||||
9: 'Comedy',
|
9: 'Comedy',
|
||||||
10: 'Entertainment',
|
10: 'Entertainment',
|
||||||
11: 'News',
|
11: 'News',
|
||||||
12: 'Howto',
|
12: 'How To',
|
||||||
13: 'Education',
|
13: 'Education',
|
||||||
14: 'Activism',
|
14: 'Activism',
|
||||||
15: 'Science & Technology',
|
15: 'Science & Technology',
|
||||||
|
@ -179,15 +187,17 @@ const VIDEO_LANGUAGES = {
|
||||||
11: 'German',
|
11: 'German',
|
||||||
12: 'Korean',
|
12: 'Korean',
|
||||||
13: 'French',
|
13: 'French',
|
||||||
14: 'Italien'
|
14: 'Italian'
|
||||||
}
|
}
|
||||||
|
|
||||||
const VIDEO_FILE_RESOLUTIONS = {
|
// TODO: use VideoResolution when https://github.com/Microsoft/TypeScript/issues/13042 is fixed
|
||||||
|
const VIDEO_FILE_RESOLUTIONS: { [ id: number ]: string } = {
|
||||||
0: 'original',
|
0: 'original',
|
||||||
1: '360p',
|
240: '240p',
|
||||||
2: '480p',
|
360: '360p',
|
||||||
3: '720p',
|
480: '480p',
|
||||||
4: '1080p'
|
720: '720p',
|
||||||
|
1080: '1080p'
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
@ -202,7 +212,7 @@ const FRIEND_SCORE = {
|
||||||
|
|
||||||
// Number of points we add/remove from a friend after a successful/bad request
|
// Number of points we add/remove from a friend after a successful/bad request
|
||||||
const PODS_SCORE = {
|
const PODS_SCORE = {
|
||||||
MALUS: -10,
|
PENALTY: -10,
|
||||||
BONUS: 10
|
BONUS: 10
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import * as videoTranscoder from './video-transcoder'
|
import * as videoFileOptimizer from './video-file-optimizer'
|
||||||
|
import * as videoFileTranscoder from './video-file-transcoder'
|
||||||
|
|
||||||
export interface JobHandler<T> {
|
export interface JobHandler<T> {
|
||||||
process (data: object): T
|
process (data: object): T
|
||||||
|
@ -7,7 +8,8 @@ export interface JobHandler<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const jobHandlers: { [ handlerName: string ]: JobHandler<any> } = {
|
const jobHandlers: { [ handlerName: string ]: JobHandler<any> } = {
|
||||||
videoTranscoder
|
videoFileOptimizer,
|
||||||
|
videoFileTranscoder
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|
78
server/lib/jobs/handlers/video-file-optimizer.ts
Normal file
78
server/lib/jobs/handlers/video-file-optimizer.ts
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
import * as Promise from 'bluebird'
|
||||||
|
|
||||||
|
import { database as db } from '../../../initializers/database'
|
||||||
|
import { logger, computeResolutionsToTranscode } from '../../../helpers'
|
||||||
|
import { VideoInstance } from '../../../models'
|
||||||
|
import { addVideoToFriends } from '../../friends'
|
||||||
|
import { JobScheduler } from '../job-scheduler'
|
||||||
|
|
||||||
|
function process (data: { videoUUID: string }) {
|
||||||
|
return db.Video.loadByUUIDAndPopulateAuthorAndPodAndTags(data.videoUUID).then(video => {
|
||||||
|
return video.optimizeOriginalVideofile().then(() => video)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function onError (err: Error, jobId: number) {
|
||||||
|
logger.error('Error when optimized video file in job %d.', jobId, err)
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSuccess (jobId: number, video: VideoInstance) {
|
||||||
|
logger.info('Job %d is a success.', jobId)
|
||||||
|
|
||||||
|
video.toAddRemoteJSON()
|
||||||
|
.then(remoteVideo => {
|
||||||
|
// Now we'll add the video's meta data to our friends
|
||||||
|
return addVideoToFriends(remoteVideo, null)
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return video.getOriginalFileHeight()
|
||||||
|
})
|
||||||
|
.then(originalFileHeight => {
|
||||||
|
// Create transcoding jobs if there are enabled resolutions
|
||||||
|
const resolutionsEnabled = computeResolutionsToTranscode(originalFileHeight)
|
||||||
|
logger.info(
|
||||||
|
'Resolutions computed for video %s and origin file height of %d.', video.uuid, originalFileHeight,
|
||||||
|
{ resolutions: resolutionsEnabled }
|
||||||
|
)
|
||||||
|
|
||||||
|
if (resolutionsEnabled.length === 0) return undefined
|
||||||
|
|
||||||
|
return db.sequelize.transaction(t => {
|
||||||
|
const tasks: Promise<any>[] = []
|
||||||
|
|
||||||
|
resolutionsEnabled.forEach(resolution => {
|
||||||
|
const dataInput = {
|
||||||
|
videoUUID: video.uuid,
|
||||||
|
resolution
|
||||||
|
}
|
||||||
|
|
||||||
|
const p = JobScheduler.Instance.createJob(t, 'videoFileTranscoder', dataInput)
|
||||||
|
tasks.push(p)
|
||||||
|
})
|
||||||
|
|
||||||
|
return Promise.all(tasks).then(() => resolutionsEnabled)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(resolutionsEnabled => {
|
||||||
|
if (resolutionsEnabled === undefined) {
|
||||||
|
logger.info('No transcoding jobs created for video %s (no resolutions enabled).')
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info('Transcoding jobs created for uuid %s.', video.uuid, { resolutionsEnabled })
|
||||||
|
})
|
||||||
|
.catch((err: Error) => {
|
||||||
|
logger.debug('Cannot transcode the video.', err)
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export {
|
||||||
|
process,
|
||||||
|
onError,
|
||||||
|
onSuccess
|
||||||
|
}
|
|
@ -1,13 +1,12 @@
|
||||||
import { database as db } from '../../../initializers/database'
|
import { database as db } from '../../../initializers/database'
|
||||||
|
import { updateVideoToFriends } from '../../friends'
|
||||||
import { logger } from '../../../helpers'
|
import { logger } from '../../../helpers'
|
||||||
import { addVideoToFriends } from '../../../lib'
|
|
||||||
import { VideoInstance } from '../../../models'
|
import { VideoInstance } from '../../../models'
|
||||||
|
import { VideoResolution } from '../../../../shared'
|
||||||
|
|
||||||
function process (data: { videoUUID: string }) {
|
function process (data: { videoUUID: string, resolution: VideoResolution }) {
|
||||||
return db.Video.loadByUUIDAndPopulateAuthorAndPodAndTags(data.videoUUID).then(video => {
|
return db.Video.loadByUUIDAndPopulateAuthorAndPodAndTags(data.videoUUID).then(video => {
|
||||||
// TODO: handle multiple resolutions
|
return video.transcodeOriginalVideofile(data.resolution).then(() => video)
|
||||||
const videoFile = video.VideoFiles[0]
|
|
||||||
return video.transcodeVideofile(videoFile).then(() => video)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,10 +18,10 @@ function onError (err: Error, jobId: number) {
|
||||||
function onSuccess (jobId: number, video: VideoInstance) {
|
function onSuccess (jobId: number, video: VideoInstance) {
|
||||||
logger.info('Job %d is a success.', jobId)
|
logger.info('Job %d is a success.', jobId)
|
||||||
|
|
||||||
video.toAddRemoteJSON().then(remoteVideo => {
|
const remoteVideo = video.toUpdateRemoteJSON()
|
||||||
// Now we'll add the video's meta data to our friends
|
|
||||||
return addVideoToFriends(remoteVideo, null)
|
// Now we'll add the video's meta data to our friends
|
||||||
})
|
return updateVideoToFriends(remoteVideo, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
|
@ -219,7 +219,7 @@ updatePodsScore = function (goodPods: number[], badPods: number[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (badPods.length !== 0) {
|
if (badPods.length !== 0) {
|
||||||
incrementScores(badPods, PODS_SCORE.MALUS)
|
incrementScores(badPods, PODS_SCORE.PENALTY)
|
||||||
.then(() => removeBadPods())
|
.then(() => removeBadPods())
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
if (err) logger.error('Cannot decrement scores of bad pods.', err)
|
if (err) logger.error('Cannot decrement scores of bad pods.', err)
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
isUserDisplayNSFWValid,
|
isUserDisplayNSFWValid,
|
||||||
isUserVideoQuotaValid
|
isUserVideoQuotaValid
|
||||||
} from '../../helpers'
|
} from '../../helpers'
|
||||||
|
import { VideoResolution } from '../../../shared'
|
||||||
|
|
||||||
import { addMethodsToModel } from '../utils'
|
import { addMethodsToModel } from '../utils'
|
||||||
import {
|
import {
|
||||||
|
@ -245,7 +246,7 @@ function getOriginalVideoFileTotalFromUser (user: UserInstance) {
|
||||||
// attributes = [] because we don't want other fields than the sum
|
// attributes = [] because we don't want other fields than the sum
|
||||||
const query = {
|
const query = {
|
||||||
where: {
|
where: {
|
||||||
resolution: 0 // Original, TODO: improve readability
|
resolution: VideoResolution.ORIGINAL
|
||||||
},
|
},
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -7,60 +7,17 @@ import { VideoFileAttributes, VideoFileInstance } from './video-file-interface'
|
||||||
|
|
||||||
// Don't use barrel, import just what we need
|
// Don't use barrel, import just what we need
|
||||||
import { Video as FormattedVideo } from '../../../shared/models/videos/video.model'
|
import { Video as FormattedVideo } from '../../../shared/models/videos/video.model'
|
||||||
|
import { RemoteVideoUpdateData } from '../../../shared/models/pods/remote-video/remote-video-update-request.model'
|
||||||
|
import { RemoteVideoCreateData } from '../../../shared/models/pods/remote-video/remote-video-create-request.model'
|
||||||
import { ResultList } from '../../../shared/models/result-list.model'
|
import { ResultList } from '../../../shared/models/result-list.model'
|
||||||
|
|
||||||
export type FormattedRemoteVideoFile = {
|
|
||||||
infoHash: string
|
|
||||||
resolution: number
|
|
||||||
extname: string
|
|
||||||
size: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export type FormattedAddRemoteVideo = {
|
|
||||||
uuid: string
|
|
||||||
name: string
|
|
||||||
category: number
|
|
||||||
licence: number
|
|
||||||
language: number
|
|
||||||
nsfw: boolean
|
|
||||||
description: string
|
|
||||||
author: string
|
|
||||||
duration: number
|
|
||||||
thumbnailData: string
|
|
||||||
tags: string[]
|
|
||||||
createdAt: Date
|
|
||||||
updatedAt: Date
|
|
||||||
views: number
|
|
||||||
likes: number
|
|
||||||
dislikes: number
|
|
||||||
files: FormattedRemoteVideoFile[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export type FormattedUpdateRemoteVideo = {
|
|
||||||
uuid: string
|
|
||||||
name: string
|
|
||||||
category: number
|
|
||||||
licence: number
|
|
||||||
language: number
|
|
||||||
nsfw: boolean
|
|
||||||
description: string
|
|
||||||
author: string
|
|
||||||
duration: number
|
|
||||||
tags: string[]
|
|
||||||
createdAt: Date
|
|
||||||
updatedAt: Date
|
|
||||||
views: number
|
|
||||||
likes: number
|
|
||||||
dislikes: number
|
|
||||||
files: FormattedRemoteVideoFile[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace VideoMethods {
|
export namespace VideoMethods {
|
||||||
export type GetThumbnailName = (this: VideoInstance) => string
|
export type GetThumbnailName = (this: VideoInstance) => string
|
||||||
export type GetPreviewName = (this: VideoInstance) => string
|
export type GetPreviewName = (this: VideoInstance) => string
|
||||||
export type IsOwned = (this: VideoInstance) => boolean
|
export type IsOwned = (this: VideoInstance) => boolean
|
||||||
export type ToFormattedJSON = (this: VideoInstance) => FormattedVideo
|
export type ToFormattedJSON = (this: VideoInstance) => FormattedVideo
|
||||||
|
|
||||||
|
export type GetOriginalFile = (this: VideoInstance) => VideoFileInstance
|
||||||
export type GenerateMagnetUri = (this: VideoInstance, videoFile: VideoFileInstance) => string
|
export type GenerateMagnetUri = (this: VideoInstance, videoFile: VideoFileInstance) => string
|
||||||
export type GetTorrentFileName = (this: VideoInstance, videoFile: VideoFileInstance) => string
|
export type GetTorrentFileName = (this: VideoInstance, videoFile: VideoFileInstance) => string
|
||||||
export type GetVideoFilename = (this: VideoInstance, videoFile: VideoFileInstance) => string
|
export type GetVideoFilename = (this: VideoInstance, videoFile: VideoFileInstance) => string
|
||||||
|
@ -69,10 +26,12 @@ export namespace VideoMethods {
|
||||||
export type GetVideoFilePath = (this: VideoInstance, videoFile: VideoFileInstance) => string
|
export type GetVideoFilePath = (this: VideoInstance, videoFile: VideoFileInstance) => string
|
||||||
export type CreateTorrentAndSetInfoHash = (this: VideoInstance, videoFile: VideoFileInstance) => Promise<void>
|
export type CreateTorrentAndSetInfoHash = (this: VideoInstance, videoFile: VideoFileInstance) => Promise<void>
|
||||||
|
|
||||||
export type ToAddRemoteJSON = (this: VideoInstance) => Promise<FormattedAddRemoteVideo>
|
export type ToAddRemoteJSON = (this: VideoInstance) => Promise<RemoteVideoCreateData>
|
||||||
export type ToUpdateRemoteJSON = (this: VideoInstance) => FormattedUpdateRemoteVideo
|
export type ToUpdateRemoteJSON = (this: VideoInstance) => RemoteVideoUpdateData
|
||||||
|
|
||||||
export type TranscodeVideofile = (this: VideoInstance, inputVideoFile: VideoFileInstance) => Promise<void>
|
export type OptimizeOriginalVideofile = (this: VideoInstance) => Promise<void>
|
||||||
|
export type TranscodeOriginalVideofile = (this: VideoInstance, resolution: number) => Promise<void>
|
||||||
|
export type GetOriginalFileHeight = (this: VideoInstance) => Promise<number>
|
||||||
|
|
||||||
// Return thumbnail name
|
// Return thumbnail name
|
||||||
export type GenerateThumbnailFromData = (video: VideoInstance, thumbnailData: string) => Promise<string>
|
export type GenerateThumbnailFromData = (video: VideoInstance, thumbnailData: string) => Promise<string>
|
||||||
|
@ -147,6 +106,7 @@ export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.In
|
||||||
createPreview: VideoMethods.CreatePreview
|
createPreview: VideoMethods.CreatePreview
|
||||||
createThumbnail: VideoMethods.CreateThumbnail
|
createThumbnail: VideoMethods.CreateThumbnail
|
||||||
createTorrentAndSetInfoHash: VideoMethods.CreateTorrentAndSetInfoHash
|
createTorrentAndSetInfoHash: VideoMethods.CreateTorrentAndSetInfoHash
|
||||||
|
getOriginalFile: VideoMethods.GetOriginalFile
|
||||||
generateMagnetUri: VideoMethods.GenerateMagnetUri
|
generateMagnetUri: VideoMethods.GenerateMagnetUri
|
||||||
getPreviewName: VideoMethods.GetPreviewName
|
getPreviewName: VideoMethods.GetPreviewName
|
||||||
getThumbnailName: VideoMethods.GetThumbnailName
|
getThumbnailName: VideoMethods.GetThumbnailName
|
||||||
|
@ -161,9 +121,12 @@ export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.In
|
||||||
toAddRemoteJSON: VideoMethods.ToAddRemoteJSON
|
toAddRemoteJSON: VideoMethods.ToAddRemoteJSON
|
||||||
toFormattedJSON: VideoMethods.ToFormattedJSON
|
toFormattedJSON: VideoMethods.ToFormattedJSON
|
||||||
toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON
|
toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON
|
||||||
transcodeVideofile: VideoMethods.TranscodeVideofile
|
optimizeOriginalVideofile: VideoMethods.OptimizeOriginalVideofile
|
||||||
|
transcodeOriginalVideofile: VideoMethods.TranscodeOriginalVideofile
|
||||||
|
getOriginalFileHeight: VideoMethods.GetOriginalFileHeight
|
||||||
|
|
||||||
setTags: Sequelize.HasManySetAssociationsMixin<TagAttributes, string>
|
setTags: Sequelize.HasManySetAssociationsMixin<TagAttributes, string>
|
||||||
|
addVideoFile: Sequelize.HasManyAddAssociationMixin<VideoFileAttributes, string>
|
||||||
setVideoFiles: Sequelize.HasManySetAssociationsMixin<VideoFileAttributes, string>
|
setVideoFiles: Sequelize.HasManySetAssociationsMixin<VideoFileAttributes, string>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,8 @@ import {
|
||||||
unlinkPromise,
|
unlinkPromise,
|
||||||
renamePromise,
|
renamePromise,
|
||||||
writeFilePromise,
|
writeFilePromise,
|
||||||
createTorrentPromise
|
createTorrentPromise,
|
||||||
|
statPromise
|
||||||
} from '../../helpers'
|
} from '../../helpers'
|
||||||
import {
|
import {
|
||||||
CONFIG,
|
CONFIG,
|
||||||
|
@ -35,7 +36,8 @@ import {
|
||||||
VIDEO_FILE_RESOLUTIONS
|
VIDEO_FILE_RESOLUTIONS
|
||||||
} from '../../initializers'
|
} from '../../initializers'
|
||||||
import { removeVideoToFriends } from '../../lib'
|
import { removeVideoToFriends } from '../../lib'
|
||||||
import { VideoFileInstance } from './video-file-interface'
|
import { VideoResolution } from '../../../shared'
|
||||||
|
import { VideoFileInstance, VideoFileModel } from './video-file-interface'
|
||||||
|
|
||||||
import { addMethodsToModel, getSort } from '../utils'
|
import { addMethodsToModel, getSort } from '../utils'
|
||||||
import {
|
import {
|
||||||
|
@ -46,6 +48,7 @@ import {
|
||||||
} from './video-interface'
|
} from './video-interface'
|
||||||
|
|
||||||
let Video: Sequelize.Model<VideoInstance, VideoAttributes>
|
let Video: Sequelize.Model<VideoInstance, VideoAttributes>
|
||||||
|
let getOriginalFile: VideoMethods.GetOriginalFile
|
||||||
let generateMagnetUri: VideoMethods.GenerateMagnetUri
|
let generateMagnetUri: VideoMethods.GenerateMagnetUri
|
||||||
let getVideoFilename: VideoMethods.GetVideoFilename
|
let getVideoFilename: VideoMethods.GetVideoFilename
|
||||||
let getThumbnailName: VideoMethods.GetThumbnailName
|
let getThumbnailName: VideoMethods.GetThumbnailName
|
||||||
|
@ -55,11 +58,13 @@ let isOwned: VideoMethods.IsOwned
|
||||||
let toFormattedJSON: VideoMethods.ToFormattedJSON
|
let toFormattedJSON: VideoMethods.ToFormattedJSON
|
||||||
let toAddRemoteJSON: VideoMethods.ToAddRemoteJSON
|
let toAddRemoteJSON: VideoMethods.ToAddRemoteJSON
|
||||||
let toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON
|
let toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON
|
||||||
let transcodeVideofile: VideoMethods.TranscodeVideofile
|
let optimizeOriginalVideofile: VideoMethods.OptimizeOriginalVideofile
|
||||||
|
let transcodeOriginalVideofile: VideoMethods.TranscodeOriginalVideofile
|
||||||
let createPreview: VideoMethods.CreatePreview
|
let createPreview: VideoMethods.CreatePreview
|
||||||
let createThumbnail: VideoMethods.CreateThumbnail
|
let createThumbnail: VideoMethods.CreateThumbnail
|
||||||
let getVideoFilePath: VideoMethods.GetVideoFilePath
|
let getVideoFilePath: VideoMethods.GetVideoFilePath
|
||||||
let createTorrentAndSetInfoHash: VideoMethods.CreateTorrentAndSetInfoHash
|
let createTorrentAndSetInfoHash: VideoMethods.CreateTorrentAndSetInfoHash
|
||||||
|
let getOriginalFileHeight: VideoMethods.GetOriginalFileHeight
|
||||||
|
|
||||||
let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData
|
let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData
|
||||||
let getDurationFromFile: VideoMethods.GetDurationFromFile
|
let getDurationFromFile: VideoMethods.GetDurationFromFile
|
||||||
|
@ -251,6 +256,7 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
|
||||||
getTorrentFileName,
|
getTorrentFileName,
|
||||||
getVideoFilename,
|
getVideoFilename,
|
||||||
getVideoFilePath,
|
getVideoFilePath,
|
||||||
|
getOriginalFile,
|
||||||
isOwned,
|
isOwned,
|
||||||
removeFile,
|
removeFile,
|
||||||
removePreview,
|
removePreview,
|
||||||
|
@ -259,7 +265,9 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
|
||||||
toAddRemoteJSON,
|
toAddRemoteJSON,
|
||||||
toFormattedJSON,
|
toFormattedJSON,
|
||||||
toUpdateRemoteJSON,
|
toUpdateRemoteJSON,
|
||||||
transcodeVideofile
|
optimizeOriginalVideofile,
|
||||||
|
transcodeOriginalVideofile,
|
||||||
|
getOriginalFileHeight
|
||||||
]
|
]
|
||||||
addMethodsToModel(Video, classMethods, instanceMethods)
|
addMethodsToModel(Video, classMethods, instanceMethods)
|
||||||
|
|
||||||
|
@ -327,9 +335,14 @@ function afterDestroy (video: VideoInstance, options: { transaction: Sequelize.T
|
||||||
return Promise.all(tasks)
|
return Promise.all(tasks)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getOriginalFile = function (this: VideoInstance) {
|
||||||
|
if (Array.isArray(this.VideoFiles) === false) return undefined
|
||||||
|
|
||||||
|
return this.VideoFiles.find(file => file.resolution === VideoResolution.ORIGINAL)
|
||||||
|
}
|
||||||
|
|
||||||
getVideoFilename = function (this: VideoInstance, videoFile: VideoFileInstance) {
|
getVideoFilename = function (this: VideoInstance, videoFile: VideoFileInstance) {
|
||||||
// return this.uuid + '-' + VIDEO_FILE_RESOLUTIONS[videoFile.resolution] + videoFile.extname
|
return this.uuid + '-' + VIDEO_FILE_RESOLUTIONS[videoFile.resolution] + videoFile.extname
|
||||||
return this.uuid + videoFile.extname
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getThumbnailName = function (this: VideoInstance) {
|
getThumbnailName = function (this: VideoInstance) {
|
||||||
|
@ -345,8 +358,7 @@ getPreviewName = function (this: VideoInstance) {
|
||||||
|
|
||||||
getTorrentFileName = function (this: VideoInstance, videoFile: VideoFileInstance) {
|
getTorrentFileName = function (this: VideoInstance, videoFile: VideoFileInstance) {
|
||||||
const extension = '.torrent'
|
const extension = '.torrent'
|
||||||
// return this.uuid + '-' + VIDEO_FILE_RESOLUTIONS[videoFile.resolution] + extension
|
return this.uuid + '-' + VIDEO_FILE_RESOLUTIONS[videoFile.resolution] + extension
|
||||||
return this.uuid + extension
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isOwned = function (this: VideoInstance) {
|
isOwned = function (this: VideoInstance) {
|
||||||
|
@ -552,9 +564,10 @@ toUpdateRemoteJSON = function (this: VideoInstance) {
|
||||||
return json
|
return json
|
||||||
}
|
}
|
||||||
|
|
||||||
transcodeVideofile = function (this: VideoInstance, inputVideoFile: VideoFileInstance) {
|
optimizeOriginalVideofile = function (this: VideoInstance) {
|
||||||
const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
|
const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
|
||||||
const newExtname = '.mp4'
|
const newExtname = '.mp4'
|
||||||
|
const inputVideoFile = this.getOriginalFile()
|
||||||
const videoInputPath = join(videosDirectory, this.getVideoFilename(inputVideoFile))
|
const videoInputPath = join(videosDirectory, this.getVideoFilename(inputVideoFile))
|
||||||
const videoOutputPath = join(videosDirectory, this.id + '-transcoded' + newExtname)
|
const videoOutputPath = join(videosDirectory, this.id + '-transcoded' + newExtname)
|
||||||
|
|
||||||
|
@ -574,6 +587,12 @@ transcodeVideofile = function (this: VideoInstance, inputVideoFile: VideoFileIns
|
||||||
|
|
||||||
return renamePromise(videoOutputPath, this.getVideoFilePath(inputVideoFile))
|
return renamePromise(videoOutputPath, this.getVideoFilePath(inputVideoFile))
|
||||||
})
|
})
|
||||||
|
.then(() => {
|
||||||
|
return statPromise(this.getVideoFilePath(inputVideoFile))
|
||||||
|
})
|
||||||
|
.then(stats => {
|
||||||
|
return inputVideoFile.set('size', stats.size)
|
||||||
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return this.createTorrentAndSetInfoHash(inputVideoFile)
|
return this.createTorrentAndSetInfoHash(inputVideoFile)
|
||||||
})
|
})
|
||||||
|
@ -594,6 +613,74 @@ transcodeVideofile = function (this: VideoInstance, inputVideoFile: VideoFileIns
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transcodeOriginalVideofile = function (this: VideoInstance, resolution: VideoResolution) {
|
||||||
|
const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
|
||||||
|
const extname = '.mp4'
|
||||||
|
|
||||||
|
// We are sure it's x264 in mp4 because optimizeOriginalVideofile was already executed
|
||||||
|
const videoInputPath = join(videosDirectory, this.getVideoFilename(this.getOriginalFile()))
|
||||||
|
|
||||||
|
const newVideoFile = (Video['sequelize'].models.VideoFile as VideoFileModel).build({
|
||||||
|
resolution,
|
||||||
|
extname,
|
||||||
|
size: 0,
|
||||||
|
videoId: this.id
|
||||||
|
})
|
||||||
|
const videoOutputPath = join(videosDirectory, this.getVideoFilename(newVideoFile))
|
||||||
|
const resolutionWidthSizes = {
|
||||||
|
1: '240x?',
|
||||||
|
2: '360x?',
|
||||||
|
3: '480x?',
|
||||||
|
4: '720x?',
|
||||||
|
5: '1080x?'
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise<void>((res, rej) => {
|
||||||
|
ffmpeg(videoInputPath)
|
||||||
|
.output(videoOutputPath)
|
||||||
|
.videoCodec('libx264')
|
||||||
|
.size(resolutionWidthSizes[resolution])
|
||||||
|
.outputOption('-threads ' + CONFIG.TRANSCODING.THREADS)
|
||||||
|
.outputOption('-movflags faststart')
|
||||||
|
.on('error', rej)
|
||||||
|
.on('end', () => {
|
||||||
|
return statPromise(videoOutputPath)
|
||||||
|
.then(stats => {
|
||||||
|
newVideoFile.set('size', stats.size)
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return this.createTorrentAndSetInfoHash(newVideoFile)
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return newVideoFile.save()
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return this.VideoFiles.push(newVideoFile)
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return res()
|
||||||
|
})
|
||||||
|
.catch(rej)
|
||||||
|
})
|
||||||
|
.run()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
getOriginalFileHeight = function (this: VideoInstance) {
|
||||||
|
const originalFilePath = this.getVideoFilePath(this.getOriginalFile())
|
||||||
|
|
||||||
|
return new Promise<number>((res, rej) => {
|
||||||
|
ffmpeg.ffprobe(originalFilePath, (err, metadata) => {
|
||||||
|
if (err) return rej(err)
|
||||||
|
|
||||||
|
const videoStream = metadata.streams.find(s => s.codec_type === 'video')
|
||||||
|
return res(videoStream.height)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
removeThumbnail = function (this: VideoInstance) {
|
removeThumbnail = function (this: VideoInstance) {
|
||||||
const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName())
|
const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName())
|
||||||
return unlinkPromise(thumbnailPath)
|
return unlinkPromise(thumbnailPath)
|
||||||
|
|
|
@ -129,7 +129,7 @@ describe('Test multiple pods', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should upload the video on pod 2 and propagate on each pod', async function () {
|
it('Should upload the video on pod 2 and propagate on each pod', async function () {
|
||||||
this.timeout(60000)
|
this.timeout(120000)
|
||||||
|
|
||||||
const videoAttributes = {
|
const videoAttributes = {
|
||||||
name: 'my super name for pod 2',
|
name: 'my super name for pod 2',
|
||||||
|
@ -143,12 +143,12 @@ describe('Test multiple pods', function () {
|
||||||
}
|
}
|
||||||
await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
|
await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
|
||||||
|
|
||||||
// Transcoding, so wait more that 22 seconds
|
// Transcoding, so wait more than 22000
|
||||||
await wait(42000)
|
await wait(60000)
|
||||||
|
|
||||||
// All pods should have this video
|
// All pods should have this video
|
||||||
for (const server of servers) {
|
for (const server of servers) {
|
||||||
let baseMagnet = null
|
let baseMagnet = {}
|
||||||
|
|
||||||
const res = await getVideosList(server.url)
|
const res = await getVideosList(server.url)
|
||||||
|
|
||||||
|
@ -172,27 +172,50 @@ describe('Test multiple pods', function () {
|
||||||
expect(dateIsValid(video.updatedAt)).to.be.true
|
expect(dateIsValid(video.updatedAt)).to.be.true
|
||||||
expect(video.author).to.equal('root')
|
expect(video.author).to.equal('root')
|
||||||
|
|
||||||
expect(video.files).to.have.lengthOf(1)
|
expect(video.files).to.have.lengthOf(5)
|
||||||
|
|
||||||
const file = video.files[0]
|
// Check common attributes
|
||||||
const magnetUri = file.magnetUri
|
for (const file of video.files) {
|
||||||
expect(file.magnetUri).to.have.lengthOf.above(2)
|
expect(file.magnetUri).to.have.lengthOf.above(2)
|
||||||
expect(file.resolution).to.equal(0)
|
|
||||||
expect(file.resolutionLabel).to.equal('original')
|
|
||||||
expect(file.size).to.equal(942961)
|
|
||||||
|
|
||||||
if (server.url !== 'http://localhost:9002') {
|
if (server.url !== 'http://localhost:9002') {
|
||||||
expect(video.isLocal).to.be.false
|
expect(video.isLocal).to.be.false
|
||||||
} else {
|
} else {
|
||||||
expect(video.isLocal).to.be.true
|
expect(video.isLocal).to.be.true
|
||||||
|
}
|
||||||
|
|
||||||
|
// All pods should have the same magnet Uri
|
||||||
|
if (baseMagnet[file.resolution] === undefined) {
|
||||||
|
baseMagnet[file.resolution] = file.magnet
|
||||||
|
} else {
|
||||||
|
expect(baseMagnet[file.resolution]).to.equal(file.magnet)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// All pods should have the same magnet Uri
|
const originalFile = video.files.find(f => f.resolution === 0)
|
||||||
if (baseMagnet === null) {
|
expect(originalFile).not.to.be.undefined
|
||||||
baseMagnet = magnetUri
|
expect(originalFile.resolutionLabel).to.equal('original')
|
||||||
} else {
|
expect(originalFile.size).to.equal(711327)
|
||||||
expect(baseMagnet).to.equal(magnetUri)
|
|
||||||
}
|
const file240p = video.files.find(f => f.resolution === 1)
|
||||||
|
expect(file240p).not.to.be.undefined
|
||||||
|
expect(file240p.resolutionLabel).to.equal('240p')
|
||||||
|
expect(file240p.size).to.equal(139953)
|
||||||
|
|
||||||
|
const file360p = video.files.find(f => f.resolution === 2)
|
||||||
|
expect(file360p).not.to.be.undefined
|
||||||
|
expect(file360p.resolutionLabel).to.equal('360p')
|
||||||
|
expect(file360p.size).to.equal(169926)
|
||||||
|
|
||||||
|
const file480p = video.files.find(f => f.resolution === 3)
|
||||||
|
expect(file480p).not.to.be.undefined
|
||||||
|
expect(file480p.resolutionLabel).to.equal('480p')
|
||||||
|
expect(file480p.size).to.equal(206758)
|
||||||
|
|
||||||
|
const file720p = video.files.find(f => f.resolution === 4)
|
||||||
|
expect(file720p).not.to.be.undefined
|
||||||
|
expect(file720p.resolutionLabel).to.equal('720p')
|
||||||
|
expect(file720p.size).to.equal(314913)
|
||||||
|
|
||||||
const test = await testVideoImage(server.url, 'video_short2.webm', video.thumbnailPath)
|
const test = await testVideoImage(server.url, 'video_short2.webm', video.thumbnailPath)
|
||||||
expect(test).to.equal(true)
|
expect(test).to.equal(true)
|
||||||
|
|
|
@ -42,6 +42,8 @@ describe('Test video transcoding', function () {
|
||||||
|
|
||||||
const res = await getVideosList(servers[0].url)
|
const res = await getVideosList(servers[0].url)
|
||||||
const video = res.body.data[0]
|
const video = res.body.data[0]
|
||||||
|
expect(video.files).to.have.lengthOf(1)
|
||||||
|
|
||||||
const magnetUri = video.files[0].magnetUri
|
const magnetUri = video.files[0].magnetUri
|
||||||
expect(magnetUri).to.match(/\.webm/)
|
expect(magnetUri).to.match(/\.webm/)
|
||||||
|
|
||||||
|
@ -66,6 +68,8 @@ describe('Test video transcoding', function () {
|
||||||
const res = await getVideosList(servers[1].url)
|
const res = await getVideosList(servers[1].url)
|
||||||
|
|
||||||
const video = res.body.data[0]
|
const video = res.body.data[0]
|
||||||
|
expect(video.files).to.have.lengthOf(5)
|
||||||
|
|
||||||
const magnetUri = video.files[0].magnetUri
|
const magnetUri = video.files[0].magnetUri
|
||||||
expect(magnetUri).to.match(/\.mp4/)
|
expect(magnetUri).to.match(/\.mp4/)
|
||||||
|
|
||||||
|
|
|
@ -12,14 +12,15 @@ import {
|
||||||
runServer,
|
runServer,
|
||||||
ServerInfo,
|
ServerInfo,
|
||||||
setAccessTokensToServers,
|
setAccessTokensToServers,
|
||||||
uploadVideo
|
uploadVideo,
|
||||||
|
wait
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
|
|
||||||
describe('Test update host scripts', function () {
|
describe('Test update host scripts', function () {
|
||||||
let server: ServerInfo
|
let server: ServerInfo
|
||||||
|
|
||||||
before(async function () {
|
before(async function () {
|
||||||
this.timeout(30000)
|
this.timeout(60000)
|
||||||
|
|
||||||
await flushTests()
|
await flushTests()
|
||||||
|
|
||||||
|
@ -28,36 +29,43 @@ describe('Test update host scripts', function () {
|
||||||
port: 9256
|
port: 9256
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
server = await runServer(1, overrideConfig)
|
// Run server 2 to have transcoding enabled
|
||||||
|
server = await runServer(2, overrideConfig)
|
||||||
await setAccessTokensToServers([ server ])
|
await setAccessTokensToServers([ server ])
|
||||||
|
|
||||||
// Upload two videos for our needs
|
// Upload two videos for our needs
|
||||||
const videoAttributes = {}
|
const videoAttributes = {}
|
||||||
await uploadVideo(server.url, server.accessToken, videoAttributes)
|
await uploadVideo(server.url, server.accessToken, videoAttributes)
|
||||||
await uploadVideo(server.url, server.accessToken, videoAttributes)
|
await uploadVideo(server.url, server.accessToken, videoAttributes)
|
||||||
|
await wait(30000)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should update torrent hosts', async function () {
|
it('Should update torrent hosts', async function () {
|
||||||
this.timeout(30000)
|
this.timeout(30000)
|
||||||
|
|
||||||
killallServers([ server ])
|
killallServers([ server ])
|
||||||
server = await runServer(1)
|
// Run server with standard configuration
|
||||||
|
server = await runServer(2)
|
||||||
|
|
||||||
const env = getEnvCli(server)
|
const env = getEnvCli(server)
|
||||||
await execCLI(`${env} npm run update-host`)
|
await execCLI(`${env} npm run update-host`)
|
||||||
|
|
||||||
const res = await getVideosList(server.url)
|
const res = await getVideosList(server.url)
|
||||||
const videos = res.body.data
|
const videos = res.body.data
|
||||||
|
expect(videos).to.have.lengthOf(2)
|
||||||
|
|
||||||
expect(videos[0].files[0].magnetUri).to.contain('localhost%3A9001%2Ftracker%2Fsocket')
|
for (const video of videos) {
|
||||||
expect(videos[0].files[0].magnetUri).to.contain('localhost%3A9001%2Fstatic%2Fwebseed%2F')
|
expect(video.files).to.have.lengthOf(5)
|
||||||
|
|
||||||
expect(videos[1].files[0].magnetUri).to.contain('localhost%3A9001%2Ftracker%2Fsocket')
|
for (const file of video.files) {
|
||||||
expect(videos[1].files[0].magnetUri).to.contain('localhost%3A9001%2Fstatic%2Fwebseed%2F')
|
expect(file.magnetUri).to.contain('localhost%3A9002%2Ftracker%2Fsocket')
|
||||||
|
expect(file.magnetUri).to.contain('localhost%3A9002%2Fstatic%2Fwebseed%2F')
|
||||||
|
|
||||||
const torrent = await parseTorrentVideo(server, videos[0].uuid)
|
const torrent = await parseTorrentVideo(server, video.uuid, file.resolutionLabel)
|
||||||
expect(torrent.announce[0]).to.equal('ws://localhost:9001/tracker/socket')
|
expect(torrent.announce[0]).to.equal('ws://localhost:9002/tracker/socket')
|
||||||
expect(torrent.urlList[0]).to.contain('http://localhost:9001/static/webseed')
|
expect(torrent.urlList[0]).to.contain('http://localhost:9002/static/webseed')
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
after(async function () {
|
after(async function () {
|
||||||
|
|
|
@ -238,9 +238,10 @@ function rateVideo (url: string, accessToken: string, id: number, rating: string
|
||||||
.expect(specialStatus)
|
.expect(specialStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTorrentVideo (server: ServerInfo, videoUUID: string) {
|
function parseTorrentVideo (server: ServerInfo, videoUUID: string, resolutionLabel: string) {
|
||||||
return new Promise<any>((res, rej) => {
|
return new Promise<any>((res, rej) => {
|
||||||
const torrentPath = join(__dirname, '..', '..', '..', 'test' + server.serverNumber, 'torrents', videoUUID + '.torrent')
|
const torrentName = videoUUID + '-' + resolutionLabel + '.torrent'
|
||||||
|
const torrentPath = join(__dirname, '..', '..', '..', 'test' + server.serverNumber, 'torrents', torrentName)
|
||||||
readFile(torrentPath, (err, data) => {
|
readFile(torrentPath, (err, data) => {
|
||||||
if (err) return rej(err)
|
if (err) return rej(err)
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,6 @@ export interface RemoteVideoUpdateData {
|
||||||
uuid: string
|
uuid: string
|
||||||
tags: string[]
|
tags: string[]
|
||||||
name: string
|
name: string
|
||||||
extname: string
|
|
||||||
infoHash: string
|
|
||||||
category: number
|
category: number
|
||||||
licence: number
|
licence: number
|
||||||
language: number
|
language: number
|
||||||
|
|
|
@ -6,5 +6,6 @@ export * from './video-abuse.model'
|
||||||
export * from './video-blacklist.model'
|
export * from './video-blacklist.model'
|
||||||
export * from './video-create.model'
|
export * from './video-create.model'
|
||||||
export * from './video-rate.type'
|
export * from './video-rate.type'
|
||||||
|
export * from './video-resolution.enum'
|
||||||
export * from './video-update.model'
|
export * from './video-update.model'
|
||||||
export * from './video.model'
|
export * from './video.model'
|
||||||
|
|
8
shared/models/videos/video-resolution.enum.ts
Normal file
8
shared/models/videos/video-resolution.enum.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
export enum VideoResolution {
|
||||||
|
ORIGINAL = 0,
|
||||||
|
H_240P = 240,
|
||||||
|
H_360P = 360,
|
||||||
|
H_480P = 480,
|
||||||
|
H_720P = 720,
|
||||||
|
H_1080P = 1080
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue