Refractoring and add thumbnails support (without tests)
[oweals/peertube.git] / server / lib / videos.js
1 'use strict'
2
3 const async = require('async')
4 const config = require('config')
5 const ffmpeg = require('fluent-ffmpeg')
6 const fs = require('fs')
7 const map = require('lodash/map')
8 const pathUtils = require('path')
9
10 const constants = require('../initializers/constants')
11 const logger = require('../helpers/logger')
12 const utils = require('../helpers/utils')
13 const Videos = require('../models/videos')
14 const webtorrent = require('../lib/webtorrent')
15
16 const uploadDir = pathUtils.join(__dirname, '..', '..', config.get('storage.uploads'))
17 const thumbnailsDir = pathUtils.join(__dirname, '..', '..', config.get('storage.thumbnails'))
18
19 const videos = {
20   createRemoteVideos: createRemoteVideos,
21   getVideoDuration: getVideoDuration,
22   getVideoState: getVideoState,
23   getVideoThumbnail: getVideoThumbnail,
24   removeVideosDataFromDisk: removeVideosDataFromDisk,
25   removeRemoteVideos: removeRemoteVideos,
26   seed: seed,
27   seedAllExisting: seedAllExisting
28 }
29
30 function createRemoteVideos (videos, callback) {
31   // Create the remote videos from the new pod
32   createRemoteVideoObjects(videos, function (err, remote_videos) {
33     if (err) return callback(err)
34
35     Videos.addRemotes(remote_videos, callback)
36   })
37 }
38
39 function getVideoDuration (video_path, callback) {
40   ffmpeg.ffprobe(video_path, function (err, metadata) {
41     if (err) return callback(err)
42
43     return callback(null, Math.floor(metadata.format.duration))
44   })
45 }
46
47 function getVideoState (video) {
48   const exist = (video !== null)
49   let owned = false
50   if (exist === true) {
51     owned = (video.namePath !== null)
52   }
53
54   return { exist: exist, owned: owned }
55 }
56
57 function getVideoThumbnail (video_path, callback) {
58   const filename = pathUtils.basename(video_path) + '.jpg'
59   ffmpeg(video_path)
60     .on('error', callback)
61     .on('end', function () {
62       callback(null, filename)
63     })
64     .thumbnail({
65       count: 1,
66       folder: thumbnailsDir,
67       size: constants.THUMBNAILS_SIZE,
68       filename: filename
69     })
70 }
71
72 // Remove video datas from disk (video file, thumbnail...)
73 function removeVideosDataFromDisk (videos, callback) {
74   async.each(videos, function (video, callback_each) {
75     fs.unlink(thumbnailsDir + video.thumbnail, function (err) {
76       if (err) logger.error('Cannot remove the video thumbnail')
77
78       if (getVideoState(video).owned === true) {
79         fs.unlink(uploadDir + video.namePath, function (err) {
80           if (err) {
81             logger.error('Cannot remove this video file.')
82             return callback_each(err)
83           }
84
85           callback_each(null)
86         })
87       } else {
88         callback_each(null)
89       }
90     })
91   }, callback)
92 }
93
94 function removeRemoteVideos (videos, callback) {
95   Videos.removeByIds(map(videos, '_id'), function (err) {
96     if (err) return callback(err)
97
98     removeVideosDataFromDisk(videos, callback)
99   })
100 }
101
102 function seed (path, callback) {
103   logger.info('Seeding %s...', path)
104
105   webtorrent.seed(path, function (torrent) {
106     logger.info('%s seeded (%s).', path, torrent.magnetURI)
107
108     return callback(null, torrent)
109   })
110 }
111
112 function seedAllExisting (callback) {
113   Videos.listOwned(function (err, videos_list) {
114     if (err) {
115       logger.error('Cannot get list of the videos to seed.')
116       return callback(err)
117     }
118
119     async.each(videos_list, function (video, each_callback) {
120       seed(uploadDir + video.namePath, function (err) {
121         if (err) {
122           logger.error('Cannot seed this video.')
123           return callback(err)
124         }
125
126         each_callback(null)
127       })
128     }, callback)
129   })
130 }
131
132 // ---------------------------------------------------------------------------
133
134 module.exports = videos
135
136 // ---------------------------------------------------------------------------
137
138 function createRemoteVideoObjects (videos, callback) {
139   const remote_videos = []
140
141   async.each(videos, function (video, callback_each) {
142     // Creating the thumbnail for this remote video
143     utils.generateRandomString(16, function (err, random_string) {
144       if (err) return callback_each(err)
145
146       const thumbnail_name = random_string + '.jpg'
147       createThumbnailFromBase64(thumbnail_name, video.thumbnail_base64, function (err) {
148         if (err) return callback_each(err)
149
150         const params = {
151           name: video.name,
152           description: video.description,
153           magnetUri: video.magnetUri,
154           podUrl: video.podUrl,
155           duration: video.duration,
156           thumbnail: thumbnail_name
157         }
158         remote_videos.push(params)
159
160         callback_each(null)
161       })
162     })
163   },
164   function (err) {
165     if (err) return callback(err)
166
167     callback(null, remote_videos)
168   })
169 }
170
171 function createThumbnailFromBase64 (thumbnail_name, data, callback) {
172   fs.writeFile(thumbnailsDir + thumbnail_name, data, { encoding: 'base64' }, callback)
173 }