Nodejs ffmpeg "The input file path can not be empty" error, but files exist - node.js

I'm trying to merge an audio file with a video file from the same source (Youtube)
In the following code I first read in the console parameters wirh commander then i define the videoOutput dir and download the highset res. video from youtube with node-ytdl-core. After that I download the audio for the video. and in the callback of the video.on("end", ....)
i call the function merge()
const path = require('path');
const fs = require('fs');
const readline = require("readline");
const ytdl = require('ytdl-core');
const { program } = require('commander');
const ffmpeg = require('ffmpeg');
program
.option("--url, --url <url>", "Youtube video url")
.option("--name, --name <name>", "Name of the video in hard drive")
program.parse(process.argv);
const options = program.opts();
let url = options.url;
let name = options.name;
let videoOutput = path.resolve(`./video${name}.mp4`);
let video = ytdl(url, {
quality: "highestvideo"
});
let starttime = 0;
video.pipe(fs.createWriteStream(videoOutput));
video.once('response', () => {
starttime = Date.now();
});
video.on('progress', (chunkLength, downloaded, total) => {
const percent = downloaded / total;
const downloadedMinutes = (Date.now() - starttime) / 1000 / 60;
const estimatedDownloadTime = (downloadedMinutes / percent) - downloadedMinutes;
readline.cursorTo(process.stdout, 0);
process.stdout.write(`${(percent * 100).toFixed(2)}% downloaded `);
process.stdout.write(`(${(downloaded / 1024 / 1024).toFixed(2)}MB of ${(total / 1024 / 1024).toFixed(2)}MB)\n`);
process.stdout.write(`running for: ${downloadedMinutes.toFixed(2)}minutes`);
process.stdout.write(`, estimated time left: ${estimatedDownloadTime.toFixed(2)}minutes `);
readline.moveCursor(process.stdout, 0, -1);
});
video.on('end', () => {
process.stdout.write('\n\n');
});
// repeat for audio
video = ytdl(url, {
quality: "highestaudio"
});
starttime = 0;
let audioOutput = path.resolve(`./audio${name}.mp3`);
video.pipe(fs.createWriteStream(audioOutput));
video.once('response', () => {
starttime = Date.now();
});
video.on('progress', (chunkLength, downloaded, total) => {
const percent = downloaded / total;
const downloadedMinutes = (Date.now() - starttime) / 1000 / 60;
const estimatedDownloadTime = (downloadedMinutes / percent) - downloadedMinutes;
readline.cursorTo(process.stdout, 0);
process.stdout.write(`${(percent * 100).toFixed(2)}% downloaded `);
process.stdout.write(`(${(downloaded / 1024 / 1024).toFixed(2)}MB of ${(total / 1024 / 1024).toFixed(2)}MB)\n`);
process.stdout.write(`running for: ${downloadedMinutes.toFixed(2)}minutes`);
process.stdout.write(`, estimated time left: ${estimatedDownloadTime.toFixed(2)}minutes `);
readline.moveCursor(process.stdout, 0, -1);
});
function merge(){
ffmpeg()
.input("./videotest.mp4") //your video file input path
.input("./audiotest.mp3") //your audio file input path
.output("./finished.mp4") //your output path
.outputOptions(['-map 0:v', '-map 1:a', '-c:v copy', '-shortest'])
.on('start', (command) => {
console.log('TCL: command -> command', command)
})
.on('error', (error) => console.log("errrrr",error))
.on('end',()=>console.log("Completed"))
.run()
}
video.on('end', () => {
process.stdout.write('\n\n');
merge();
});
But even though the files are there ffmpeg throws me this error:
I also tried this in the video-end callback, because maybe the audio is finished downloading before the video, still doesn't work. I've also tried to rename the outputDirs for the files and keep the old files and rerun the script so the files are 100% there. Still doesn't work.
I have also tried absolute paths ("C:/..." also with backslash "C:\...") but I still get the error message that the input file path can't be empty.
Appreciate any piece of advise or help!

Ok I have found a solution.
First i moved my code for downloading the audio into the video.on("end",... callback of the video download.
I changed from require("ffmpeg") to these 5 lines:
const ffmpeg = require('fluent-ffmpeg');
const ffmpegPath = require('#ffmpeg-installer/ffmpeg').path;
const ffprobePath = require('#ffprobe-installer/ffprobe').path;
ffmpeg.setFfmpegPath(ffmpegPath);
ffmpeg.setFfprobePath(ffprobePath);
make sure to run:
npm i fluent-ffmpeg
and
npm i #ffprobe-installer/ffprobe
and
npm i #ffmpeg-installer/ffmpeg
that's my solution!

Related

Load random file from directory

So i'm building a game and i want to choose a random file from a directory as a map. I found this old topic which gave the answer
const randomFile = require('random-file')
const dir = '/tmp/whatever'
randomFile(dir, (err, file) => {
console.log(`The random file is: ${file}.`)
})
but it seems that fs is no longer in use, but fs.realpath
Its pretty simple:
const fs = require("fs");
const path = require("path");
fs.readdir(path.join(process.cwd(), "maps"), (err, files) => {
console.log(err, files)
let max = files.length - 1;
let min = 0;
let index = Math.round(Math.random() * (max - min) + min);
let file = files[index];
console.log("Random file is", file);
});
In less then 20 lines.
Why do people use for every simple task a external module?!
Regardless that the package does not what you want...

Trying to download a file in a loop with node.js, but seems to get some sync problems

I'm trying to make a simple program in node.js that will download the same file with some interval. If the downloaded file is newer than the previous, then it will be saved in a new filename with the help of a counter.
If it's a new file, then I want to save it in the last_unique.jpg name and use it to compare next time the file will be downloaded. But it doesn't seem to work. For test, I just have an empty last_unique.jpg that I would expect to be overwritten. But it never is, so every time the jpg file is downloaded, it is unique and saves it in file3.jpg, file3.jpg, etc.
However, the output also looks like maybe some async issues? It skips the first couple of times.
OUTPUT:
downloading 1
downloading 2
downloading 3
Unique file spotted!
downloading 4
Unique file spotted!
downloading 5
Unique file spotted!
downloading 6
Unique file spotted!
downloading 7
Unique file spotted!
downloading 8
Unique file spotted!
Here is the code:
const http = require('http');
const fs = require('fs');
const md5File = require('md5-file');
const fileToDownload = "http://i3.ytimg.com/vi/J---aiyznGQ/mqdefault.jpg";
var counter = 0;
function request() {
counter = counter + 1
console.log("downloading " + counter);
const save = fs.createWriteStream("last_download.jpg");
http.get(fileToDownload, function(response) {
response.pipe(save)
});
const hash1 = md5File.sync('last_download.jpg');
const hash2 = md5File.sync('last_unique.jpg');
// it is a new file
if (hash1.localeCompare(hash2) != 0) {
console.log('Unique file spotted!');
fs.copyFileSync('last_download.jpg','last_unique.jpg');
fs.copyFileSync('last_unique.jpg','file' + counter + '.jpg');
}
}
setInterval(request, 3000);
const http = require('http');
const fs = require('fs');
const md5File = require('md5-file');
const fileToDownload = "http://i3.ytimg.com/vi/J---aiyznGQ/mqdefault.jpg";
var counter = 0;
function request() {
counter = counter + 1;
console.log("downloading " + counter);
const save = fs.createWriteStream("last_download.jpg");
http.get(fileToDownload, function(response) {
response.pipe(save);
response.on('end',function () {
save.end();
})
});
save.on('finish',function () {
const hash1 = md5File.sync('last_download.jpg');
const hash2 = md5File.sync('last_unique.jpg');
console.log(hash1,hash2);
// it is a new file
if (hash1.localeCompare(hash2) != 0) {
console.log('Unique file spotted!');
fs.copyFileSync('last_download.jpg','last_unique.jpg');
fs.copyFileSync('last_unique.jpg','file' + counter + '.jpg');
}
});
}
setInterval(request, 3000);
You need to listen for the finish event on the stream otherwise it maybe the case that you call the copy function before the stream has completely been written. Hence a partial image is copied from the last_download.jpg to last_unique.jpg which means the hashes would be different. This is due the asynchronous nature of copying and http request.

After upload a media file can not play the file in angular 2, shows 404 not found

I have a problem in Angular2: I have module for media upload (Images/Videos). So upload module is working perfect but after uploading the file when I try to watch the video or view the image file, I get a 404 Not Found error. But if i run ng build and refresh it again I can watch the video or view the image files. Am i doing something wrong or we can handle this with someway?
GET http://localhost:4200/dist/assets/mediauploads/Desktop%20(19).jpg 404 (Not Found)
//This is my node js code
socket.on('Upload', function (data){
var Name = data['Name'];
Files[Name]['Downloaded'] += data['Data'].length;
Files[Name]['Data'] += data['Data'];
if(Files[Name]['Downloaded'] == Files[Name]['FileSize']) //If File is Fully Uploaded
{
fs.write(Files[Name]['Handler'], Files[Name]['Data'], null, 'Binary', function(err, Writen){
var inp = fs.createReadStream("./src/assets/temp/" + Name);
var out = fs.createWriteStream("./src/assets/mediauploads/" + Name);
inp.pipe(out);
inp.on("end", function() {
var path="./src/assets/temp/" + Name;
var tempFile = fs.openSync(path, 'r');
if(fs.existsSync(path)){
fs.closeSync(tempFile);
fs.unlinkSync(path);
socket.emit('Done', {'Image' : './src/assets/mediauploads/' + Name});
}
});
});
}
else if(Files[Name]['Data'].length > 10485760){ //If the Data Buffer reaches 10MB
fs.write(Files[Name]['Handler'], Files[Name]['Data'], null, 'Binary', function(err, Writen){
Files[Name]['Data'] = ""; //Reset The Buffer
var Place = Files[Name]['Downloaded'] / 524288;
var Percent = (Files[Name]['Downloaded'] / Files[Name]['FileSize']) * 100;
socket.emit('MoreData', { 'Place' : Place, 'Percent' : Percent});
});
}
else
{
var Place = Files[Name]['Downloaded'] / 524288;
var Percent = (Files[Name]['Downloaded'] / Files[Name]['FileSize']) * 100;
socket.emit('MoreData', { 'Place' : Place, 'Percent' : Percent});
}
});
When you run ng build the angular app is built into the dist folder. My guess is you have your app setup in such a way you are actually uploading the image into your src/assets folder.
As you are running the app from the dist folder your newly uploaded image/video is not visible. When you rebuild the app the file is copied to the dist folder and becomes accesible.

Chrome Native Messaging - Hanging Child Process

I'm trying to make an extension that uses chrome native messaging to communicate with youtube-dl using a node.js host script. I've been able to successfully parse the stdin from the extension & also been able to run a child process (i.e. touch file.dat), but when I try to exec/spawn youtube-dl it hangs on the command. I've tried the host script independently of chrome native input and it works fine. I think the problem may have something to do with 1MB limitations on buffer size of chrome native messaging. Is there a way around reading the buffer?
#! /usr/bin/env node
"use strict";
const fs = require('fs');
const exec = require('child_process').execSync;
const dlPath = '/home/toughluck/Music';
let first = true;
let buffers = [];
process.stdin.on('readable', () => {
let chunk = process.stdin.read();
if (chunk !== null) {
if (first) {
chunk = chunk.slice(4);
first = false;
}
buffers.push(chunk);
}
});
process.stdin.on('end', () => {
const res = Buffer.concat(buffers);
const url = JSON.parse(res).url;
const outTemplate = `${dlPath}/%(title)s.%(ext)s`;
const cmdOptions = {
shell: '/bin/bash'
};
const cmd = `youtube-dl --extract-audio --audio-format mp3 -o \"${outTemplate}\" ${url}`;
// const args = ['--extract-audio', '--audio-format', 'mp3', '-o', outTemplate, url];
// const cmd2 = 'youtube-dl';
process.stderr.write('Suck it chrome');
process.stderr.write('stderr doesnt stop host');
exec(cmd, cmdOptions, (err, stdout, stderr) => {
if (err) throw err;
process.stderr.write(stdout);
process.stderr.write(stderr);
});
process.stderr.write('\n Okay....');
});
The full codebase can be found at https://github.com/wrleskovec/chrome-youtube-mp3-dl
So I was right about what was causing the problem. It had to do with 1 MB limitation on host to chrome message. You can avoid this by redirecting the stdout/stderr to a file.
const cmd = `youtube-dl --extract-audio --audio-format mp3 -o \"${outTemplate}\" ${url} &> d.txt`;
This worked for me. To be honest I'm not entirely why the message is considered > 1 MB and if someone can give a better explanation that would be great.

Node.js: realtime conversion from jpeg images to video file

I'm using node.js and through the socket.io library I receive chunks of data that are actually jpeg images. These images are frames of a realtime video captured from a remote webcam. I'm forced to stream the video as jpeg frames. I'm looking for a way to convert on the fly these jpeg images in a video file (mpeg 4 or mjpeg file). Does node have a library that can do this? I already took a look at the Node-fluent-FFMPEG library but the only examples given were about conversions of jpeg files to a video and not a conversion on the fly from a stream of jpeg images. Or alternatively, does ffmpeg for windows support a stream of jpeg images as input?
FFMPEG supports streams as inputs, as stated in the docs.
You can add any number of inputs to an Ffmpeg command. An input can
be [...] a readable stream
So for instance it supports using
ffmpeg().input(fs.createReadStream('/path/to/input3.avi'));
which creates a Readable stream from the file at '/path/to/input3.avi'.
I don't know anything about FFMPEG, but you may pull your messages coming from socket.io (messages may be a Buffer already) and wrap it with your own implementation of Readable stream.
I think you should look at videofy
var exec = require("child_process").exec;
var escape = require("shell-escape");
var debug = require("debug")("videofy");
var mkdirp = require("mkdirp");
var uid = require("uid2");
/*
* Expose videofy
*/
module.exports = videofy;
/**
* Convert `input` file to `output` video with the given `opts`:
*
* - `rate` frame rate [10]
* - `encoders` the video codec format, default is libx264
*
* #param {String} input
* #param {String} output
* #return
* #api public
*/
function videofy(input, output, opts, fn) {
if (!input) throw new Error('input filename required');
if (!output) throw new Error('output filename required');
var FORMAT = '-%05d';
// options
if ('function' == typeof opts) {
fn = opts;
opts = {};
} else {
opts = opts || {};
}
opts.rate = opts.rate || 10;
opts.codec = opts.codec || 'libx264';
// tmpfile(s)
var id = uid(10);
var dir = 'tmp/' + id;
var tmp = dir + '/tmp' + FORMAT + '.jpg';
function gc(err) {
debug('remove %s', dir);
exec('rm -fr ' + dir);
fn(err);
}
debug('mkdirp -p %s', dir);
mkdirp(dir, function(error) {
if (error) return fn(error);
// convert gif to tmp jpg
var cmd = ['convert', input, tmp];
cmd = escape(cmd);
debug('exec %s', cmd);
// covert jpg collection to video
exec(cmd, function(err) {
if (err) return gc(err);
var cmd = ['ffmpeg'];
cmd.push('-f', 'image2');
cmd.push('-r', String(opts.rate));
cmd.push('-i', tmp);
cmd.push('-c:v', String(opts.codec));
cmd.push(output);
cmd = escape(cmd);
debug("exec %s", cmd);
exec(cmd, gc);
});
});
}
Using require("child_process") you can use ffmpeg, or there are probably npm modules to help with this. ffmpeg will allow you to first take a list of jpegs and convert that to a video, second you can add a list (or just one) jpegs to the beginning or end of videos.

Resources