-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathservice.js
318 lines (278 loc) · 9.63 KB
/
service.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
const fs = require('fs');
const ytdl = require('ytdl-core');
const ffmpeg = require('ffmpeg-static');
const cp = require('child_process');
const readline = require('readline');
const help = require('./helpers');
const videos = require('./videos');
const STD_MSG = '[MSG service] ';
const STREAM_MSG = '[MSG DURING STREAM] ';
const DEMUXER_LIST_PATH = './bin/list.txt';
const OUTPUT_PATH ='./bin/';
const tracker = {
start: Date.now(),
audio: { downloaded: 0, total: Infinity },
video: { downloaded: 0, total: Infinity },
merged: { frame: 0, speed: '0x', fps: 0 },
};
const showProgress = () => {
readline.cursorTo(process.stdout, 0);
const toMB = i => (i / 1024 / 1024).toFixed(2);
process.stdout.write(`Audio | ${(tracker.audio.downloaded / tracker.audio.total * 100).toFixed(2)}% processed `);
process.stdout.write(`(${toMB(tracker.audio.downloaded)}MB of ${toMB(tracker.audio.total)}MB).${' '.repeat(10)}\n`);
process.stdout.write(`Video | ${(tracker.video.downloaded / tracker.video.total * 100).toFixed(2)}% processed `);
process.stdout.write(`(${toMB(tracker.video.downloaded)}MB of ${toMB(tracker.video.total)}MB).${' '.repeat(10)}\n`);
process.stdout.write(`Merged | processing frame ${tracker.merged.frame} `);
process.stdout.write(`(at ${tracker.merged.fps} fps => ${tracker.merged.speed}).${' '.repeat(10)}\n`);
process.stdout.write(`running for: ${((Date.now() - tracker.start) / 1000 / 60).toFixed(2)} Minutes.`);
readline.moveCursor(process.stdout, 0, -3);
};
//validates url of YouTube video
exports.validate = function (url){
return ytdl.validateURL(url)
}
//gets the info of a valid YT link and stores json return value in a file
exports.info = async function(url){
const info = await ytdl.getBasicInfo(url);
//create filename
const title = info.videoDetails.media.song;
const filename = help.replace(title);
//create file path from filename
const filepath = filename == null? './bin/video_info.json':'./bin/'+filename+'.json';
await fs.writeFile(filepath, JSON.stringify(info, null, 2), err =>{
if(err){
console.error(err);
}
return
});
return filepath;
}
/**
* Download youtube video in standard 360p quality
* @param {string} url url of valid youtube video
* @returns {string} relative path of downloaded video
*/
exports.downloadYT = function(url){
let title = '';
let path = '';
try {
const readStream = ytdl(url);
readStream.on('info',(info, format) => {
console.log(STREAM_MSG + 'Now Downloading: ' + info.videoDetails.title);
title = help.replace(info.videoDetails.title);
const fileType = format.container;
if(!fs.existsSync('./bin'))
fs.mkdirSync('./bin');
path = `./bin/${title}.${fileType}`;
const writeStream = fs.createWriteStream(`./bin/${title}.${fileType}`);
readStream.pipe(writeStream);
})
.on('progress',(_,downloaded,total) =>{
const percent = (downloaded/total*100).toFixed(1);
if(percent % 5 == 0){ //print log every 5%
readline.cursorTo(process.stdout, 0); //untested
console.log(`Progress: ${percent}%\t downloaded: ${downloaded}\t total: ${total}`);
}if(percent == 100 && path != ''){ //finished
return path;
}
})
.on('error', (err) => {
throw err
})
} catch (err) {
console.error(STD_MSG, "Error during download: ", err);
}
}
/**
* Download youtube video in highest quality available
* @param {string} url
* @param {int} index
* @returns {string} relative path of downloaded video
*/
exports.downloadHD = function(url, index){
!(index==null)? null:index=00;
let path = `${OUTPUT_PATH}out${index}.mp4`;
// Get audio and video streams
const audio = ytdl(url, { quality: 'highestaudio' })
.on('progress', (_, downloaded, total) => {
tracker.audio = { downloaded, total };
});
const video = ytdl(url, { quality: 'highestvideo' })
.on('progress', (_, downloaded, total) => {
tracker.video = { downloaded, total };
});
// Prepare the progress bar
let progressbarHandle = null;
const progressbarInterval = 1000;
help.deleteIfExists(path);
// Start the ffmpeg child process
const ffmpegProcess = cp.spawn(ffmpeg, [
// Remove ffmpeg's console spamming
'-loglevel', '8', '-hide_banner',
// Redirect/Enable progress messages
'-progress', 'pipe:3',
// Set inputs
'-i', 'pipe:4',
'-i', 'pipe:5',
// Map audio & video from streams
'-map', '0:a',
'-map', '1:v',
// Keep encoding
'-c:v', 'copy',
// Define output file
path,
], {
windowsHide: true,
stdio: [
/* Standard: stdin, stdout, stderr */
'inherit', 'inherit', 'inherit',
/* Custom: pipe:3, pipe:4, pipe:5 */
'pipe', 'pipe', 'pipe',
],
});
ffmpegProcess.on('close', () => {
// Cleanup
process.stdout.write('\n\n\n\n');
clearInterval(progressbarHandle);
return path;
});
// Link streams
// FFmpeg creates the transformer streams and we just have to insert / read data
ffmpegProcess.stdio[3].on('data', chunk => {
// Start the progress bar
if (!progressbarHandle) progressbarHandle = setInterval(showProgress, progressbarInterval);
// Parse the param=value list returned by ffmpeg
const lines = chunk.toString().trim().split('\n');
const args = {};
for (const l of lines) {
const [key, value] = l.split('=');
args[key.trim()] = value.trim();
}
tracker.merged = args;
});
audio.pipe(ffmpegProcess.stdio[4]);
video.pipe(ffmpegProcess.stdio[5]);
}
/**
* Creates a shortened video of the given path for specified time & starting interval
* @param {string} inputPath path of mp4 video to manipulate
* @param {int} startTime index to start new video in seconds
* @param {int} length duration of new video in seconds
*/
exports.clipVideo = function (inputPath, startTime=30.0, length=60) {
let title = inputPath.slice(inputPath.indexOf(OUTPUT_PATH), inputPath.indexOf('.mp4'));
let outputPath = `${OUTPUT_PATH}clip${title}.mp4`;
console.log(STD_MSG, `Clipping from path : ${inputPath}`);
help.deleteIfExists(outputPath);
try{
const ffmpegProcess = cp.spawn(ffmpeg, [
'-y',
'-ss', startTime,
'-i', inputPath,
'-t', length,
'-b:a', '192K',
'-nostdin',
//output file
outputPath
], {
windowsHide: true
});
ffmpegProcess.on('message', (msg) => {
console.log(STD_MSG, 'message from clipping of ', inputPath, ': ', msg);
});
ffmpegProcess.on('close', () => {
console.log(STD_MSG, 'clipping done for path: ', outputPath);
return outputPath;
});
}catch(err){
console.error(err);
}
}
//Downloads video from given video object index and sends video off to be clipped
exports.prepClip = async function(index){
let path = `./bin/vid${index}.mp4`;
let video_info = videos.getVideo(index);
help.deleteIfExists(path);
// Get audio and video streams
const audio = ytdl(video_info.url, { quality: 'highestaudio' });
const video = ytdl(video_info.url, { quality: 'highestvideo' });
// Start the ffmpeg child process
const ffmpegProcess = cp.spawn(ffmpeg, [
// Remove ffmpeg's console spamming
'-loglevel', '8', '-hide_banner',
// Redirect/Enable progress messages
'-progress', 'pipe:3',
// Set inputs
'-i', 'pipe:4',
'-i', 'pipe:5',
// Map audio & video from streams
'-map', '0:a',
'-map', '1:v',
// Keep encoding
'-c:v', 'copy',
// Define output file
path,
], {
windowsHide: true,
stdio: [
/* Standard: stdin, stdout, stderr */
'inherit', 'inherit', 'inherit',
/* Custom: pipe:3, pipe:4, pipe:5 */
'pipe', 'pipe', 'pipe',
],
});
ffmpegProcess.on('close', () => {
console.log(STD_MSG, `Full video #${index} finished at ${path}`);
try{
videos.setVideoPath(index, path);
}catch(error){
console.error(error);
}
//exports.clipVideo(index, videos.getStartTime(index), videos.getLength(index));
});
// Link streams
// FFmpeg creates the transformer streams and we just have to insert / read data
audio.pipe(ffmpegProcess.stdio[4]);
video.pipe(ffmpegProcess.stdio[5]);
}
//Combines two clips together
exports.combineTwo = async function(index){
await help.waitTillReady();
const outputPath = 'bin/output.mp4';
help.deleteIfExists(outputPath);
const v = videos.getAll();
const inputParams = [];
var filterCmd = '';
for(idx in v){
inputParams.push('-i',`${v[idx].clipPath}`);
filterCmd += `[${idx}:v]scale=1920:1080:force_original_aspect_ratio=decrease:eval=frame,pad=1920:1080:-1:-1:color=black,setsar=1[v${idx}],`;
//console.log('input params: \n', inputParams);
//console.log('filterCmd: \n', filterCmd);
}
// adding final concat filter mapping
for(idx in v){
filterCmd += `[v${idx}] [${idx}:a] `;
}
filterCmd += `concat=n=${v.length}:v=1:a=1 [v] [a]`;
//console.log(filterCmd);
const params = [
// expanding clip paths to be combined
...inputParams,
'-filter_complex', filterCmd,
'-map', '[v]',
'-map', '[a]',
'-vsync', '2',
outputPath
]
//console.log('params: \n',params);
const combineStream = cp.spawn(ffmpeg, params);
combineStream.on('message', (msg) => {
console.log(STD_MSG, 'combining videos msg: ', msg);
});
combineStream.on('close', (msg) => {
console.log(STD_MSG, 'combining \'closed\' msg: ', msg);
});
combineStream.on('error', (msg) => {
console.log(STD_MSG, 'combining \'error\' msg: ', msg);
})
}