1
+ import 'dotenv/config' ;
1
2
import express from 'express' ;
3
+ import fileUpload from 'express-fileupload' ;
2
4
import fs from 'fs' ;
3
- import { getDb } from '../db/mongo' ;
5
+ import { spawn } from 'child_process' ;
6
+ import { ObjectId } from 'mongodb' ;
7
+ import { getDb } from '../db/mongo.js' ;
4
8
9
+ /* eslint new-cap: ["error", { "capIsNewExceptions": ["Router"] }] */
5
10
const video = express . Router ( ) ;
6
11
7
- video . get ( '/:videoId/info' , function ( req , res ) {
12
+ video . get ( '/:videoId/info' , function ( req , res ) {
8
13
const db = getDb ( ) ;
9
14
10
- const uuid = req . params . videoId ;
15
+ const videoId = req . params . videoId ;
11
16
12
17
db . collection ( 'videos' )
13
- . findOne ( { uuid : uuid } )
14
- . then ( ( result : any ) => {
15
- res . json ( result ) ;
16
- } ) ;
18
+ . findOne ( { _id : new ObjectId ( videoId ) } )
19
+ . then ( ( result : any ) => {
20
+ res . json ( result ) ;
21
+ } ) ;
17
22
} ) ;
18
23
19
- video . get ( '/:videoId' , function ( req , res ) {
20
- let range = req . headers . range ;
24
+ video . get ( '/:videoId' , function ( req , res ) {
25
+ const range = req . headers . range ;
21
26
22
27
if ( range === undefined ) {
23
28
res . status ( 400 ) . send ( 'Requires Range header' ) ;
24
29
return ;
25
30
}
26
31
32
+ const db = getDb ( ) ;
27
33
const videoId = req . params . videoId ;
28
- let filename = videoId ;
29
-
30
- const videoPath = 'store/videos/files/' + filename ;
31
- const videoSize = fs . statSync ( 'store/videos/files/' + filename ) . size ;
32
- const CHUNK_SIZE = 10 ** 6 ;
33
- const start = Number ( range . replace ( / \D / g, '' ) ) ;
34
- const end = Math . min ( start + CHUNK_SIZE , videoSize - 1 ) ;
35
- const contentLength = end - start + 1 ;
36
- const headers = {
37
- 'Content-Range' : `bytes ${ start } -${ end } /${ videoSize } ` ,
38
- 'Accept-Ranges' : 'bytes' ,
39
- 'Content-Length' : contentLength ,
40
- 'Content-Type' : 'video/mp4' ,
41
- } ;
42
- res . writeHead ( 206 , headers ) ;
43
- const videoStream = fs . createReadStream ( videoPath , { start, end } ) ;
44
- videoStream . pipe ( res ) ;
34
+
35
+ db . collection ( 'videos' )
36
+ . findOne ( { _id : new ObjectId ( videoId ) } )
37
+ . then ( ( result : any ) => {
38
+ const videoPath =
39
+ `${ process . env . STORAGE } /videos${ result . path } /videos/${ videoId } ` ;
40
+
41
+ const videoSize = fs . statSync ( videoPath ) . size ;
42
+ const CHUNK_SIZE = 10 ** 6 ;
43
+ const start = Number ( range . replace ( / \D / g, '' ) ) ;
44
+ const end = Math . min ( start + CHUNK_SIZE , videoSize - 1 ) ;
45
+ const contentLength = end - start + 1 ;
46
+ const headers = {
47
+ 'Content-Range' : `bytes ${ start } -${ end } /${ videoSize } ` ,
48
+ 'Accept-Ranges' : 'bytes' ,
49
+ 'Content-Length' : contentLength ,
50
+ 'Content-Type' : 'video/mp4' ,
51
+ } ;
52
+ res . writeHead ( 206 , headers ) ;
53
+ const videoStream = fs . createReadStream ( videoPath , { start, end} ) ;
54
+ videoStream . pipe ( res ) ;
55
+ } ) ;
56
+ } ) ;
57
+
58
+ video . post ( '/upload' , function ( req , res ) {
59
+ try {
60
+ if ( ! req . files ) {
61
+ res . send ( {
62
+ status : false ,
63
+ message : 'No file uploaded' ,
64
+ } ) ;
65
+ } else {
66
+ const video = req . files . myFile as fileUpload . UploadedFile ;
67
+ const tempDir = process . env . STORAGE + '/temp' ;
68
+
69
+ /** Check if temp dir exists, if it doesn't, create one */
70
+ if ( ! fs . existsSync ( tempDir ) ) {
71
+ fs . mkdirSync ( tempDir ) ;
72
+ }
73
+
74
+ const timestamp = new Date ( ) ;
75
+
76
+ /* Check and create storage directories */
77
+ const year = timestamp . getUTCFullYear ( ) ;
78
+ const month = timestamp . getUTCMonth ( ) + 1 ;
79
+ const day = timestamp . getUTCDate ( ) ;
80
+ let path = `${ process . env . STORAGE } /videos/${ year } ` ;
81
+
82
+ if ( ! fs . existsSync ( path ) ) fs . mkdirSync ( path ) ;
83
+ path += `/${ month } ` ;
84
+ if ( ! fs . existsSync ( path ) ) fs . mkdirSync ( path ) ;
85
+ path += `/${ day } ` ;
86
+ if ( ! fs . existsSync ( path ) ) fs . mkdirSync ( path ) ;
87
+
88
+ /* Videos directory */
89
+ if ( ! fs . existsSync ( path + '/videos' ) ) fs . mkdirSync ( path + '/videos' ) ;
90
+ /* Thumbs directory */
91
+ if ( ! fs . existsSync ( path + '/thumbs' ) ) fs . mkdirSync ( path + '/thumbs' ) ;
92
+
93
+ const relPath = `/${ year } /${ month } /${ day } ` ;
94
+
95
+ /** Insert video in database */
96
+ const db = getDb ( ) ;
97
+
98
+ video . mv ( tempDir + '/' + video . name ) . then ( ( ) => {
99
+ const args = [
100
+ '-v' ,
101
+ 'quiet' ,
102
+ '-print_format' ,
103
+ 'json' ,
104
+ '-show_streams' ,
105
+ '-select_streams' ,
106
+ 'v:0' ,
107
+ tempDir + '/' + video . name ,
108
+ ] ;
109
+
110
+ const proc = spawn ( process . env . FFPROBE as string , args ) ;
111
+ let output = '' ;
112
+
113
+ proc . stdout . setEncoding ( 'utf8' ) ;
114
+ proc . stdout . on ( 'data' , function ( data ) {
115
+ output += data ;
116
+ } ) ;
117
+
118
+ proc . on ( 'close' , function ( ) {
119
+ const json = JSON . parse ( output ) ;
120
+ const fpsArray = json . streams [ 0 ] . avg_frame_rate . split ( '/' ) ;
121
+ const fps = parseInt ( fpsArray [ 0 ] ) / parseInt ( fpsArray [ 1 ] ) ;
122
+
123
+ db . collection ( 'videos' )
124
+ . insertOne ( {
125
+ name : video . name ,
126
+ path : relPath ,
127
+ ts : timestamp ,
128
+ width : json . streams [ 0 ] . width ,
129
+ height : json . streams [ 0 ] . height ,
130
+ fps : fps . toFixed ( 2 ) ,
131
+ duration : json . streams [ 0 ] . duration ,
132
+ bitrate : json . streams [ 0 ] . bit_rate ,
133
+ status : 1 ,
134
+ } )
135
+ . then ( ( object : any ) => {
136
+ const videoId = object . insertedId . toString ( ) ;
137
+ encodeVideo ( tempDir , video . name , videoId , path ) ;
138
+
139
+ res . send ( {
140
+ status : true ,
141
+ message : 'File is uploaded' ,
142
+ } ) ;
143
+ } ) ;
144
+ } ) ;
145
+ } ) ;
146
+ }
147
+ } catch ( err ) {
148
+ res . status ( 500 ) . send ( err ) ;
149
+ }
45
150
} ) ;
46
151
47
- export default video ;
152
+ /**
153
+ *
154
+ * @param {string } tempDir Temporary directory where file is
155
+ * @param {string } filename Video filename
156
+ * @param {string } videoId Id of video in the database
157
+ * @param {string } path Path where the output file should go
158
+ */
159
+ function encodeVideo (
160
+ tempDir : string ,
161
+ filename : string ,
162
+ videoId : string ,
163
+ path : string ,
164
+ ) {
165
+ console . log ( 'encoding video...' ) ;
166
+ const args = [
167
+ '-y' ,
168
+ '-i' ,
169
+ tempDir + '/' + filename ,
170
+ '-codec:a' ,
171
+ 'aac' ,
172
+ '-b:a' ,
173
+ '44.1k' ,
174
+ '-c:v' ,
175
+ 'libx264' ,
176
+ '-preset' ,
177
+ 'slow' ,
178
+ '-crf' ,
179
+ '22' ,
180
+ '-f' ,
181
+ 'mp4' ,
182
+ path + '/videos/' + videoId ,
183
+ ] ;
184
+
185
+ const proc = spawn ( process . env . FFMPEG as string , args ) ;
186
+
187
+ proc . stdout . on ( 'data' , function ( data ) {
188
+ // console.log(data);
189
+ } ) ;
190
+
191
+ proc . stderr . setEncoding ( 'utf8' ) ;
192
+ proc . stderr . on ( 'data' , function ( data ) {
193
+ console . log ( data ) ;
194
+ } ) ;
195
+
196
+ proc . on ( 'close' , function ( ) {
197
+ console . log ( 'encoding finished' ) ;
198
+
199
+ fs . unlink ( tempDir + '/' + filename , ( err ) => {
200
+ if ( err ) {
201
+ console . error ( err ) ;
202
+ return ;
203
+ }
204
+
205
+ console . log ( 'Temp file removed' ) ;
206
+ createThumbnail ( videoId , path ) ;
207
+ } ) ;
208
+ } ) ;
209
+ }
210
+
211
+ /**
212
+ *
213
+ * @param {string } videoId Id of video in the database
214
+ * @param {string } path path where to store thumbnail
215
+ */
216
+ function createThumbnail ( videoId : string , path : string ) {
217
+ console . log ( 'creating thumbnail' ) ;
218
+
219
+ const args = [
220
+ '-y' ,
221
+ '-i' ,
222
+ path + '/videos/' + videoId ,
223
+ '-vf' ,
224
+ 'thumbnail,scale=320:180:force_original_aspect_ratio=increase,crop=320:180' ,
225
+ '-frames:v' ,
226
+ '1' ,
227
+ '-f' ,
228
+ 'image2' ,
229
+ '-c' ,
230
+ 'png' ,
231
+ path + '/thumbs/' + videoId ,
232
+ ] ;
233
+
234
+ const proc = spawn ( process . env . FFMPEG as string , args ) ;
235
+
236
+ proc . stdout . on ( 'data' , function ( data ) {
237
+ console . log ( data ) ;
238
+ } ) ;
239
+
240
+ proc . stderr . setEncoding ( 'utf8' ) ;
241
+ proc . stderr . on ( 'data' , function ( data ) {
242
+ console . log ( data ) ;
243
+ } ) ;
244
+
245
+ proc . on ( 'close' , function ( ) {
246
+ console . log ( 'thumbnail created' ) ;
247
+
248
+ const db = getDb ( ) ;
249
+
250
+ db . collection ( 'videos' )
251
+ . updateOne (
252
+ {
253
+ _id : new ObjectId ( videoId ) ,
254
+ } ,
255
+ {
256
+ $set : { status : 2 } ,
257
+ } ,
258
+ )
259
+ . then ( ( ) => {
260
+ console . log ( 'Status changed to 2' ) ;
261
+ } ) ;
262
+ } ) ;
263
+ }
264
+
265
+ export default video ;
0 commit comments