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
|
@ -19,3 +19,4 @@
|
||||||
/*.sublime-workspace
|
/*.sublime-workspace
|
||||||
/dist
|
/dist
|
||||||
/.idea
|
/.idea
|
||||||
|
/PeerTube.iml
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Video as VideoServerModel } from '../../../../../shared'
|
import { Video as VideoServerModel, VideoFile } from '../../../../../shared'
|
||||||
import { User } from '../../shared'
|
import { User } from '../../shared'
|
||||||
|
|
||||||
export class Video implements VideoServerModel {
|
export class Video implements VideoServerModel {
|
||||||
|
@ -17,7 +17,6 @@ export class Video implements VideoServerModel {
|
||||||
id: number
|
id: number
|
||||||
uuid: string
|
uuid: string
|
||||||
isLocal: boolean
|
isLocal: boolean
|
||||||
magnetUri: string
|
|
||||||
name: string
|
name: string
|
||||||
podHost: string
|
podHost: string
|
||||||
tags: string[]
|
tags: string[]
|
||||||
|
@ -29,6 +28,7 @@ export class Video implements VideoServerModel {
|
||||||
likes: number
|
likes: number
|
||||||
dislikes: number
|
dislikes: number
|
||||||
nsfw: boolean
|
nsfw: boolean
|
||||||
|
files: VideoFile[]
|
||||||
|
|
||||||
private static createByString (author: string, podHost: string) {
|
private static createByString (author: string, podHost: string) {
|
||||||
return author + '@' + podHost
|
return author + '@' + podHost
|
||||||
|
@ -57,7 +57,6 @@ export class Video implements VideoServerModel {
|
||||||
id: number,
|
id: number,
|
||||||
uuid: string,
|
uuid: string,
|
||||||
isLocal: boolean,
|
isLocal: boolean,
|
||||||
magnetUri: string,
|
|
||||||
name: string,
|
name: string,
|
||||||
podHost: string,
|
podHost: string,
|
||||||
tags: string[],
|
tags: string[],
|
||||||
|
@ -66,7 +65,8 @@ export class Video implements VideoServerModel {
|
||||||
views: number,
|
views: number,
|
||||||
likes: number,
|
likes: number,
|
||||||
dislikes: number,
|
dislikes: number,
|
||||||
nsfw: boolean
|
nsfw: boolean,
|
||||||
|
files: VideoFile[]
|
||||||
}) {
|
}) {
|
||||||
this.author = hash.author
|
this.author = hash.author
|
||||||
this.createdAt = new Date(hash.createdAt)
|
this.createdAt = new Date(hash.createdAt)
|
||||||
|
@ -82,7 +82,6 @@ export class Video implements VideoServerModel {
|
||||||
this.id = hash.id
|
this.id = hash.id
|
||||||
this.uuid = hash.uuid
|
this.uuid = hash.uuid
|
||||||
this.isLocal = hash.isLocal
|
this.isLocal = hash.isLocal
|
||||||
this.magnetUri = hash.magnetUri
|
|
||||||
this.name = hash.name
|
this.name = hash.name
|
||||||
this.podHost = hash.podHost
|
this.podHost = hash.podHost
|
||||||
this.tags = hash.tags
|
this.tags = hash.tags
|
||||||
|
@ -94,6 +93,7 @@ export class Video implements VideoServerModel {
|
||||||
this.likes = hash.likes
|
this.likes = hash.likes
|
||||||
this.dislikes = hash.dislikes
|
this.dislikes = hash.dislikes
|
||||||
this.nsfw = hash.nsfw
|
this.nsfw = hash.nsfw
|
||||||
|
this.files = hash.files
|
||||||
|
|
||||||
this.by = Video.createByString(hash.author, hash.podHost)
|
this.by = Video.createByString(hash.author, hash.podHost)
|
||||||
}
|
}
|
||||||
|
@ -115,6 +115,13 @@ export class Video implements VideoServerModel {
|
||||||
return (this.nsfw && (!user || user.displayNSFW === false))
|
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) {
|
patch (values: Object) {
|
||||||
Object.keys(values).forEach((key) => {
|
Object.keys(values).forEach((key) => {
|
||||||
this[key] = values[key]
|
this[key] = values[key]
|
||||||
|
@ -132,7 +139,6 @@ export class Video implements VideoServerModel {
|
||||||
duration: this.duration,
|
duration: this.duration,
|
||||||
id: this.id,
|
id: this.id,
|
||||||
isLocal: this.isLocal,
|
isLocal: this.isLocal,
|
||||||
magnetUri: this.magnetUri,
|
|
||||||
name: this.name,
|
name: this.name,
|
||||||
podHost: this.podHost,
|
podHost: this.podHost,
|
||||||
tags: this.tags,
|
tags: this.tags,
|
||||||
|
@ -140,7 +146,8 @@ export class Video implements VideoServerModel {
|
||||||
views: this.views,
|
views: this.views,
|
||||||
likes: this.likes,
|
likes: this.likes,
|
||||||
dislikes: this.dislikes,
|
dislikes: this.dislikes,
|
||||||
nsfw: this.nsfw
|
nsfw: this.nsfw,
|
||||||
|
files: this.files
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -90,8 +90,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
||||||
window.clearInterval(this.torrentInfosInterval)
|
window.clearInterval(this.torrentInfosInterval)
|
||||||
window.clearTimeout(this.errorTimer)
|
window.clearTimeout(this.errorTimer)
|
||||||
|
|
||||||
if (this.video !== null && this.webTorrentService.has(this.video.magnetUri)) {
|
if (this.video !== null && this.webTorrentService.has(this.video.getDefaultMagnetUri())) {
|
||||||
this.webTorrentService.remove(this.video.magnetUri)
|
this.webTorrentService.remove(this.video.getDefaultMagnetUri())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove player
|
// Remove player
|
||||||
|
@ -108,13 +108,13 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
||||||
// We are loading the video
|
// We are loading the video
|
||||||
this.loading = true
|
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
|
// The callback might never return if there are network issues
|
||||||
// So we create a timer to inform the user the load is abnormally long
|
// So we create a timer to inform the user the load is abnormally long
|
||||||
this.errorTimer = window.setTimeout(() => this.loadTooLong(), VideoWatchComponent.LOADTIME_TOO_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
|
// Clear the error timer
|
||||||
window.clearTimeout(this.errorTimer)
|
window.clearTimeout(this.errorTimer)
|
||||||
// Maybe the error was fired by the timer, so reset it
|
// 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
|
// We are not loading the video anymore
|
||||||
this.loading = false
|
this.loading = false
|
||||||
|
|
||||||
console.log('Added ' + this.video.magnetUri + '.')
|
console.log('Added ' + this.video.getDefaultMagnetUri() + '.')
|
||||||
torrent.files[0].renderTo(this.playerElement, (err) => {
|
torrent.files[0].renderTo(this.playerElement, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
this.notificationsService.error('Error', 'Cannot append the file in the video element.')
|
this.notificationsService.error('Error', 'Cannot append the file in the video element.')
|
||||||
|
|
|
@ -57,7 +57,11 @@ loadVideoInfos(videoId, (err, videoInfos) => {
|
||||||
return
|
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 videoContainer = document.getElementById('video-container') as HTMLVideoElement
|
||||||
const previewUrl = window.location.origin + videoInfos.previewPath
|
const previewUrl = window.location.origin + videoInfos.previewPath
|
||||||
videoContainer.poster = previewUrl
|
videoContainer.poster = previewUrl
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
"danger:clean:modules": "scripty",
|
"danger:clean:modules": "scripty",
|
||||||
"reset-password": "ts-node ./scripts/reset-password.ts",
|
"reset-password": "ts-node ./scripts/reset-password.ts",
|
||||||
"play": "scripty",
|
"play": "scripty",
|
||||||
|
"dev": "scripty",
|
||||||
"dev:server": "scripty",
|
"dev:server": "scripty",
|
||||||
"dev:client": "scripty",
|
"dev:client": "scripty",
|
||||||
"start": "node dist/server",
|
"start": "node dist/server",
|
||||||
|
|
|
@ -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 { readFileSync, writeFileSync } from 'fs'
|
||||||
|
import { join } from 'path'
|
||||||
import * as parseTorrent from 'parse-torrent'
|
import * as parseTorrent from 'parse-torrent'
|
||||||
|
|
||||||
import { CONFIG, STATIC_PATHS } from '../server/initializers/constants'
|
import { CONFIG, STATIC_PATHS } from '../server/initializers/constants'
|
||||||
|
@ -19,17 +20,10 @@ db.init(true)
|
||||||
return db.Video.list()
|
return db.Video.list()
|
||||||
})
|
})
|
||||||
.then(videos => {
|
.then(videos => {
|
||||||
videos.forEach(function (video) {
|
videos.forEach(video => {
|
||||||
const torrentName = video.id + '.torrent'
|
video.VideoFiles.forEach(file => {
|
||||||
const torrentPath = CONFIG.STORAGE.TORRENTS_DIR + torrentName
|
video.createTorrentAndSetInfoHash(file)
|
||||||
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)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
process.exit(0)
|
process.exit(0)
|
||||||
|
|
20
server.ts
20
server.ts
|
@ -26,7 +26,7 @@ const app = express()
|
||||||
// ----------- Database -----------
|
// ----------- Database -----------
|
||||||
// Do not use barrels because we don't want to load all modules here (we need to initialize database first)
|
// 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 { 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
|
// Initialize database and models
|
||||||
import { database as db } from './server/initializers/database'
|
import { database as db } from './server/initializers/database'
|
||||||
db.init(false).then(() => onDatabaseInitDone())
|
db.init(false).then(() => onDatabaseInitDone())
|
||||||
|
@ -57,10 +57,20 @@ import { apiRouter, clientsRouter, staticRouter } from './server/controllers'
|
||||||
|
|
||||||
// Enable CORS for develop
|
// Enable CORS for develop
|
||||||
if (isTestInstance()) {
|
if (isTestInstance()) {
|
||||||
app.use(cors({
|
app.use((req, res, next) => {
|
||||||
origin: 'http://localhost:3000',
|
// These routes have already cors
|
||||||
credentials: true
|
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
|
// For the logger
|
||||||
|
|
|
@ -258,8 +258,6 @@ function addRemoteVideo (videoToCreateData: RemoteVideoCreateData, fromPod: PodI
|
||||||
const videoData = {
|
const videoData = {
|
||||||
name: videoToCreateData.name,
|
name: videoToCreateData.name,
|
||||||
uuid: videoToCreateData.uuid,
|
uuid: videoToCreateData.uuid,
|
||||||
extname: videoToCreateData.extname,
|
|
||||||
infoHash: videoToCreateData.infoHash,
|
|
||||||
category: videoToCreateData.category,
|
category: videoToCreateData.category,
|
||||||
licence: videoToCreateData.licence,
|
licence: videoToCreateData.licence,
|
||||||
language: videoToCreateData.language,
|
language: videoToCreateData.language,
|
||||||
|
@ -289,6 +287,26 @@ function addRemoteVideo (videoToCreateData: RemoteVideoCreateData, fromPod: PodI
|
||||||
|
|
||||||
return video.save(options).then(videoCreated => ({ tagInstances, videoCreated }))
|
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 }) => {
|
.then(({ tagInstances, videoCreated }) => {
|
||||||
const options = {
|
const options = {
|
||||||
transaction: t
|
transaction: t
|
||||||
|
@ -344,6 +362,26 @@ function updateRemoteVideo (videoAttributesToUpdate: RemoteVideoUpdateData, from
|
||||||
|
|
||||||
return videoInstance.save(options).then(() => ({ videoInstance, tagInstances }))
|
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 }) => {
|
.then(({ videoInstance, tagInstances }) => {
|
||||||
const options = { transaction: t }
|
const options = { transaction: t }
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import * as express from 'express'
|
import * as express from 'express'
|
||||||
import * as Promise from 'bluebird'
|
import * as Promise from 'bluebird'
|
||||||
import * as multer from 'multer'
|
import * as multer from 'multer'
|
||||||
import * as path from 'path'
|
import { extname, join } from 'path'
|
||||||
|
|
||||||
import { database as db } from '../../../initializers/database'
|
import { database as db } from '../../../initializers/database'
|
||||||
import {
|
import {
|
||||||
|
@ -16,7 +16,8 @@ import {
|
||||||
addEventToRemoteVideo,
|
addEventToRemoteVideo,
|
||||||
quickAndDirtyUpdateVideoToFriends,
|
quickAndDirtyUpdateVideoToFriends,
|
||||||
addVideoToFriends,
|
addVideoToFriends,
|
||||||
updateVideoToFriends
|
updateVideoToFriends,
|
||||||
|
JobScheduler
|
||||||
} from '../../../lib'
|
} from '../../../lib'
|
||||||
import {
|
import {
|
||||||
authenticate,
|
authenticate,
|
||||||
|
@ -155,7 +156,7 @@ function addVideoRetryWrapper (req: express.Request, res: express.Response, next
|
||||||
.catch(err => next(err))
|
.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
|
const videoInfos: VideoCreate = req.body
|
||||||
|
|
||||||
return db.sequelize.transaction(t => {
|
return db.sequelize.transaction(t => {
|
||||||
|
@ -177,13 +178,13 @@ function addVideo (req: express.Request, res: express.Response, videoFile: Expre
|
||||||
const videoData = {
|
const videoData = {
|
||||||
name: videoInfos.name,
|
name: videoInfos.name,
|
||||||
remote: false,
|
remote: false,
|
||||||
extname: path.extname(videoFile.filename),
|
extname: extname(videoPhysicalFile.filename),
|
||||||
category: videoInfos.category,
|
category: videoInfos.category,
|
||||||
licence: videoInfos.licence,
|
licence: videoInfos.licence,
|
||||||
language: videoInfos.language,
|
language: videoInfos.language,
|
||||||
nsfw: videoInfos.nsfw,
|
nsfw: videoInfos.nsfw,
|
||||||
description: videoInfos.description,
|
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
|
authorId: author.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,18 +192,50 @@ function addVideo (req: express.Request, res: express.Response, videoFile: Expre
|
||||||
return { author, tagInstances, video }
|
return { author, tagInstances, video }
|
||||||
})
|
})
|
||||||
.then(({ 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 videoDir = CONFIG.STORAGE.VIDEOS_DIR
|
||||||
const source = path.join(videoDir, videoFile.filename)
|
const source = join(videoDir, videoPhysicalFile.filename)
|
||||||
const destination = path.join(videoDir, video.getVideoFilename())
|
const destination = join(videoDir, video.getVideoFilename(videoFile))
|
||||||
|
|
||||||
return renamePromise(source, destination)
|
return renamePromise(source, destination)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// This is important in case if there is another attempt in the retry process
|
// This is important in case if there is another attempt in the retry process
|
||||||
videoFile.filename = video.getVideoFilename()
|
videoPhysicalFile.filename = video.getVideoFilename(videoFile)
|
||||||
return { author, tagInstances, video }
|
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 }
|
const options = { transaction: t }
|
||||||
|
|
||||||
return video.save(options)
|
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
|
// Do not forget to add Author informations to the created video
|
||||||
videoCreated.Author = author
|
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 }) => {
|
.then(({ tagInstances, video }) => {
|
||||||
if (!tagInstances) return 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))
|
.then(() => logger.info('Video with name %s created.', videoInfos.name))
|
||||||
.catch((err: Error) => {
|
.catch((err: Error) => {
|
||||||
logger.debug('Cannot insert the video.', { error: err.stack })
|
logger.debug('Cannot insert the video.', err)
|
||||||
throw err
|
throw err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,10 +23,11 @@ import {
|
||||||
isVideoNSFWValid,
|
isVideoNSFWValid,
|
||||||
isVideoDescriptionValid,
|
isVideoDescriptionValid,
|
||||||
isVideoDurationValid,
|
isVideoDurationValid,
|
||||||
isVideoInfoHashValid,
|
isVideoFileInfoHashValid,
|
||||||
isVideoNameValid,
|
isVideoNameValid,
|
||||||
isVideoTagsValid,
|
isVideoTagsValid,
|
||||||
isVideoExtnameValid
|
isVideoFileExtnameValid,
|
||||||
|
isVideoFileResolutionValid
|
||||||
} from '../videos'
|
} from '../videos'
|
||||||
|
|
||||||
const ENDPOINT_ACTIONS = REQUEST_ENDPOINT_ACTIONS[REQUEST_ENDPOINTS.VIDEOS]
|
const ENDPOINT_ACTIONS = REQUEST_ENDPOINT_ACTIONS[REQUEST_ENDPOINTS.VIDEOS]
|
||||||
|
@ -121,14 +122,22 @@ function isCommonVideoAttributesValid (video: any) {
|
||||||
isVideoNSFWValid(video.nsfw) &&
|
isVideoNSFWValid(video.nsfw) &&
|
||||||
isVideoDescriptionValid(video.description) &&
|
isVideoDescriptionValid(video.description) &&
|
||||||
isVideoDurationValid(video.duration) &&
|
isVideoDurationValid(video.duration) &&
|
||||||
isVideoInfoHashValid(video.infoHash) &&
|
|
||||||
isVideoNameValid(video.name) &&
|
isVideoNameValid(video.name) &&
|
||||||
isVideoTagsValid(video.tags) &&
|
isVideoTagsValid(video.tags) &&
|
||||||
isVideoUUIDValid(video.uuid) &&
|
isVideoUUIDValid(video.uuid) &&
|
||||||
isVideoExtnameValid(video.extname) &&
|
|
||||||
isVideoViewsValid(video.views) &&
|
isVideoViewsValid(video.views) &&
|
||||||
isVideoLikesValid(video.likes) &&
|
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) {
|
function isRequestTypeAddValid (value: string) {
|
||||||
|
|
|
@ -7,7 +7,8 @@ import {
|
||||||
VIDEO_CATEGORIES,
|
VIDEO_CATEGORIES,
|
||||||
VIDEO_LICENCES,
|
VIDEO_LICENCES,
|
||||||
VIDEO_LANGUAGES,
|
VIDEO_LANGUAGES,
|
||||||
VIDEO_RATE_TYPES
|
VIDEO_RATE_TYPES,
|
||||||
|
VIDEO_FILE_RESOLUTIONS
|
||||||
} from '../../initializers'
|
} from '../../initializers'
|
||||||
import { isUserUsernameValid } from './users'
|
import { isUserUsernameValid } from './users'
|
||||||
import { isArray, exists } from './misc'
|
import { isArray, exists } from './misc'
|
||||||
|
@ -53,14 +54,6 @@ function isVideoDurationValid (value: string) {
|
||||||
return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.DURATION)
|
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) {
|
function isVideoNameValid (value: string) {
|
||||||
return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.NAME)
|
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)
|
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 {
|
export {
|
||||||
|
@ -140,12 +149,12 @@ export {
|
||||||
isVideoNSFWValid,
|
isVideoNSFWValid,
|
||||||
isVideoDescriptionValid,
|
isVideoDescriptionValid,
|
||||||
isVideoDurationValid,
|
isVideoDurationValid,
|
||||||
isVideoInfoHashValid,
|
isVideoFileInfoHashValid,
|
||||||
isVideoNameValid,
|
isVideoNameValid,
|
||||||
isVideoTagsValid,
|
isVideoTagsValid,
|
||||||
isVideoThumbnailValid,
|
isVideoThumbnailValid,
|
||||||
isVideoThumbnailDataValid,
|
isVideoThumbnailDataValid,
|
||||||
isVideoExtnameValid,
|
isVideoFileExtnameValid,
|
||||||
isVideoUUIDValid,
|
isVideoUUIDValid,
|
||||||
isVideoAbuseReasonValid,
|
isVideoAbuseReasonValid,
|
||||||
isVideoAbuseReporterUsernameValid,
|
isVideoAbuseReporterUsernameValid,
|
||||||
|
@ -154,7 +163,9 @@ export {
|
||||||
isVideoLikesValid,
|
isVideoLikesValid,
|
||||||
isVideoRatingTypeValid,
|
isVideoRatingTypeValid,
|
||||||
isVideoDislikesValid,
|
isVideoDislikesValid,
|
||||||
isVideoEventCountValid
|
isVideoEventCountValid,
|
||||||
|
isVideoFileSizeValid,
|
||||||
|
isVideoFileResolutionValid
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -183,7 +194,9 @@ declare global {
|
||||||
isVideoLikesValid,
|
isVideoLikesValid,
|
||||||
isVideoRatingTypeValid,
|
isVideoRatingTypeValid,
|
||||||
isVideoDislikesValid,
|
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
|
THUMBNAIL_DATA: { min: 0, max: 20000 }, // Bytes
|
||||||
VIEWS: { min: 0 },
|
VIEWS: { min: 0 },
|
||||||
LIKES: { min: 0 },
|
LIKES: { min: 0 },
|
||||||
DISLIKES: { min: 0 }
|
DISLIKES: { min: 0 },
|
||||||
|
FILE_SIZE: { min: 10, max: 1024 * 1024 * 1024 * 3 /* 3Go */ }
|
||||||
},
|
},
|
||||||
VIDEO_EVENTS: {
|
VIDEO_EVENTS: {
|
||||||
COUNT: { min: 0 }
|
COUNT: { min: 0 }
|
||||||
|
@ -176,6 +177,14 @@ const VIDEO_LANGUAGES = {
|
||||||
14: 'Italien'
|
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
|
// Score a pod has when we create it as a friend
|
||||||
|
@ -362,6 +371,7 @@ export {
|
||||||
THUMBNAILS_SIZE,
|
THUMBNAILS_SIZE,
|
||||||
USER_ROLES,
|
USER_ROLES,
|
||||||
VIDEO_CATEGORIES,
|
VIDEO_CATEGORIES,
|
||||||
|
VIDEO_FILE_RESOLUTIONS,
|
||||||
VIDEO_LANGUAGES,
|
VIDEO_LANGUAGES,
|
||||||
VIDEO_LICENCES,
|
VIDEO_LICENCES,
|
||||||
VIDEO_RATE_TYPES
|
VIDEO_RATE_TYPES
|
||||||
|
|
|
@ -23,6 +23,7 @@ import {
|
||||||
UserVideoRateModel,
|
UserVideoRateModel,
|
||||||
VideoAbuseModel,
|
VideoAbuseModel,
|
||||||
BlacklistedVideoModel,
|
BlacklistedVideoModel,
|
||||||
|
VideoFileModel,
|
||||||
VideoTagModel,
|
VideoTagModel,
|
||||||
VideoModel
|
VideoModel
|
||||||
} from '../models'
|
} from '../models'
|
||||||
|
@ -49,6 +50,7 @@ const database: {
|
||||||
UserVideoRate?: UserVideoRateModel,
|
UserVideoRate?: UserVideoRateModel,
|
||||||
User?: UserModel,
|
User?: UserModel,
|
||||||
VideoAbuse?: VideoAbuseModel,
|
VideoAbuse?: VideoAbuseModel,
|
||||||
|
VideoFile?: VideoFileModel,
|
||||||
BlacklistedVideo?: BlacklistedVideoModel,
|
BlacklistedVideo?: BlacklistedVideoModel,
|
||||||
VideoTag?: VideoTagModel,
|
VideoTag?: VideoTagModel,
|
||||||
Video?: VideoModel
|
Video?: VideoModel
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
script: string
|
||||||
}[] = []
|
}[] = []
|
||||||
|
|
||||||
files.forEach(file => {
|
files
|
||||||
// Filename is something like 'version-blabla.js'
|
.filter(file => file.endsWith('.js.map') === false)
|
||||||
const version = file.split('-')[0]
|
.forEach(file => {
|
||||||
filesToMigrate.push({
|
// Filename is something like 'version-blabla.js'
|
||||||
version,
|
const version = file.split('-')[0]
|
||||||
script: file
|
filesToMigrate.push({
|
||||||
|
version,
|
||||||
|
script: file
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
return filesToMigrate
|
return filesToMigrate
|
||||||
})
|
})
|
||||||
|
@ -93,7 +95,8 @@ function executeMigration (actualVersion: number, entity: { version: string, scr
|
||||||
const options = {
|
const options = {
|
||||||
transaction: t,
|
transaction: t,
|
||||||
queryInterface: db.sequelize.getQueryInterface(),
|
queryInterface: db.sequelize.getQueryInterface(),
|
||||||
sequelize: db.sequelize
|
sequelize: db.sequelize,
|
||||||
|
db
|
||||||
}
|
}
|
||||||
|
|
||||||
return migrationScript.up(options)
|
return migrationScript.up(options)
|
||||||
|
|
|
@ -5,7 +5,9 @@ import { VideoInstance } from '../../../models'
|
||||||
|
|
||||||
function process (data: { videoUUID: string }) {
|
function process (data: { videoUUID: string }) {
|
||||||
return db.Video.loadByUUIDAndPopulateAuthorAndPodAndTags(data.videoUUID).then(video => {
|
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-abuse-interface'
|
||||||
export * from './video-blacklist-interface'
|
export * from './video-blacklist-interface'
|
||||||
export * from './video-tag-interface'
|
export * from './video-tag-interface'
|
||||||
|
export * from './video-file-interface'
|
||||||
export * from './video-interface'
|
export * from './video-interface'
|
||||||
|
|
|
@ -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> {}
|
|
@ -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 { AuthorInstance } from './author-interface'
|
||||||
import { TagAttributes, TagInstance } from './tag-interface'
|
import { TagAttributes, TagInstance } from './tag-interface'
|
||||||
|
import { VideoFileAttributes, VideoFileInstance } from './video-file-interface'
|
||||||
|
|
||||||
// Don't use barrel, import just what we need
|
// Don't use barrel, import just what we need
|
||||||
import { Video as FormatedVideo } from '../../../shared/models/videos/video.model'
|
import { Video as FormatedVideo } from '../../../shared/models/videos/video.model'
|
||||||
import { ResultList } from '../../../shared/models/result-list.model'
|
import { ResultList } from '../../../shared/models/result-list.model'
|
||||||
|
|
||||||
|
export type FormatedRemoteVideoFile = {
|
||||||
|
infoHash: string
|
||||||
|
resolution: number
|
||||||
|
extname: string
|
||||||
|
size: number
|
||||||
|
}
|
||||||
|
|
||||||
export type FormatedAddRemoteVideo = {
|
export type FormatedAddRemoteVideo = {
|
||||||
uuid: string
|
uuid: string
|
||||||
name: string
|
name: string
|
||||||
|
@ -16,17 +24,16 @@ export type FormatedAddRemoteVideo = {
|
||||||
language: number
|
language: number
|
||||||
nsfw: boolean
|
nsfw: boolean
|
||||||
description: string
|
description: string
|
||||||
infoHash: string
|
|
||||||
author: string
|
author: string
|
||||||
duration: number
|
duration: number
|
||||||
thumbnailData: string
|
thumbnailData: string
|
||||||
tags: string[]
|
tags: string[]
|
||||||
createdAt: Date
|
createdAt: Date
|
||||||
updatedAt: Date
|
updatedAt: Date
|
||||||
extname: string
|
|
||||||
views: number
|
views: number
|
||||||
likes: number
|
likes: number
|
||||||
dislikes: number
|
dislikes: number
|
||||||
|
files: FormatedRemoteVideoFile[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FormatedUpdateRemoteVideo = {
|
export type FormatedUpdateRemoteVideo = {
|
||||||
|
@ -37,31 +44,35 @@ export type FormatedUpdateRemoteVideo = {
|
||||||
language: number
|
language: number
|
||||||
nsfw: boolean
|
nsfw: boolean
|
||||||
description: string
|
description: string
|
||||||
infoHash: string
|
|
||||||
author: string
|
author: string
|
||||||
duration: number
|
duration: number
|
||||||
tags: string[]
|
tags: string[]
|
||||||
createdAt: Date
|
createdAt: Date
|
||||||
updatedAt: Date
|
updatedAt: Date
|
||||||
extname: string
|
|
||||||
views: number
|
views: number
|
||||||
likes: number
|
likes: number
|
||||||
dislikes: number
|
dislikes: number
|
||||||
|
files: FormatedRemoteVideoFile[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace VideoMethods {
|
export namespace VideoMethods {
|
||||||
export type GenerateMagnetUri = (this: VideoInstance) => string
|
|
||||||
export type GetVideoFilename = (this: VideoInstance) => string
|
|
||||||
export type GetThumbnailName = (this: VideoInstance) => string
|
export type GetThumbnailName = (this: VideoInstance) => string
|
||||||
export type GetPreviewName = (this: VideoInstance) => string
|
export type GetPreviewName = (this: VideoInstance) => string
|
||||||
export type GetTorrentName = (this: VideoInstance) => string
|
|
||||||
export type IsOwned = (this: VideoInstance) => boolean
|
export type IsOwned = (this: VideoInstance) => boolean
|
||||||
export type ToFormatedJSON = (this: VideoInstance) => FormatedVideo
|
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 ToAddRemoteJSON = (this: VideoInstance) => Promise<FormatedAddRemoteVideo>
|
||||||
export type ToUpdateRemoteJSON = (this: VideoInstance) => FormatedUpdateRemoteVideo
|
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
|
// Return thumbnail name
|
||||||
export type GenerateThumbnailFromData = (video: VideoInstance, thumbnailData: string) => Promise<string>
|
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 LoadAndPopulateAuthor = (id: number) => Promise<VideoInstance>
|
||||||
export type LoadAndPopulateAuthorAndPodAndTags = (id: number) => Promise<VideoInstance>
|
export type LoadAndPopulateAuthorAndPodAndTags = (id: number) => Promise<VideoInstance>
|
||||||
export type LoadByUUIDAndPopulateAuthorAndPodAndTags = (uuid: string) => 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 {
|
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
|
generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData
|
||||||
getDurationFromFile: VideoMethods.GetDurationFromFile
|
getDurationFromFile: VideoMethods.GetDurationFromFile
|
||||||
list: VideoMethods.List
|
list: VideoMethods.List
|
||||||
listForApi: VideoMethods.ListForApi
|
listForApi: VideoMethods.ListForApi
|
||||||
loadByHostAndUUID: VideoMethods.LoadByHostAndUUID
|
|
||||||
listOwnedAndPopulateAuthorAndTags: VideoMethods.ListOwnedAndPopulateAuthorAndTags
|
listOwnedAndPopulateAuthorAndTags: VideoMethods.ListOwnedAndPopulateAuthorAndTags
|
||||||
listOwnedByAuthor: VideoMethods.ListOwnedByAuthor
|
listOwnedByAuthor: VideoMethods.ListOwnedByAuthor
|
||||||
load: VideoMethods.Load
|
load: VideoMethods.Load
|
||||||
loadByUUID: VideoMethods.LoadByUUID
|
|
||||||
loadAndPopulateAuthor: VideoMethods.LoadAndPopulateAuthor
|
loadAndPopulateAuthor: VideoMethods.LoadAndPopulateAuthor
|
||||||
loadAndPopulateAuthorAndPodAndTags: VideoMethods.LoadAndPopulateAuthorAndPodAndTags
|
loadAndPopulateAuthorAndPodAndTags: VideoMethods.LoadAndPopulateAuthorAndPodAndTags
|
||||||
|
loadByHostAndUUID: VideoMethods.LoadByHostAndUUID
|
||||||
|
loadByUUID: VideoMethods.LoadByUUID
|
||||||
loadByUUIDAndPopulateAuthorAndPodAndTags: VideoMethods.LoadByUUIDAndPopulateAuthorAndPodAndTags
|
loadByUUIDAndPopulateAuthorAndPodAndTags: VideoMethods.LoadByUUIDAndPopulateAuthorAndPodAndTags
|
||||||
searchAndPopulateAuthorAndPodAndTags: VideoMethods.SearchAndPopulateAuthorAndPodAndTags
|
searchAndPopulateAuthorAndPodAndTags: VideoMethods.SearchAndPopulateAuthorAndPodAndTags
|
||||||
}
|
}
|
||||||
|
@ -118,13 +123,11 @@ export interface VideoClass {
|
||||||
export interface VideoAttributes {
|
export interface VideoAttributes {
|
||||||
uuid?: string
|
uuid?: string
|
||||||
name: string
|
name: string
|
||||||
extname: string
|
|
||||||
category: number
|
category: number
|
||||||
licence: number
|
licence: number
|
||||||
language: number
|
language: number
|
||||||
nsfw: boolean
|
nsfw: boolean
|
||||||
description: string
|
description: string
|
||||||
infoHash?: string
|
|
||||||
duration: number
|
duration: number
|
||||||
views?: number
|
views?: number
|
||||||
likes?: number
|
likes?: number
|
||||||
|
@ -133,6 +136,7 @@ export interface VideoAttributes {
|
||||||
|
|
||||||
Author?: AuthorInstance
|
Author?: AuthorInstance
|
||||||
Tags?: TagInstance[]
|
Tags?: TagInstance[]
|
||||||
|
VideoFiles?: VideoFileInstance[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.Instance<VideoAttributes> {
|
export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.Instance<VideoAttributes> {
|
||||||
|
@ -140,18 +144,27 @@ export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.In
|
||||||
createdAt: Date
|
createdAt: Date
|
||||||
updatedAt: Date
|
updatedAt: Date
|
||||||
|
|
||||||
|
createPreview: VideoMethods.CreatePreview
|
||||||
|
createThumbnail: VideoMethods.CreateThumbnail
|
||||||
|
createTorrentAndSetInfoHash: VideoMethods.CreateTorrentAndSetInfoHash
|
||||||
generateMagnetUri: VideoMethods.GenerateMagnetUri
|
generateMagnetUri: VideoMethods.GenerateMagnetUri
|
||||||
getVideoFilename: VideoMethods.GetVideoFilename
|
|
||||||
getThumbnailName: VideoMethods.GetThumbnailName
|
|
||||||
getPreviewName: VideoMethods.GetPreviewName
|
getPreviewName: VideoMethods.GetPreviewName
|
||||||
getTorrentName: VideoMethods.GetTorrentName
|
getThumbnailName: VideoMethods.GetThumbnailName
|
||||||
|
getTorrentFileName: VideoMethods.GetTorrentFileName
|
||||||
|
getVideoFilename: VideoMethods.GetVideoFilename
|
||||||
|
getVideoFilePath: VideoMethods.GetVideoFilePath
|
||||||
isOwned: VideoMethods.IsOwned
|
isOwned: VideoMethods.IsOwned
|
||||||
toFormatedJSON: VideoMethods.ToFormatedJSON
|
removeFile: VideoMethods.RemoveFile
|
||||||
|
removePreview: VideoMethods.RemovePreview
|
||||||
|
removeThumbnail: VideoMethods.RemoveThumbnail
|
||||||
|
removeTorrent: VideoMethods.RemoveTorrent
|
||||||
toAddRemoteJSON: VideoMethods.ToAddRemoteJSON
|
toAddRemoteJSON: VideoMethods.ToAddRemoteJSON
|
||||||
|
toFormatedJSON: VideoMethods.ToFormatedJSON
|
||||||
toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON
|
toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON
|
||||||
transcodeVideofile: VideoMethods.TranscodeVideofile
|
transcodeVideofile: VideoMethods.TranscodeVideofile
|
||||||
|
|
||||||
setTags: Sequelize.HasManySetAssociationsMixin<TagAttributes, string>
|
setTags: Sequelize.HasManySetAssociationsMixin<TagAttributes, string>
|
||||||
|
setVideoFiles: Sequelize.HasManySetAssociationsMixin<VideoFileAttributes, string>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VideoModel extends VideoClass, Sequelize.Model<VideoInstance, VideoAttributes> {}
|
export interface VideoModel extends VideoClass, Sequelize.Model<VideoInstance, VideoAttributes> {}
|
||||||
|
|
|
@ -2,13 +2,12 @@ import * as safeBuffer from 'safe-buffer'
|
||||||
const Buffer = safeBuffer.Buffer
|
const Buffer = safeBuffer.Buffer
|
||||||
import * as ffmpeg from 'fluent-ffmpeg'
|
import * as ffmpeg from 'fluent-ffmpeg'
|
||||||
import * as magnetUtil from 'magnet-uri'
|
import * as magnetUtil from 'magnet-uri'
|
||||||
import { map, values } from 'lodash'
|
import { map } from 'lodash'
|
||||||
import * as parseTorrent from 'parse-torrent'
|
import * as parseTorrent from 'parse-torrent'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import * as Sequelize from 'sequelize'
|
import * as Sequelize from 'sequelize'
|
||||||
import * as Promise from 'bluebird'
|
import * as Promise from 'bluebird'
|
||||||
|
|
||||||
import { database as db } from '../../initializers/database'
|
|
||||||
import { TagInstance } from './tag-interface'
|
import { TagInstance } from './tag-interface'
|
||||||
import {
|
import {
|
||||||
logger,
|
logger,
|
||||||
|
@ -18,7 +17,6 @@ import {
|
||||||
isVideoLanguageValid,
|
isVideoLanguageValid,
|
||||||
isVideoNSFWValid,
|
isVideoNSFWValid,
|
||||||
isVideoDescriptionValid,
|
isVideoDescriptionValid,
|
||||||
isVideoInfoHashValid,
|
|
||||||
isVideoDurationValid,
|
isVideoDurationValid,
|
||||||
readFileBufferPromise,
|
readFileBufferPromise,
|
||||||
unlinkPromise,
|
unlinkPromise,
|
||||||
|
@ -27,16 +25,17 @@ import {
|
||||||
createTorrentPromise
|
createTorrentPromise
|
||||||
} from '../../helpers'
|
} from '../../helpers'
|
||||||
import {
|
import {
|
||||||
CONSTRAINTS_FIELDS,
|
|
||||||
CONFIG,
|
CONFIG,
|
||||||
REMOTE_SCHEME,
|
REMOTE_SCHEME,
|
||||||
STATIC_PATHS,
|
STATIC_PATHS,
|
||||||
VIDEO_CATEGORIES,
|
VIDEO_CATEGORIES,
|
||||||
VIDEO_LICENCES,
|
VIDEO_LICENCES,
|
||||||
VIDEO_LANGUAGES,
|
VIDEO_LANGUAGES,
|
||||||
THUMBNAILS_SIZE
|
THUMBNAILS_SIZE,
|
||||||
|
VIDEO_FILE_RESOLUTIONS
|
||||||
} from '../../initializers'
|
} from '../../initializers'
|
||||||
import { JobScheduler, removeVideoToFriends } from '../../lib'
|
import { removeVideoToFriends } from '../../lib'
|
||||||
|
import { VideoFileInstance } from './video-file-interface'
|
||||||
|
|
||||||
import { addMethodsToModel, getSort } from '../utils'
|
import { addMethodsToModel, getSort } from '../utils'
|
||||||
import {
|
import {
|
||||||
|
@ -51,12 +50,16 @@ let generateMagnetUri: VideoMethods.GenerateMagnetUri
|
||||||
let getVideoFilename: VideoMethods.GetVideoFilename
|
let getVideoFilename: VideoMethods.GetVideoFilename
|
||||||
let getThumbnailName: VideoMethods.GetThumbnailName
|
let getThumbnailName: VideoMethods.GetThumbnailName
|
||||||
let getPreviewName: VideoMethods.GetPreviewName
|
let getPreviewName: VideoMethods.GetPreviewName
|
||||||
let getTorrentName: VideoMethods.GetTorrentName
|
let getTorrentFileName: VideoMethods.GetTorrentFileName
|
||||||
let isOwned: VideoMethods.IsOwned
|
let isOwned: VideoMethods.IsOwned
|
||||||
let toFormatedJSON: VideoMethods.ToFormatedJSON
|
let toFormatedJSON: VideoMethods.ToFormatedJSON
|
||||||
let toAddRemoteJSON: VideoMethods.ToAddRemoteJSON
|
let toAddRemoteJSON: VideoMethods.ToAddRemoteJSON
|
||||||
let toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON
|
let toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON
|
||||||
let transcodeVideofile: VideoMethods.TranscodeVideofile
|
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 generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData
|
||||||
let getDurationFromFile: VideoMethods.GetDurationFromFile
|
let getDurationFromFile: VideoMethods.GetDurationFromFile
|
||||||
|
@ -71,6 +74,10 @@ let loadAndPopulateAuthor: VideoMethods.LoadAndPopulateAuthor
|
||||||
let loadAndPopulateAuthorAndPodAndTags: VideoMethods.LoadAndPopulateAuthorAndPodAndTags
|
let loadAndPopulateAuthorAndPodAndTags: VideoMethods.LoadAndPopulateAuthorAndPodAndTags
|
||||||
let loadByUUIDAndPopulateAuthorAndPodAndTags: VideoMethods.LoadByUUIDAndPopulateAuthorAndPodAndTags
|
let loadByUUIDAndPopulateAuthorAndPodAndTags: VideoMethods.LoadByUUIDAndPopulateAuthorAndPodAndTags
|
||||||
let searchAndPopulateAuthorAndPodAndTags: VideoMethods.SearchAndPopulateAuthorAndPodAndTags
|
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) {
|
export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
|
||||||
Video = sequelize.define<VideoInstance, VideoAttributes>('Video',
|
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: {
|
category: {
|
||||||
type: DataTypes.INTEGER,
|
type: DataTypes.INTEGER,
|
||||||
allowNull: false,
|
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: {
|
duration: {
|
||||||
type: DataTypes.INTEGER,
|
type: DataTypes.INTEGER,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
|
@ -215,9 +208,6 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
|
||||||
{
|
{
|
||||||
fields: [ 'duration' ]
|
fields: [ 'duration' ]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
fields: [ 'infoHash' ]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
fields: [ 'views' ]
|
fields: [ 'views' ]
|
||||||
},
|
},
|
||||||
|
@ -229,8 +219,6 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
hooks: {
|
hooks: {
|
||||||
beforeValidate,
|
|
||||||
beforeCreate,
|
|
||||||
afterDestroy
|
afterDestroy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -246,23 +234,30 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
|
||||||
listOwnedAndPopulateAuthorAndTags,
|
listOwnedAndPopulateAuthorAndTags,
|
||||||
listOwnedByAuthor,
|
listOwnedByAuthor,
|
||||||
load,
|
load,
|
||||||
loadByUUID,
|
|
||||||
loadByHostAndUUID,
|
|
||||||
loadAndPopulateAuthor,
|
loadAndPopulateAuthor,
|
||||||
loadAndPopulateAuthorAndPodAndTags,
|
loadAndPopulateAuthorAndPodAndTags,
|
||||||
|
loadByHostAndUUID,
|
||||||
|
loadByUUID,
|
||||||
loadByUUIDAndPopulateAuthorAndPodAndTags,
|
loadByUUIDAndPopulateAuthorAndPodAndTags,
|
||||||
searchAndPopulateAuthorAndPodAndTags,
|
searchAndPopulateAuthorAndPodAndTags
|
||||||
removeFromBlacklist
|
|
||||||
]
|
]
|
||||||
const instanceMethods = [
|
const instanceMethods = [
|
||||||
|
createPreview,
|
||||||
|
createThumbnail,
|
||||||
|
createTorrentAndSetInfoHash,
|
||||||
generateMagnetUri,
|
generateMagnetUri,
|
||||||
getVideoFilename,
|
|
||||||
getThumbnailName,
|
|
||||||
getPreviewName,
|
getPreviewName,
|
||||||
getTorrentName,
|
getThumbnailName,
|
||||||
|
getTorrentFileName,
|
||||||
|
getVideoFilename,
|
||||||
|
getVideoFilePath,
|
||||||
isOwned,
|
isOwned,
|
||||||
toFormatedJSON,
|
removeFile,
|
||||||
|
removePreview,
|
||||||
|
removeThumbnail,
|
||||||
|
removeTorrent,
|
||||||
toAddRemoteJSON,
|
toAddRemoteJSON,
|
||||||
|
toFormatedJSON,
|
||||||
toUpdateRemoteJSON,
|
toUpdateRemoteJSON,
|
||||||
transcodeVideofile
|
transcodeVideofile
|
||||||
]
|
]
|
||||||
|
@ -271,65 +266,6 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
|
||||||
return Video
|
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 ------------------------------
|
// ------------------------------ METHODS ------------------------------
|
||||||
|
|
||||||
function associate (models) {
|
function associate (models) {
|
||||||
|
@ -354,37 +290,46 @@ function associate (models) {
|
||||||
},
|
},
|
||||||
onDelete: 'cascade'
|
onDelete: 'cascade'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Video.hasMany(models.VideoFile, {
|
||||||
|
foreignKey: {
|
||||||
|
name: 'videoId',
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
onDelete: 'cascade'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
generateMagnetUri = function (this: VideoInstance) {
|
function afterDestroy (video: VideoInstance) {
|
||||||
let baseUrlHttp
|
const tasks = []
|
||||||
let baseUrlWs
|
|
||||||
|
|
||||||
if (this.isOwned()) {
|
tasks.push(
|
||||||
baseUrlHttp = CONFIG.WEBSERVER.URL
|
video.removeThumbnail()
|
||||||
baseUrlWs = CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT
|
)
|
||||||
} else {
|
|
||||||
baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + this.Author.Pod.host
|
if (video.isOwned()) {
|
||||||
baseUrlWs = REMOTE_SCHEME.WS + '://' + this.Author.Pod.host
|
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()
|
return Promise.all(tasks)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getVideoFilename = function (this: VideoInstance) {
|
getVideoFilename = function (this: VideoInstance, videoFile: VideoFileInstance) {
|
||||||
return this.uuid + this.extname
|
// return this.uuid + '-' + VIDEO_FILE_RESOLUTIONS[videoFile.resolution] + videoFile.extname
|
||||||
|
return this.uuid + videoFile.extname
|
||||||
}
|
}
|
||||||
|
|
||||||
getThumbnailName = function (this: VideoInstance) {
|
getThumbnailName = function (this: VideoInstance) {
|
||||||
|
@ -398,8 +343,9 @@ getPreviewName = function (this: VideoInstance) {
|
||||||
return this.uuid + extension
|
return this.uuid + extension
|
||||||
}
|
}
|
||||||
|
|
||||||
getTorrentName = function (this: VideoInstance) {
|
getTorrentFileName = function (this: VideoInstance, videoFile: VideoFileInstance) {
|
||||||
const extension = '.torrent'
|
const extension = '.torrent'
|
||||||
|
// return this.uuid + '-' + VIDEO_FILE_RESOLUTIONS[videoFile.resolution] + extension
|
||||||
return this.uuid + extension
|
return this.uuid + extension
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -407,6 +353,67 @@ isOwned = function (this: VideoInstance) {
|
||||||
return this.remote === false
|
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) {
|
toFormatedJSON = function (this: VideoInstance) {
|
||||||
let podHost
|
let podHost
|
||||||
|
|
||||||
|
@ -443,7 +450,6 @@ toFormatedJSON = function (this: VideoInstance) {
|
||||||
description: this.description,
|
description: this.description,
|
||||||
podHost,
|
podHost,
|
||||||
isLocal: this.isOwned(),
|
isLocal: this.isOwned(),
|
||||||
magnetUri: this.generateMagnetUri(),
|
|
||||||
author: this.Author.name,
|
author: this.Author.name,
|
||||||
duration: this.duration,
|
duration: this.duration,
|
||||||
views: this.views,
|
views: this.views,
|
||||||
|
@ -453,9 +459,24 @@ toFormatedJSON = function (this: VideoInstance) {
|
||||||
thumbnailPath: join(STATIC_PATHS.THUMBNAILS, this.getThumbnailName()),
|
thumbnailPath: join(STATIC_PATHS.THUMBNAILS, this.getThumbnailName()),
|
||||||
previewPath: join(STATIC_PATHS.PREVIEWS, this.getPreviewName()),
|
previewPath: join(STATIC_PATHS.PREVIEWS, this.getPreviewName()),
|
||||||
createdAt: this.createdAt,
|
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
|
return json
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -472,19 +493,27 @@ toAddRemoteJSON = function (this: VideoInstance) {
|
||||||
language: this.language,
|
language: this.language,
|
||||||
nsfw: this.nsfw,
|
nsfw: this.nsfw,
|
||||||
description: this.description,
|
description: this.description,
|
||||||
infoHash: this.infoHash,
|
|
||||||
author: this.Author.name,
|
author: this.Author.name,
|
||||||
duration: this.duration,
|
duration: this.duration,
|
||||||
thumbnailData: thumbnailData.toString('binary'),
|
thumbnailData: thumbnailData.toString('binary'),
|
||||||
tags: map<TagInstance, string>(this.Tags, 'name'),
|
tags: map<TagInstance, string>(this.Tags, 'name'),
|
||||||
createdAt: this.createdAt,
|
createdAt: this.createdAt,
|
||||||
updatedAt: this.updatedAt,
|
updatedAt: this.updatedAt,
|
||||||
extname: this.extname,
|
|
||||||
views: this.views,
|
views: this.views,
|
||||||
likes: this.likes,
|
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
|
return remoteVideo
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -498,28 +527,34 @@ toUpdateRemoteJSON = function (this: VideoInstance) {
|
||||||
language: this.language,
|
language: this.language,
|
||||||
nsfw: this.nsfw,
|
nsfw: this.nsfw,
|
||||||
description: this.description,
|
description: this.description,
|
||||||
infoHash: this.infoHash,
|
|
||||||
author: this.Author.name,
|
author: this.Author.name,
|
||||||
duration: this.duration,
|
duration: this.duration,
|
||||||
tags: map<TagInstance, string>(this.Tags, 'name'),
|
tags: map<TagInstance, string>(this.Tags, 'name'),
|
||||||
createdAt: this.createdAt,
|
createdAt: this.createdAt,
|
||||||
updatedAt: this.updatedAt,
|
updatedAt: this.updatedAt,
|
||||||
extname: this.extname,
|
|
||||||
views: this.views,
|
views: this.views,
|
||||||
likes: this.likes,
|
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
|
return json
|
||||||
}
|
}
|
||||||
|
|
||||||
transcodeVideofile = function (this: VideoInstance) {
|
transcodeVideofile = function (this: VideoInstance, inputVideoFile: VideoFileInstance) {
|
||||||
const video = this
|
|
||||||
|
|
||||||
const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
|
const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
|
||||||
const newExtname = '.mp4'
|
const newExtname = '.mp4'
|
||||||
const videoInputPath = join(videosDirectory, video.getVideoFilename())
|
const videoInputPath = join(videosDirectory, this.getVideoFilename(inputVideoFile))
|
||||||
const videoOutputPath = join(videosDirectory, video.id + '-transcoded' + newExtname)
|
const videoOutputPath = join(videosDirectory, this.id + '-transcoded' + newExtname)
|
||||||
|
|
||||||
return new Promise<void>((res, rej) => {
|
return new Promise<void>((res, rej) => {
|
||||||
ffmpeg(videoInputPath)
|
ffmpeg(videoInputPath)
|
||||||
|
@ -533,24 +568,22 @@ transcodeVideofile = function (this: VideoInstance) {
|
||||||
return unlinkPromise(videoInputPath)
|
return unlinkPromise(videoInputPath)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// Important to do this before getVideoFilename() to take in account the new file extension
|
// 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, this.getVideoFilePath(inputVideoFile))
|
||||||
return renamePromise(videoOutputPath, newVideoPath)
|
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
const newVideoPath = join(videosDirectory, video.getVideoFilename())
|
return this.createTorrentAndSetInfoHash(inputVideoFile)
|
||||||
return createTorrentFromVideo(video, newVideoPath)
|
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return video.save()
|
return inputVideoFile.save()
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return res()
|
return res()
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
// Autodesctruction...
|
// Autodestruction...
|
||||||
video.destroy().catch(err => logger.error('Cannot destruct video after transcoding failure.', err))
|
this.destroy().catch(err => logger.error('Cannot destruct video after transcoding failure.', err))
|
||||||
|
|
||||||
return rej(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 ------------------------------
|
// ------------------------------ STATICS ------------------------------
|
||||||
|
|
||||||
generateThumbnailFromData = function (video: VideoInstance, thumbnailData: string) {
|
generateThumbnailFromData = function (video: VideoInstance, thumbnailData: string) {
|
||||||
|
@ -582,7 +635,11 @@ getDurationFromFile = function (videoPath: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
list = function () {
|
list = function () {
|
||||||
return Video.findAll()
|
const query = {
|
||||||
|
include: [ Video['sequelize'].models.VideoFile ]
|
||||||
|
}
|
||||||
|
|
||||||
|
return Video.findAll(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
listForApi = function (start: number, count: number, sort: string) {
|
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,
|
model: Video['sequelize'].models.Author,
|
||||||
include: [ { model: Video['sequelize'].models.Pod, required: false } ]
|
include: [ { model: Video['sequelize'].models.Pod, required: false } ]
|
||||||
},
|
},
|
||||||
|
Video['sequelize'].models.Tag,
|
||||||
Video['sequelize'].models.Tag
|
Video['sequelize'].models.VideoFile
|
||||||
],
|
],
|
||||||
where: createBaseVideosWhere()
|
where: createBaseVideosWhere()
|
||||||
}
|
}
|
||||||
|
@ -617,6 +674,9 @@ loadByHostAndUUID = function (fromHost: string, uuid: string) {
|
||||||
uuid
|
uuid
|
||||||
},
|
},
|
||||||
include: [
|
include: [
|
||||||
|
{
|
||||||
|
model: Video['sequelize'].models.VideoFile
|
||||||
|
},
|
||||||
{
|
{
|
||||||
model: Video['sequelize'].models.Author,
|
model: Video['sequelize'].models.Author,
|
||||||
include: [
|
include: [
|
||||||
|
@ -640,7 +700,11 @@ listOwnedAndPopulateAuthorAndTags = function () {
|
||||||
where: {
|
where: {
|
||||||
remote: false
|
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)
|
return Video.findAll(query)
|
||||||
|
@ -652,6 +716,9 @@ listOwnedByAuthor = function (author: string) {
|
||||||
remote: false
|
remote: false
|
||||||
},
|
},
|
||||||
include: [
|
include: [
|
||||||
|
{
|
||||||
|
model: Video['sequelize'].models.VideoFile
|
||||||
|
},
|
||||||
{
|
{
|
||||||
model: Video['sequelize'].models.Author,
|
model: Video['sequelize'].models.Author,
|
||||||
where: {
|
where: {
|
||||||
|
@ -672,14 +739,15 @@ loadByUUID = function (uuid: string) {
|
||||||
const query = {
|
const query = {
|
||||||
where: {
|
where: {
|
||||||
uuid
|
uuid
|
||||||
}
|
},
|
||||||
|
include: [ Video['sequelize'].models.VideoFile ]
|
||||||
}
|
}
|
||||||
return Video.findOne(query)
|
return Video.findOne(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
loadAndPopulateAuthor = function (id: number) {
|
loadAndPopulateAuthor = function (id: number) {
|
||||||
const options = {
|
const options = {
|
||||||
include: [ Video['sequelize'].models.Author ]
|
include: [ Video['sequelize'].models.VideoFile, Video['sequelize'].models.Author ]
|
||||||
}
|
}
|
||||||
|
|
||||||
return Video.findById(id, options)
|
return Video.findById(id, options)
|
||||||
|
@ -692,7 +760,8 @@ loadAndPopulateAuthorAndPodAndTags = function (id: number) {
|
||||||
model: Video['sequelize'].models.Author,
|
model: Video['sequelize'].models.Author,
|
||||||
include: [ { model: Video['sequelize'].models.Pod, required: false } ]
|
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,
|
model: Video['sequelize'].models.Author,
|
||||||
include: [ { model: Video['sequelize'].models.Pod, required: false } ]
|
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
|
model: Video['sequelize'].models.Tag
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const videoFileInclude: Sequelize.IncludeOptions = {
|
||||||
|
model: Video['sequelize'].models.VideoFile
|
||||||
|
}
|
||||||
|
|
||||||
const query: Sequelize.FindOptions = {
|
const query: Sequelize.FindOptions = {
|
||||||
distinct: true,
|
distinct: true,
|
||||||
where: createBaseVideosWhere(),
|
where: createBaseVideosWhere(),
|
||||||
|
@ -743,8 +817,9 @@ searchAndPopulateAuthorAndPodAndTags = function (value: string, field: string, s
|
||||||
|
|
||||||
// Make an exact search with the magnet
|
// Make an exact search with the magnet
|
||||||
if (field === 'magnetUri') {
|
if (field === 'magnetUri') {
|
||||||
const infoHash = magnetUtil.decode(value).infoHash
|
videoFileInclude.where = {
|
||||||
query.where['infoHash'] = infoHash
|
infoHash: magnetUtil.decode(value).infoHash
|
||||||
|
}
|
||||||
} else if (field === 'tags') {
|
} else if (field === 'tags') {
|
||||||
const escapedValue = Video['sequelize'].escape('%' + value + '%')
|
const escapedValue = Video['sequelize'].escape('%' + value + '%')
|
||||||
query.where['id'].$in = Video['sequelize'].literal(
|
query.where['id'].$in = Video['sequelize'].literal(
|
||||||
|
@ -777,7 +852,7 @@ searchAndPopulateAuthorAndPodAndTags = function (value: string, field: string, s
|
||||||
}
|
}
|
||||||
|
|
||||||
query.include = [
|
query.include = [
|
||||||
authorInclude, tagInclude
|
authorInclude, tagInclude, videoFileInclude
|
||||||
]
|
]
|
||||||
|
|
||||||
return Video.findAndCountAll(query).then(({ rows, count }) => {
|
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) {
|
function generateImage (video: VideoInstance, videoPath: string, folder: string, imageName: string, size: string) {
|
||||||
const options = {
|
const options = {
|
||||||
filename: imageName,
|
filename: imageName,
|
||||||
|
@ -868,16 +893,3 @@ function generateImage (video: VideoInstance, videoPath: string, folder: string,
|
||||||
.thumbnail(options)
|
.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.nsfw).to.be.ok
|
||||||
expect(video.description).to.equal('my super description for pod 1')
|
expect(video.description).to.equal('my super description for pod 1')
|
||||||
expect(video.podHost).to.equal('localhost:9001')
|
expect(video.podHost).to.equal('localhost:9001')
|
||||||
expect(video.magnetUri).to.exist
|
|
||||||
expect(video.duration).to.equal(10)
|
expect(video.duration).to.equal(10)
|
||||||
expect(video.tags).to.deep.equal([ 'tag1p1', 'tag2p1' ])
|
expect(video.tags).to.deep.equal([ 'tag1p1', 'tag2p1' ])
|
||||||
expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
|
expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
|
||||||
expect(miscsUtils.dateIsValid(video.updatedAt)).to.be.true
|
expect(miscsUtils.dateIsValid(video.updatedAt)).to.be.true
|
||||||
expect(video.author).to.equal('root')
|
expect(video.author).to.equal('root')
|
||||||
|
|
||||||
|
expect(video.files).to.have.lengthOf(1)
|
||||||
|
|
||||||
|
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') {
|
if (server.url !== 'http://localhost:9001') {
|
||||||
expect(video.isLocal).to.be.false
|
expect(video.isLocal).to.be.false
|
||||||
} else {
|
} else {
|
||||||
|
@ -136,9 +144,9 @@ describe('Test multiple pods', function () {
|
||||||
|
|
||||||
// All pods should have the same magnet Uri
|
// All pods should have the same magnet Uri
|
||||||
if (baseMagnet === null) {
|
if (baseMagnet === null) {
|
||||||
baseMagnet = video.magnetUri
|
baseMagnet = magnetUri
|
||||||
} else {
|
} else {
|
||||||
expect(video.magnetUri).to.equal.magnetUri
|
expect(baseMagnet).to.equal(magnetUri)
|
||||||
}
|
}
|
||||||
|
|
||||||
videosUtils.testVideoImage(server.url, 'video_short1.webm', video.thumbnailPath, function (err, test) {
|
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.nsfw).to.be.true
|
||||||
expect(video.description).to.equal('my super description for pod 2')
|
expect(video.description).to.equal('my super description for pod 2')
|
||||||
expect(video.podHost).to.equal('localhost:9002')
|
expect(video.podHost).to.equal('localhost:9002')
|
||||||
expect(video.magnetUri).to.exist
|
|
||||||
expect(video.duration).to.equal(5)
|
expect(video.duration).to.equal(5)
|
||||||
expect(video.tags).to.deep.equal([ 'tag1p2', 'tag2p2', 'tag3p2' ])
|
expect(video.tags).to.deep.equal([ 'tag1p2', 'tag2p2', 'tag3p2' ])
|
||||||
expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
|
expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
|
||||||
expect(miscsUtils.dateIsValid(video.updatedAt)).to.be.true
|
expect(miscsUtils.dateIsValid(video.updatedAt)).to.be.true
|
||||||
expect(video.author).to.equal('root')
|
expect(video.author).to.equal('root')
|
||||||
|
|
||||||
|
expect(video.files).to.have.lengthOf(1)
|
||||||
|
|
||||||
|
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') {
|
if (server.url !== 'http://localhost:9002') {
|
||||||
expect(video.isLocal).to.be.false
|
expect(video.isLocal).to.be.false
|
||||||
} else {
|
} else {
|
||||||
|
@ -213,9 +229,9 @@ describe('Test multiple pods', function () {
|
||||||
|
|
||||||
// All pods should have the same magnet Uri
|
// All pods should have the same magnet Uri
|
||||||
if (baseMagnet === null) {
|
if (baseMagnet === null) {
|
||||||
baseMagnet = video.magnetUri
|
baseMagnet = magnetUri
|
||||||
} else {
|
} else {
|
||||||
expect(video.magnetUri).to.equal.magnetUri
|
expect(baseMagnet).to.equal(magnetUri)
|
||||||
}
|
}
|
||||||
|
|
||||||
videosUtils.testVideoImage(server.url, 'video_short2.webm', video.thumbnailPath, function (err, test) {
|
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.nsfw).to.be.ok
|
||||||
expect(video1.description).to.equal('my super description for pod 3')
|
expect(video1.description).to.equal('my super description for pod 3')
|
||||||
expect(video1.podHost).to.equal('localhost:9003')
|
expect(video1.podHost).to.equal('localhost:9003')
|
||||||
expect(video1.magnetUri).to.exist
|
|
||||||
expect(video1.duration).to.equal(5)
|
expect(video1.duration).to.equal(5)
|
||||||
expect(video1.tags).to.deep.equal([ 'tag1p3' ])
|
expect(video1.tags).to.deep.equal([ 'tag1p3' ])
|
||||||
expect(video1.author).to.equal('root')
|
expect(video1.author).to.equal('root')
|
||||||
expect(miscsUtils.dateIsValid(video1.createdAt)).to.be.true
|
expect(miscsUtils.dateIsValid(video1.createdAt)).to.be.true
|
||||||
expect(miscsUtils.dateIsValid(video1.updatedAt)).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.name).to.equal('my super name for pod 3-2')
|
||||||
expect(video2.category).to.equal(7)
|
expect(video2.category).to.equal(7)
|
||||||
expect(video2.categoryLabel).to.equal('Gaming')
|
expect(video2.categoryLabel).to.equal('Gaming')
|
||||||
|
@ -314,13 +338,21 @@ describe('Test multiple pods', function () {
|
||||||
expect(video2.nsfw).to.be.false
|
expect(video2.nsfw).to.be.false
|
||||||
expect(video2.description).to.equal('my super description for pod 3-2')
|
expect(video2.description).to.equal('my super description for pod 3-2')
|
||||||
expect(video2.podHost).to.equal('localhost:9003')
|
expect(video2.podHost).to.equal('localhost:9003')
|
||||||
expect(video2.magnetUri).to.exist
|
|
||||||
expect(video2.duration).to.equal(5)
|
expect(video2.duration).to.equal(5)
|
||||||
expect(video2.tags).to.deep.equal([ 'tag2p3', 'tag3p3', 'tag4p3' ])
|
expect(video2.tags).to.deep.equal([ 'tag2p3', 'tag3p3', 'tag4p3' ])
|
||||||
expect(video2.author).to.equal('root')
|
expect(video2.author).to.equal('root')
|
||||||
expect(miscsUtils.dateIsValid(video2.createdAt)).to.be.true
|
expect(miscsUtils.dateIsValid(video2.createdAt)).to.be.true
|
||||||
expect(miscsUtils.dateIsValid(video2.updatedAt)).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') {
|
if (server.url !== 'http://localhost:9003') {
|
||||||
expect(video1.isLocal).to.be.false
|
expect(video1.isLocal).to.be.false
|
||||||
expect(video2.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
|
// All pods should have the same magnet Uri
|
||||||
if (baseMagnet === null) {
|
if (baseMagnet === null) {
|
||||||
baseMagnet = video2.magnetUri
|
baseMagnet = magnetUri2
|
||||||
} else {
|
} else {
|
||||||
expect(video2.magnetUri).to.equal.magnetUri
|
expect(baseMagnet).to.equal(magnetUri2)
|
||||||
}
|
}
|
||||||
|
|
||||||
videosUtils.testVideoImage(server.url, 'video_short3.webm', video1.thumbnailPath, function (err, test) {
|
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[2])
|
||||||
toRemove.push(res.body.data[3])
|
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).to.exist
|
||||||
expect(torrent.files.length).to.equal(1)
|
expect(torrent.files.length).to.equal(1)
|
||||||
expect(torrent.files[0].path).to.exist.and.to.not.equal('')
|
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]
|
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).to.exist
|
||||||
expect(torrent.files.length).to.equal(1)
|
expect(torrent.files.length).to.equal(1)
|
||||||
expect(torrent.files[0].path).to.exist.and.to.not.equal('')
|
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]
|
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).to.exist
|
||||||
expect(torrent.files.length).to.equal(1)
|
expect(torrent.files.length).to.equal(1)
|
||||||
expect(torrent.files[0].path).to.exist.and.to.not.equal('')
|
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]
|
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).to.exist
|
||||||
expect(torrent.files.length).to.equal(1)
|
expect(torrent.files.length).to.equal(1)
|
||||||
expect(torrent.files[0].path).to.exist.and.to.not.equal('')
|
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(videoUpdated.tags).to.deep.equal([ 'tagup1', 'tagup2' ])
|
||||||
expect(miscsUtils.dateIsValid(videoUpdated.updatedAt, 20000)).to.be.true
|
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) {
|
videosUtils.testVideoImage(server.url, 'video_short3.webm', videoUpdated.thumbnailPath, function (err, test) {
|
||||||
if (err) throw err
|
if (err) throw err
|
||||||
expect(test).to.equal(true)
|
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).to.exist
|
||||||
expect(torrent.files.length).to.equal(1)
|
expect(torrent.files.length).to.equal(1)
|
||||||
expect(torrent.files[0].path).to.exist.and.to.not.equal('')
|
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.nsfw).to.be.ok
|
||||||
expect(video.description).to.equal('my super description')
|
expect(video.description).to.equal('my super description')
|
||||||
expect(video.podHost).to.equal('localhost:9001')
|
expect(video.podHost).to.equal('localhost:9001')
|
||||||
expect(video.magnetUri).to.exist
|
|
||||||
expect(video.author).to.equal('root')
|
expect(video.author).to.equal('root')
|
||||||
expect(video.isLocal).to.be.true
|
expect(video.isLocal).to.be.true
|
||||||
expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
|
expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
|
||||||
expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
|
expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
|
||||||
expect(miscsUtils.dateIsValid(video.updatedAt)).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) {
|
videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) {
|
||||||
if (err) throw err
|
if (err) throw err
|
||||||
expect(test).to.equal(true)
|
expect(test).to.equal(true)
|
||||||
|
@ -143,7 +151,7 @@ describe('Test a single pod', function () {
|
||||||
videoId = video.id
|
videoId = video.id
|
||||||
videoUUID = video.uuid
|
videoUUID = video.uuid
|
||||||
|
|
||||||
webtorrent.add(video.magnetUri, function (torrent) {
|
webtorrent.add(magnetUri, function (torrent) {
|
||||||
expect(torrent.files).to.exist
|
expect(torrent.files).to.exist
|
||||||
expect(torrent.files.length).to.equal(1)
|
expect(torrent.files.length).to.equal(1)
|
||||||
expect(torrent.files[0].path).to.exist.and.to.not.equal('')
|
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.nsfw).to.be.ok
|
||||||
expect(video.description).to.equal('my super description')
|
expect(video.description).to.equal('my super description')
|
||||||
expect(video.podHost).to.equal('localhost:9001')
|
expect(video.podHost).to.equal('localhost:9001')
|
||||||
expect(video.magnetUri).to.exist
|
|
||||||
expect(video.author).to.equal('root')
|
expect(video.author).to.equal('root')
|
||||||
expect(video.isLocal).to.be.true
|
expect(video.isLocal).to.be.true
|
||||||
expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
|
expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
|
||||||
expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
|
expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
|
||||||
expect(miscsUtils.dateIsValid(video.updatedAt)).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) {
|
videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) {
|
||||||
if (err) throw err
|
if (err) throw err
|
||||||
expect(test).to.equal(true)
|
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.createdAt)).to.be.true
|
||||||
expect(miscsUtils.dateIsValid(video.updatedAt)).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) {
|
videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) {
|
||||||
if (err) throw err
|
if (err) throw err
|
||||||
expect(test).to.equal(true)
|
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.createdAt)).to.be.true
|
||||||
expect(miscsUtils.dateIsValid(video.updatedAt)).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) {
|
videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) {
|
||||||
if (err) throw err
|
if (err) throw err
|
||||||
expect(test).to.equal(true)
|
expect(test).to.equal(true)
|
||||||
|
@ -564,7 +598,7 @@ describe('Test a single pod', function () {
|
||||||
|
|
||||||
it('Should search the right magnetUri video', function (done) {
|
it('Should search the right magnetUri video', function (done) {
|
||||||
const video = videosListBase[0]
|
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
|
if (err) throw err
|
||||||
|
|
||||||
const videos = res.body.data
|
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.createdAt)).to.be.true
|
||||||
expect(miscsUtils.dateIsValid(video.updatedAt)).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) {
|
videosUtils.testVideoImage(server.url, 'video_short3.webm', video.thumbnailPath, function (err, test) {
|
||||||
if (err) throw err
|
if (err) throw err
|
||||||
expect(test).to.equal(true)
|
expect(test).to.equal(true)
|
||||||
|
|
||||||
webtorrent.add(video.magnetUri, function (torrent) {
|
webtorrent.add(magnetUri, function (torrent) {
|
||||||
expect(torrent.files).to.exist
|
expect(torrent.files).to.exist
|
||||||
expect(torrent.files.length).to.equal(1)
|
expect(torrent.files.length).to.equal(1)
|
||||||
expect(torrent.files[0].path).to.exist.and.to.not.equal('')
|
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.createdAt)).to.be.true
|
||||||
expect(miscsUtils.dateIsValid(video.updatedAt)).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()
|
done()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -728,6 +780,15 @@ describe('Test a single pod', function () {
|
||||||
expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
|
expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
|
||||||
expect(miscsUtils.dateIsValid(video.updatedAt)).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()
|
done()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -56,9 +56,10 @@ describe('Test video transcoding', function () {
|
||||||
if (err) throw err
|
if (err) throw err
|
||||||
|
|
||||||
const video = res.body.data[0]
|
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).to.exist
|
||||||
expect(torrent.files.length).to.equal(1)
|
expect(torrent.files.length).to.equal(1)
|
||||||
expect(torrent.files[0].path).match(/\.webm$/)
|
expect(torrent.files[0].path).match(/\.webm$/)
|
||||||
|
@ -86,9 +87,10 @@ describe('Test video transcoding', function () {
|
||||||
if (err) throw err
|
if (err) throw err
|
||||||
|
|
||||||
const video = res.body.data[0]
|
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).to.exist
|
||||||
expect(torrent.files.length).to.equal(1)
|
expect(torrent.files.length).to.equal(1)
|
||||||
expect(torrent.files[0].path).match(/\.mp4$/)
|
expect(torrent.files[0].path).match(/\.mp4$/)
|
||||||
|
|
|
@ -5,8 +5,6 @@ export interface RemoteVideoCreateData {
|
||||||
author: string
|
author: string
|
||||||
tags: string[]
|
tags: string[]
|
||||||
name: string
|
name: string
|
||||||
extname: string
|
|
||||||
infoHash: string
|
|
||||||
category: number
|
category: number
|
||||||
licence: number
|
licence: number
|
||||||
language: number
|
language: number
|
||||||
|
@ -19,6 +17,12 @@ export interface RemoteVideoCreateData {
|
||||||
likes: number
|
likes: number
|
||||||
dislikes: number
|
dislikes: number
|
||||||
thumbnailData: string
|
thumbnailData: string
|
||||||
|
files: {
|
||||||
|
infoHash: string
|
||||||
|
extname: string
|
||||||
|
resolution: number
|
||||||
|
size: number
|
||||||
|
}[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RemoteVideoCreateRequest extends RemoteVideoRequest {
|
export interface RemoteVideoCreateRequest extends RemoteVideoRequest {
|
||||||
|
|
|
@ -15,6 +15,12 @@ export interface RemoteVideoUpdateData {
|
||||||
views: number
|
views: number
|
||||||
likes: number
|
likes: number
|
||||||
dislikes: number
|
dislikes: number
|
||||||
|
files: {
|
||||||
|
infoHash: string
|
||||||
|
extname: string
|
||||||
|
resolution: number
|
||||||
|
size: number
|
||||||
|
}[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RemoteVideoUpdateRequest {
|
export interface RemoteVideoUpdateRequest {
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
export interface VideoFile {
|
||||||
|
magnetUri: string
|
||||||
|
resolution: number
|
||||||
|
resolutionLabel: string
|
||||||
|
size: number // Bytes
|
||||||
|
}
|
||||||
|
|
||||||
export interface Video {
|
export interface Video {
|
||||||
id: number
|
id: number
|
||||||
uuid: string
|
uuid: string
|
||||||
|
@ -12,7 +19,6 @@ export interface Video {
|
||||||
description: string
|
description: string
|
||||||
duration: number
|
duration: number
|
||||||
isLocal: boolean
|
isLocal: boolean
|
||||||
magnetUri: string
|
|
||||||
name: string
|
name: string
|
||||||
podHost: string
|
podHost: string
|
||||||
tags: string[]
|
tags: string[]
|
||||||
|
@ -22,4 +28,5 @@ export interface Video {
|
||||||
likes: number
|
likes: number
|
||||||
dislikes: number
|
dislikes: number
|
||||||
nsfw: boolean
|
nsfw: boolean
|
||||||
|
files: VideoFile[]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue