-import safeBuffer = require('safe-buffer')
+import * as safeBuffer from 'safe-buffer'
const Buffer = safeBuffer.Buffer
-import createTorrent = require('create-torrent')
-import ffmpeg = require('fluent-ffmpeg')
-import fs = require('fs')
-import magnetUtil = require('magnet-uri')
+import * as createTorrent from 'create-torrent'
+import * as ffmpeg from 'fluent-ffmpeg'
+import * as fs from 'fs'
+import * as magnetUtil from 'magnet-uri'
import { map, values } from 'lodash'
import { parallel, series } from 'async'
-import parseTorrent = require('parse-torrent')
+import * as parseTorrent from 'parse-torrent'
import { join } from 'path'
+import * as Sequelize from 'sequelize'
-const db = require('../initializers/database')
+import { database as db } from '../initializers/database'
+import { VideoTagInstance } from './video-tag-interface'
import {
logger,
isVideoNameValid,
THUMBNAILS_SIZE
} from '../initializers'
import { JobScheduler, removeVideoToFriends } from '../lib'
-import { getSort } from './utils'
-// ---------------------------------------------------------------------------
-
-module.exports = function (sequelize, DataTypes) {
- const Video = sequelize.define('Video',
+import { addMethodsToModel, getSort } from './utils'
+import {
+ VideoClass,
+ VideoInstance,
+ VideoAttributes,
+
+ VideoMethods
+} from './video-interface'
+
+let Video: Sequelize.Model<VideoInstance, VideoAttributes>
+let generateMagnetUri: VideoMethods.GenerateMagnetUri
+let getVideoFilename: VideoMethods.GetVideoFilename
+let getThumbnailName: VideoMethods.GetThumbnailName
+let getPreviewName: VideoMethods.GetPreviewName
+let getTorrentName: VideoMethods.GetTorrentName
+let isOwned: VideoMethods.IsOwned
+let toFormatedJSON: VideoMethods.ToFormatedJSON
+let toAddRemoteJSON: VideoMethods.ToAddRemoteJSON
+let toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON
+let transcodeVideofile: VideoMethods.TranscodeVideofile
+
+let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData
+let getDurationFromFile: VideoMethods.GetDurationFromFile
+let list: VideoMethods.List
+let listForApi: VideoMethods.ListForApi
+let loadByHostAndRemoteId: VideoMethods.LoadByHostAndRemoteId
+let listOwnedAndPopulateAuthorAndTags: VideoMethods.ListOwnedAndPopulateAuthorAndTags
+let listOwnedByAuthor: VideoMethods.ListOwnedByAuthor
+let load: VideoMethods.Load
+let loadAndPopulateAuthor: VideoMethods.LoadAndPopulateAuthor
+let loadAndPopulateAuthorAndPodAndTags: VideoMethods.LoadAndPopulateAuthorAndPodAndTags
+let searchAndPopulateAuthorAndPodAndTags: VideoMethods.SearchAndPopulateAuthorAndPodAndTags
+
+export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
+ Video = sequelize.define<VideoInstance, VideoAttributes>('Video',
{
id: {
type: DataTypes.UUID,
fields: [ 'likes' ]
}
],
- classMethods: {
- associate,
-
- generateThumbnailFromData,
- getDurationFromFile,
- list,
- listForApi,
- listOwnedAndPopulateAuthorAndTags,
- listOwnedByAuthor,
- load,
- loadByHostAndRemoteId,
- loadAndPopulateAuthor,
- loadAndPopulateAuthorAndPodAndTags,
- searchAndPopulateAuthorAndPodAndTags
- },
- instanceMethods: {
- generateMagnetUri,
- getVideoFilename,
- getThumbnailName,
- getPreviewName,
- getTorrentName,
- isOwned,
- toFormatedJSON,
- toAddRemoteJSON,
- toUpdateRemoteJSON,
- transcodeVideofile,
- removeFromBlacklist
- },
hooks: {
beforeValidate,
beforeCreate,
}
)
+ const classMethods = [
+ associate,
+
+ generateThumbnailFromData,
+ getDurationFromFile,
+ list,
+ listForApi,
+ listOwnedAndPopulateAuthorAndTags,
+ listOwnedByAuthor,
+ load,
+ loadByHostAndRemoteId,
+ loadAndPopulateAuthor,
+ loadAndPopulateAuthorAndPodAndTags,
+ searchAndPopulateAuthorAndPodAndTags
+ ]
+ const instanceMethods = [
+ generateMagnetUri,
+ getVideoFilename,
+ getThumbnailName,
+ getPreviewName,
+ getTorrentName,
+ isOwned,
+ toFormatedJSON,
+ toAddRemoteJSON,
+ toUpdateRemoteJSON,
+ transcodeVideofile,
+ removeFromBlacklist
+ ]
+ addMethodsToModel(Video, classMethods, instanceMethods)
+
return Video
}
-function beforeValidate (video, options, next) {
+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'
}
-
- return next(null)
}
-function beforeCreate (video, options, next) {
- const tasks = []
-
- if (video.isOwned()) {
- const videoPath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename())
-
- tasks.push(
- function createVideoTorrent (callback) {
- createTorrentFromVideo(video, videoPath, callback)
- },
-
- function createVideoThumbnail (callback) {
- createThumbnail(video, videoPath, callback)
- },
+function beforeCreate (video: VideoInstance, options: { transaction: Sequelize.Transaction }) {
+ return new Promise(function (resolve, reject) {
+ const tasks = []
- function createVideoPreview (callback) {
- createPreview(video, videoPath, callback)
- }
- )
+ if (video.isOwned()) {
+ const videoPath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename())
- if (CONFIG.TRANSCODING.ENABLED === true) {
tasks.push(
- function createVideoTranscoderJob (callback) {
- const dataInput = {
- id: video.id
- }
+ function createVideoTorrent (callback) {
+ createTorrentFromVideo(video, videoPath, callback)
+ },
+
+ function createVideoThumbnail (callback) {
+ createThumbnail(video, videoPath, callback)
+ },
- JobScheduler.Instance.createJob(options.transaction, 'videoTranscoder', dataInput, callback)
+ function createVideoPreview (callback) {
+ createPreview(video, videoPath, callback)
}
)
- }
- return parallel(tasks, next)
- }
+ if (CONFIG.TRANSCODING.ENABLED === true) {
+ tasks.push(
+ function createVideoTranscoderJob (callback) {
+ const dataInput = {
+ id: video.id
+ }
- return next()
-}
+ JobScheduler.Instance.createJob(options.transaction, 'videoTranscoder', dataInput, callback)
+ }
+ )
+ }
-function afterDestroy (video, options, next) {
- const tasks = []
+ return parallel(tasks, function (err) {
+ if (err) return reject(err)
- tasks.push(
- function (callback) {
- removeThumbnail(video, callback)
+ return resolve()
+ })
}
- )
- if (video.isOwned()) {
+ return resolve()
+ })
+}
+
+function afterDestroy (video: VideoInstance) {
+ return new Promise(function (resolve, reject) {
+ const tasks = []
+
tasks.push(
- function removeVideoFile (callback) {
- removeFile(video, callback)
- },
+ function (callback) {
+ removeThumbnail(video, callback)
+ }
+ )
- function removeVideoTorrent (callback) {
- removeTorrent(video, callback)
- },
+ if (video.isOwned()) {
+ tasks.push(
+ function removeVideoFile (callback) {
+ removeFile(video, callback)
+ },
- function removeVideoPreview (callback) {
- removePreview(video, callback)
- },
+ function removeVideoTorrent (callback) {
+ removeTorrent(video, callback)
+ },
- function removeVideoToFriends (callback) {
- const params = {
- remoteId: video.id
- }
+ function removeVideoPreview (callback) {
+ removePreview(video, callback)
+ },
- removeVideoToFriends(params)
+ function notifyFriends (callback) {
+ const params = {
+ remoteId: video.id
+ }
- return callback()
- }
- )
- }
+ removeVideoToFriends(params)
+
+ return callback()
+ }
+ )
+ }
- parallel(tasks, next)
+ parallel(tasks, function (err) {
+ if (err) return reject(err)
+
+ return resolve()
+ })
+ })
}
// ------------------------------ METHODS ------------------------------
function associate (models) {
- this.belongsTo(models.Author, {
+ Video.belongsTo(models.Author, {
foreignKey: {
name: 'authorId',
allowNull: false
onDelete: 'cascade'
})
- this.belongsToMany(models.Tag, {
+ Video.belongsToMany(models.Tag, {
foreignKey: 'videoId',
through: models.VideoTag,
onDelete: 'cascade'
})
- this.hasMany(models.VideoAbuse, {
+ Video.hasMany(models.VideoAbuse, {
foreignKey: {
name: 'videoId',
allowNull: false
})
}
-function generateMagnetUri () {
+generateMagnetUri = function () {
let baseUrlHttp
let baseUrlWs
}
const xs = baseUrlHttp + STATIC_PATHS.TORRENTS + this.getTorrentName()
- const announce = baseUrlWs + '/tracker/socket'
+ const announce = [ baseUrlWs + '/tracker/socket' ]
const urlList = [ baseUrlHttp + STATIC_PATHS.WEBSEED + this.getVideoFilename() ]
const magnetHash = {
return magnetUtil.encode(magnetHash)
}
-function getVideoFilename () {
+getVideoFilename = function () {
if (this.isOwned()) return this.id + this.extname
return this.remoteId + this.extname
}
-function getThumbnailName () {
+getThumbnailName = function () {
// We always have a copy of the thumbnail
return this.id + '.jpg'
}
-function getPreviewName () {
+getPreviewName = function () {
const extension = '.jpg'
if (this.isOwned()) return this.id + extension
return this.remoteId + extension
}
-function getTorrentName () {
+getTorrentName = function () {
const extension = '.torrent'
if (this.isOwned()) return this.id + extension
return this.remoteId + extension
}
-function isOwned () {
+isOwned = function () {
return this.remoteId === null
}
-function toFormatedJSON () {
+toFormatedJSON = function (this: VideoInstance) {
let podHost
if (this.Author.Pod) {
views: this.views,
likes: this.likes,
dislikes: this.dislikes,
- tags: map(this.Tags, 'name'),
+ tags: map<VideoTagInstance, string>(this.Tags, 'name'),
thumbnailPath: join(STATIC_PATHS.THUMBNAILS, this.getThumbnailName()),
createdAt: this.createdAt,
updatedAt: this.updatedAt
return json
}
-function toAddRemoteJSON (callback) {
- const self = this
-
+toAddRemoteJSON = function (callback: VideoMethods.ToAddRemoteJSONCallback) {
// Get thumbnail data to send to the other pod
const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName())
- fs.readFile(thumbnailPath, function (err, thumbnailData) {
+ fs.readFile(thumbnailPath, (err, thumbnailData) => {
if (err) {
logger.error('Cannot read the thumbnail of the video')
return callback(err)
}
const remoteVideo = {
- name: self.name,
- category: self.category,
- licence: self.licence,
- language: self.language,
- nsfw: self.nsfw,
- description: self.description,
- infoHash: self.infoHash,
- remoteId: self.id,
- author: self.Author.name,
- duration: self.duration,
+ name: this.name,
+ category: this.category,
+ licence: this.licence,
+ language: this.language,
+ nsfw: this.nsfw,
+ description: this.description,
+ infoHash: this.infoHash,
+ remoteId: this.id,
+ author: this.Author.name,
+ duration: this.duration,
thumbnailData: thumbnailData.toString('binary'),
- tags: map(self.Tags, 'name'),
- createdAt: self.createdAt,
- updatedAt: self.updatedAt,
- extname: self.extname,
- views: self.views,
- likes: self.likes,
- dislikes: self.dislikes
+ tags: map<VideoTagInstance, string>(this.Tags, 'name'),
+ createdAt: this.createdAt,
+ updatedAt: this.updatedAt,
+ extname: this.extname,
+ views: this.views,
+ likes: this.likes,
+ dislikes: this.dislikes
}
return callback(null, remoteVideo)
})
}
-function toUpdateRemoteJSON (callback) {
+toUpdateRemoteJSON = function () {
const json = {
name: this.name,
category: this.category,
remoteId: this.id,
author: this.Author.name,
duration: this.duration,
- tags: map(this.Tags, 'name'),
+ tags: map<VideoTagInstance, string>(this.Tags, 'name'),
createdAt: this.createdAt,
updatedAt: this.updatedAt,
extname: this.extname,
return json
}
-function transcodeVideofile (finalCallback) {
+transcodeVideofile = function (finalCallback: VideoMethods.TranscodeVideofileCallback) {
const video = this
const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
video.save().asCallback(callback)
}
- ], function (err) {
+ ], function (err: Error) {
if (err) {
- // Autodescruction...
+ // Autodesctruction...
video.destroy().asCallback(function (err) {
if (err) logger.error('Cannot destruct video after transcoding failure.', { error: err })
})
// ------------------------------ STATICS ------------------------------
-function generateThumbnailFromData (video, thumbnailData, callback) {
+generateThumbnailFromData = function (video: VideoInstance, thumbnailData: string, callback: VideoMethods.GenerateThumbnailFromDataCallback) {
// Creating the thumbnail for a remote video
const thumbnailName = video.getThumbnailName()
})
}
-function getDurationFromFile (videoPath, callback) {
+getDurationFromFile = function (videoPath: string, callback: VideoMethods.GetDurationFromFileCallback) {
ffmpeg.ffprobe(videoPath, function (err, metadata) {
if (err) return callback(err)
})
}
-function list (callback) {
- return this.findAll().asCallback(callback)
+list = function (callback: VideoMethods.ListCallback) {
+ return Video.findAll().asCallback(callback)
}
-function listForApi (start, count, sort, callback) {
+listForApi = function (start: number, count: number, sort: string, callback: VideoMethods.ListForApiCallback) {
// Exclude Blakclisted videos from the list
const query = {
+ distinct: true,
offset: start,
limit: count,
- distinct: true, // For the count, a video can have many tags
- order: [ getSort(sort), [ this.sequelize.models.Tag, 'name', 'ASC' ] ],
+ order: [ getSort(sort), [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ],
include: [
{
- model: this.sequelize.models.Author,
- include: [ { model: this.sequelize.models.Pod, required: false } ]
+ model: Video['sequelize'].models.Author,
+ include: [ { model: Video['sequelize'].models.Pod, required: false } ]
},
- this.sequelize.models.Tag
+ Video['sequelize'].models.Tag
],
- where: createBaseVideosWhere.call(this)
+ where: createBaseVideosWhere()
}
- return this.findAndCountAll(query).asCallback(function (err, result) {
+ return Video.findAndCountAll(query).asCallback(function (err, result) {
if (err) return callback(err)
return callback(null, result.rows, result.count)
})
}
-function loadByHostAndRemoteId (fromHost, remoteId, callback) {
+loadByHostAndRemoteId = function (fromHost: string, remoteId: string, callback: VideoMethods.LoadByHostAndRemoteIdCallback) {
const query = {
where: {
remoteId: remoteId
},
include: [
{
- model: this.sequelize.models.Author,
+ model: Video['sequelize'].models.Author,
include: [
{
- model: this.sequelize.models.Pod,
+ model: Video['sequelize'].models.Pod,
required: true,
where: {
host: fromHost
]
}
- return this.findOne(query).asCallback(callback)
+ return Video.findOne(query).asCallback(callback)
}
-function listOwnedAndPopulateAuthorAndTags (callback) {
+listOwnedAndPopulateAuthorAndTags = function (callback: VideoMethods.ListOwnedAndPopulateAuthorAndTagsCallback) {
// If remoteId is null this is *our* video
const query = {
where: {
remoteId: null
},
- include: [ this.sequelize.models.Author, this.sequelize.models.Tag ]
+ include: [ Video['sequelize'].models.Author, Video['sequelize'].models.Tag ]
}
- return this.findAll(query).asCallback(callback)
+ return Video.findAll(query).asCallback(callback)
}
-function listOwnedByAuthor (author, callback) {
+listOwnedByAuthor = function (author: string, callback: VideoMethods.ListOwnedByAuthorCallback) {
const query = {
where: {
remoteId: null
},
include: [
{
- model: this.sequelize.models.Author,
+ model: Video['sequelize'].models.Author,
where: {
name: author
}
]
}
- return this.findAll(query).asCallback(callback)
+ return Video.findAll(query).asCallback(callback)
}
-function load (id, callback) {
- return this.findById(id).asCallback(callback)
+load = function (id: string, callback: VideoMethods.LoadCallback) {
+ return Video.findById(id).asCallback(callback)
}
-function loadAndPopulateAuthor (id, callback) {
+loadAndPopulateAuthor = function (id: string, callback: VideoMethods.LoadAndPopulateAuthorCallback) {
const options = {
- include: [ this.sequelize.models.Author ]
+ include: [ Video['sequelize'].models.Author ]
}
- return this.findById(id, options).asCallback(callback)
+ return Video.findById(id, options).asCallback(callback)
}
-function loadAndPopulateAuthorAndPodAndTags (id, callback) {
+loadAndPopulateAuthorAndPodAndTags = function (id: string, callback: VideoMethods.LoadAndPopulateAuthorAndPodAndTagsCallback) {
const options = {
include: [
{
- model: this.sequelize.models.Author,
- include: [ { model: this.sequelize.models.Pod, required: false } ]
+ model: Video['sequelize'].models.Author,
+ include: [ { model: Video['sequelize'].models.Pod, required: false } ]
},
- this.sequelize.models.Tag
+ Video['sequelize'].models.Tag
]
}
- return this.findById(id, options).asCallback(callback)
+ return Video.findById(id, options).asCallback(callback)
}
-function searchAndPopulateAuthorAndPodAndTags (value, field, start, count, sort, callback) {
+searchAndPopulateAuthorAndPodAndTags = function (
+ value: string,
+ field: string,
+ start: number,
+ count: number,
+ sort: string,
+ callback: VideoMethods.SearchAndPopulateAuthorAndPodAndTagsCallback
+) {
const podInclude: any = {
- model: this.sequelize.models.Pod,
+ model: Video['sequelize'].models.Pod,
required: false
}
const authorInclude: any = {
- model: this.sequelize.models.Author,
+ model: Video['sequelize'].models.Author,
include: [
podInclude
]
}
const tagInclude: any = {
- model: this.sequelize.models.Tag
+ model: Video['sequelize'].models.Tag
}
const query: any = {
- where: createBaseVideosWhere.call(this),
+ distinct: true,
+ where: createBaseVideosWhere(),
offset: start,
limit: count,
- distinct: true, // For the count, a video can have many tags
- order: [ getSort(sort), [ this.sequelize.models.Tag, 'name', 'ASC' ] ]
+ order: [ getSort(sort), [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ]
}
// Make an exact search with the magnet
const infoHash = magnetUtil.decode(value).infoHash
query.where.infoHash = infoHash
} else if (field === 'tags') {
- const escapedValue = this.sequelize.escape('%' + value + '%')
- query.where.id.$in = this.sequelize.literal(
+ const escapedValue = Video['sequelize'].escape('%' + value + '%')
+ query.where.id.$in = Video['sequelize'].literal(
'(SELECT "VideoTags"."videoId" FROM "Tags" INNER JOIN "VideoTags" ON "Tags"."id" = "VideoTags"."tagId" WHERE name LIKE ' + escapedValue + ')'
)
} else if (field === 'host') {
]
if (tagInclude.where) {
- // query.include.push([ this.sequelize.models.Tag ])
+ // query.include.push([ Video['sequelize'].models.Tag ])
}
- return this.findAndCountAll(query).asCallback(function (err, result) {
+ return Video.findAndCountAll(query).asCallback(function (err, result) {
if (err) return callback(err)
return callback(null, result.rows, result.count)
function createBaseVideosWhere () {
return {
id: {
- $notIn: this.sequelize.literal(
+ $notIn: Video['sequelize'].literal(
'(SELECT "BlacklistedVideos"."videoId" FROM "BlacklistedVideos")'
)
}
}
}
-function removeThumbnail (video, callback) {
+function removeThumbnail (video: VideoInstance, callback: (err: Error) => void) {
const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName())
fs.unlink(thumbnailPath, callback)
}
-function removeFile (video, callback) {
+function removeFile (video: VideoInstance, callback: (err: Error) => void) {
const filePath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename())
fs.unlink(filePath, callback)
}
-function removeTorrent (video, callback) {
+function removeTorrent (video: VideoInstance, callback: (err: Error) => void) {
const torrenPath = join(CONFIG.STORAGE.TORRENTS_DIR, video.getTorrentName())
fs.unlink(torrenPath, callback)
}
-function removePreview (video, callback) {
+function removePreview (video: VideoInstance, callback: (err: Error) => void) {
// Same name than video thumnail
fs.unlink(CONFIG.STORAGE.PREVIEWS_DIR + video.getPreviewName(), callback)
}
-function createTorrentFromVideo (video, videoPath, callback) {
+function createTorrentFromVideo (video: VideoInstance, videoPath: string, callback: (err: Error) => void) {
const options = {
announceList: [
[ CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT + '/tracker/socket' ]
})
}
-function createPreview (video, videoPath, callback) {
- generateImage(video, videoPath, CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName(), callback)
+function createPreview (video: VideoInstance, videoPath: string, callback: (err: Error) => void) {
+ generateImage(video, videoPath, CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName(), null, callback)
}
-function createThumbnail (video, videoPath, callback) {
+function createThumbnail (video: VideoInstance, videoPath: string, callback: (err: Error) => void) {
generateImage(video, videoPath, CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName(), THUMBNAILS_SIZE, callback)
}
-function generateImage (video, videoPath, folder, imageName, size, callback?) {
+type GenerateImageCallback = (err: Error, imageName: string) => void
+function generateImage (video: VideoInstance, videoPath: string, folder: string, imageName: string, size: string, callback?: GenerateImageCallback) {
const options: any = {
filename: imageName,
count: 1,
folder
}
- if (!callback) {
- callback = size
- } else {
+ if (size) {
options.size = size
}
.thumbnail(options)
}
-function removeFromBlacklist (video, callback) {
+function removeFromBlacklist (video: VideoInstance, callback: (err: Error) => void) {
// Find the blacklisted video
db.BlacklistedVideo.loadByVideoId(video.id, function (err, video) {
// If an error occured, stop here
video.destroy().asCallback(callback)
} else {
// If haven't found it, simply ignore it and do nothing
- return callback()
+ return callback(null)
}
})
}