mirror of
https://github.com/Chocobozzz/PeerTube.git
synced 2024-11-22 08:46:54 -06:00
Change how we handle resolution
It was an enum before, now we just use video height
This commit is contained in:
parent
aa8b6df4a5
commit
14d3270f36
@ -46,3 +46,9 @@ user:
|
||||
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
|
||||
|
@ -37,10 +37,11 @@ import {
|
||||
retryTransactionWrapper,
|
||||
generateRandomString,
|
||||
getFormattedObjects,
|
||||
renamePromise
|
||||
renamePromise,
|
||||
getVideoFileHeight
|
||||
} from '../../../helpers'
|
||||
import { TagInstance, VideoInstance } from '../../../models'
|
||||
import { VideoCreate, VideoUpdate, VideoResolution } from '../../../../shared'
|
||||
import { VideoCreate, VideoUpdate } from '../../../../shared'
|
||||
|
||||
import { abuseVideoRouter } from './abuse'
|
||||
import { blacklistRouter } from './blacklist'
|
||||
@ -192,9 +193,14 @@ function addVideo (req: express.Request, res: express.Response, videoPhysicalFil
|
||||
return { author, tagInstances, video }
|
||||
})
|
||||
.then(({ author, tagInstances, video }) => {
|
||||
const videoFilePath = join(CONFIG.STORAGE.VIDEOS_DIR, videoPhysicalFile.filename)
|
||||
return getVideoFileHeight(videoFilePath)
|
||||
.then(height => ({ author, tagInstances, video, videoFileHeight: height }))
|
||||
})
|
||||
.then(({ author, tagInstances, video, videoFileHeight }) => {
|
||||
const videoFileData = {
|
||||
extname: extname(videoPhysicalFile.filename),
|
||||
resolution: VideoResolution.ORIGINAL,
|
||||
resolution: videoFileHeight,
|
||||
size: videoPhysicalFile.size
|
||||
}
|
||||
|
||||
|
@ -8,8 +8,7 @@ import {
|
||||
VIDEO_CATEGORIES,
|
||||
VIDEO_LICENCES,
|
||||
VIDEO_LANGUAGES,
|
||||
VIDEO_RATE_TYPES,
|
||||
VIDEO_FILE_RESOLUTIONS
|
||||
VIDEO_RATE_TYPES
|
||||
} from '../../initializers'
|
||||
import { isUserUsernameValid } from './users'
|
||||
import { isArray, exists } from './misc'
|
||||
@ -128,7 +127,7 @@ function isVideoFileSizeValid (value: string) {
|
||||
}
|
||||
|
||||
function isVideoFileResolutionValid (value: string) {
|
||||
return VIDEO_FILE_RESOLUTIONS[value] !== undefined
|
||||
return exists(value) && validator.isInt(value + '')
|
||||
}
|
||||
|
||||
function isVideoFileExtnameValid (value: string) {
|
||||
|
79
server/helpers/ffmpeg-utils.ts
Normal file
79
server/helpers/ffmpeg-utils.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import * as Promise from 'bluebird'
|
||||
import * as ffmpeg from 'fluent-ffmpeg'
|
||||
|
||||
import { CONFIG } from '../initializers'
|
||||
import { VideoResolution } from '../../shared/models/videos/video-resolution.enum'
|
||||
|
||||
function getVideoFileHeight (path: string) {
|
||||
return new Promise<number>((res, rej) => {
|
||||
ffmpeg.ffprobe(path, (err, metadata) => {
|
||||
if (err) return rej(err)
|
||||
|
||||
const videoStream = metadata.streams.find(s => s.codec_type === 'video')
|
||||
return res(videoStream.height)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function getDurationFromVideoFile (path: string) {
|
||||
return new Promise<number>((res, rej) => {
|
||||
ffmpeg.ffprobe(path, (err, metadata) => {
|
||||
if (err) return rej(err)
|
||||
|
||||
return res(Math.floor(metadata.format.duration))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function generateImageFromVideoFile (fromPath: string, folder: string, imageName: string, size?: string) {
|
||||
const options = {
|
||||
filename: imageName,
|
||||
count: 1,
|
||||
folder
|
||||
}
|
||||
|
||||
if (size !== undefined) {
|
||||
options['size'] = size
|
||||
}
|
||||
|
||||
return new Promise<string>((res, rej) => {
|
||||
ffmpeg(fromPath)
|
||||
.on('error', rej)
|
||||
.on('end', () => res(imageName))
|
||||
.thumbnail(options)
|
||||
})
|
||||
}
|
||||
|
||||
type TranscodeOptions = {
|
||||
inputPath: string
|
||||
outputPath: string
|
||||
resolution?: VideoResolution
|
||||
}
|
||||
|
||||
function transcode (options: TranscodeOptions) {
|
||||
return new Promise<void>((res, rej) => {
|
||||
let command = ffmpeg(options.inputPath)
|
||||
.output(options.outputPath)
|
||||
.videoCodec('libx264')
|
||||
.outputOption('-threads ' + CONFIG.TRANSCODING.THREADS)
|
||||
.outputOption('-movflags faststart')
|
||||
|
||||
if (options.resolution !== undefined) {
|
||||
const size = `${options.resolution}x?` // '720x?' for example
|
||||
command = command.size(size)
|
||||
}
|
||||
|
||||
command.on('error', rej)
|
||||
.on('end', res)
|
||||
.run()
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
getVideoFileHeight,
|
||||
getDurationFromVideoFile,
|
||||
generateImageFromVideoFile,
|
||||
transcode
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
export * from './core-utils'
|
||||
export * from './logger'
|
||||
export * from './custom-validators'
|
||||
export * from './ffmpeg-utils'
|
||||
export * from './database-utils'
|
||||
export * from './peertube-crypto'
|
||||
export * from './requests'
|
||||
|
@ -61,7 +61,7 @@ function computeResolutionsToTranscode (videoFileHeight: number) {
|
||||
]
|
||||
|
||||
for (const resolution of resolutions) {
|
||||
if (configResolutions[resolution.toString()] === true && videoFileHeight >= resolution) {
|
||||
if (configResolutions[resolution.toString()] === true && videoFileHeight > resolution) {
|
||||
resolutionsEnabled.push(resolution)
|
||||
}
|
||||
}
|
||||
|
@ -189,16 +189,6 @@ const VIDEO_LANGUAGES = {
|
||||
14: 'Italian'
|
||||
}
|
||||
|
||||
// TODO: use VideoResolution when https://github.com/Microsoft/TypeScript/issues/13042 is fixed
|
||||
const VIDEO_FILE_RESOLUTIONS: { [ id: number ]: string } = {
|
||||
0: 'original',
|
||||
240: '240p',
|
||||
360: '360p',
|
||||
480: '480p',
|
||||
720: '720p',
|
||||
1080: '1080p'
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Score a pod has when we create it as a friend
|
||||
@ -385,7 +375,6 @@ export {
|
||||
THUMBNAILS_SIZE,
|
||||
USER_ROLES,
|
||||
VIDEO_CATEGORIES,
|
||||
VIDEO_FILE_RESOLUTIONS,
|
||||
VIDEO_LANGUAGES,
|
||||
VIDEO_LICENCES,
|
||||
VIDEO_RATE_TYPES
|
||||
|
@ -4,6 +4,7 @@ import { join } from 'path'
|
||||
|
||||
import { readdirPromise, renamePromise } from '../../helpers/core-utils'
|
||||
import { CONFIG } from '../../initializers/constants'
|
||||
import { getVideoFileHeight } from '../../helpers/ffmpeg-utils'
|
||||
|
||||
function up (utils: {
|
||||
transaction: Sequelize.Transaction,
|
||||
@ -14,26 +15,7 @@ function up (utils: {
|
||||
const torrentDir = CONFIG.STORAGE.TORRENTS_DIR
|
||||
const videoFileDir = CONFIG.STORAGE.VIDEOS_DIR
|
||||
|
||||
return readdirPromise(torrentDir)
|
||||
.then(torrentFiles => {
|
||||
const tasks: Promise<any>[] = []
|
||||
for (const torrentFile of torrentFiles) {
|
||||
const matches = /^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.torrent/.exec(torrentFile)
|
||||
if (matches === null) {
|
||||
console.log('Invalid torrent file name %s.', torrentFile)
|
||||
continue
|
||||
}
|
||||
|
||||
const newTorrentName = matches[1] + '-original.torrent'
|
||||
const p = renamePromise(join(torrentDir, torrentFile), join(torrentDir, newTorrentName))
|
||||
tasks.push(p)
|
||||
}
|
||||
|
||||
return Promise.all(tasks)
|
||||
})
|
||||
.then(() => {
|
||||
return readdirPromise(videoFileDir)
|
||||
})
|
||||
return readdirPromise(videoFileDir)
|
||||
.then(videoFiles => {
|
||||
const tasks: Promise<any>[] = []
|
||||
for (const videoFile of videoFiles) {
|
||||
@ -43,8 +25,25 @@ function up (utils: {
|
||||
continue
|
||||
}
|
||||
|
||||
const newVideoFileName = matches[1] + '-original.' + matches[2]
|
||||
const p = renamePromise(join(videoFileDir, videoFile), join(videoFileDir, newVideoFileName))
|
||||
const uuid = matches[1]
|
||||
const ext = matches[2]
|
||||
|
||||
const p = getVideoFileHeight(join(videoFileDir, videoFile))
|
||||
.then(height => {
|
||||
const oldTorrentName = uuid + '.torrent'
|
||||
const newTorrentName = uuid + '-' + height + '.torrent'
|
||||
return renamePromise(join(torrentDir, oldTorrentName), join(torrentDir, newTorrentName)).then(() => height)
|
||||
})
|
||||
.then(height => {
|
||||
const newVideoFileName = uuid + '-' + height + '.' + ext
|
||||
return renamePromise(join(videoFileDir, videoFile), join(videoFileDir, newVideoFileName)).then(() => height)
|
||||
})
|
||||
.then(height => {
|
||||
const query = 'UPDATE "VideoFiles" SET "resolution" = ' + height +
|
||||
' WHERE "videoId" = (SELECT "id" FROM "Videos" WHERE "uuid" = \'' + uuid + '\')'
|
||||
return utils.sequelize.query(query)
|
||||
})
|
||||
|
||||
tasks.push(p)
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,8 @@ import {
|
||||
isVideoNSFWValid,
|
||||
isVideoIdOrUUIDValid,
|
||||
isVideoAbuseReasonValid,
|
||||
isVideoRatingTypeValid
|
||||
isVideoRatingTypeValid,
|
||||
getDurationFromVideoFile
|
||||
} from '../../helpers'
|
||||
import { VideoInstance } from '../../models'
|
||||
|
||||
@ -50,7 +51,7 @@ const videosAddValidator = [
|
||||
return undefined
|
||||
}
|
||||
|
||||
return db.Video.getDurationFromFile(videoFile.path)
|
||||
return getDurationFromVideoFile(videoFile.path)
|
||||
.catch(err => {
|
||||
logger.error('Invalid input file in videosAddValidator.', err)
|
||||
res.status(400)
|
||||
|
@ -12,7 +12,6 @@ import {
|
||||
isUserDisplayNSFWValid,
|
||||
isUserVideoQuotaValid
|
||||
} from '../../helpers'
|
||||
import { VideoResolution } from '../../../shared'
|
||||
|
||||
import { addMethodsToModel } from '../utils'
|
||||
import {
|
||||
@ -243,33 +242,21 @@ loadByUsernameOrEmail = function (username: string, email: string) {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function getOriginalVideoFileTotalFromUser (user: UserInstance) {
|
||||
// attributes = [] because we don't want other fields than the sum
|
||||
const query = {
|
||||
where: {
|
||||
resolution: VideoResolution.ORIGINAL
|
||||
},
|
||||
include: [
|
||||
{
|
||||
attributes: [],
|
||||
model: User['sequelize'].models.Video,
|
||||
include: [
|
||||
{
|
||||
attributes: [],
|
||||
model: User['sequelize'].models.Author,
|
||||
include: [
|
||||
{
|
||||
attributes: [],
|
||||
model: User['sequelize'].models.User,
|
||||
where: {
|
||||
id: user.id
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
// Don't use sequelize because we need to use a subquery
|
||||
const query = 'SELECT SUM("size") AS "total" FROM ' +
|
||||
'(SELECT MAX("VideoFiles"."size") AS "size" FROM "VideoFiles" ' +
|
||||
'INNER JOIN "Videos" ON "VideoFiles"."videoId" = "Videos"."id" ' +
|
||||
'INNER JOIN "Authors" ON "Videos"."authorId" = "Authors"."id" ' +
|
||||
'INNER JOIN "Users" ON "Authors"."userId" = "Users"."id" ' +
|
||||
'WHERE "Users"."id" = $userId GROUP BY "Videos"."id") t'
|
||||
|
||||
return User['sequelize'].models.VideoFile.sum('size', query)
|
||||
const options = {
|
||||
bind: { userId: user.id },
|
||||
type: Sequelize.QueryTypes.SELECT
|
||||
}
|
||||
return User['sequelize'].query(query, options).then(([ { total } ]) => {
|
||||
if (total === null) return 0
|
||||
|
||||
return parseInt(total, 10)
|
||||
})
|
||||
}
|
||||
|
@ -35,7 +35,6 @@ export namespace VideoMethods {
|
||||
|
||||
// Return thumbnail name
|
||||
export type GenerateThumbnailFromData = (video: VideoInstance, thumbnailData: string) => Promise<string>
|
||||
export type GetDurationFromFile = (videoPath: string) => Promise<number>
|
||||
|
||||
export type List = () => Promise<VideoInstance[]>
|
||||
export type ListOwnedAndPopulateAuthorAndTags = () => Promise<VideoInstance[]>
|
||||
@ -65,7 +64,6 @@ export namespace VideoMethods {
|
||||
|
||||
export interface VideoClass {
|
||||
generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData
|
||||
getDurationFromFile: VideoMethods.GetDurationFromFile
|
||||
list: VideoMethods.List
|
||||
listForApi: VideoMethods.ListForApi
|
||||
listOwnedAndPopulateAuthorAndTags: VideoMethods.ListOwnedAndPopulateAuthorAndTags
|
||||
|
@ -1,12 +1,12 @@
|
||||
import * as safeBuffer from 'safe-buffer'
|
||||
const Buffer = safeBuffer.Buffer
|
||||
import * as ffmpeg from 'fluent-ffmpeg'
|
||||
import * as magnetUtil from 'magnet-uri'
|
||||
import { map } from 'lodash'
|
||||
import * as parseTorrent from 'parse-torrent'
|
||||
import { join } from 'path'
|
||||
import * as Sequelize from 'sequelize'
|
||||
import * as Promise from 'bluebird'
|
||||
import { maxBy } from 'lodash'
|
||||
|
||||
import { TagInstance } from './tag-interface'
|
||||
import {
|
||||
@ -23,7 +23,10 @@ import {
|
||||
renamePromise,
|
||||
writeFilePromise,
|
||||
createTorrentPromise,
|
||||
statPromise
|
||||
statPromise,
|
||||
generateImageFromVideoFile,
|
||||
transcode,
|
||||
getVideoFileHeight
|
||||
} from '../../helpers'
|
||||
import {
|
||||
CONFIG,
|
||||
@ -32,8 +35,7 @@ import {
|
||||
VIDEO_CATEGORIES,
|
||||
VIDEO_LICENCES,
|
||||
VIDEO_LANGUAGES,
|
||||
THUMBNAILS_SIZE,
|
||||
VIDEO_FILE_RESOLUTIONS
|
||||
THUMBNAILS_SIZE
|
||||
} from '../../initializers'
|
||||
import { removeVideoToFriends } from '../../lib'
|
||||
import { VideoResolution } from '../../../shared'
|
||||
@ -67,7 +69,6 @@ let createTorrentAndSetInfoHash: VideoMethods.CreateTorrentAndSetInfoHash
|
||||
let getOriginalFileHeight: VideoMethods.GetOriginalFileHeight
|
||||
|
||||
let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData
|
||||
let getDurationFromFile: VideoMethods.GetDurationFromFile
|
||||
let list: VideoMethods.List
|
||||
let listForApi: VideoMethods.ListForApi
|
||||
let loadByHostAndUUID: VideoMethods.LoadByHostAndUUID
|
||||
@ -233,7 +234,6 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
|
||||
associate,
|
||||
|
||||
generateThumbnailFromData,
|
||||
getDurationFromFile,
|
||||
list,
|
||||
listForApi,
|
||||
listOwnedAndPopulateAuthorAndTags,
|
||||
@ -338,11 +338,12 @@ function afterDestroy (video: VideoInstance, options: { transaction: Sequelize.T
|
||||
getOriginalFile = function (this: VideoInstance) {
|
||||
if (Array.isArray(this.VideoFiles) === false) return undefined
|
||||
|
||||
return this.VideoFiles.find(file => file.resolution === VideoResolution.ORIGINAL)
|
||||
// The original file is the file that have the higher resolution
|
||||
return maxBy(this.VideoFiles, file => file.resolution)
|
||||
}
|
||||
|
||||
getVideoFilename = function (this: VideoInstance, videoFile: VideoFileInstance) {
|
||||
return this.uuid + '-' + VIDEO_FILE_RESOLUTIONS[videoFile.resolution] + videoFile.extname
|
||||
return this.uuid + '-' + videoFile.resolution + videoFile.extname
|
||||
}
|
||||
|
||||
getThumbnailName = function (this: VideoInstance) {
|
||||
@ -358,7 +359,7 @@ getPreviewName = function (this: VideoInstance) {
|
||||
|
||||
getTorrentFileName = function (this: VideoInstance, videoFile: VideoFileInstance) {
|
||||
const extension = '.torrent'
|
||||
return this.uuid + '-' + VIDEO_FILE_RESOLUTIONS[videoFile.resolution] + extension
|
||||
return this.uuid + '-' + videoFile.resolution + extension
|
||||
}
|
||||
|
||||
isOwned = function (this: VideoInstance) {
|
||||
@ -366,11 +367,20 @@ isOwned = function (this: VideoInstance) {
|
||||
}
|
||||
|
||||
createPreview = function (this: VideoInstance, videoFile: VideoFileInstance) {
|
||||
return generateImage(this, this.getVideoFilePath(videoFile), CONFIG.STORAGE.PREVIEWS_DIR, this.getPreviewName(), null)
|
||||
return generateImageFromVideoFile(
|
||||
this.getVideoFilePath(videoFile),
|
||||
CONFIG.STORAGE.PREVIEWS_DIR,
|
||||
this.getPreviewName()
|
||||
)
|
||||
}
|
||||
|
||||
createThumbnail = function (this: VideoInstance, videoFile: VideoFileInstance) {
|
||||
return generateImage(this, this.getVideoFilePath(videoFile), CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName(), THUMBNAILS_SIZE)
|
||||
return generateImageFromVideoFile(
|
||||
this.getVideoFilePath(videoFile),
|
||||
CONFIG.STORAGE.THUMBNAILS_DIR,
|
||||
this.getThumbnailName(),
|
||||
THUMBNAILS_SIZE
|
||||
)
|
||||
}
|
||||
|
||||
getVideoFilePath = function (this: VideoInstance, videoFile: VideoFileInstance) {
|
||||
@ -480,8 +490,7 @@ toFormattedJSON = function (this: VideoInstance) {
|
||||
// Format and sort video files
|
||||
json.files = this.VideoFiles
|
||||
.map(videoFile => {
|
||||
let resolutionLabel = VIDEO_FILE_RESOLUTIONS[videoFile.resolution]
|
||||
if (!resolutionLabel) resolutionLabel = 'Unknown'
|
||||
let resolutionLabel = videoFile.resolution + 'p'
|
||||
|
||||
const videoFileJson = {
|
||||
resolution: videoFile.resolution,
|
||||
@ -578,46 +587,42 @@ optimizeOriginalVideofile = function (this: VideoInstance) {
|
||||
const videoInputPath = join(videosDirectory, this.getVideoFilename(inputVideoFile))
|
||||
const videoOutputPath = join(videosDirectory, this.id + '-transcoded' + newExtname)
|
||||
|
||||
return new Promise<void>((res, rej) => {
|
||||
ffmpeg(videoInputPath)
|
||||
.output(videoOutputPath)
|
||||
.videoCodec('libx264')
|
||||
.outputOption('-threads ' + CONFIG.TRANSCODING.THREADS)
|
||||
.outputOption('-movflags faststart')
|
||||
.on('error', rej)
|
||||
.on('end', () => {
|
||||
const transcodeOptions = {
|
||||
inputPath: videoInputPath,
|
||||
outputPath: videoOutputPath
|
||||
}
|
||||
|
||||
return unlinkPromise(videoInputPath)
|
||||
.then(() => {
|
||||
// Important to do this before getVideoFilename() to take in account the new file extension
|
||||
inputVideoFile.set('extname', newExtname)
|
||||
return transcode(transcodeOptions)
|
||||
.then(() => {
|
||||
return unlinkPromise(videoInputPath)
|
||||
})
|
||||
.then(() => {
|
||||
// Important to do this before getVideoFilename() to take in account the new file extension
|
||||
inputVideoFile.set('extname', 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)
|
||||
})
|
||||
.then(() => {
|
||||
return inputVideoFile.save()
|
||||
})
|
||||
.then(() => {
|
||||
return res()
|
||||
})
|
||||
.catch(err => {
|
||||
// Auto destruction...
|
||||
this.destroy().catch(err => logger.error('Cannot destruct video after transcoding failure.', err))
|
||||
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)
|
||||
})
|
||||
.then(() => {
|
||||
return inputVideoFile.save()
|
||||
})
|
||||
.then(() => {
|
||||
return undefined
|
||||
})
|
||||
.catch(err => {
|
||||
// Auto destruction...
|
||||
this.destroy().catch(err => logger.error('Cannot destruct video after transcoding failure.', err))
|
||||
|
||||
return rej(err)
|
||||
})
|
||||
})
|
||||
.run()
|
||||
})
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
transcodeOriginalVideofile = function (this: VideoInstance, resolution: VideoResolution) {
|
||||
@ -634,52 +639,37 @@ transcodeOriginalVideofile = function (this: VideoInstance, resolution: VideoRes
|
||||
videoId: this.id
|
||||
})
|
||||
const videoOutputPath = join(videosDirectory, this.getVideoFilename(newVideoFile))
|
||||
const resolutionOption = `${resolution}x?` // '720x?' for example
|
||||
|
||||
return new Promise<void>((res, rej) => {
|
||||
ffmpeg(videoInputPath)
|
||||
.output(videoOutputPath)
|
||||
.videoCodec('libx264')
|
||||
.size(resolutionOption)
|
||||
.outputOption('-threads ' + CONFIG.TRANSCODING.THREADS)
|
||||
.outputOption('-movflags faststart')
|
||||
.on('error', rej)
|
||||
.on('end', () => {
|
||||
return statPromise(videoOutputPath)
|
||||
.then(stats => {
|
||||
newVideoFile.set('size', stats.size)
|
||||
const transcodeOptions = {
|
||||
inputPath: videoInputPath,
|
||||
outputPath: videoOutputPath,
|
||||
resolution
|
||||
}
|
||||
return transcode(transcodeOptions)
|
||||
.then(() => {
|
||||
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()
|
||||
})
|
||||
return undefined
|
||||
})
|
||||
.then(() => {
|
||||
return this.createTorrentAndSetInfoHash(newVideoFile)
|
||||
})
|
||||
.then(() => {
|
||||
return newVideoFile.save()
|
||||
})
|
||||
.then(() => {
|
||||
return this.VideoFiles.push(newVideoFile)
|
||||
})
|
||||
.then(() => undefined)
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
})
|
||||
return getVideoFileHeight(originalFilePath)
|
||||
}
|
||||
|
||||
removeThumbnail = function (this: VideoInstance) {
|
||||
@ -714,16 +704,6 @@ generateThumbnailFromData = function (video: VideoInstance, thumbnailData: strin
|
||||
})
|
||||
}
|
||||
|
||||
getDurationFromFile = function (videoPath: string) {
|
||||
return new Promise<number>((res, rej) => {
|
||||
ffmpeg.ffprobe(videoPath, (err, metadata) => {
|
||||
if (err) return rej(err)
|
||||
|
||||
return res(Math.floor(metadata.format.duration))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
list = function () {
|
||||
const query = {
|
||||
include: [ Video['sequelize'].models.VideoFile ]
|
||||
@ -964,22 +944,3 @@ function createBaseVideosWhere () {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function generateImage (video: VideoInstance, videoPath: string, folder: string, imageName: string, size: string) {
|
||||
const options = {
|
||||
filename: imageName,
|
||||
count: 1,
|
||||
folder
|
||||
}
|
||||
|
||||
if (size) {
|
||||
options['size'] = size
|
||||
}
|
||||
|
||||
return new Promise<string>((res, rej) => {
|
||||
ffmpeg(videoPath)
|
||||
.on('error', rej)
|
||||
.on('end', () => res(imageName))
|
||||
.thumbnail(options)
|
||||
})
|
||||
}
|
||||
|
@ -106,8 +106,8 @@ describe('Test multiple pods', function () {
|
||||
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.resolution).to.equal(720)
|
||||
expect(file.resolutionLabel).to.equal('720p')
|
||||
expect(file.size).to.equal(572456)
|
||||
|
||||
if (server.url !== 'http://localhost:9001') {
|
||||
@ -172,7 +172,7 @@ describe('Test multiple pods', function () {
|
||||
expect(dateIsValid(video.updatedAt)).to.be.true
|
||||
expect(video.author).to.equal('root')
|
||||
|
||||
expect(video.files).to.have.lengthOf(5)
|
||||
expect(video.files).to.have.lengthOf(4)
|
||||
|
||||
// Check common attributes
|
||||
for (const file of video.files) {
|
||||
@ -192,11 +192,6 @@ describe('Test multiple pods', function () {
|
||||
}
|
||||
}
|
||||
|
||||
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.be.above(700000).and.below(720000)
|
||||
|
||||
const file240p = video.files.find(f => f.resolution === 240)
|
||||
expect(file240p).not.to.be.undefined
|
||||
expect(file240p.resolutionLabel).to.equal('240p')
|
||||
@ -215,7 +210,7 @@ describe('Test multiple pods', function () {
|
||||
const file720p = video.files.find(f => f.resolution === 720)
|
||||
expect(file720p).not.to.be.undefined
|
||||
expect(file720p.resolutionLabel).to.equal('720p')
|
||||
expect(file720p.size).to.be.above(310000).and.below(320000)
|
||||
expect(file720p.size).to.be.above(700000).and.below(7200000)
|
||||
|
||||
const test = await testVideoImage(server.url, 'video_short2.webm', video.thumbnailPath)
|
||||
expect(test).to.equal(true)
|
||||
@ -291,8 +286,8 @@ describe('Test multiple pods', function () {
|
||||
|
||||
const file1 = video1.files[0]
|
||||
expect(file1.magnetUri).to.have.lengthOf.above(2)
|
||||
expect(file1.resolution).to.equal(0)
|
||||
expect(file1.resolutionLabel).to.equal('original')
|
||||
expect(file1.resolution).to.equal(720)
|
||||
expect(file1.resolutionLabel).to.equal('720p')
|
||||
expect(file1.size).to.equal(292677)
|
||||
|
||||
expect(video2.name).to.equal('my super name for pod 3-2')
|
||||
@ -316,8 +311,8 @@ describe('Test multiple pods', function () {
|
||||
const file2 = video2.files[0]
|
||||
const magnetUri2 = file2.magnetUri
|
||||
expect(file2.magnetUri).to.have.lengthOf.above(2)
|
||||
expect(file2.resolution).to.equal(0)
|
||||
expect(file2.resolutionLabel).to.equal('original')
|
||||
expect(file2.resolution).to.equal(720)
|
||||
expect(file2.resolutionLabel).to.equal('720p')
|
||||
expect(file2.size).to.equal(218910)
|
||||
|
||||
if (server.url !== 'http://localhost:9003') {
|
||||
@ -402,6 +397,22 @@ describe('Test multiple pods', function () {
|
||||
expect(torrent.files.length).to.equal(1)
|
||||
expect(torrent.files[0].path).to.exist.and.to.not.equal('')
|
||||
})
|
||||
|
||||
it('Should add the file 2 in 360p by asking pod 1', async function () {
|
||||
// Yes, this could be long
|
||||
this.timeout(200000)
|
||||
|
||||
const res = await getVideosList(servers[0].url)
|
||||
|
||||
const video = res.body.data.find(v => v.name === 'my super name for pod 2')
|
||||
const file = video.files.find(f => f.resolution === 360)
|
||||
expect(file).not.to.be.undefined
|
||||
|
||||
const torrent = await webtorrentAdd(file.magnetUri)
|
||||
expect(torrent.files).to.be.an('array')
|
||||
expect(torrent.files.length).to.equal(1)
|
||||
expect(torrent.files[0].path).to.exist.and.to.not.equal('')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Should update video views, likes and dislikes', function () {
|
||||
@ -562,8 +573,8 @@ describe('Test multiple pods', function () {
|
||||
|
||||
const file = videoUpdated.files[0]
|
||||
expect(file.magnetUri).to.have.lengthOf.above(2)
|
||||
expect(file.resolution).to.equal(0)
|
||||
expect(file.resolutionLabel).to.equal('original')
|
||||
expect(file.resolution).to.equal(720)
|
||||
expect(file.resolutionLabel).to.equal('720p')
|
||||
expect(file.size).to.equal(292677)
|
||||
|
||||
const test = await testVideoImage(server.url, 'video_short3.webm', videoUpdated.thumbnailPath)
|
||||
|
@ -127,8 +127,8 @@ describe('Test a single pod', function () {
|
||||
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.resolution).to.equal(720)
|
||||
expect(file.resolutionLabel).to.equal('720p')
|
||||
expect(file.size).to.equal(218910)
|
||||
|
||||
const test = await testVideoImage(server.url, 'video_short.webm', video.thumbnailPath)
|
||||
@ -170,8 +170,8 @@ describe('Test a single pod', function () {
|
||||
|
||||
const file = video.files[0]
|
||||
expect(file.magnetUri).to.have.lengthOf.above(2)
|
||||
expect(file.resolution).to.equal(0)
|
||||
expect(file.resolutionLabel).to.equal('original')
|
||||
expect(file.resolution).to.equal(720)
|
||||
expect(file.resolutionLabel).to.equal('720p')
|
||||
expect(file.size).to.equal(218910)
|
||||
|
||||
const test = await testVideoImage(server.url, 'video_short.webm', video.thumbnailPath)
|
||||
@ -229,8 +229,8 @@ describe('Test a single pod', function () {
|
||||
|
||||
const file = video.files[0]
|
||||
expect(file.magnetUri).to.have.lengthOf.above(2)
|
||||
expect(file.resolution).to.equal(0)
|
||||
expect(file.resolutionLabel).to.equal('original')
|
||||
expect(file.resolution).to.equal(720)
|
||||
expect(file.resolutionLabel).to.equal('720p')
|
||||
expect(file.size).to.equal(218910)
|
||||
|
||||
const test = await testVideoImage(server.url, 'video_short.webm', video.thumbnailPath)
|
||||
@ -291,8 +291,8 @@ describe('Test a single pod', function () {
|
||||
|
||||
const file = video.files[0]
|
||||
expect(file.magnetUri).to.have.lengthOf.above(2)
|
||||
expect(file.resolution).to.equal(0)
|
||||
expect(file.resolutionLabel).to.equal('original')
|
||||
expect(file.resolution).to.equal(720)
|
||||
expect(file.resolutionLabel).to.equal('720p')
|
||||
expect(file.size).to.equal(218910)
|
||||
|
||||
const test = await testVideoImage(server.url, 'video_short.webm', video.thumbnailPath)
|
||||
@ -569,8 +569,8 @@ describe('Test a single pod', function () {
|
||||
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.resolution).to.equal(720)
|
||||
expect(file.resolutionLabel).to.equal('720p')
|
||||
expect(file.size).to.equal(292677)
|
||||
|
||||
const test = await testVideoImage(server.url, 'video_short3.webm', video.thumbnailPath)
|
||||
@ -612,8 +612,8 @@ describe('Test a single pod', function () {
|
||||
|
||||
const file = video.files[0]
|
||||
expect(file.magnetUri).to.have.lengthOf.above(2)
|
||||
expect(file.resolution).to.equal(0)
|
||||
expect(file.resolutionLabel).to.equal('original')
|
||||
expect(file.resolution).to.equal(720)
|
||||
expect(file.resolutionLabel).to.equal('720p')
|
||||
expect(file.size).to.equal(292677)
|
||||
})
|
||||
|
||||
@ -647,8 +647,8 @@ describe('Test a single pod', function () {
|
||||
|
||||
const file = video.files[0]
|
||||
expect(file.magnetUri).to.have.lengthOf.above(2)
|
||||
expect(file.resolution).to.equal(0)
|
||||
expect(file.resolutionLabel).to.equal('original')
|
||||
expect(file.resolution).to.equal(720)
|
||||
expect(file.resolutionLabel).to.equal('720p')
|
||||
expect(file.size).to.equal(292677)
|
||||
})
|
||||
|
||||
|
@ -68,7 +68,7 @@ describe('Test video transcoding', function () {
|
||||
const res = await getVideosList(servers[1].url)
|
||||
|
||||
const video = res.body.data[0]
|
||||
expect(video.files).to.have.lengthOf(5)
|
||||
expect(video.files).to.have.lengthOf(4)
|
||||
|
||||
const magnetUri = video.files[0].magnetUri
|
||||
expect(magnetUri).to.match(/\.mp4/)
|
||||
|
@ -55,13 +55,13 @@ describe('Test update host scripts', function () {
|
||||
expect(videos).to.have.lengthOf(2)
|
||||
|
||||
for (const video of videos) {
|
||||
expect(video.files).to.have.lengthOf(5)
|
||||
expect(video.files).to.have.lengthOf(4)
|
||||
|
||||
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, video.uuid, file.resolutionLabel)
|
||||
const torrent = await parseTorrentVideo(server, video.uuid, file.resolution)
|
||||
expect(torrent.announce[0]).to.equal('ws://localhost:9002/tracker/socket')
|
||||
expect(torrent.urlList[0]).to.contain('http://localhost:9002/static/webseed')
|
||||
}
|
||||
|
@ -196,14 +196,14 @@ function uploadVideo (url: string, accessToken: string, videoAttributesArg: Vide
|
||||
req.field('tags[' + i + ']', attributes.tags[i])
|
||||
}
|
||||
|
||||
let filepath = ''
|
||||
let filePath = ''
|
||||
if (isAbsolute(attributes.fixture)) {
|
||||
filepath = attributes.fixture
|
||||
filePath = attributes.fixture
|
||||
} else {
|
||||
filepath = join(__dirname, '..', 'api', 'fixtures', attributes.fixture)
|
||||
filePath = join(__dirname, '..', 'api', 'fixtures', attributes.fixture)
|
||||
}
|
||||
|
||||
return req.attach('videofile', filepath)
|
||||
return req.attach('videofile', filePath)
|
||||
.expect(specialStatus)
|
||||
}
|
||||
|
||||
@ -238,9 +238,9 @@ function rateVideo (url: string, accessToken: string, id: number, rating: string
|
||||
.expect(specialStatus)
|
||||
}
|
||||
|
||||
function parseTorrentVideo (server: ServerInfo, videoUUID: string, resolutionLabel: string) {
|
||||
function parseTorrentVideo (server: ServerInfo, videoUUID: string, resolution: number) {
|
||||
return new Promise<any>((res, rej) => {
|
||||
const torrentName = videoUUID + '-' + resolutionLabel + '.torrent'
|
||||
const torrentName = videoUUID + '-' + resolution + '.torrent'
|
||||
const torrentPath = join(__dirname, '..', '..', '..', 'test' + server.serverNumber, 'torrents', torrentName)
|
||||
readFile(torrentPath, (err, data) => {
|
||||
if (err) return rej(err)
|
||||
|
@ -1,5 +1,4 @@
|
||||
export enum VideoResolution {
|
||||
ORIGINAL = 0,
|
||||
H_240P = 240,
|
||||
H_360P = 360,
|
||||
H_480P = 480,
|
||||
|
Loading…
Reference in New Issue
Block a user