Move video file metadata in their own table
Will be used for user video quotas and multiple video resolutions
This commit is contained in:
parent
69f224587e
commit
93e1258c7c
30 changed files with 818 additions and 340 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -19,3 +19,4 @@
|
|||
/*.sublime-workspace
|
||||
/dist
|
||||
/.idea
|
||||
/PeerTube.iml
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Video as VideoServerModel } from '../../../../../shared'
|
||||
import { Video as VideoServerModel, VideoFile } from '../../../../../shared'
|
||||
import { User } from '../../shared'
|
||||
|
||||
export class Video implements VideoServerModel {
|
||||
|
@ -17,7 +17,6 @@ export class Video implements VideoServerModel {
|
|||
id: number
|
||||
uuid: string
|
||||
isLocal: boolean
|
||||
magnetUri: string
|
||||
name: string
|
||||
podHost: string
|
||||
tags: string[]
|
||||
|
@ -29,6 +28,7 @@ export class Video implements VideoServerModel {
|
|||
likes: number
|
||||
dislikes: number
|
||||
nsfw: boolean
|
||||
files: VideoFile[]
|
||||
|
||||
private static createByString (author: string, podHost: string) {
|
||||
return author + '@' + podHost
|
||||
|
@ -57,7 +57,6 @@ export class Video implements VideoServerModel {
|
|||
id: number,
|
||||
uuid: string,
|
||||
isLocal: boolean,
|
||||
magnetUri: string,
|
||||
name: string,
|
||||
podHost: string,
|
||||
tags: string[],
|
||||
|
@ -66,7 +65,8 @@ export class Video implements VideoServerModel {
|
|||
views: number,
|
||||
likes: number,
|
||||
dislikes: number,
|
||||
nsfw: boolean
|
||||
nsfw: boolean,
|
||||
files: VideoFile[]
|
||||
}) {
|
||||
this.author = hash.author
|
||||
this.createdAt = new Date(hash.createdAt)
|
||||
|
@ -82,7 +82,6 @@ export class Video implements VideoServerModel {
|
|||
this.id = hash.id
|
||||
this.uuid = hash.uuid
|
||||
this.isLocal = hash.isLocal
|
||||
this.magnetUri = hash.magnetUri
|
||||
this.name = hash.name
|
||||
this.podHost = hash.podHost
|
||||
this.tags = hash.tags
|
||||
|
@ -94,6 +93,7 @@ export class Video implements VideoServerModel {
|
|||
this.likes = hash.likes
|
||||
this.dislikes = hash.dislikes
|
||||
this.nsfw = hash.nsfw
|
||||
this.files = hash.files
|
||||
|
||||
this.by = Video.createByString(hash.author, hash.podHost)
|
||||
}
|
||||
|
@ -115,6 +115,13 @@ export class Video implements VideoServerModel {
|
|||
return (this.nsfw && (!user || user.displayNSFW === false))
|
||||
}
|
||||
|
||||
getDefaultMagnetUri () {
|
||||
if (this.files === undefined || this.files.length === 0) return ''
|
||||
|
||||
// TODO: choose the original file
|
||||
return this.files[0].magnetUri
|
||||
}
|
||||
|
||||
patch (values: Object) {
|
||||
Object.keys(values).forEach((key) => {
|
||||
this[key] = values[key]
|
||||
|
@ -132,7 +139,6 @@ export class Video implements VideoServerModel {
|
|||
duration: this.duration,
|
||||
id: this.id,
|
||||
isLocal: this.isLocal,
|
||||
magnetUri: this.magnetUri,
|
||||
name: this.name,
|
||||
podHost: this.podHost,
|
||||
tags: this.tags,
|
||||
|
@ -140,7 +146,8 @@ export class Video implements VideoServerModel {
|
|||
views: this.views,
|
||||
likes: this.likes,
|
||||
dislikes: this.dislikes,
|
||||
nsfw: this.nsfw
|
||||
nsfw: this.nsfw,
|
||||
files: this.files
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<input #magnetUriInput (click)="magnetUriInput.select()" type="text" class="form-control input-sm readonly" readonly [value]="video.magnetUri" />
|
||||
<input #magnetUriInput (click)="magnetUriInput.select()" type="text" class="form-control input-sm readonly" readonly [value]="video.getDefaultMagnetUri()" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -90,8 +90,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
|||
window.clearInterval(this.torrentInfosInterval)
|
||||
window.clearTimeout(this.errorTimer)
|
||||
|
||||
if (this.video !== null && this.webTorrentService.has(this.video.magnetUri)) {
|
||||
this.webTorrentService.remove(this.video.magnetUri)
|
||||
if (this.video !== null && this.webTorrentService.has(this.video.getDefaultMagnetUri())) {
|
||||
this.webTorrentService.remove(this.video.getDefaultMagnetUri())
|
||||
}
|
||||
|
||||
// Remove player
|
||||
|
@ -108,13 +108,13 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
|||
// We are loading the video
|
||||
this.loading = true
|
||||
|
||||
console.log('Adding ' + this.video.magnetUri + '.')
|
||||
console.log('Adding ' + this.video.getDefaultMagnetUri() + '.')
|
||||
|
||||
// The callback might never return if there are network issues
|
||||
// So we create a timer to inform the user the load is abnormally long
|
||||
this.errorTimer = window.setTimeout(() => this.loadTooLong(), VideoWatchComponent.LOADTIME_TOO_LONG)
|
||||
|
||||
const torrent = this.webTorrentService.add(this.video.magnetUri, torrent => {
|
||||
const torrent = this.webTorrentService.add(this.video.getDefaultMagnetUri(), torrent => {
|
||||
// Clear the error timer
|
||||
window.clearTimeout(this.errorTimer)
|
||||
// Maybe the error was fired by the timer, so reset it
|
||||
|
@ -123,7 +123,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
|||
// We are not loading the video anymore
|
||||
this.loading = false
|
||||
|
||||
console.log('Added ' + this.video.magnetUri + '.')
|
||||
console.log('Added ' + this.video.getDefaultMagnetUri() + '.')
|
||||
torrent.files[0].renderTo(this.playerElement, (err) => {
|
||||
if (err) {
|
||||
this.notificationsService.error('Error', 'Cannot append the file in the video element.')
|
||||
|
|
|
@ -57,7 +57,11 @@ loadVideoInfos(videoId, (err, videoInfos) => {
|
|||
return
|
||||
}
|
||||
|
||||
const magnetUri = videoInfos.magnetUri
|
||||
let magnetUri = ''
|
||||
if (videoInfos.files !== undefined && videoInfos.files.length !== 0) {
|
||||
magnetUri = videoInfos.files[0].magnetUri
|
||||
}
|
||||
|
||||
const videoContainer = document.getElementById('video-container') as HTMLVideoElement
|
||||
const previewUrl = window.location.origin + videoInfos.previewPath
|
||||
videoContainer.poster = previewUrl
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
"danger:clean:modules": "scripty",
|
||||
"reset-password": "ts-node ./scripts/reset-password.ts",
|
||||
"play": "scripty",
|
||||
"dev": "scripty",
|
||||
"dev:server": "scripty",
|
||||
"dev:client": "scripty",
|
||||
"start": "node dist/server",
|
||||
|
|
5
scripts/dev/index.sh
Executable file
5
scripts/dev/index.sh
Executable file
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
NODE_ENV=test concurrently -k \
|
||||
"npm run watch:client" \
|
||||
"npm run watch:server"
|
|
@ -1,4 +1,5 @@
|
|||
import { readFileSync, writeFileSync } from 'fs'
|
||||
import { join } from 'path'
|
||||
import * as parseTorrent from 'parse-torrent'
|
||||
|
||||
import { CONFIG, STATIC_PATHS } from '../server/initializers/constants'
|
||||
|
@ -19,17 +20,10 @@ db.init(true)
|
|||
return db.Video.list()
|
||||
})
|
||||
.then(videos => {
|
||||
videos.forEach(function (video) {
|
||||
const torrentName = video.id + '.torrent'
|
||||
const torrentPath = CONFIG.STORAGE.TORRENTS_DIR + torrentName
|
||||
const filename = video.id + video.extname
|
||||
|
||||
const parsed = parseTorrent(readFileSync(torrentPath))
|
||||
parsed.announce = [ CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOST + '/tracker/socket' ]
|
||||
parsed.urlList = [ CONFIG.WEBSERVER.URL + STATIC_PATHS.WEBSEED + filename ]
|
||||
|
||||
const buf = parseTorrent.toTorrentFile(parsed)
|
||||
writeFileSync(torrentPath, buf)
|
||||
videos.forEach(video => {
|
||||
video.VideoFiles.forEach(file => {
|
||||
video.createTorrentAndSetInfoHash(file)
|
||||
})
|
||||
})
|
||||
|
||||
process.exit(0)
|
||||
|
|
20
server.ts
20
server.ts
|
@ -26,7 +26,7 @@ const app = express()
|
|||
// ----------- Database -----------
|
||||
// Do not use barrels because we don't want to load all modules here (we need to initialize database first)
|
||||
import { logger } from './server/helpers/logger'
|
||||
import { API_VERSION, CONFIG } from './server/initializers/constants'
|
||||
import { API_VERSION, CONFIG, STATIC_PATHS } from './server/initializers/constants'
|
||||
// Initialize database and models
|
||||
import { database as db } from './server/initializers/database'
|
||||
db.init(false).then(() => onDatabaseInitDone())
|
||||
|
@ -57,10 +57,20 @@ import { apiRouter, clientsRouter, staticRouter } from './server/controllers'
|
|||
|
||||
// Enable CORS for develop
|
||||
if (isTestInstance()) {
|
||||
app.use(cors({
|
||||
origin: 'http://localhost:3000',
|
||||
credentials: true
|
||||
}))
|
||||
app.use((req, res, next) => {
|
||||
// These routes have already cors
|
||||
if (
|
||||
req.path.indexOf(STATIC_PATHS.TORRENTS) === -1 &&
|
||||
req.path.indexOf(STATIC_PATHS.WEBSEED) === -1
|
||||
) {
|
||||
return (cors({
|
||||
origin: 'http://localhost:3000',
|
||||
credentials: true
|
||||
}))(req, res, next)
|
||||
}
|
||||
|
||||
return next()
|
||||
})
|
||||
}
|
||||
|
||||
// For the logger
|
||||
|
|
|
@ -258,8 +258,6 @@ function addRemoteVideo (videoToCreateData: RemoteVideoCreateData, fromPod: PodI
|
|||
const videoData = {
|
||||
name: videoToCreateData.name,
|
||||
uuid: videoToCreateData.uuid,
|
||||
extname: videoToCreateData.extname,
|
||||
infoHash: videoToCreateData.infoHash,
|
||||
category: videoToCreateData.category,
|
||||
licence: videoToCreateData.licence,
|
||||
language: videoToCreateData.language,
|
||||
|
@ -289,6 +287,26 @@ function addRemoteVideo (videoToCreateData: RemoteVideoCreateData, fromPod: PodI
|
|||
|
||||
return video.save(options).then(videoCreated => ({ tagInstances, videoCreated }))
|
||||
})
|
||||
.then(({ tagInstances, videoCreated }) => {
|
||||
const tasks = []
|
||||
const options = {
|
||||
transaction: t
|
||||
}
|
||||
|
||||
videoToCreateData.files.forEach(fileData => {
|
||||
const videoFileInstance = db.VideoFile.build({
|
||||
extname: fileData.extname,
|
||||
infoHash: fileData.infoHash,
|
||||
resolution: fileData.resolution,
|
||||
size: fileData.size,
|
||||
videoId: videoCreated.id
|
||||
})
|
||||
|
||||
tasks.push(videoFileInstance.save(options))
|
||||
})
|
||||
|
||||
return Promise.all(tasks).then(() => ({ tagInstances, videoCreated }))
|
||||
})
|
||||
.then(({ tagInstances, videoCreated }) => {
|
||||
const options = {
|
||||
transaction: t
|
||||
|
@ -344,6 +362,26 @@ function updateRemoteVideo (videoAttributesToUpdate: RemoteVideoUpdateData, from
|
|||
|
||||
return videoInstance.save(options).then(() => ({ videoInstance, tagInstances }))
|
||||
})
|
||||
.then(({ tagInstances, videoInstance }) => {
|
||||
const tasks = []
|
||||
const options = {
|
||||
transaction: t
|
||||
}
|
||||
|
||||
videoAttributesToUpdate.files.forEach(fileData => {
|
||||
const videoFileInstance = db.VideoFile.build({
|
||||
extname: fileData.extname,
|
||||
infoHash: fileData.infoHash,
|
||||
resolution: fileData.resolution,
|
||||
size: fileData.size,
|
||||
videoId: videoInstance.id
|
||||
})
|
||||
|
||||
tasks.push(videoFileInstance.save(options))
|
||||
})
|
||||
|
||||
return Promise.all(tasks).then(() => ({ tagInstances, videoInstance }))
|
||||
})
|
||||
.then(({ videoInstance, tagInstances }) => {
|
||||
const options = { transaction: t }
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as express from 'express'
|
||||
import * as Promise from 'bluebird'
|
||||
import * as multer from 'multer'
|
||||
import * as path from 'path'
|
||||
import { extname, join } from 'path'
|
||||
|
||||
import { database as db } from '../../../initializers/database'
|
||||
import {
|
||||
|
@ -16,7 +16,8 @@ import {
|
|||
addEventToRemoteVideo,
|
||||
quickAndDirtyUpdateVideoToFriends,
|
||||
addVideoToFriends,
|
||||
updateVideoToFriends
|
||||
updateVideoToFriends,
|
||||
JobScheduler
|
||||
} from '../../../lib'
|
||||
import {
|
||||
authenticate,
|
||||
|
@ -155,7 +156,7 @@ function addVideoRetryWrapper (req: express.Request, res: express.Response, next
|
|||
.catch(err => next(err))
|
||||
}
|
||||
|
||||
function addVideo (req: express.Request, res: express.Response, videoFile: Express.Multer.File) {
|
||||
function addVideo (req: express.Request, res: express.Response, videoPhysicalFile: Express.Multer.File) {
|
||||
const videoInfos: VideoCreate = req.body
|
||||
|
||||
return db.sequelize.transaction(t => {
|
||||
|
@ -177,13 +178,13 @@ function addVideo (req: express.Request, res: express.Response, videoFile: Expre
|
|||
const videoData = {
|
||||
name: videoInfos.name,
|
||||
remote: false,
|
||||
extname: path.extname(videoFile.filename),
|
||||
extname: extname(videoPhysicalFile.filename),
|
||||
category: videoInfos.category,
|
||||
licence: videoInfos.licence,
|
||||
language: videoInfos.language,
|
||||
nsfw: videoInfos.nsfw,
|
||||
description: videoInfos.description,
|
||||
duration: videoFile['duration'], // duration was added by a previous middleware
|
||||
duration: videoPhysicalFile['duration'], // duration was added by a previous middleware
|
||||
authorId: author.id
|
||||
}
|
||||
|
||||
|
@ -191,18 +192,50 @@ function addVideo (req: express.Request, res: express.Response, videoFile: Expre
|
|||
return { author, tagInstances, video }
|
||||
})
|
||||
.then(({ author, tagInstances, video }) => {
|
||||
const videoFileData = {
|
||||
extname: extname(videoPhysicalFile.filename),
|
||||
resolution: 0, // TODO: improve readability,
|
||||
size: videoPhysicalFile.size
|
||||
}
|
||||
|
||||
const videoFile = db.VideoFile.build(videoFileData)
|
||||
return { author, tagInstances, video, videoFile }
|
||||
})
|
||||
.then(({ author, tagInstances, video, videoFile }) => {
|
||||
const videoDir = CONFIG.STORAGE.VIDEOS_DIR
|
||||
const source = path.join(videoDir, videoFile.filename)
|
||||
const destination = path.join(videoDir, video.getVideoFilename())
|
||||
const source = join(videoDir, videoPhysicalFile.filename)
|
||||
const destination = join(videoDir, video.getVideoFilename(videoFile))
|
||||
|
||||
return renamePromise(source, destination)
|
||||
.then(() => {
|
||||
// This is important in case if there is another attempt in the retry process
|
||||
videoFile.filename = video.getVideoFilename()
|
||||
return { author, tagInstances, video }
|
||||
videoPhysicalFile.filename = video.getVideoFilename(videoFile)
|
||||
return { author, tagInstances, video, videoFile }
|
||||
})
|
||||
})
|
||||
.then(({ author, tagInstances, video }) => {
|
||||
.then(({ author, tagInstances, video, videoFile }) => {
|
||||
const tasks = []
|
||||
|
||||
tasks.push(
|
||||
video.createTorrentAndSetInfoHash(videoFile),
|
||||
video.createThumbnail(videoFile),
|
||||
video.createPreview(videoFile)
|
||||
)
|
||||
|
||||
if (CONFIG.TRANSCODING.ENABLED === true) {
|
||||
// Put uuid because we don't have id auto incremented for now
|
||||
const dataInput = {
|
||||
videoUUID: video.uuid
|
||||
}
|
||||
|
||||
tasks.push(
|
||||
JobScheduler.Instance.createJob(t, 'videoTranscoder', dataInput)
|
||||
)
|
||||
}
|
||||
|
||||
return Promise.all(tasks).then(() => ({ author, tagInstances, video, videoFile }))
|
||||
})
|
||||
.then(({ author, tagInstances, video, videoFile }) => {
|
||||
const options = { transaction: t }
|
||||
|
||||
return video.save(options)
|
||||
|
@ -210,9 +243,17 @@ function addVideo (req: express.Request, res: express.Response, videoFile: Expre
|
|||
// Do not forget to add Author informations to the created video
|
||||
videoCreated.Author = author
|
||||
|
||||
return { tagInstances, video: videoCreated }
|
||||
return { tagInstances, video: videoCreated, videoFile }
|
||||
})
|
||||
})
|
||||
.then(({ tagInstances, video, videoFile }) => {
|
||||
const options = { transaction: t }
|
||||
videoFile.videoId = video.id
|
||||
|
||||
return videoFile.save(options)
|
||||
.then(() => video.VideoFiles = [ videoFile ])
|
||||
.then(() => ({ tagInstances, video }))
|
||||
})
|
||||
.then(({ tagInstances, video }) => {
|
||||
if (!tagInstances) return video
|
||||
|
||||
|
@ -236,7 +277,7 @@ function addVideo (req: express.Request, res: express.Response, videoFile: Expre
|
|||
})
|
||||
.then(() => logger.info('Video with name %s created.', videoInfos.name))
|
||||
.catch((err: Error) => {
|
||||
logger.debug('Cannot insert the video.', { error: err.stack })
|
||||
logger.debug('Cannot insert the video.', err)
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
|
|
@ -23,10 +23,11 @@ import {
|
|||
isVideoNSFWValid,
|
||||
isVideoDescriptionValid,
|
||||
isVideoDurationValid,
|
||||
isVideoInfoHashValid,
|
||||
isVideoFileInfoHashValid,
|
||||
isVideoNameValid,
|
||||
isVideoTagsValid,
|
||||
isVideoExtnameValid
|
||||
isVideoFileExtnameValid,
|
||||
isVideoFileResolutionValid
|
||||
} from '../videos'
|
||||
|
||||
const ENDPOINT_ACTIONS = REQUEST_ENDPOINT_ACTIONS[REQUEST_ENDPOINTS.VIDEOS]
|
||||
|
@ -121,14 +122,22 @@ function isCommonVideoAttributesValid (video: any) {
|
|||
isVideoNSFWValid(video.nsfw) &&
|
||||
isVideoDescriptionValid(video.description) &&
|
||||
isVideoDurationValid(video.duration) &&
|
||||
isVideoInfoHashValid(video.infoHash) &&
|
||||
isVideoNameValid(video.name) &&
|
||||
isVideoTagsValid(video.tags) &&
|
||||
isVideoUUIDValid(video.uuid) &&
|
||||
isVideoExtnameValid(video.extname) &&
|
||||
isVideoViewsValid(video.views) &&
|
||||
isVideoLikesValid(video.likes) &&
|
||||
isVideoDislikesValid(video.dislikes)
|
||||
isVideoDislikesValid(video.dislikes) &&
|
||||
isArray(video.files) &&
|
||||
video.files.every(videoFile => {
|
||||
if (!videoFile) return false
|
||||
|
||||
return (
|
||||
isVideoFileInfoHashValid(videoFile.infoHash) &&
|
||||
isVideoFileExtnameValid(videoFile.extname) &&
|
||||
isVideoFileResolutionValid(videoFile.resolution)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
function isRequestTypeAddValid (value: string) {
|
||||
|
|
|
@ -7,7 +7,8 @@ import {
|
|||
VIDEO_CATEGORIES,
|
||||
VIDEO_LICENCES,
|
||||
VIDEO_LANGUAGES,
|
||||
VIDEO_RATE_TYPES
|
||||
VIDEO_RATE_TYPES,
|
||||
VIDEO_FILE_RESOLUTIONS
|
||||
} from '../../initializers'
|
||||
import { isUserUsernameValid } from './users'
|
||||
import { isArray, exists } from './misc'
|
||||
|
@ -53,14 +54,6 @@ function isVideoDurationValid (value: string) {
|
|||
return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.DURATION)
|
||||
}
|
||||
|
||||
function isVideoExtnameValid (value: string) {
|
||||
return VIDEOS_CONSTRAINTS_FIELDS.EXTNAME.indexOf(value) !== -1
|
||||
}
|
||||
|
||||
function isVideoInfoHashValid (value: string) {
|
||||
return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.INFO_HASH)
|
||||
}
|
||||
|
||||
function isVideoNameValid (value: string) {
|
||||
return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.NAME)
|
||||
}
|
||||
|
@ -128,6 +121,22 @@ function isVideoFile (value: string, files: { [ fieldname: string ]: Express.Mul
|
|||
return new RegExp('^video/(webm|mp4|ogg)$', 'i').test(file.mimetype)
|
||||
}
|
||||
|
||||
function isVideoFileSizeValid (value: string) {
|
||||
return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.FILE_SIZE)
|
||||
}
|
||||
|
||||
function isVideoFileResolutionValid (value: string) {
|
||||
return VIDEO_FILE_RESOLUTIONS[value] !== undefined
|
||||
}
|
||||
|
||||
function isVideoFileExtnameValid (value: string) {
|
||||
return VIDEOS_CONSTRAINTS_FIELDS.EXTNAME.indexOf(value) !== -1
|
||||
}
|
||||
|
||||
function isVideoFileInfoHashValid (value: string) {
|
||||
return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.INFO_HASH)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
|
@ -140,12 +149,12 @@ export {
|
|||
isVideoNSFWValid,
|
||||
isVideoDescriptionValid,
|
||||
isVideoDurationValid,
|
||||
isVideoInfoHashValid,
|
||||
isVideoFileInfoHashValid,
|
||||
isVideoNameValid,
|
||||
isVideoTagsValid,
|
||||
isVideoThumbnailValid,
|
||||
isVideoThumbnailDataValid,
|
||||
isVideoExtnameValid,
|
||||
isVideoFileExtnameValid,
|
||||
isVideoUUIDValid,
|
||||
isVideoAbuseReasonValid,
|
||||
isVideoAbuseReporterUsernameValid,
|
||||
|
@ -154,7 +163,9 @@ export {
|
|||
isVideoLikesValid,
|
||||
isVideoRatingTypeValid,
|
||||
isVideoDislikesValid,
|
||||
isVideoEventCountValid
|
||||
isVideoEventCountValid,
|
||||
isVideoFileSizeValid,
|
||||
isVideoFileResolutionValid
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@ -183,7 +194,9 @@ declare global {
|
|||
isVideoLikesValid,
|
||||
isVideoRatingTypeValid,
|
||||
isVideoDislikesValid,
|
||||
isVideoEventCountValid
|
||||
isVideoEventCountValid,
|
||||
isVideoFileSizeValid,
|
||||
isVideoFileResolutionValid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ import {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const LAST_MIGRATION_VERSION = 55
|
||||
const LAST_MIGRATION_VERSION = 65
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
|
@ -114,7 +114,8 @@ const CONSTRAINTS_FIELDS = {
|
|||
THUMBNAIL_DATA: { min: 0, max: 20000 }, // Bytes
|
||||
VIEWS: { min: 0 },
|
||||
LIKES: { min: 0 },
|
||||
DISLIKES: { min: 0 }
|
||||
DISLIKES: { min: 0 },
|
||||
FILE_SIZE: { min: 10, max: 1024 * 1024 * 1024 * 3 /* 3Go */ }
|
||||
},
|
||||
VIDEO_EVENTS: {
|
||||
COUNT: { min: 0 }
|
||||
|
@ -176,6 +177,14 @@ const VIDEO_LANGUAGES = {
|
|||
14: 'Italien'
|
||||
}
|
||||
|
||||
const VIDEO_FILE_RESOLUTIONS = {
|
||||
0: 'original',
|
||||
1: '360p',
|
||||
2: '480p',
|
||||
3: '720p',
|
||||
4: '1080p'
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Score a pod has when we create it as a friend
|
||||
|
@ -362,6 +371,7 @@ export {
|
|||
THUMBNAILS_SIZE,
|
||||
USER_ROLES,
|
||||
VIDEO_CATEGORIES,
|
||||
VIDEO_FILE_RESOLUTIONS,
|
||||
VIDEO_LANGUAGES,
|
||||
VIDEO_LICENCES,
|
||||
VIDEO_RATE_TYPES
|
||||
|
|
|
@ -23,6 +23,7 @@ import {
|
|||
UserVideoRateModel,
|
||||
VideoAbuseModel,
|
||||
BlacklistedVideoModel,
|
||||
VideoFileModel,
|
||||
VideoTagModel,
|
||||
VideoModel
|
||||
} from '../models'
|
||||
|
@ -49,6 +50,7 @@ const database: {
|
|||
UserVideoRate?: UserVideoRateModel,
|
||||
User?: UserModel,
|
||||
VideoAbuse?: VideoAbuseModel,
|
||||
VideoFile?: VideoFileModel,
|
||||
BlacklistedVideo?: BlacklistedVideoModel,
|
||||
VideoTag?: VideoTagModel,
|
||||
Video?: VideoModel
|
||||
|
|
34
server/initializers/migrations/0060-video-file.ts
Normal file
34
server/initializers/migrations/0060-video-file.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import * as Sequelize from 'sequelize'
|
||||
import * as Promise from 'bluebird'
|
||||
|
||||
function up (utils: {
|
||||
transaction: Sequelize.Transaction,
|
||||
queryInterface: Sequelize.QueryInterface,
|
||||
sequelize: Sequelize.Sequelize,
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
const q = utils.queryInterface
|
||||
|
||||
const query = 'INSERT INTO "VideoFiles" ("videoId", "resolution", "size", "extname", "infoHash", "createdAt", "updatedAt") ' +
|
||||
'SELECT "id" AS "videoId", 0 AS "resolution", 0 AS "size", ' +
|
||||
'"extname"::"text"::"enum_VideoFiles_extname" as "extname", "infoHash", "createdAt", "updatedAt" ' +
|
||||
'FROM "Videos"'
|
||||
|
||||
return utils.db.VideoFile.sync()
|
||||
.then(() => utils.sequelize.query(query))
|
||||
.then(() => {
|
||||
return q.removeColumn('Videos', 'extname')
|
||||
})
|
||||
.then(() => {
|
||||
return q.removeColumn('Videos', 'infoHash')
|
||||
})
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
46
server/initializers/migrations/0065-video-file-size.ts
Normal file
46
server/initializers/migrations/0065-video-file-size.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
import * as Sequelize from 'sequelize'
|
||||
import * as Promise from 'bluebird'
|
||||
import { stat } from 'fs'
|
||||
|
||||
import { VideoInstance } from '../../models'
|
||||
|
||||
function up (utils: {
|
||||
transaction: Sequelize.Transaction,
|
||||
queryInterface: Sequelize.QueryInterface,
|
||||
sequelize: Sequelize.Sequelize,
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
return utils.db.Video.listOwnedAndPopulateAuthorAndTags()
|
||||
.then((videos: VideoInstance[]) => {
|
||||
const tasks: Promise<any>[] = []
|
||||
|
||||
videos.forEach(video => {
|
||||
video.VideoFiles.forEach(videoFile => {
|
||||
const p = new Promise((res, rej) => {
|
||||
stat(video.getVideoFilePath(videoFile), (err, stats) => {
|
||||
if (err) return rej(err)
|
||||
|
||||
videoFile.size = stats.size
|
||||
videoFile.save().then(res).catch(rej)
|
||||
})
|
||||
})
|
||||
|
||||
tasks.push(p)
|
||||
})
|
||||
})
|
||||
|
||||
return tasks
|
||||
})
|
||||
.then((tasks: Promise<any>[]) => {
|
||||
return Promise.all(tasks)
|
||||
})
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
|
@ -64,14 +64,16 @@ function getMigrationScripts () {
|
|||
script: string
|
||||
}[] = []
|
||||
|
||||
files.forEach(file => {
|
||||
// Filename is something like 'version-blabla.js'
|
||||
const version = file.split('-')[0]
|
||||
filesToMigrate.push({
|
||||
version,
|
||||
script: file
|
||||
files
|
||||
.filter(file => file.endsWith('.js.map') === false)
|
||||
.forEach(file => {
|
||||
// Filename is something like 'version-blabla.js'
|
||||
const version = file.split('-')[0]
|
||||
filesToMigrate.push({
|
||||
version,
|
||||
script: file
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return filesToMigrate
|
||||
})
|
||||
|
@ -93,7 +95,8 @@ function executeMigration (actualVersion: number, entity: { version: string, scr
|
|||
const options = {
|
||||
transaction: t,
|
||||
queryInterface: db.sequelize.getQueryInterface(),
|
||||
sequelize: db.sequelize
|
||||
sequelize: db.sequelize,
|
||||
db
|
||||
}
|
||||
|
||||
return migrationScript.up(options)
|
||||
|
|
|
@ -5,7 +5,9 @@ import { VideoInstance } from '../../../models'
|
|||
|
||||
function process (data: { videoUUID: string }) {
|
||||
return db.Video.loadByUUIDAndPopulateAuthorAndPodAndTags(data.videoUUID).then(video => {
|
||||
return video.transcodeVideofile().then(() => video)
|
||||
// TODO: handle multiple resolutions
|
||||
const videoFile = video.VideoFiles[0]
|
||||
return video.transcodeVideofile(videoFile).then(() => video)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -3,4 +3,5 @@ export * from './tag-interface'
|
|||
export * from './video-abuse-interface'
|
||||
export * from './video-blacklist-interface'
|
||||
export * from './video-tag-interface'
|
||||
export * from './video-file-interface'
|
||||
export * from './video-interface'
|
||||
|
|
24
server/models/video/video-file-interface.ts
Normal file
24
server/models/video/video-file-interface.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
import * as Sequelize from 'sequelize'
|
||||
|
||||
export namespace VideoFileMethods {
|
||||
}
|
||||
|
||||
export interface VideoFileClass {
|
||||
}
|
||||
|
||||
export interface VideoFileAttributes {
|
||||
resolution: number
|
||||
size: number
|
||||
infoHash?: string
|
||||
extname: string
|
||||
|
||||
videoId?: number
|
||||
}
|
||||
|
||||
export interface VideoFileInstance extends VideoFileClass, VideoFileAttributes, Sequelize.Instance<VideoFileAttributes> {
|
||||
id: number
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
}
|
||||
|
||||
export interface VideoFileModel extends VideoFileClass, Sequelize.Model<VideoFileInstance, VideoFileAttributes> {}
|
89
server/models/video/video-file.ts
Normal file
89
server/models/video/video-file.ts
Normal file
|
@ -0,0 +1,89 @@
|
|||
import * as Sequelize from 'sequelize'
|
||||
import { values } from 'lodash'
|
||||
|
||||
import { CONSTRAINTS_FIELDS } from '../../initializers'
|
||||
import {
|
||||
isVideoFileResolutionValid,
|
||||
isVideoFileSizeValid,
|
||||
isVideoFileInfoHashValid
|
||||
} from '../../helpers'
|
||||
|
||||
import { addMethodsToModel } from '../utils'
|
||||
import {
|
||||
VideoFileInstance,
|
||||
VideoFileAttributes
|
||||
} from './video-file-interface'
|
||||
|
||||
let VideoFile: Sequelize.Model<VideoFileInstance, VideoFileAttributes>
|
||||
|
||||
export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
|
||||
VideoFile = sequelize.define<VideoFileInstance, VideoFileAttributes>('VideoFile',
|
||||
{
|
||||
resolution: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
resolutionValid: value => {
|
||||
const res = isVideoFileResolutionValid(value)
|
||||
if (res === false) throw new Error('Video file resolution is not valid.')
|
||||
}
|
||||
}
|
||||
},
|
||||
size: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
sizeValid: value => {
|
||||
const res = isVideoFileSizeValid(value)
|
||||
if (res === false) throw new Error('Video file size is not valid.')
|
||||
}
|
||||
}
|
||||
},
|
||||
extname: {
|
||||
type: DataTypes.ENUM(values(CONSTRAINTS_FIELDS.VIDEOS.EXTNAME)),
|
||||
allowNull: false
|
||||
},
|
||||
infoHash: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
infoHashValid: value => {
|
||||
const res = isVideoFileInfoHashValid(value)
|
||||
if (res === false) throw new Error('Video file info hash is not valid.')
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
indexes: [
|
||||
{
|
||||
fields: [ 'videoId' ]
|
||||
},
|
||||
{
|
||||
fields: [ 'infoHash' ]
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
const classMethods = [
|
||||
associate
|
||||
]
|
||||
addMethodsToModel(VideoFile, classMethods)
|
||||
|
||||
return VideoFile
|
||||
}
|
||||
|
||||
// ------------------------------ STATICS ------------------------------
|
||||
|
||||
function associate (models) {
|
||||
VideoFile.belongsTo(models.Video, {
|
||||
foreignKey: {
|
||||
name: 'videoId',
|
||||
allowNull: false
|
||||
},
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
}
|
||||
|
||||
// ------------------------------ METHODS ------------------------------
|
|
@ -3,11 +3,19 @@ import * as Promise from 'bluebird'
|
|||
|
||||
import { AuthorInstance } from './author-interface'
|
||||
import { TagAttributes, TagInstance } from './tag-interface'
|
||||
import { VideoFileAttributes, VideoFileInstance } from './video-file-interface'
|
||||
|
||||
// Don't use barrel, import just what we need
|
||||
import { Video as FormatedVideo } from '../../../shared/models/videos/video.model'
|
||||
import { ResultList } from '../../../shared/models/result-list.model'
|
||||
|
||||
export type FormatedRemoteVideoFile = {
|
||||
infoHash: string
|
||||
resolution: number
|
||||
extname: string
|
||||
size: number
|
||||
}
|
||||
|
||||
export type FormatedAddRemoteVideo = {
|
||||
uuid: string
|
||||
name: string
|
||||
|
@ -16,17 +24,16 @@ export type FormatedAddRemoteVideo = {
|
|||
language: number
|
||||
nsfw: boolean
|
||||
description: string
|
||||
infoHash: string
|
||||
author: string
|
||||
duration: number
|
||||
thumbnailData: string
|
||||
tags: string[]
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
extname: string
|
||||
views: number
|
||||
likes: number
|
||||
dislikes: number
|
||||
files: FormatedRemoteVideoFile[]
|
||||
}
|
||||
|
||||
export type FormatedUpdateRemoteVideo = {
|
||||
|
@ -37,31 +44,35 @@ export type FormatedUpdateRemoteVideo = {
|
|||
language: number
|
||||
nsfw: boolean
|
||||
description: string
|
||||
infoHash: string
|
||||
author: string
|
||||
duration: number
|
||||
tags: string[]
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
extname: string
|
||||
views: number
|
||||
likes: number
|
||||
dislikes: number
|
||||
files: FormatedRemoteVideoFile[]
|
||||
}
|
||||
|
||||
export namespace VideoMethods {
|
||||
export type GenerateMagnetUri = (this: VideoInstance) => string
|
||||
export type GetVideoFilename = (this: VideoInstance) => string
|
||||
export type GetThumbnailName = (this: VideoInstance) => string
|
||||
export type GetPreviewName = (this: VideoInstance) => string
|
||||
export type GetTorrentName = (this: VideoInstance) => string
|
||||
export type IsOwned = (this: VideoInstance) => boolean
|
||||
export type ToFormatedJSON = (this: VideoInstance) => FormatedVideo
|
||||
|
||||
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 CreatePreview = (this: VideoInstance, videoFile: VideoFileInstance) => Promise<string>
|
||||
export type CreateThumbnail = (this: VideoInstance, videoFile: VideoFileInstance) => Promise<string>
|
||||
export type GetVideoFilePath = (this: VideoInstance, videoFile: VideoFileInstance) => string
|
||||
export type CreateTorrentAndSetInfoHash = (this: VideoInstance, videoFile: VideoFileInstance) => Promise<void>
|
||||
|
||||
export type ToAddRemoteJSON = (this: VideoInstance) => Promise<FormatedAddRemoteVideo>
|
||||
export type ToUpdateRemoteJSON = (this: VideoInstance) => FormatedUpdateRemoteVideo
|
||||
|
||||
export type TranscodeVideofile = (this: VideoInstance) => Promise<void>
|
||||
export type TranscodeVideofile = (this: VideoInstance, inputVideoFile: VideoFileInstance) => Promise<void>
|
||||
|
||||
// Return thumbnail name
|
||||
export type GenerateThumbnailFromData = (video: VideoInstance, thumbnailData: string) => Promise<string>
|
||||
|
@ -86,31 +97,25 @@ export namespace VideoMethods {
|
|||
export type LoadAndPopulateAuthor = (id: number) => Promise<VideoInstance>
|
||||
export type LoadAndPopulateAuthorAndPodAndTags = (id: number) => Promise<VideoInstance>
|
||||
export type LoadByUUIDAndPopulateAuthorAndPodAndTags = (uuid: string) => Promise<VideoInstance>
|
||||
|
||||
export type RemoveThumbnail = (this: VideoInstance) => Promise<void>
|
||||
export type RemovePreview = (this: VideoInstance) => Promise<void>
|
||||
export type RemoveFile = (this: VideoInstance, videoFile: VideoFileInstance) => Promise<void>
|
||||
export type RemoveTorrent = (this: VideoInstance, videoFile: VideoFileInstance) => Promise<void>
|
||||
}
|
||||
|
||||
export interface VideoClass {
|
||||
generateMagnetUri: VideoMethods.GenerateMagnetUri
|
||||
getVideoFilename: VideoMethods.GetVideoFilename
|
||||
getThumbnailName: VideoMethods.GetThumbnailName
|
||||
getPreviewName: VideoMethods.GetPreviewName
|
||||
getTorrentName: VideoMethods.GetTorrentName
|
||||
isOwned: VideoMethods.IsOwned
|
||||
toFormatedJSON: VideoMethods.ToFormatedJSON
|
||||
toAddRemoteJSON: VideoMethods.ToAddRemoteJSON
|
||||
toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON
|
||||
transcodeVideofile: VideoMethods.TranscodeVideofile
|
||||
|
||||
generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData
|
||||
getDurationFromFile: VideoMethods.GetDurationFromFile
|
||||
list: VideoMethods.List
|
||||
listForApi: VideoMethods.ListForApi
|
||||
loadByHostAndUUID: VideoMethods.LoadByHostAndUUID
|
||||
listOwnedAndPopulateAuthorAndTags: VideoMethods.ListOwnedAndPopulateAuthorAndTags
|
||||
listOwnedByAuthor: VideoMethods.ListOwnedByAuthor
|
||||
load: VideoMethods.Load
|
||||
loadByUUID: VideoMethods.LoadByUUID
|
||||
loadAndPopulateAuthor: VideoMethods.LoadAndPopulateAuthor
|
||||
loadAndPopulateAuthorAndPodAndTags: VideoMethods.LoadAndPopulateAuthorAndPodAndTags
|
||||
loadByHostAndUUID: VideoMethods.LoadByHostAndUUID
|
||||
loadByUUID: VideoMethods.LoadByUUID
|
||||
loadByUUIDAndPopulateAuthorAndPodAndTags: VideoMethods.LoadByUUIDAndPopulateAuthorAndPodAndTags
|
||||
searchAndPopulateAuthorAndPodAndTags: VideoMethods.SearchAndPopulateAuthorAndPodAndTags
|
||||
}
|
||||
|
@ -118,13 +123,11 @@ export interface VideoClass {
|
|||
export interface VideoAttributes {
|
||||
uuid?: string
|
||||
name: string
|
||||
extname: string
|
||||
category: number
|
||||
licence: number
|
||||
language: number
|
||||
nsfw: boolean
|
||||
description: string
|
||||
infoHash?: string
|
||||
duration: number
|
||||
views?: number
|
||||
likes?: number
|
||||
|
@ -133,6 +136,7 @@ export interface VideoAttributes {
|
|||
|
||||
Author?: AuthorInstance
|
||||
Tags?: TagInstance[]
|
||||
VideoFiles?: VideoFileInstance[]
|
||||
}
|
||||
|
||||
export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.Instance<VideoAttributes> {
|
||||
|
@ -140,18 +144,27 @@ export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.In
|
|||
createdAt: Date
|
||||
updatedAt: Date
|
||||
|
||||
createPreview: VideoMethods.CreatePreview
|
||||
createThumbnail: VideoMethods.CreateThumbnail
|
||||
createTorrentAndSetInfoHash: VideoMethods.CreateTorrentAndSetInfoHash
|
||||
generateMagnetUri: VideoMethods.GenerateMagnetUri
|
||||
getVideoFilename: VideoMethods.GetVideoFilename
|
||||
getThumbnailName: VideoMethods.GetThumbnailName
|
||||
getPreviewName: VideoMethods.GetPreviewName
|
||||
getTorrentName: VideoMethods.GetTorrentName
|
||||
getThumbnailName: VideoMethods.GetThumbnailName
|
||||
getTorrentFileName: VideoMethods.GetTorrentFileName
|
||||
getVideoFilename: VideoMethods.GetVideoFilename
|
||||
getVideoFilePath: VideoMethods.GetVideoFilePath
|
||||
isOwned: VideoMethods.IsOwned
|
||||
toFormatedJSON: VideoMethods.ToFormatedJSON
|
||||
removeFile: VideoMethods.RemoveFile
|
||||
removePreview: VideoMethods.RemovePreview
|
||||
removeThumbnail: VideoMethods.RemoveThumbnail
|
||||
removeTorrent: VideoMethods.RemoveTorrent
|
||||
toAddRemoteJSON: VideoMethods.ToAddRemoteJSON
|
||||
toFormatedJSON: VideoMethods.ToFormatedJSON
|
||||
toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON
|
||||
transcodeVideofile: VideoMethods.TranscodeVideofile
|
||||
|
||||
setTags: Sequelize.HasManySetAssociationsMixin<TagAttributes, string>
|
||||
setVideoFiles: Sequelize.HasManySetAssociationsMixin<VideoFileAttributes, string>
|
||||
}
|
||||
|
||||
export interface VideoModel extends VideoClass, Sequelize.Model<VideoInstance, VideoAttributes> {}
|
||||
|
|
|
@ -2,13 +2,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, values } from 'lodash'
|
||||
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 { database as db } from '../../initializers/database'
|
||||
import { TagInstance } from './tag-interface'
|
||||
import {
|
||||
logger,
|
||||
|
@ -18,7 +17,6 @@ import {
|
|||
isVideoLanguageValid,
|
||||
isVideoNSFWValid,
|
||||
isVideoDescriptionValid,
|
||||
isVideoInfoHashValid,
|
||||
isVideoDurationValid,
|
||||
readFileBufferPromise,
|
||||
unlinkPromise,
|
||||
|
@ -27,16 +25,17 @@ import {
|
|||
createTorrentPromise
|
||||
} from '../../helpers'
|
||||
import {
|
||||
CONSTRAINTS_FIELDS,
|
||||
CONFIG,
|
||||
REMOTE_SCHEME,
|
||||
STATIC_PATHS,
|
||||
VIDEO_CATEGORIES,
|
||||
VIDEO_LICENCES,
|
||||
VIDEO_LANGUAGES,
|
||||
THUMBNAILS_SIZE
|
||||
THUMBNAILS_SIZE,
|
||||
VIDEO_FILE_RESOLUTIONS
|
||||
} from '../../initializers'
|
||||
import { JobScheduler, removeVideoToFriends } from '../../lib'
|
||||
import { removeVideoToFriends } from '../../lib'
|
||||
import { VideoFileInstance } from './video-file-interface'
|
||||
|
||||
import { addMethodsToModel, getSort } from '../utils'
|
||||
import {
|
||||
|
@ -51,12 +50,16 @@ let generateMagnetUri: VideoMethods.GenerateMagnetUri
|
|||
let getVideoFilename: VideoMethods.GetVideoFilename
|
||||
let getThumbnailName: VideoMethods.GetThumbnailName
|
||||
let getPreviewName: VideoMethods.GetPreviewName
|
||||
let getTorrentName: VideoMethods.GetTorrentName
|
||||
let getTorrentFileName: VideoMethods.GetTorrentFileName
|
||||
let isOwned: VideoMethods.IsOwned
|
||||
let toFormatedJSON: VideoMethods.ToFormatedJSON
|
||||
let toAddRemoteJSON: VideoMethods.ToAddRemoteJSON
|
||||
let toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON
|
||||
let transcodeVideofile: VideoMethods.TranscodeVideofile
|
||||
let createPreview: VideoMethods.CreatePreview
|
||||
let createThumbnail: VideoMethods.CreateThumbnail
|
||||
let getVideoFilePath: VideoMethods.GetVideoFilePath
|
||||
let createTorrentAndSetInfoHash: VideoMethods.CreateTorrentAndSetInfoHash
|
||||
|
||||
let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData
|
||||
let getDurationFromFile: VideoMethods.GetDurationFromFile
|
||||
|
@ -71,6 +74,10 @@ let loadAndPopulateAuthor: VideoMethods.LoadAndPopulateAuthor
|
|||
let loadAndPopulateAuthorAndPodAndTags: VideoMethods.LoadAndPopulateAuthorAndPodAndTags
|
||||
let loadByUUIDAndPopulateAuthorAndPodAndTags: VideoMethods.LoadByUUIDAndPopulateAuthorAndPodAndTags
|
||||
let searchAndPopulateAuthorAndPodAndTags: VideoMethods.SearchAndPopulateAuthorAndPodAndTags
|
||||
let removeThumbnail: VideoMethods.RemoveThumbnail
|
||||
let removePreview: VideoMethods.RemovePreview
|
||||
let removeFile: VideoMethods.RemoveFile
|
||||
let removeTorrent: VideoMethods.RemoveTorrent
|
||||
|
||||
export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
|
||||
Video = sequelize.define<VideoInstance, VideoAttributes>('Video',
|
||||
|
@ -93,10 +100,6 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
|
|||
}
|
||||
}
|
||||
},
|
||||
extname: {
|
||||
type: DataTypes.ENUM(values(CONSTRAINTS_FIELDS.VIDEOS.EXTNAME)),
|
||||
allowNull: false
|
||||
},
|
||||
category: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
|
@ -148,16 +151,6 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
|
|||
}
|
||||
}
|
||||
},
|
||||
infoHash: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
infoHashValid: value => {
|
||||
const res = isVideoInfoHashValid(value)
|
||||
if (res === false) throw new Error('Video info hash is not valid.')
|
||||
}
|
||||
}
|
||||
},
|
||||
duration: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
|
@ -215,9 +208,6 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
|
|||
{
|
||||
fields: [ 'duration' ]
|
||||
},
|
||||
{
|
||||
fields: [ 'infoHash' ]
|
||||
},
|
||||
{
|
||||
fields: [ 'views' ]
|
||||
},
|
||||
|
@ -229,8 +219,6 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
|
|||
}
|
||||
],
|
||||
hooks: {
|
||||
beforeValidate,
|
||||
beforeCreate,
|
||||
afterDestroy
|
||||
}
|
||||
}
|
||||
|
@ -246,23 +234,30 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
|
|||
listOwnedAndPopulateAuthorAndTags,
|
||||
listOwnedByAuthor,
|
||||
load,
|
||||
loadByUUID,
|
||||
loadByHostAndUUID,
|
||||
loadAndPopulateAuthor,
|
||||
loadAndPopulateAuthorAndPodAndTags,
|
||||
loadByHostAndUUID,
|
||||
loadByUUID,
|
||||
loadByUUIDAndPopulateAuthorAndPodAndTags,
|
||||
searchAndPopulateAuthorAndPodAndTags,
|
||||
removeFromBlacklist
|
||||
searchAndPopulateAuthorAndPodAndTags
|
||||
]
|
||||
const instanceMethods = [
|
||||
createPreview,
|
||||
createThumbnail,
|
||||
createTorrentAndSetInfoHash,
|
||||
generateMagnetUri,
|
||||
getVideoFilename,
|
||||
getThumbnailName,
|
||||
getPreviewName,
|
||||
getTorrentName,
|
||||
getThumbnailName,
|
||||
getTorrentFileName,
|
||||
getVideoFilename,
|
||||
getVideoFilePath,
|
||||
isOwned,
|
||||
toFormatedJSON,
|
||||
removeFile,
|
||||
removePreview,
|
||||
removeThumbnail,
|
||||
removeTorrent,
|
||||
toAddRemoteJSON,
|
||||
toFormatedJSON,
|
||||
toUpdateRemoteJSON,
|
||||
transcodeVideofile
|
||||
]
|
||||
|
@ -271,65 +266,6 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
|
|||
return Video
|
||||
}
|
||||
|
||||
function beforeValidate (video: VideoInstance) {
|
||||
// Put a fake infoHash if it does not exists yet
|
||||
if (video.isOwned() && !video.infoHash) {
|
||||
// 40 hexa length
|
||||
video.infoHash = '0123456789abcdef0123456789abcdef01234567'
|
||||
}
|
||||
}
|
||||
|
||||
function beforeCreate (video: VideoInstance, options: { transaction: Sequelize.Transaction }) {
|
||||
if (video.isOwned()) {
|
||||
const videoPath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename())
|
||||
const tasks = []
|
||||
|
||||
tasks.push(
|
||||
createTorrentFromVideo(video, videoPath),
|
||||
createThumbnail(video, videoPath),
|
||||
createPreview(video, videoPath)
|
||||
)
|
||||
|
||||
if (CONFIG.TRANSCODING.ENABLED === true) {
|
||||
// Put uuid because we don't have id auto incremented for now
|
||||
const dataInput = {
|
||||
videoUUID: video.uuid
|
||||
}
|
||||
|
||||
tasks.push(
|
||||
JobScheduler.Instance.createJob(options.transaction, 'videoTranscoder', dataInput)
|
||||
)
|
||||
}
|
||||
|
||||
return Promise.all(tasks)
|
||||
}
|
||||
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
function afterDestroy (video: VideoInstance) {
|
||||
const tasks = []
|
||||
|
||||
tasks.push(
|
||||
removeThumbnail(video)
|
||||
)
|
||||
|
||||
if (video.isOwned()) {
|
||||
const removeVideoToFriendsParams = {
|
||||
uuid: video.uuid
|
||||
}
|
||||
|
||||
tasks.push(
|
||||
removeFile(video),
|
||||
removeTorrent(video),
|
||||
removePreview(video),
|
||||
removeVideoToFriends(removeVideoToFriendsParams)
|
||||
)
|
||||
}
|
||||
|
||||
return Promise.all(tasks)
|
||||
}
|
||||
|
||||
// ------------------------------ METHODS ------------------------------
|
||||
|
||||
function associate (models) {
|
||||
|
@ -354,37 +290,46 @@ function associate (models) {
|
|||
},
|
||||
onDelete: 'cascade'
|
||||
})
|
||||
|
||||
Video.hasMany(models.VideoFile, {
|
||||
foreignKey: {
|
||||
name: 'videoId',
|
||||
allowNull: false
|
||||
},
|
||||
onDelete: 'cascade'
|
||||
})
|
||||
}
|
||||
|
||||
generateMagnetUri = function (this: VideoInstance) {
|
||||
let baseUrlHttp
|
||||
let baseUrlWs
|
||||
function afterDestroy (video: VideoInstance) {
|
||||
const tasks = []
|
||||
|
||||
if (this.isOwned()) {
|
||||
baseUrlHttp = CONFIG.WEBSERVER.URL
|
||||
baseUrlWs = CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT
|
||||
} else {
|
||||
baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + this.Author.Pod.host
|
||||
baseUrlWs = REMOTE_SCHEME.WS + '://' + this.Author.Pod.host
|
||||
tasks.push(
|
||||
video.removeThumbnail()
|
||||
)
|
||||
|
||||
if (video.isOwned()) {
|
||||
const removeVideoToFriendsParams = {
|
||||
uuid: video.uuid
|
||||
}
|
||||
|
||||
tasks.push(
|
||||
video.removePreview(),
|
||||
removeVideoToFriends(removeVideoToFriendsParams)
|
||||
)
|
||||
|
||||
// TODO: check files is populated
|
||||
video.VideoFiles.forEach(file => {
|
||||
video.removeFile(file),
|
||||
video.removeTorrent(file)
|
||||
})
|
||||
}
|
||||
|
||||
const xs = baseUrlHttp + STATIC_PATHS.TORRENTS + this.getTorrentName()
|
||||
const announce = [ baseUrlWs + '/tracker/socket' ]
|
||||
const urlList = [ baseUrlHttp + STATIC_PATHS.WEBSEED + this.getVideoFilename() ]
|
||||
|
||||
const magnetHash = {
|
||||
xs,
|
||||
announce,
|
||||
urlList,
|
||||
infoHash: this.infoHash,
|
||||
name: this.name
|
||||
}
|
||||
|
||||
return magnetUtil.encode(magnetHash)
|
||||
return Promise.all(tasks)
|
||||
}
|
||||
|
||||
getVideoFilename = function (this: VideoInstance) {
|
||||
return this.uuid + this.extname
|
||||
getVideoFilename = function (this: VideoInstance, videoFile: VideoFileInstance) {
|
||||
// return this.uuid + '-' + VIDEO_FILE_RESOLUTIONS[videoFile.resolution] + videoFile.extname
|
||||
return this.uuid + videoFile.extname
|
||||
}
|
||||
|
||||
getThumbnailName = function (this: VideoInstance) {
|
||||
|
@ -398,8 +343,9 @@ getPreviewName = function (this: VideoInstance) {
|
|||
return this.uuid + extension
|
||||
}
|
||||
|
||||
getTorrentName = 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
|
||||
}
|
||||
|
||||
|
@ -407,6 +353,67 @@ isOwned = function (this: VideoInstance) {
|
|||
return this.remote === false
|
||||
}
|
||||
|
||||
createPreview = function (this: VideoInstance, videoFile: VideoFileInstance) {
|
||||
return generateImage(this, this.getVideoFilePath(videoFile), CONFIG.STORAGE.PREVIEWS_DIR, this.getPreviewName(), null)
|
||||
}
|
||||
|
||||
createThumbnail = function (this: VideoInstance, videoFile: VideoFileInstance) {
|
||||
return generateImage(this, this.getVideoFilePath(videoFile), CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName(), THUMBNAILS_SIZE)
|
||||
}
|
||||
|
||||
getVideoFilePath = function (this: VideoInstance, videoFile: VideoFileInstance) {
|
||||
return join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile))
|
||||
}
|
||||
|
||||
createTorrentAndSetInfoHash = function (this: VideoInstance, videoFile: VideoFileInstance) {
|
||||
const options = {
|
||||
announceList: [
|
||||
[ CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT + '/tracker/socket' ]
|
||||
],
|
||||
urlList: [
|
||||
CONFIG.WEBSERVER.URL + STATIC_PATHS.WEBSEED + this.getVideoFilename(videoFile)
|
||||
]
|
||||
}
|
||||
|
||||
return createTorrentPromise(this.getVideoFilePath(videoFile), options)
|
||||
.then(torrent => {
|
||||
const filePath = join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile))
|
||||
return writeFilePromise(filePath, torrent).then(() => torrent)
|
||||
})
|
||||
.then(torrent => {
|
||||
const parsedTorrent = parseTorrent(torrent)
|
||||
|
||||
videoFile.infoHash = parsedTorrent.infoHash
|
||||
})
|
||||
}
|
||||
|
||||
generateMagnetUri = function (this: VideoInstance, videoFile: VideoFileInstance) {
|
||||
let baseUrlHttp
|
||||
let baseUrlWs
|
||||
|
||||
if (this.isOwned()) {
|
||||
baseUrlHttp = CONFIG.WEBSERVER.URL
|
||||
baseUrlWs = CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT
|
||||
} else {
|
||||
baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + this.Author.Pod.host
|
||||
baseUrlWs = REMOTE_SCHEME.WS + '://' + this.Author.Pod.host
|
||||
}
|
||||
|
||||
const xs = baseUrlHttp + STATIC_PATHS.TORRENTS + this.getTorrentFileName(videoFile)
|
||||
const announce = [ baseUrlWs + '/tracker/socket' ]
|
||||
const urlList = [ baseUrlHttp + STATIC_PATHS.WEBSEED + this.getVideoFilename(videoFile) ]
|
||||
|
||||
const magnetHash = {
|
||||
xs,
|
||||
announce,
|
||||
urlList,
|
||||
infoHash: videoFile.infoHash,
|
||||
name: this.name
|
||||
}
|
||||
|
||||
return magnetUtil.encode(magnetHash)
|
||||
}
|
||||
|
||||
toFormatedJSON = function (this: VideoInstance) {
|
||||
let podHost
|
||||
|
||||
|
@ -443,7 +450,6 @@ toFormatedJSON = function (this: VideoInstance) {
|
|||
description: this.description,
|
||||
podHost,
|
||||
isLocal: this.isOwned(),
|
||||
magnetUri: this.generateMagnetUri(),
|
||||
author: this.Author.name,
|
||||
duration: this.duration,
|
||||
views: this.views,
|
||||
|
@ -453,9 +459,24 @@ toFormatedJSON = function (this: VideoInstance) {
|
|||
thumbnailPath: join(STATIC_PATHS.THUMBNAILS, this.getThumbnailName()),
|
||||
previewPath: join(STATIC_PATHS.PREVIEWS, this.getPreviewName()),
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt
|
||||
updatedAt: this.updatedAt,
|
||||
files: []
|
||||
}
|
||||
|
||||
this.VideoFiles.forEach(videoFile => {
|
||||
let resolutionLabel = VIDEO_FILE_RESOLUTIONS[videoFile.resolution]
|
||||
if (!resolutionLabel) resolutionLabel = 'Unknown'
|
||||
|
||||
const videoFileJson = {
|
||||
resolution: videoFile.resolution,
|
||||
resolutionLabel,
|
||||
magnetUri: this.generateMagnetUri(videoFile),
|
||||
size: videoFile.size
|
||||
}
|
||||
|
||||
json.files.push(videoFileJson)
|
||||
})
|
||||
|
||||
return json
|
||||
}
|
||||
|
||||
|
@ -472,19 +493,27 @@ toAddRemoteJSON = function (this: VideoInstance) {
|
|||
language: this.language,
|
||||
nsfw: this.nsfw,
|
||||
description: this.description,
|
||||
infoHash: this.infoHash,
|
||||
author: this.Author.name,
|
||||
duration: this.duration,
|
||||
thumbnailData: thumbnailData.toString('binary'),
|
||||
tags: map<TagInstance, string>(this.Tags, 'name'),
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt,
|
||||
extname: this.extname,
|
||||
views: this.views,
|
||||
likes: this.likes,
|
||||
dislikes: this.dislikes
|
||||
dislikes: this.dislikes,
|
||||
files: []
|
||||
}
|
||||
|
||||
this.VideoFiles.forEach(videoFile => {
|
||||
remoteVideo.files.push({
|
||||
infoHash: videoFile.infoHash,
|
||||
resolution: videoFile.resolution,
|
||||
extname: videoFile.extname,
|
||||
size: videoFile.size
|
||||
})
|
||||
})
|
||||
|
||||
return remoteVideo
|
||||
})
|
||||
}
|
||||
|
@ -498,28 +527,34 @@ toUpdateRemoteJSON = function (this: VideoInstance) {
|
|||
language: this.language,
|
||||
nsfw: this.nsfw,
|
||||
description: this.description,
|
||||
infoHash: this.infoHash,
|
||||
author: this.Author.name,
|
||||
duration: this.duration,
|
||||
tags: map<TagInstance, string>(this.Tags, 'name'),
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt,
|
||||
extname: this.extname,
|
||||
views: this.views,
|
||||
likes: this.likes,
|
||||
dislikes: this.dislikes
|
||||
dislikes: this.dislikes,
|
||||
files: []
|
||||
}
|
||||
|
||||
this.VideoFiles.forEach(videoFile => {
|
||||
json.files.push({
|
||||
infoHash: videoFile.infoHash,
|
||||
resolution: videoFile.resolution,
|
||||
extname: videoFile.extname,
|
||||
size: videoFile.size
|
||||
})
|
||||
})
|
||||
|
||||
return json
|
||||
}
|
||||
|
||||
transcodeVideofile = function (this: VideoInstance) {
|
||||
const video = this
|
||||
|
||||
transcodeVideofile = function (this: VideoInstance, inputVideoFile: VideoFileInstance) {
|
||||
const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
|
||||
const newExtname = '.mp4'
|
||||
const videoInputPath = join(videosDirectory, video.getVideoFilename())
|
||||
const videoOutputPath = join(videosDirectory, video.id + '-transcoded' + newExtname)
|
||||
const videoInputPath = join(videosDirectory, this.getVideoFilename(inputVideoFile))
|
||||
const videoOutputPath = join(videosDirectory, this.id + '-transcoded' + newExtname)
|
||||
|
||||
return new Promise<void>((res, rej) => {
|
||||
ffmpeg(videoInputPath)
|
||||
|
@ -533,24 +568,22 @@ transcodeVideofile = function (this: VideoInstance) {
|
|||
return unlinkPromise(videoInputPath)
|
||||
.then(() => {
|
||||
// Important to do this before getVideoFilename() to take in account the new file extension
|
||||
video.set('extname', newExtname)
|
||||
inputVideoFile.set('extname', newExtname)
|
||||
|
||||
const newVideoPath = join(videosDirectory, video.getVideoFilename())
|
||||
return renamePromise(videoOutputPath, newVideoPath)
|
||||
return renamePromise(videoOutputPath, this.getVideoFilePath(inputVideoFile))
|
||||
})
|
||||
.then(() => {
|
||||
const newVideoPath = join(videosDirectory, video.getVideoFilename())
|
||||
return createTorrentFromVideo(video, newVideoPath)
|
||||
return this.createTorrentAndSetInfoHash(inputVideoFile)
|
||||
})
|
||||
.then(() => {
|
||||
return video.save()
|
||||
return inputVideoFile.save()
|
||||
})
|
||||
.then(() => {
|
||||
return res()
|
||||
})
|
||||
.catch(err => {
|
||||
// Autodesctruction...
|
||||
video.destroy().catch(err => logger.error('Cannot destruct video after transcoding failure.', err))
|
||||
// Autodestruction...
|
||||
this.destroy().catch(err => logger.error('Cannot destruct video after transcoding failure.', err))
|
||||
|
||||
return rej(err)
|
||||
})
|
||||
|
@ -559,6 +592,26 @@ transcodeVideofile = function (this: VideoInstance) {
|
|||
})
|
||||
}
|
||||
|
||||
removeThumbnail = function (this: VideoInstance) {
|
||||
const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName())
|
||||
return unlinkPromise(thumbnailPath)
|
||||
}
|
||||
|
||||
removePreview = function (this: VideoInstance) {
|
||||
// Same name than video thumbnail
|
||||
return unlinkPromise(CONFIG.STORAGE.PREVIEWS_DIR + this.getPreviewName())
|
||||
}
|
||||
|
||||
removeFile = function (this: VideoInstance, videoFile: VideoFileInstance) {
|
||||
const filePath = join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile))
|
||||
return unlinkPromise(filePath)
|
||||
}
|
||||
|
||||
removeTorrent = function (this: VideoInstance, videoFile: VideoFileInstance) {
|
||||
const torrenPath = join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile))
|
||||
return unlinkPromise(torrenPath)
|
||||
}
|
||||
|
||||
// ------------------------------ STATICS ------------------------------
|
||||
|
||||
generateThumbnailFromData = function (video: VideoInstance, thumbnailData: string) {
|
||||
|
@ -582,7 +635,11 @@ getDurationFromFile = function (videoPath: string) {
|
|||
}
|
||||
|
||||
list = function () {
|
||||
return Video.findAll()
|
||||
const query = {
|
||||
include: [ Video['sequelize'].models.VideoFile ]
|
||||
}
|
||||
|
||||
return Video.findAll(query)
|
||||
}
|
||||
|
||||
listForApi = function (start: number, count: number, sort: string) {
|
||||
|
@ -597,8 +654,8 @@ listForApi = function (start: number, count: number, sort: string) {
|
|||
model: Video['sequelize'].models.Author,
|
||||
include: [ { model: Video['sequelize'].models.Pod, required: false } ]
|
||||
},
|
||||
|
||||
Video['sequelize'].models.Tag
|
||||
Video['sequelize'].models.Tag,
|
||||
Video['sequelize'].models.VideoFile
|
||||
],
|
||||
where: createBaseVideosWhere()
|
||||
}
|
||||
|
@ -617,6 +674,9 @@ loadByHostAndUUID = function (fromHost: string, uuid: string) {
|
|||
uuid
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: Video['sequelize'].models.VideoFile
|
||||
},
|
||||
{
|
||||
model: Video['sequelize'].models.Author,
|
||||
include: [
|
||||
|
@ -640,7 +700,11 @@ listOwnedAndPopulateAuthorAndTags = function () {
|
|||
where: {
|
||||
remote: false
|
||||
},
|
||||
include: [ Video['sequelize'].models.Author, Video['sequelize'].models.Tag ]
|
||||
include: [
|
||||
Video['sequelize'].models.VideoFile,
|
||||
Video['sequelize'].models.Author,
|
||||
Video['sequelize'].models.Tag
|
||||
]
|
||||
}
|
||||
|
||||
return Video.findAll(query)
|
||||
|
@ -652,6 +716,9 @@ listOwnedByAuthor = function (author: string) {
|
|||
remote: false
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: Video['sequelize'].models.VideoFile
|
||||
},
|
||||
{
|
||||
model: Video['sequelize'].models.Author,
|
||||
where: {
|
||||
|
@ -672,14 +739,15 @@ loadByUUID = function (uuid: string) {
|
|||
const query = {
|
||||
where: {
|
||||
uuid
|
||||
}
|
||||
},
|
||||
include: [ Video['sequelize'].models.VideoFile ]
|
||||
}
|
||||
return Video.findOne(query)
|
||||
}
|
||||
|
||||
loadAndPopulateAuthor = function (id: number) {
|
||||
const options = {
|
||||
include: [ Video['sequelize'].models.Author ]
|
||||
include: [ Video['sequelize'].models.VideoFile, Video['sequelize'].models.Author ]
|
||||
}
|
||||
|
||||
return Video.findById(id, options)
|
||||
|
@ -692,7 +760,8 @@ loadAndPopulateAuthorAndPodAndTags = function (id: number) {
|
|||
model: Video['sequelize'].models.Author,
|
||||
include: [ { model: Video['sequelize'].models.Pod, required: false } ]
|
||||
},
|
||||
Video['sequelize'].models.Tag
|
||||
Video['sequelize'].models.Tag,
|
||||
Video['sequelize'].models.VideoFile
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -709,7 +778,8 @@ loadByUUIDAndPopulateAuthorAndPodAndTags = function (uuid: string) {
|
|||
model: Video['sequelize'].models.Author,
|
||||
include: [ { model: Video['sequelize'].models.Pod, required: false } ]
|
||||
},
|
||||
Video['sequelize'].models.Tag
|
||||
Video['sequelize'].models.Tag,
|
||||
Video['sequelize'].models.VideoFile
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -733,6 +803,10 @@ searchAndPopulateAuthorAndPodAndTags = function (value: string, field: string, s
|
|||
model: Video['sequelize'].models.Tag
|
||||
}
|
||||
|
||||
const videoFileInclude: Sequelize.IncludeOptions = {
|
||||
model: Video['sequelize'].models.VideoFile
|
||||
}
|
||||
|
||||
const query: Sequelize.FindOptions = {
|
||||
distinct: true,
|
||||
where: createBaseVideosWhere(),
|
||||
|
@ -743,8 +817,9 @@ searchAndPopulateAuthorAndPodAndTags = function (value: string, field: string, s
|
|||
|
||||
// Make an exact search with the magnet
|
||||
if (field === 'magnetUri') {
|
||||
const infoHash = magnetUtil.decode(value).infoHash
|
||||
query.where['infoHash'] = infoHash
|
||||
videoFileInclude.where = {
|
||||
infoHash: magnetUtil.decode(value).infoHash
|
||||
}
|
||||
} else if (field === 'tags') {
|
||||
const escapedValue = Video['sequelize'].escape('%' + value + '%')
|
||||
query.where['id'].$in = Video['sequelize'].literal(
|
||||
|
@ -777,7 +852,7 @@ searchAndPopulateAuthorAndPodAndTags = function (value: string, field: string, s
|
|||
}
|
||||
|
||||
query.include = [
|
||||
authorInclude, tagInclude
|
||||
authorInclude, tagInclude, videoFileInclude
|
||||
]
|
||||
|
||||
return Video.findAndCountAll(query).then(({ rows, count }) => {
|
||||
|
@ -800,56 +875,6 @@ function createBaseVideosWhere () {
|
|||
}
|
||||
}
|
||||
|
||||
function removeThumbnail (video: VideoInstance) {
|
||||
const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName())
|
||||
return unlinkPromise(thumbnailPath)
|
||||
}
|
||||
|
||||
function removeFile (video: VideoInstance) {
|
||||
const filePath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename())
|
||||
return unlinkPromise(filePath)
|
||||
}
|
||||
|
||||
function removeTorrent (video: VideoInstance) {
|
||||
const torrenPath = join(CONFIG.STORAGE.TORRENTS_DIR, video.getTorrentName())
|
||||
return unlinkPromise(torrenPath)
|
||||
}
|
||||
|
||||
function removePreview (video: VideoInstance) {
|
||||
// Same name than video thumnail
|
||||
return unlinkPromise(CONFIG.STORAGE.PREVIEWS_DIR + video.getPreviewName())
|
||||
}
|
||||
|
||||
function createTorrentFromVideo (video: VideoInstance, videoPath: string) {
|
||||
const options = {
|
||||
announceList: [
|
||||
[ CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT + '/tracker/socket' ]
|
||||
],
|
||||
urlList: [
|
||||
CONFIG.WEBSERVER.URL + STATIC_PATHS.WEBSEED + video.getVideoFilename()
|
||||
]
|
||||
}
|
||||
|
||||
return createTorrentPromise(videoPath, options)
|
||||
.then(torrent => {
|
||||
const filePath = join(CONFIG.STORAGE.TORRENTS_DIR, video.getTorrentName())
|
||||
return writeFilePromise(filePath, torrent).then(() => torrent)
|
||||
})
|
||||
.then(torrent => {
|
||||
const parsedTorrent = parseTorrent(torrent)
|
||||
video.set('infoHash', parsedTorrent.infoHash)
|
||||
return video.validate()
|
||||
})
|
||||
}
|
||||
|
||||
function createPreview (video: VideoInstance, videoPath: string) {
|
||||
return generateImage(video, videoPath, CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName(), null)
|
||||
}
|
||||
|
||||
function createThumbnail (video: VideoInstance, videoPath: string) {
|
||||
return generateImage(video, videoPath, CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName(), THUMBNAILS_SIZE)
|
||||
}
|
||||
|
||||
function generateImage (video: VideoInstance, videoPath: string, folder: string, imageName: string, size: string) {
|
||||
const options = {
|
||||
filename: imageName,
|
||||
|
@ -868,16 +893,3 @@ function generateImage (video: VideoInstance, videoPath: string, folder: string,
|
|||
.thumbnail(options)
|
||||
})
|
||||
}
|
||||
|
||||
function removeFromBlacklist (video: VideoInstance) {
|
||||
// Find the blacklisted video
|
||||
return db.BlacklistedVideo.loadByVideoId(video.id).then(video => {
|
||||
// Not found the video, skip
|
||||
if (!video) {
|
||||
return null
|
||||
}
|
||||
|
||||
// If we found the video, remove it from the blacklist
|
||||
return video.destroy()
|
||||
})
|
||||
}
|
||||
|
|
|
@ -121,13 +121,21 @@ describe('Test multiple pods', function () {
|
|||
expect(video.nsfw).to.be.ok
|
||||
expect(video.description).to.equal('my super description for pod 1')
|
||||
expect(video.podHost).to.equal('localhost:9001')
|
||||
expect(video.magnetUri).to.exist
|
||||
expect(video.duration).to.equal(10)
|
||||
expect(video.tags).to.deep.equal([ 'tag1p1', 'tag2p1' ])
|
||||
expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
|
||||
expect(miscsUtils.dateIsValid(video.updatedAt)).to.be.true
|
||||
expect(video.author).to.equal('root')
|
||||
|
||||
expect(video.files).to.have.lengthOf(1)
|
||||
|
||||
const file = video.files[0]
|
||||
const magnetUri = file.magnetUri
|
||||
expect(file.magnetUri).to.exist
|
||||
expect(file.resolution).to.equal(0)
|
||||
expect(file.resolutionLabel).to.equal('original')
|
||||
expect(file.size).to.equal(572456)
|
||||
|
||||
if (server.url !== 'http://localhost:9001') {
|
||||
expect(video.isLocal).to.be.false
|
||||
} else {
|
||||
|
@ -136,9 +144,9 @@ describe('Test multiple pods', function () {
|
|||
|
||||
// All pods should have the same magnet Uri
|
||||
if (baseMagnet === null) {
|
||||
baseMagnet = video.magnetUri
|
||||
baseMagnet = magnetUri
|
||||
} else {
|
||||
expect(video.magnetUri).to.equal.magnetUri
|
||||
expect(baseMagnet).to.equal(magnetUri)
|
||||
}
|
||||
|
||||
videosUtils.testVideoImage(server.url, 'video_short1.webm', video.thumbnailPath, function (err, test) {
|
||||
|
@ -198,13 +206,21 @@ describe('Test multiple pods', function () {
|
|||
expect(video.nsfw).to.be.true
|
||||
expect(video.description).to.equal('my super description for pod 2')
|
||||
expect(video.podHost).to.equal('localhost:9002')
|
||||
expect(video.magnetUri).to.exist
|
||||
expect(video.duration).to.equal(5)
|
||||
expect(video.tags).to.deep.equal([ 'tag1p2', 'tag2p2', 'tag3p2' ])
|
||||
expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
|
||||
expect(miscsUtils.dateIsValid(video.updatedAt)).to.be.true
|
||||
expect(video.author).to.equal('root')
|
||||
|
||||
expect(video.files).to.have.lengthOf(1)
|
||||
|
||||
const file = video.files[0]
|
||||
const magnetUri = file.magnetUri
|
||||
expect(file.magnetUri).to.exist
|
||||
expect(file.resolution).to.equal(0)
|
||||
expect(file.resolutionLabel).to.equal('original')
|
||||
expect(file.size).to.equal(942961)
|
||||
|
||||
if (server.url !== 'http://localhost:9002') {
|
||||
expect(video.isLocal).to.be.false
|
||||
} else {
|
||||
|
@ -213,9 +229,9 @@ describe('Test multiple pods', function () {
|
|||
|
||||
// All pods should have the same magnet Uri
|
||||
if (baseMagnet === null) {
|
||||
baseMagnet = video.magnetUri
|
||||
baseMagnet = magnetUri
|
||||
} else {
|
||||
expect(video.magnetUri).to.equal.magnetUri
|
||||
expect(baseMagnet).to.equal(magnetUri)
|
||||
}
|
||||
|
||||
videosUtils.testVideoImage(server.url, 'video_short2.webm', video.thumbnailPath, function (err, test) {
|
||||
|
@ -297,13 +313,21 @@ describe('Test multiple pods', function () {
|
|||
expect(video1.nsfw).to.be.ok
|
||||
expect(video1.description).to.equal('my super description for pod 3')
|
||||
expect(video1.podHost).to.equal('localhost:9003')
|
||||
expect(video1.magnetUri).to.exist
|
||||
expect(video1.duration).to.equal(5)
|
||||
expect(video1.tags).to.deep.equal([ 'tag1p3' ])
|
||||
expect(video1.author).to.equal('root')
|
||||
expect(miscsUtils.dateIsValid(video1.createdAt)).to.be.true
|
||||
expect(miscsUtils.dateIsValid(video1.updatedAt)).to.be.true
|
||||
|
||||
expect(video1.files).to.have.lengthOf(1)
|
||||
|
||||
const file1 = video1.files[0]
|
||||
const magnetUri1 = file1.magnetUri
|
||||
expect(file1.magnetUri).to.exist
|
||||
expect(file1.resolution).to.equal(0)
|
||||
expect(file1.resolutionLabel).to.equal('original')
|
||||
expect(file1.size).to.equal(292677)
|
||||
|
||||
expect(video2.name).to.equal('my super name for pod 3-2')
|
||||
expect(video2.category).to.equal(7)
|
||||
expect(video2.categoryLabel).to.equal('Gaming')
|
||||
|
@ -314,13 +338,21 @@ describe('Test multiple pods', function () {
|
|||
expect(video2.nsfw).to.be.false
|
||||
expect(video2.description).to.equal('my super description for pod 3-2')
|
||||
expect(video2.podHost).to.equal('localhost:9003')
|
||||
expect(video2.magnetUri).to.exist
|
||||
expect(video2.duration).to.equal(5)
|
||||
expect(video2.tags).to.deep.equal([ 'tag2p3', 'tag3p3', 'tag4p3' ])
|
||||
expect(video2.author).to.equal('root')
|
||||
expect(miscsUtils.dateIsValid(video2.createdAt)).to.be.true
|
||||
expect(miscsUtils.dateIsValid(video2.updatedAt)).to.be.true
|
||||
|
||||
expect(video2.files).to.have.lengthOf(1)
|
||||
|
||||
const file2 = video2.files[0]
|
||||
const magnetUri2 = file2.magnetUri
|
||||
expect(file2.magnetUri).to.exist
|
||||
expect(file2.resolution).to.equal(0)
|
||||
expect(file2.resolutionLabel).to.equal('original')
|
||||
expect(file2.size).to.equal(218910)
|
||||
|
||||
if (server.url !== 'http://localhost:9003') {
|
||||
expect(video1.isLocal).to.be.false
|
||||
expect(video2.isLocal).to.be.false
|
||||
|
@ -331,9 +363,9 @@ describe('Test multiple pods', function () {
|
|||
|
||||
// All pods should have the same magnet Uri
|
||||
if (baseMagnet === null) {
|
||||
baseMagnet = video2.magnetUri
|
||||
baseMagnet = magnetUri2
|
||||
} else {
|
||||
expect(video2.magnetUri).to.equal.magnetUri
|
||||
expect(baseMagnet).to.equal(magnetUri2)
|
||||
}
|
||||
|
||||
videosUtils.testVideoImage(server.url, 'video_short3.webm', video1.thumbnailPath, function (err, test) {
|
||||
|
@ -366,7 +398,7 @@ describe('Test multiple pods', function () {
|
|||
toRemove.push(res.body.data[2])
|
||||
toRemove.push(res.body.data[3])
|
||||
|
||||
webtorrent.add(video.magnetUri, function (torrent) {
|
||||
webtorrent.add(video.files[0].magnetUri, function (torrent) {
|
||||
expect(torrent.files).to.exist
|
||||
expect(torrent.files.length).to.equal(1)
|
||||
expect(torrent.files[0].path).to.exist.and.to.not.equal('')
|
||||
|
@ -385,7 +417,7 @@ describe('Test multiple pods', function () {
|
|||
|
||||
const video = res.body.data[1]
|
||||
|
||||
webtorrent.add(video.magnetUri, function (torrent) {
|
||||
webtorrent.add(video.files[0].magnetUri, function (torrent) {
|
||||
expect(torrent.files).to.exist
|
||||
expect(torrent.files.length).to.equal(1)
|
||||
expect(torrent.files[0].path).to.exist.and.to.not.equal('')
|
||||
|
@ -404,7 +436,7 @@ describe('Test multiple pods', function () {
|
|||
|
||||
const video = res.body.data[2]
|
||||
|
||||
webtorrent.add(video.magnetUri, function (torrent) {
|
||||
webtorrent.add(video.files[0].magnetUri, function (torrent) {
|
||||
expect(torrent.files).to.exist
|
||||
expect(torrent.files.length).to.equal(1)
|
||||
expect(torrent.files[0].path).to.exist.and.to.not.equal('')
|
||||
|
@ -423,7 +455,7 @@ describe('Test multiple pods', function () {
|
|||
|
||||
const video = res.body.data[3]
|
||||
|
||||
webtorrent.add(video.magnetUri, function (torrent) {
|
||||
webtorrent.add(video.files[0].magnetUri, function (torrent) {
|
||||
expect(torrent.files).to.exist
|
||||
expect(torrent.files.length).to.equal(1)
|
||||
expect(torrent.files[0].path).to.exist.and.to.not.equal('')
|
||||
|
@ -700,11 +732,18 @@ describe('Test multiple pods', function () {
|
|||
expect(videoUpdated.tags).to.deep.equal([ 'tagup1', 'tagup2' ])
|
||||
expect(miscsUtils.dateIsValid(videoUpdated.updatedAt, 20000)).to.be.true
|
||||
|
||||
const file = videoUpdated.files[0]
|
||||
const magnetUri = file.magnetUri
|
||||
expect(file.magnetUri).to.exist
|
||||
expect(file.resolution).to.equal(0)
|
||||
expect(file.resolutionLabel).to.equal('original')
|
||||
expect(file.size).to.equal(292677)
|
||||
|
||||
videosUtils.testVideoImage(server.url, 'video_short3.webm', videoUpdated.thumbnailPath, function (err, test) {
|
||||
if (err) throw err
|
||||
expect(test).to.equal(true)
|
||||
|
||||
webtorrent.add(videoUpdated.magnetUri, function (torrent) {
|
||||
webtorrent.add(videoUpdated.files[0].magnetUri, function (torrent) {
|
||||
expect(torrent.files).to.exist
|
||||
expect(torrent.files.length).to.equal(1)
|
||||
expect(torrent.files[0].path).to.exist.and.to.not.equal('')
|
||||
|
|
|
@ -129,13 +129,21 @@ describe('Test a single pod', function () {
|
|||
expect(video.nsfw).to.be.ok
|
||||
expect(video.description).to.equal('my super description')
|
||||
expect(video.podHost).to.equal('localhost:9001')
|
||||
expect(video.magnetUri).to.exist
|
||||
expect(video.author).to.equal('root')
|
||||
expect(video.isLocal).to.be.true
|
||||
expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
|
||||
expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
|
||||
expect(miscsUtils.dateIsValid(video.updatedAt)).to.be.true
|
||||
|
||||
expect(video.files).to.have.lengthOf(1)
|
||||
|
||||
const file = video.files[0]
|
||||
const magnetUri = file.magnetUri
|
||||
expect(file.magnetUri).to.exist
|
||||
expect(file.resolution).to.equal(0)
|
||||
expect(file.resolutionLabel).to.equal('original')
|
||||
expect(file.size).to.equal(218910)
|
||||
|
||||
videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) {
|
||||
if (err) throw err
|
||||
expect(test).to.equal(true)
|
||||
|
@ -143,7 +151,7 @@ describe('Test a single pod', function () {
|
|||
videoId = video.id
|
||||
videoUUID = video.uuid
|
||||
|
||||
webtorrent.add(video.magnetUri, function (torrent) {
|
||||
webtorrent.add(magnetUri, function (torrent) {
|
||||
expect(torrent.files).to.exist
|
||||
expect(torrent.files.length).to.equal(1)
|
||||
expect(torrent.files[0].path).to.exist.and.to.not.equal('')
|
||||
|
@ -172,13 +180,21 @@ describe('Test a single pod', function () {
|
|||
expect(video.nsfw).to.be.ok
|
||||
expect(video.description).to.equal('my super description')
|
||||
expect(video.podHost).to.equal('localhost:9001')
|
||||
expect(video.magnetUri).to.exist
|
||||
expect(video.author).to.equal('root')
|
||||
expect(video.isLocal).to.be.true
|
||||
expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
|
||||
expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
|
||||
expect(miscsUtils.dateIsValid(video.updatedAt)).to.be.true
|
||||
|
||||
expect(video.files).to.have.lengthOf(1)
|
||||
|
||||
const file = video.files[0]
|
||||
const magnetUri = file.magnetUri
|
||||
expect(file.magnetUri).to.exist
|
||||
expect(file.resolution).to.equal(0)
|
||||
expect(file.resolutionLabel).to.equal('original')
|
||||
expect(file.size).to.equal(218910)
|
||||
|
||||
videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) {
|
||||
if (err) throw err
|
||||
expect(test).to.equal(true)
|
||||
|
@ -240,6 +256,15 @@ describe('Test a single pod', function () {
|
|||
expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
|
||||
expect(miscsUtils.dateIsValid(video.updatedAt)).to.be.true
|
||||
|
||||
expect(video.files).to.have.lengthOf(1)
|
||||
|
||||
const file = video.files[0]
|
||||
const magnetUri = file.magnetUri
|
||||
expect(file.magnetUri).to.exist
|
||||
expect(file.resolution).to.equal(0)
|
||||
expect(file.resolutionLabel).to.equal('original')
|
||||
expect(file.size).to.equal(218910)
|
||||
|
||||
videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) {
|
||||
if (err) throw err
|
||||
expect(test).to.equal(true)
|
||||
|
@ -302,6 +327,15 @@ describe('Test a single pod', function () {
|
|||
expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
|
||||
expect(miscsUtils.dateIsValid(video.updatedAt)).to.be.true
|
||||
|
||||
expect(video.files).to.have.lengthOf(1)
|
||||
|
||||
const file = video.files[0]
|
||||
const magnetUri = file.magnetUri
|
||||
expect(file.magnetUri).to.exist
|
||||
expect(file.resolution).to.equal(0)
|
||||
expect(file.resolutionLabel).to.equal('original')
|
||||
expect(file.size).to.equal(218910)
|
||||
|
||||
videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) {
|
||||
if (err) throw err
|
||||
expect(test).to.equal(true)
|
||||
|
@ -564,7 +598,7 @@ describe('Test a single pod', function () {
|
|||
|
||||
it('Should search the right magnetUri video', function (done) {
|
||||
const video = videosListBase[0]
|
||||
videosUtils.searchVideoWithPagination(server.url, encodeURIComponent(video.magnetUri), 'magnetUri', 0, 15, function (err, res) {
|
||||
videosUtils.searchVideoWithPagination(server.url, encodeURIComponent(video.files[0].magnetUri), 'magnetUri', 0, 15, function (err, res) {
|
||||
if (err) throw err
|
||||
|
||||
const videos = res.body.data
|
||||
|
@ -650,11 +684,20 @@ describe('Test a single pod', function () {
|
|||
expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
|
||||
expect(miscsUtils.dateIsValid(video.updatedAt)).to.be.true
|
||||
|
||||
expect(video.files).to.have.lengthOf(1)
|
||||
|
||||
const file = video.files[0]
|
||||
const magnetUri = file.magnetUri
|
||||
expect(file.magnetUri).to.exist
|
||||
expect(file.resolution).to.equal(0)
|
||||
expect(file.resolutionLabel).to.equal('original')
|
||||
expect(file.size).to.equal(292677)
|
||||
|
||||
videosUtils.testVideoImage(server.url, 'video_short3.webm', video.thumbnailPath, function (err, test) {
|
||||
if (err) throw err
|
||||
expect(test).to.equal(true)
|
||||
|
||||
webtorrent.add(video.magnetUri, function (torrent) {
|
||||
webtorrent.add(magnetUri, function (torrent) {
|
||||
expect(torrent.files).to.exist
|
||||
expect(torrent.files.length).to.equal(1)
|
||||
expect(torrent.files[0].path).to.exist.and.to.not.equal('')
|
||||
|
@ -694,6 +737,15 @@ describe('Test a single pod', function () {
|
|||
expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
|
||||
expect(miscsUtils.dateIsValid(video.updatedAt)).to.be.true
|
||||
|
||||
expect(video.files).to.have.lengthOf(1)
|
||||
|
||||
const file = video.files[0]
|
||||
const magnetUri = file.magnetUri
|
||||
expect(file.magnetUri).to.exist
|
||||
expect(file.resolution).to.equal(0)
|
||||
expect(file.resolutionLabel).to.equal('original')
|
||||
expect(file.size).to.equal(292677)
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
@ -728,6 +780,15 @@ describe('Test a single pod', function () {
|
|||
expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
|
||||
expect(miscsUtils.dateIsValid(video.updatedAt)).to.be.true
|
||||
|
||||
expect(video.files).to.have.lengthOf(1)
|
||||
|
||||
const file = video.files[0]
|
||||
const magnetUri = file.magnetUri
|
||||
expect(file.magnetUri).to.exist
|
||||
expect(file.resolution).to.equal(0)
|
||||
expect(file.resolutionLabel).to.equal('original')
|
||||
expect(file.size).to.equal(292677)
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -56,9 +56,10 @@ describe('Test video transcoding', function () {
|
|||
if (err) throw err
|
||||
|
||||
const video = res.body.data[0]
|
||||
expect(video.magnetUri).to.match(/\.webm/)
|
||||
const magnetUri = video.files[0].magnetUri
|
||||
expect(magnetUri).to.match(/\.webm/)
|
||||
|
||||
webtorrent.add(video.magnetUri, function (torrent) {
|
||||
webtorrent.add(magnetUri, function (torrent) {
|
||||
expect(torrent.files).to.exist
|
||||
expect(torrent.files.length).to.equal(1)
|
||||
expect(torrent.files[0].path).match(/\.webm$/)
|
||||
|
@ -86,9 +87,10 @@ describe('Test video transcoding', function () {
|
|||
if (err) throw err
|
||||
|
||||
const video = res.body.data[0]
|
||||
expect(video.magnetUri).to.match(/\.mp4/)
|
||||
const magnetUri = video.files[0].magnetUri
|
||||
expect(magnetUri).to.match(/\.mp4/)
|
||||
|
||||
webtorrent.add(video.magnetUri, function (torrent) {
|
||||
webtorrent.add(magnetUri, function (torrent) {
|
||||
expect(torrent.files).to.exist
|
||||
expect(torrent.files.length).to.equal(1)
|
||||
expect(torrent.files[0].path).match(/\.mp4$/)
|
||||
|
|
|
@ -5,8 +5,6 @@ export interface RemoteVideoCreateData {
|
|||
author: string
|
||||
tags: string[]
|
||||
name: string
|
||||
extname: string
|
||||
infoHash: string
|
||||
category: number
|
||||
licence: number
|
||||
language: number
|
||||
|
@ -19,6 +17,12 @@ export interface RemoteVideoCreateData {
|
|||
likes: number
|
||||
dislikes: number
|
||||
thumbnailData: string
|
||||
files: {
|
||||
infoHash: string
|
||||
extname: string
|
||||
resolution: number
|
||||
size: number
|
||||
}[]
|
||||
}
|
||||
|
||||
export interface RemoteVideoCreateRequest extends RemoteVideoRequest {
|
||||
|
|
|
@ -15,6 +15,12 @@ export interface RemoteVideoUpdateData {
|
|||
views: number
|
||||
likes: number
|
||||
dislikes: number
|
||||
files: {
|
||||
infoHash: string
|
||||
extname: string
|
||||
resolution: number
|
||||
size: number
|
||||
}[]
|
||||
}
|
||||
|
||||
export interface RemoteVideoUpdateRequest {
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
export interface VideoFile {
|
||||
magnetUri: string
|
||||
resolution: number
|
||||
resolutionLabel: string
|
||||
size: number // Bytes
|
||||
}
|
||||
|
||||
export interface Video {
|
||||
id: number
|
||||
uuid: string
|
||||
|
@ -12,7 +19,6 @@ export interface Video {
|
|||
description: string
|
||||
duration: number
|
||||
isLocal: boolean
|
||||
magnetUri: string
|
||||
name: string
|
||||
podHost: string
|
||||
tags: string[]
|
||||
|
@ -22,4 +28,5 @@ export interface Video {
|
|||
likes: number
|
||||
dislikes: number
|
||||
nsfw: boolean
|
||||
files: VideoFile[]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue