]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - server/models/video.ts
Convert scripts to typescript
[github/Chocobozzz/PeerTube.git] / server / models / video.ts
index 1e29f13555022b8b43e2cf10d4707d2fe10f4dda..78119f51629a36373082806361c4ad7380157323 100644 (file)
@@ -1,15 +1,17 @@
-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,
@@ -32,12 +34,42 @@ import {
   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,
@@ -194,34 +226,6 @@ module.exports = function (sequelize, DataTypes) {
           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,
@@ -230,99 +234,139 @@ module.exports = function (sequelize, DataTypes) {
     }
   )
 
+  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
@@ -330,13 +374,13 @@ function associate (models) {
     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
@@ -345,7 +389,7 @@ function associate (models) {
   })
 }
 
-function generateMagnetUri () {
+generateMagnetUri = function () {
   let baseUrlHttp
   let baseUrlWs
 
@@ -358,7 +402,7 @@ function generateMagnetUri () {
   }
 
   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 = {
@@ -372,18 +416,18 @@ function generateMagnetUri () {
   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
@@ -391,7 +435,7 @@ function getPreviewName () {
   return this.remoteId + extension
 }
 
-function getTorrentName () {
+getTorrentName = function () {
   const extension = '.torrent'
 
   if (this.isOwned()) return this.id + extension
@@ -399,11 +443,11 @@ function getTorrentName () {
   return this.remoteId + extension
 }
 
-function isOwned () {
+isOwned = function () {
   return this.remoteId === null
 }
 
-function toFormatedJSON () {
+toFormatedJSON = function (this: VideoInstance) {
   let podHost
 
   if (this.Author.Pod) {
@@ -444,7 +488,7 @@ function toFormatedJSON () {
     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
@@ -453,43 +497,41 @@ function toFormatedJSON () {
   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,
@@ -501,7 +543,7 @@ function toUpdateRemoteJSON (callback) {
     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,
@@ -513,7 +555,7 @@ function toUpdateRemoteJSON (callback) {
   return json
 }
 
-function transcodeVideofile (finalCallback) {
+transcodeVideofile = function (finalCallback: VideoMethods.TranscodeVideofileCallback) {
   const video = this
 
   const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
@@ -550,9 +592,9 @@ function transcodeVideofile (finalCallback) {
           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 })
           })
@@ -568,7 +610,7 @@ function transcodeVideofile (finalCallback) {
 
 // ------------------------------ 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()
@@ -580,7 +622,7 @@ function generateThumbnailFromData (video, thumbnailData, callback) {
   })
 }
 
-function getDurationFromFile (videoPath, callback) {
+getDurationFromFile = function (videoPath: string, callback: VideoMethods.GetDurationFromFileCallback) {
   ffmpeg.ffprobe(videoPath, function (err, metadata) {
     if (err) return callback(err)
 
@@ -588,46 +630,46 @@ function getDurationFromFile (videoPath, callback) {
   })
 }
 
-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
@@ -638,29 +680,29 @@ function loadByHostAndRemoteId (fromHost, remoteId, callback) {
     ]
   }
 
-  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
         }
@@ -668,58 +710,65 @@ function listOwnedByAuthor (author, callback) {
     ]
   }
 
-  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
@@ -727,8 +776,8 @@ function searchAndPopulateAuthorAndPodAndTags (value, field, start, count, sort,
     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') {
@@ -758,10 +807,10 @@ function searchAndPopulateAuthorAndPodAndTags (value, field, start, count, sort,
   ]
 
   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)
@@ -773,34 +822,34 @@ function searchAndPopulateAuthorAndPodAndTags (value, field, start, count, sort,
 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' ]
@@ -824,24 +873,23 @@ function createTorrentFromVideo (video, videoPath, callback) {
   })
 }
 
-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
   }
 
@@ -853,7 +901,7 @@ function generateImage (video, videoPath, folder, imageName, size, callback?) {
     .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
@@ -867,7 +915,7 @@ function removeFromBlacklist (video, callback) {
       video.destroy().asCallback(callback)
     } else {
       // If haven't found it, simply ignore it and do nothing
-      return callback()
+      return callback(null)
     }
   })
 }