video_quota: -1
# 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:
enabled: false
threads: 2
+ resolutions: # Only created if the original video has a higher resolution
+ 240p: true
+ 360p: true
+ 480p: true
+ 720p: true
+ 1080p: true
getFormattedObjects,
renamePromise
} from '../../../helpers'
-import { TagInstance } from '../../../models'
-import { VideoCreate, VideoUpdate } from '../../../../shared'
+import { TagInstance, VideoInstance } from '../../../models'
+import { VideoCreate, VideoUpdate, VideoResolution } from '../../../../shared'
import { abuseVideoRouter } from './abuse'
import { blacklistRouter } from './blacklist'
import { rateVideoRouter } from './rate'
-import { VideoInstance } from '../../../models/video/video-interface'
const videosRouter = express.Router()
.then(({ author, tagInstances, video }) => {
const videoFileData = {
extname: extname(videoPhysicalFile.filename),
- resolution: 0, // TODO: improve readability,
+ resolution: VideoResolution.ORIGINAL,
size: videoPhysicalFile.size
}
}
tasks.push(
- JobScheduler.Instance.createJob(t, 'videoTranscoder', dataInput)
+ JobScheduler.Instance.createJob(t, 'videoFileOptimizer', dataInput)
)
}
rename,
unlink,
writeFile,
- access
+ access,
+ stat,
+ Stats
} from 'fs'
import * as mkdirp from 'mkdirp'
import * as bcrypt from 'bcrypt'
const bcryptHashPromise = promisify2<any, string|number, string>(bcrypt.hash)
const createTorrentPromise = promisify2<string, any, any>(createTorrent)
const rimrafPromise = promisify1WithVoid<string>(rimraf)
+const statPromise = promisify1<string, Stats>(stat)
// ---------------------------------------------------------------------------
bcryptGenSaltPromise,
bcryptHashPromise,
createTorrentPromise,
- rimrafPromise
+ rimrafPromise,
+ statPromise
}
import { pseudoRandomBytesPromise } from './core-utils'
import { CONFIG, database as db } from '../initializers'
import { ResultList } from '../../shared'
+import { VideoResolution } from '../../shared/models/videos/video-resolution.enum'
function badRequest (req: express.Request, res: express.Response, next: express.NextFunction) {
res.type('json').status(400).end()
return pseudoRandomBytesPromise(size).then(raw => raw.toString('hex'))
}
-interface FormatableToJSON {
+interface FormattableToJSON {
toFormattedJSON ()
}
-function getFormattedObjects<U, T extends FormatableToJSON> (objects: T[], objectsTotal: number) {
+function getFormattedObjects<U, T extends FormattableToJSON> (objects: T[], objectsTotal: number) {
const formattedObjects: U[] = []
objects.forEach(object => {
})
}
+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 }
// ---------------------------------------------------------------------------
generateRandomString,
getFormattedObjects,
isSignupAllowed,
+ computeResolutionsToTranscode,
SortType
}
RequestEndpoint,
RequestVideoEventType,
RequestVideoQaduType,
- JobState
+ JobState,
+ VideoResolution
} from '../../shared/models'
// ---------------------------------------------------------------------------
},
TRANSCODING: {
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: {
PREVIEWS: {
9: 'Comedy',
10: 'Entertainment',
11: 'News',
- 12: 'Howto',
+ 12: 'How To',
13: 'Education',
14: 'Activism',
15: 'Science & Technology',
11: 'German',
12: 'Korean',
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',
- 1: '360p',
- 2: '480p',
- 3: '720p',
- 4: '1080p'
+ 240: '240p',
+ 360: '360p',
+ 480: '480p',
+ 720: '720p',
+ 1080: '1080p'
}
// ---------------------------------------------------------------------------
// Number of points we add/remove from a friend after a successful/bad request
const PODS_SCORE = {
- MALUS: -10,
+ PENALTY: -10,
BONUS: 10
}
-import * as videoTranscoder from './video-transcoder'
+import * as videoFileOptimizer from './video-file-optimizer'
+import * as videoFileTranscoder from './video-file-transcoder'
export interface JobHandler<T> {
process (data: object): T
}
const jobHandlers: { [ handlerName: string ]: JobHandler<any> } = {
- videoTranscoder
+ videoFileOptimizer,
+ videoFileTranscoder
}
export {
--- /dev/null
+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
+}
--- /dev/null
+import { database as db } from '../../../initializers/database'
+import { updateVideoToFriends } from '../../friends'
+import { logger } from '../../../helpers'
+import { VideoInstance } from '../../../models'
+import { VideoResolution } from '../../../../shared'
+
+function process (data: { videoUUID: string, resolution: VideoResolution }) {
+ return db.Video.loadByUUIDAndPopulateAuthorAndPodAndTags(data.videoUUID).then(video => {
+ return video.transcodeOriginalVideofile(data.resolution).then(() => video)
+ })
+}
+
+function onError (err: Error, jobId: number) {
+ logger.error('Error when transcoding video file in job %d.', jobId, err)
+ return Promise.resolve()
+}
+
+function onSuccess (jobId: number, video: VideoInstance) {
+ logger.info('Job %d is a success.', jobId)
+
+ const remoteVideo = video.toUpdateRemoteJSON()
+
+ // Now we'll add the video's meta data to our friends
+ return updateVideoToFriends(remoteVideo, null)
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+ process,
+ onError,
+ onSuccess
+}
+++ /dev/null
-import { database as db } from '../../../initializers/database'
-import { logger } from '../../../helpers'
-import { addVideoToFriends } from '../../../lib'
-import { VideoInstance } from '../../../models'
-
-function process (data: { videoUUID: string }) {
- return db.Video.loadByUUIDAndPopulateAuthorAndPodAndTags(data.videoUUID).then(video => {
- // TODO: handle multiple resolutions
- const videoFile = video.VideoFiles[0]
- return video.transcodeVideofile(videoFile).then(() => video)
- })
-}
-
-function onError (err: Error, jobId: number) {
- logger.error('Error when transcoding 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)
- })
-}
-
-// ---------------------------------------------------------------------------
-
-export {
- process,
- onError,
- onSuccess
-}
}
if (badPods.length !== 0) {
- incrementScores(badPods, PODS_SCORE.MALUS)
+ incrementScores(badPods, PODS_SCORE.PENALTY)
.then(() => removeBadPods())
.catch(err => {
if (err) logger.error('Cannot decrement scores of bad pods.', err)
isUserDisplayNSFWValid,
isUserVideoQuotaValid
} from '../../helpers'
+import { VideoResolution } from '../../../shared'
import { addMethodsToModel } from '../utils'
import {
// attributes = [] because we don't want other fields than the sum
const query = {
where: {
- resolution: 0 // Original, TODO: improve readability
+ resolution: VideoResolution.ORIGINAL
},
include: [
{
// Don't use barrel, import just what we need
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'
-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 type GetThumbnailName = (this: VideoInstance) => string
export type GetPreviewName = (this: VideoInstance) => string
export type IsOwned = (this: VideoInstance) => boolean
export type ToFormattedJSON = (this: VideoInstance) => FormattedVideo
+ export type GetOriginalFile = (this: VideoInstance) => VideoFileInstance
export type GenerateMagnetUri = (this: VideoInstance, videoFile: VideoFileInstance) => string
export type GetTorrentFileName = (this: VideoInstance, videoFile: VideoFileInstance) => string
export type GetVideoFilename = (this: VideoInstance, videoFile: VideoFileInstance) => string
export type GetVideoFilePath = (this: VideoInstance, videoFile: VideoFileInstance) => string
export type CreateTorrentAndSetInfoHash = (this: VideoInstance, videoFile: VideoFileInstance) => Promise<void>
- export type ToAddRemoteJSON = (this: VideoInstance) => Promise<FormattedAddRemoteVideo>
- export type ToUpdateRemoteJSON = (this: VideoInstance) => FormattedUpdateRemoteVideo
+ export type ToAddRemoteJSON = (this: VideoInstance) => Promise<RemoteVideoCreateData>
+ 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
export type GenerateThumbnailFromData = (video: VideoInstance, thumbnailData: string) => Promise<string>
createPreview: VideoMethods.CreatePreview
createThumbnail: VideoMethods.CreateThumbnail
createTorrentAndSetInfoHash: VideoMethods.CreateTorrentAndSetInfoHash
+ getOriginalFile: VideoMethods.GetOriginalFile
generateMagnetUri: VideoMethods.GenerateMagnetUri
getPreviewName: VideoMethods.GetPreviewName
getThumbnailName: VideoMethods.GetThumbnailName
toAddRemoteJSON: VideoMethods.ToAddRemoteJSON
toFormattedJSON: VideoMethods.ToFormattedJSON
toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON
- transcodeVideofile: VideoMethods.TranscodeVideofile
+ optimizeOriginalVideofile: VideoMethods.OptimizeOriginalVideofile
+ transcodeOriginalVideofile: VideoMethods.TranscodeOriginalVideofile
+ getOriginalFileHeight: VideoMethods.GetOriginalFileHeight
setTags: Sequelize.HasManySetAssociationsMixin<TagAttributes, string>
+ addVideoFile: Sequelize.HasManyAddAssociationMixin<VideoFileAttributes, string>
setVideoFiles: Sequelize.HasManySetAssociationsMixin<VideoFileAttributes, string>
}
unlinkPromise,
renamePromise,
writeFilePromise,
- createTorrentPromise
+ createTorrentPromise,
+ statPromise
} from '../../helpers'
import {
CONFIG,
VIDEO_FILE_RESOLUTIONS
} from '../../initializers'
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 {
} from './video-interface'
let Video: Sequelize.Model<VideoInstance, VideoAttributes>
+let getOriginalFile: VideoMethods.GetOriginalFile
let generateMagnetUri: VideoMethods.GenerateMagnetUri
let getVideoFilename: VideoMethods.GetVideoFilename
let getThumbnailName: VideoMethods.GetThumbnailName
let toFormattedJSON: VideoMethods.ToFormattedJSON
let toAddRemoteJSON: VideoMethods.ToAddRemoteJSON
let toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON
-let transcodeVideofile: VideoMethods.TranscodeVideofile
+let optimizeOriginalVideofile: VideoMethods.OptimizeOriginalVideofile
+let transcodeOriginalVideofile: VideoMethods.TranscodeOriginalVideofile
let createPreview: VideoMethods.CreatePreview
let createThumbnail: VideoMethods.CreateThumbnail
let getVideoFilePath: VideoMethods.GetVideoFilePath
let createTorrentAndSetInfoHash: VideoMethods.CreateTorrentAndSetInfoHash
+let getOriginalFileHeight: VideoMethods.GetOriginalFileHeight
let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData
let getDurationFromFile: VideoMethods.GetDurationFromFile
getTorrentFileName,
getVideoFilename,
getVideoFilePath,
+ getOriginalFile,
isOwned,
removeFile,
removePreview,
toAddRemoteJSON,
toFormattedJSON,
toUpdateRemoteJSON,
- transcodeVideofile
+ optimizeOriginalVideofile,
+ transcodeOriginalVideofile,
+ getOriginalFileHeight
]
addMethodsToModel(Video, classMethods, instanceMethods)
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) {
- // return this.uuid + '-' + VIDEO_FILE_RESOLUTIONS[videoFile.resolution] + videoFile.extname
- return this.uuid + videoFile.extname
+ return this.uuid + '-' + VIDEO_FILE_RESOLUTIONS[videoFile.resolution] + videoFile.extname
}
getThumbnailName = function (this: VideoInstance) {
getTorrentFileName = function (this: VideoInstance, videoFile: VideoFileInstance) {
const extension = '.torrent'
- // return this.uuid + '-' + VIDEO_FILE_RESOLUTIONS[videoFile.resolution] + extension
- return this.uuid + extension
+ return this.uuid + '-' + VIDEO_FILE_RESOLUTIONS[videoFile.resolution] + extension
}
isOwned = function (this: VideoInstance) {
return json
}
-transcodeVideofile = function (this: VideoInstance, inputVideoFile: VideoFileInstance) {
+optimizeOriginalVideofile = function (this: VideoInstance) {
const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
const newExtname = '.mp4'
+ const inputVideoFile = this.getOriginalFile()
const videoInputPath = join(videosDirectory, this.getVideoFilename(inputVideoFile))
const videoOutputPath = join(videosDirectory, this.id + '-transcoded' + newExtname)
return renamePromise(videoOutputPath, this.getVideoFilePath(inputVideoFile))
})
+ .then(() => {
+ return statPromise(this.getVideoFilePath(inputVideoFile))
+ })
+ .then(stats => {
+ return inputVideoFile.set('size', stats.size)
+ })
.then(() => {
return this.createTorrentAndSetInfoHash(inputVideoFile)
})
})
}
+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) {
const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName())
return unlinkPromise(thumbnailPath)
})
it('Should upload the video on pod 2 and propagate on each pod', async function () {
- this.timeout(60000)
+ this.timeout(120000)
const videoAttributes = {
name: 'my super name for pod 2',
}
await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
- // Transcoding, so wait more that 22 seconds
- await wait(42000)
+ // Transcoding, so wait more than 22000
+ await wait(60000)
// All pods should have this video
for (const server of servers) {
- let baseMagnet = null
+ let baseMagnet = {}
const res = await getVideosList(server.url)
expect(dateIsValid(video.updatedAt)).to.be.true
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]
- const magnetUri = file.magnetUri
- 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)
+ // Check common attributes
+ for (const file of video.files) {
+ expect(file.magnetUri).to.have.lengthOf.above(2)
- if (server.url !== 'http://localhost:9002') {
- expect(video.isLocal).to.be.false
- } else {
- expect(video.isLocal).to.be.true
- }
+ if (server.url !== 'http://localhost:9002') {
+ expect(video.isLocal).to.be.false
+ } else {
+ expect(video.isLocal).to.be.true
+ }
- // All pods should have the same magnet Uri
- if (baseMagnet === null) {
- baseMagnet = magnetUri
- } else {
- expect(baseMagnet).to.equal(magnetUri)
+ // 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)
+ }
}
+ const originalFile = video.files.find(f => f.resolution === 0)
+ expect(originalFile).not.to.be.undefined
+ expect(originalFile.resolutionLabel).to.equal('original')
+ expect(originalFile.size).to.equal(711327)
+
+ 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)
expect(test).to.equal(true)
}
const res = await getVideosList(servers[0].url)
const video = res.body.data[0]
+ expect(video.files).to.have.lengthOf(1)
+
const magnetUri = video.files[0].magnetUri
expect(magnetUri).to.match(/\.webm/)
const res = await getVideosList(servers[1].url)
const video = res.body.data[0]
+ expect(video.files).to.have.lengthOf(5)
+
const magnetUri = video.files[0].magnetUri
expect(magnetUri).to.match(/\.mp4/)
runServer,
ServerInfo,
setAccessTokensToServers,
- uploadVideo
+ uploadVideo,
+ wait
} from '../utils'
describe('Test update host scripts', function () {
let server: ServerInfo
before(async function () {
- this.timeout(30000)
+ this.timeout(60000)
await flushTests()
port: 9256
}
}
- server = await runServer(1, overrideConfig)
+ // Run server 2 to have transcoding enabled
+ server = await runServer(2, overrideConfig)
await setAccessTokensToServers([ server ])
// Upload two videos for our needs
const 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 () {
this.timeout(30000)
killallServers([ server ])
- server = await runServer(1)
+ // Run server with standard configuration
+ server = await runServer(2)
const env = getEnvCli(server)
await execCLI(`${env} npm run update-host`)
const res = await getVideosList(server.url)
const videos = res.body.data
+ expect(videos).to.have.lengthOf(2)
- expect(videos[0].files[0].magnetUri).to.contain('localhost%3A9001%2Ftracker%2Fsocket')
- expect(videos[0].files[0].magnetUri).to.contain('localhost%3A9001%2Fstatic%2Fwebseed%2F')
+ for (const video of videos) {
+ expect(video.files).to.have.lengthOf(5)
- expect(videos[1].files[0].magnetUri).to.contain('localhost%3A9001%2Ftracker%2Fsocket')
- expect(videos[1].files[0].magnetUri).to.contain('localhost%3A9001%2Fstatic%2Fwebseed%2F')
+ for (const file of video.files) {
+ 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)
- expect(torrent.announce[0]).to.equal('ws://localhost:9001/tracker/socket')
- expect(torrent.urlList[0]).to.contain('http://localhost:9001/static/webseed')
+ const torrent = await parseTorrentVideo(server, video.uuid, file.resolutionLabel)
+ expect(torrent.announce[0]).to.equal('ws://localhost:9002/tracker/socket')
+ expect(torrent.urlList[0]).to.contain('http://localhost:9002/static/webseed')
+ }
+ }
})
after(async function () {
.expect(specialStatus)
}
-function parseTorrentVideo (server: ServerInfo, videoUUID: string) {
+function parseTorrentVideo (server: ServerInfo, videoUUID: string, resolutionLabel: string) {
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) => {
if (err) return rej(err)
uuid: string
tags: string[]
name: string
- extname: string
- infoHash: string
category: number
licence: number
language: number
export * from './video-blacklist.model'
export * from './video-create.model'
export * from './video-rate.type'
+export * from './video-resolution.enum'
export * from './video-update.model'
export * from './video.model'
--- /dev/null
+export enum VideoResolution {
+ ORIGINAL = 0,
+ H_240P = 240,
+ H_360P = 360,
+ H_480P = 480,
+ H_720P = 720,
+ H_1080P = 1080
+}