Convert midi to mp3 in node - node.js

So im developing an app in node where i use scribbletune
To create a midi-file:
const clip = scribble.clip({
notes: cMajor,
pattern: 'xxxxxxx'
});
scribble.midi(clip, 'c-major.mid');
I want to be able to play this file in the browser.
From what i gather there is no way to play midi in the browser using audio-tags:
<audio controls>
<source src="horse.ogg" type="audio/ogg">
</audio>
So im thinking that i should first convert the midi-file into an mp3-file. I've been searching the web for a node-package that could do this but have not found anything.
It sounds like something that should be possible.
Any tips on how to achieve this in node?

If you only want to play the generated MIDI in the browser and converting the file to ogg first and playing it using an audio tag is not a requirement, MidiConvert and Tone.js (as briefly explained in Scribbletune docs) can do the job.
I've hacked together an example:
const http = require("http");
const MidiConvert = require("midiconvert"); // npm install midiconvert
const scribble = require("scribbletune"); // npm install scribbletune
const createMidiForToneJs = (notes, pattern) => {
let clip = scribble.clip({
notes,
pattern
});
let midiData;
// The callback is called synchronously (https://github.com/scribbletune/scribbletune/blob/ebda52d7a2835f28b3ddab15488c22bc1d425e7b/src/midi.js#L37)
scribble.midi(
clip,
null, // setting filename to null will cause this method to return bytes (via the callback)
(err, bytes) => {
// err is always null
midiData = bytes;
}
);
return MidiConvert.parse(midiData);
};
const server = http.createServer((req, res) => {
// Don't mind the naivety of the following URL router
switch (req.url.toLowerCase()) {
case "/":
res.end(indexHTMLContents);
break;
case "/midi.json":
res.statusCode = 200;
res.setHeader("Content-Type", "application/json");
const jsonString = JSON.stringify(createMidiForToneJs("CM", "xxxxxxx"));
res.end(jsonString);
break;
default:
res.statusCode = 404;
res.end();
}
});
server.listen(3000); // Open http://localhost:3000 in browser
// -- The HTML and Javascript for the "/" route. Obviously, don't do this in production!
const jsToBeExecutedInBrowser = () => {
document.querySelector("button").addEventListener(
"click",
async () => {
Tone.Transport.clear();
Tone.Transport.stop();
const synth = new Tone.Synth().toMaster();
const response = await fetch("/midi.json");
const midiJson = await response.json();
const midi = MidiConvert.fromJSON(midiJson);
Tone.Transport.bpm.value = midi.bpm;
Tone.Transport.timeSignature = midi.timeSignature;
midi.tracks.forEach(track => {
new Tone.Part((time, event) => {
synth.triggerAttackRelease(
event.name,
event.duration,
time,
event.velocity
);
}, track.notes).start(midi.startTime);
});
Tone.Transport.start();
},
false
);
};
const indexHTMLContents = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Midi Player</title>
</head>
<body>
<button>Play!</button>
<script src="https://unpkg.com/tone"></script>
<script src="https://unpkg.com/midiconvert"></script>
<script>
(${jsToBeExecutedInBrowser.toString()})();
</script>
</body>
</html>`;

Related

Unable to Stop DetectIntentStream on DialogFlow Es

I am trying to create a two-way communication voice bot using detectIntentStream Api of Dialogflow ES. But unable to stop streaming or end the session after communication.
I use example2 of this repository: https://github.com/dialogflow/selfservicekiosk-audio-streaming/tree/master/examples
here is the server code
const projectId = process.env.npm_config_PROJECT_ID;
const example = process.env.npm_config_EXAMPLE;
const port = process.env.npm_config_PORT || 3000;
const languageCode = "en-US";
let encoding = "AUDIO_ENCODING_LINEAR_16";
if (example > 3) {
// NOTE: ENCODING NAMING FOR SPEECH API IS DIFFERENT
encoding = "LINEAR16";
}
console.log(example);
if (example == 7) {
// NOTE: ENCODING NAMING FOR SPEECH API IS DIFFERENT
encoding = "linear16";
}
const singleUtterance = false;
const interimResults = false;
const sampleRateHertz = 16000;
const speechContexts = [
{
phrases: ["mail", "email"],
boost: 20.0,
},
];
console.log(example);
console.log(projectId);
// ----------------------
// load all the libraries for the server
const socketIo = require("socket.io");
const path = require("path");
const fs = require("fs");
const http = require("http");
const cors = require("cors");
const express = require("express");
const ss = require("socket.io-stream");
// load all the libraries for the Dialogflow part
const uuid = require("uuid");
const util = require("util");
const { Transform, pipeline } = require("stream");
const pump = util.promisify(pipeline);
const df = require("dialogflow").v2beta1;
// set some server variables
const app = express();
var server;
var sessionId, sessionClient, sessionPath, request;
var speechClient,
requestSTT,
ttsClient,
requestTTS,
mediaTranslationClient,
requestMedia;
// STT demo
const speech = require("#google-cloud/speech");
// TTS demo
const textToSpeech = require("#google-cloud/text-to-speech");
// Media Translation Demo
const mediatranslation = require("#google-cloud/media-translation");
/**
* Setup Express Server with CORS and SocketIO
*/
function setupServer() {
// setup Express
app.use(cors());
app.get("/", function (req, res) {
res.sendFile(path.join(__dirname + "/example" + example + ".html"));
});
server = http.createServer(app);
io = socketIo(server);
server.listen(port, () => {
console.log("Running server on port %s", port);
});
// Listener, once the client connect to the server socket
io.on("connect", (client) => {
console.log(`Client connected [id=${client.id}]`);
client.emit("server_setup", `Server connected [id=${client.id}]`);
ss(client).on("stream", function (stream, data) {
// get the name of the stream
const filename = path.basename(data.name);
// pipe the filename to the stream
stream.pipe(fs.createWriteStream(filename));
// make a detectIntStream call
detectIntentStream(stream, function (results) {
console.log(results);
console.log(results.outputAudio);
client.emit("results", results);
});
});
});
}
function setupDialogflow() {
// Dialogflow will need a session Id
sessionId = uuid.v4();
// Dialogflow will need a DF Session Client
// So each DF session is unique
sessionClient = new df.SessionsClient();
// Create a session path from the Session client,
// which is a combination of the projectId and sessionId.
sessionPath = sessionClient.sessionPath(projectId, sessionId);
// Create the initial request object
// When streaming, this is the first call you will
// make, a request without the audio stream
// which prepares Dialogflow in receiving audio
// with a certain sampleRateHerz, encoding and languageCode
// this needs to be in line with the audio settings
// that are set in the client
request = {
session: sessionPath,
queryInput: {
audioConfig: {
sampleRateHertz: sampleRateHertz,
encoding: encoding,
languageCode: languageCode,
speechContexts: speechContexts,
},
singleUtterance: singleUtterance,
},
outputAudioConfig: {
audioEncoding: "OUTPUT_AUDIO_ENCODING_LINEAR_16",
},
};
}
async function detectIntentStream(audio, cb) {
// execute the Dialogflow Call: streamingDetectIntent()
const stream = sessionClient
.streamingDetectIntent()
.on("data", function (data) {
// when data comes in
// log the intermediate transcripts
if (data.recognitionResult) {
console.log(
`Intermediate transcript:
${data.recognitionResult.transcript}`
);
} else {
// log the detected intent
console.log(`Detected intent:`);
cb(data);
}
})
.on("error", (e) => {
console.log(e);
})
.on("end", () => {
console.log("on end");
return null;
});
// Write request objects.
// Thee first message must contain StreamingDetectIntentRequest.session,
// [StreamingDetectIntentRequest.query_input] plus optionally
// [StreamingDetectIntentRequest.query_params]. If the client wants
// to receive an audio response, it should also contain
// StreamingDetectIntentRequest.output_audio_config.
// The message must not contain StreamingDetectIntentRequest.input_audio.
stream.write(request);
// pump is a small node module that pipes streams together and
// destroys all of them if one of them closes.
await pump(
audio,
// Format the audio stream into the request format.
new Transform({
objectMode: true,
transform: (obj, _, next) => {
next(null, {
inputAudio: obj,
outputAudioConfig: {
audioEncoding: `OUTPUT_AUDIO_ENCODING_LINEAR_16`,
},
});
},
}),
stream
);
}
setupDialogflow();
setupServer();
and here is client code
<!DOCTYPE html>
<!--
Copyright 2020 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<html>
<head>
<meta charset="utf-8" />
<title>RecordRTC over Socket.io</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://code.getmdl.io/1.3.0/material.indigo-pink.min.css">
<script src="https://www.WebRTC-Experiment.com/RecordRTC.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.3.0/socket.io.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io-stream/0.9.1/socket.io-stream.js"></script>
</head>
<body>
<div style="margin: 20px">
<h1 style="font-size: 18px;">Example 2: Dialogflow Speech Detection through streaming</h1>
<div>
<button id="start-recording" disabled>Start Streaming</button>
<button id="stop-recording" disabled>Stop Streaming</button>
</div>
<h2 style="font-size: 16px; margin-bottom: 10px;">Query Text</h2>
<code>data.queryResult.queryText</code><br/>
<input id="queryText" type="text" style="width: 400px;"/>
<h2 style="font-size: 16px; margin-bottom: 10px;">Intent</h2>
<code>data.queryResult.intent.displayName</code><br/>
<input id="intent" type="text" style="width: 400px;"/>
<h2 style="font-size: 16px;">Responses</h2>
<code>data.queryResult.fulfillmentText</code><br/>
<textarea id="results" style="width: 800px; height: 300px;"></textarea>
<code>data.queryResult.audio</code><br/>
<textarea id="result_audio" style="width: 800px; height: 300px;"></textarea>
</div>
<script type="text/javascript">
const startRecording = document.getElementById('start-recording');
const stopRecording = document.getElementById('stop-recording');
let recordAudio;
const socketio = io();
const socket = socketio.on('connect', function() {
startRecording.disabled = false;
});
startRecording.onclick = function() {
startRecording.disabled = true;
navigator.getUserMedia({
audio: true
}, function(stream) {
recordAudio = RecordRTC(stream, {
type: 'audio',
mimeType: 'audio/webm',
sampleRate: 44100,
desiredSampRate: 16000,
recorderType: StereoAudioRecorder,
numberOfAudioChannels: 1,
//1)
// get intervals based blobs
// value in milliseconds
// as you might not want to make detect calls every seconds
timeSlice: 4000,
//2)
// as soon as the stream is available
ondataavailable: function(blob) {
// 3
// making use of socket.io-stream for bi-directional
// streaming, create a stream
var stream = ss.createStream();
// stream directly to server
// it will be temp. stored locally
ss(socket).emit('stream', stream, {
name: 'stream.wav',
size: blob.size
});
// pipe the audio blob to the read stream
ss.createBlobReadStream(blob).pipe(stream);
}
});
recordAudio.stopRecording();
stopRecording.disabled = false;
}, function(error) {
console.error(JSON.stringify(error));
});
};
// 4)
// on stop button handler
stopRecording.onclick = function() {
// recording stopped
recordAudio.startRecording();
startRecording.disabled = false;
stopRecording.disabled = true;
// socketio.emit("close",function(){
// })
};
// const aduiopreview = document.getElementById('result_audio');
const resultpreview = document.getElementById('results');
const intentInput = document.getElementById('intent');
const textInput = document.getElementById('queryText');
socketio.on('results', function (data) {
console.log(data);
if(data.queryResult){
resultpreview.innerHTML += "" + data.queryResult.fulfillmentText;
intentInput.value = data.queryResult.intent.displayName;
textInput.value = "" + data.queryResult.queryText;
}
if(data.outputAudio){
console.log(data.outputAudio);
playOutput(data.outputAudio)
}
});
/*
* When working with Dialogflow and Dialogflow matched an intent,
* and returned an audio buffer. Play this output.
*/
function playOutput(arrayBuffer){
let audioContext = new AudioContext();
let outputSource;
try {
if(arrayBuffer.byteLength > 0){
console.log(arrayBuffer.byteLength);
audioContext.decodeAudioData(arrayBuffer,
function(buffer){
audioContext.resume();
outputSource = audioContext.createBufferSource();
outputSource.connect(audioContext.destination);
outputSource.buffer = buffer;
outputSource.start(0);
},
function(){
console.log(arguments);
});
}
} catch(e) {
console.log(e);
}
}
</script>
</body>
</html>

Forcing Client to Stop AJAX from Node.js Server

I looked at several SO posts trying to find a way to make a Node.js server tell a client to stop uploading after a certain file size has been reached. The most promising of these is ed-ta's technique at Avoiding further processing on busyboy file upload size limit.
Per ed-ta, my Node.js server seemed to be doing what it should. The server sent the 455 status code as soon as the size limit was reached and stopped accepting any more data. Unfortunately, my client kept processing the file until it was completely done anyway. This is a less than ideal experience when the user tries to upload extremely large files since the client doesn't alert the user that the threshold is reached until the AJAX request is completely done.
How do I get the client to see the 455 status code in a timely manner?
I tried checking for the 455 status inside xhr.onreadystatechange, but I can't seem to find that information from inside onreadystatehange even if the server has already sent the 455 in the response 1. Also, the onreadystatechange event doesn't seem to trigger until after the entire file has been processed by the client anyway.
I have tried to simplify the problem by getting rid of the irrelevant details and my current demo code follows:
Server.js
// This code is based on
// https://stackoverflow.com/questions/23691194/node-express-file-upload
//
// [1] - https://stackoverflow.com/questions/39681966/
// avoiding-further-processing-on-busyboy-file-upload-size-limit
//
// [2] - https://stackoverflow.com/questions/18310394/
// no-access-control-allow-origin-node-apache-port-issue
//
// [3] - https://stackoverflow.com/questions/39681966/
// avoiding-further-processing-on-busyboy-file-upload-size-limit
//
// [4] - https://stackoverflow.com/questions/44736327/
// node-js-cors-issue-response-to-preflight-
// request-doesnt-pass-access-control-c
var express = require('express');
var busboy = require('connect-busboy');
var fs = require('fs-extra');
var cors = require('cors'); // [4]
const app = express();
// See [2][4]
app.use(
function(req, res, next) {
res.setHeader("Access-Control-Allow-Origin", "null");
res.setHeader("Access-Control-Allow-Methods", "GET, PUT, POST")
next();
}
);
app.options('*', cors());
app.use(
busboy({ limits: { files: 1, fileSize: 500000000 } }) // [1]
);
app.post('/uploadEndpoint', function (req, res, next) {
var fStream;
req.pipe(req.busboy);
req.busboy.on('file', function (fieldName, file, filename) {
console.log("Uploading: " + filename);
var destPath = './' + filename;
fStream = fs.createWriteStream(destPath);
file.pipe(fStream);
// ed-ta [3]
// Despite being asynchronous limit_reach
// will be seen here as true if it hits max size
// as set in file.on.limit because once it hits
// max size the stream will stop and on.finish
// will be triggered.
var limit_reach = false;
req.busboy.on('finish', function() {
if(!limit_reach){
res.send(filename + " uploaded");
}
});
file.on('limit', function() {
fs.unlink(destPath, function(){
limit_reach = true;
res.status(455).send("File too big.");
console.log('Telling client to stop...');
});
});
});
})
app.listen(8000);
test.html
<!DOCTYPE html>
<!--
This code is based off timdream and Basar at
https://stackoverflow.com/questions/6211145/
upload-file-with-ajax-xmlhttprequest
[1] - https://stackoverflow.com/questions/49692745/
express-using-multer-error-multipart-boundary-not-found-request-sent-by-pos
-->
<html>
<head>
<meta charset="utf-8" />
<script>
function uploadFile() {
var progress = document.getElementById("output");
progress.innerText = "Starting";
var fileCtl = document.getElementById("theFile");
var file = fileCtl.files[0];
var xhr = new XMLHttpRequest();
// timdream
var formData = new FormData();
formData.append('theFile', file);
xhr.upload.onprogress = (progressEvent) => {
var percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
progress.innerText =
"Percent Uploaded: " + percentCompleted + "%";
};
xhr.onreadystatechange = function(e) {
if (this.status === 455) {
alert('Sorry, file was too big.');
}
};
xhr.open('post', 'http://localhost:8000/uploadEndpoint', true);
// Apparently this line causes issues Multipart Boundary not
// found error [1]
// xhr.setRequestHeader("Content-Type","multipart/form-data");
// timdream
xhr.send(formData);
}
</script>
</head>
<body>
<input type="file" id="theFile" name="theName" /><br />
<div id="output">Upload Progress</div>
<input type="button" id="theButton"
onclick="uploadFile();" value="Send" />
</body>
</html>
1 - I could setup another endpoint on the Node.js server and use AJAX to poll that endpoint for the current status inside the client's onprogress but that seems like a kludgy solution that would waste bandwidth.
To get around the problem described above, I wound up using a separate WebSocket channel to send a message from the server back down to the client to tell the said client to stop the upload. I then called abort on the client's XMLHttpRequest object per the Mozilla docs.
Final sample code looks like this:
Server.js
// This code is based on
// https://stackoverflow.com/questions/23691194/node-express-file-upload
//
// [1] - https://stackoverflow.com/questions/39681966/
// avoiding-further-processing-on-busyboy-file-upload-size-limit
//
// [2] - https://stackoverflow.com/questions/18310394/
// no-access-control-allow-origin-node-apache-port-issue
//
// [3] - https://stackoverflow.com/questions/39681966/
// avoiding-further-processing-on-busyboy-file-upload-size-limit
//
// [4] - https://stackoverflow.com/questions/44736327/
// node-js-cors-issue-response-to-preflight-
// request-doesnt-pass-access-control-c
var express = require('express');
var busboy = require('connect-busboy');
var fs = require('fs-extra');
var cors = require('cors'); // [4]
var ws = require('ws');
var WebSocketServer = ws.WebSocketServer;
var g_ws;
// BEGIN FROM: https://www.npmjs.com/package/ws
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', function connection(ws) {
g_ws = ws;
});
// END FROM: https://www.npmjs.com/package/ws
const app = express();
// See [2][4]
app.use(
function(req, res, next) {
res.setHeader("Access-Control-Allow-Origin", "null");
res.setHeader("Access-Control-Allow-Methods", "GET, PUT, POST")
next();
}
);
app.options('*', cors());
app.use(
busboy({ limits: { files: 1, fileSize: 300000000 } }) // [1]
);
app.post('/uploadEndpoint', function (req, res, next) {
var fStream;
req.pipe(req.busboy);
req.busboy.on('file', function (fieldName, file, fileNameObject) {
var filename = fileNameObject.filename;
console.log("Uploading: " + filename);
var destPath = './' + filename;
fStream = fs.createWriteStream(destPath);
file.pipe(fStream);
// ed-ta [3]
// Despite being asynchronous limit_reach
// will be seen here as true if it hits max size
// as set in file.on.limit because once it hits
// max size the stream will stop and on.finish
// will be triggered.
var limit_reach = false;
req.busboy.on('finish', function() {
var message;
if(!limit_reach){
message = 'success';
res.send(filename + " uploaded");
} else {
message = 'TooBig';
}
g_ws.send(message);
});
file.on('limit', function() {
fs.unlink(destPath, function(){
limit_reach = true;
res.status(455).send("File too big.");
console.log('Telling client to stop...');
// https://www.npmjs.com/package/ws
g_ws.send("TooBig");
});
});
});
})
app.listen(8000);
test.html
<!DOCTYPE html>
<!--
This code is based off timdream and Basar at
https://stackoverflow.com/questions/6211145/
upload-file-with-ajax-xmlhttprequest
[1] - https://stackoverflow.com/questions/49692745/
express-using-multer-error-multipart-boundary-not-found-request-sent-by-pos
-->
<html>
<head>
<meta charset="utf-8" />
<script>
function uploadFile() {
var progress = document.getElementById("output");
progress.innerText = "Starting";
var fileCtl = document.getElementById("theFile");
var file = fileCtl.files[0];
var xhr = new XMLHttpRequest();
// https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
const socket = new WebSocket('ws://localhost:8080');
socket.addEventListener('message', function (event) {
if (event.data === 'TooBig') {
// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/abort
xhr.abort();
alert('Server says file was too big.');
} else if (event.data === 'success') {
alert('File uploaded sucessfully.');
} else {
alert('Unknown server error');
}
socket.close();
});
// timdream
var formData = new FormData();
formData.append('theFile', file);
xhr.upload.onprogress = (progressEvent) => {
var percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
progress.innerText =
"Percent Uploaded: " + percentCompleted + "%";
};
xhr.onreadystatechange = function(e) {
if (this.status === 455) {
alert('Sorry, file was too big.');
}
};
xhr.open('post', 'http://localhost:8000/uploadEndpoint', true);
// Apparently this line causes issues Multipart Boundary not
// found error [1]
// xhr.setRequestHeader("Content-Type","multipart/form-data");
// timdream
xhr.send(formData);
}
</script>
</head>
<body>
<input type="file" id="theFile" name="theName" /><br />
<div id="output">Upload Progress</div>
<input type="button" id="theButton"
onclick="uploadFile();" value="Send" />
</body>
</html>
This is not the exact code I used for my solution but this simplified working demo illustrates the concept.
BTW: Filesize limit was lowered to 300000000 on the server to make testing easier but that doesn't matter.

No audio in above 360p video downloaded from ytdl-core express?

I have made an api which is downloading videos from the link of youtube link but I'm not enable to download the video with its audio above 360p format. It is downloading only video and there is no audio.
Is there any solution to this ?
Typically 1080p or better video does not have audio encoded with it. The audio must be downloaded separately and merged via an appropriate encoding library. ffmpeg is the most widely used tool, with many Node.js modules available. Use the format objects returned from ytdl.getInfo to download specific streams to combine to fit your needs. Look at https://github.com/fent/node-ytdl-core/blob/master/example/ffmpeg.js for an example on doing this.
You can specific the video quality
const video = ytdl('http://www.youtube.com/watch?v=e_RsG3HPpA0',{quality: 18});
video.on('progress', function(info) {
console.log('Download progress')
})
video.on('end', function(info) {
console.log('Download finish')
})
video.pipe(fs.createWriteStream('video.mp4'));
Please check the option of quality value by this link
If you are using ytdl-core then the problem is with the itags and the availability of the itag you need.There are 3 itags only that support both video and audio and rest itags only support either audio or just video. For video and audio in ytdl-core you need to specifically check if the URl supports 720p or 1080p or not. I created two functions that can help you alot. what you can do is that simple send an xmlhttprequest from index.html and wait for link response so that you or your user can download from that link. "fup90" means false url provided and inc90 denotes incorrect URL so that you can handle the error if the URL is not a youtube URL. The code is shown below and note that you send an xmlhttpreqest using post method and with data in json string in this format var data = {downloadType:"audio"/"video",quality:"required itag",url:"youtube video url"}.
const ytdl = require('ytdl-core');
const express = require('express');
const parser = require('body-parser');
const app = express();
app.use(parser.text());
app.use(express.static(__dirname + "\\static"));
// video formats 18 - 360p and 22 - 720p
// audio
async function getAudioData(videoURL) {
let videoid = await ytdl.getURLVideoID(videoURL);
let info = await ytdl.getInfo(videoid);
// let format = ytdl.chooseFormat(info.formats, { quality: '134' }); for video
// var format = ytdl.filterFormats(info.formats, 'videoandaudio');
let format = ytdl.filterFormats(info.formats, 'audioonly');
let required_url = 0;
format.forEach(element => {
if (element.mimeType == `audio/mp4; codecs="mp4a.40.2"`) {
required_url = element.url;
}
});
return required_url;
}
async function getVideoData(videoURL, qualityCode) {
try {
let videoid = ytdl.getURLVideoID(videoURL);
let info = await ytdl.getInfo(videoid);
var ifExists = true;
if (qualityCode == 22) {
info.formats.forEach(element => {
if (element.itag == 22) {
qualityCode = 22;
} else {
qualityCode = 18;
ifExists = false;
}
});
}
let format = ytdl.chooseFormat(info.formats, { quality: qualityCode });
var answers = {
url: format.url,
exists: ifExists
}
} catch (e) {
var answers = {
url: "fup90",
exists: false
}
}
return answers;
}
app.get("/", (req, res) => {
res.sendFile(__dirname + "\\index.html");
});
app.post("/getdownload", async(req, res) => {
let data = JSON.parse(req.body);
if (data.downloadType === "video") {
var answer = await getVideoData(data.url, data.quality);
if (answer.url === "fup90") {
res.send("inc90");
} else {
res.send(answer.exists ? answer.url : "xformat");
}
} else if (data.downloadType === "audio") {
var audioLink = await getAudioData(data.url);
res.send(audioLink);
} else {
res.send("error from server");
}
});
app.listen(8000, () => {
console.log("server started at http://localhost:8000");
});

I am running a simple Node.JS server that gives data in stream in application/octet-stream format

I have a simple setup that includes a simple web interface that uses webcam to send the media data to Node.JS Server, the server then sends this data to another where I need to display the live video on the interface. The issue that I am facing is that I am getting the data in a Node.JS Stream and when I convert it into Blob I see a Content Type of application/octet-stream. I not able to figure out how to show this data in video tag in HTML.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Jimmy Test Receiver</title>
</head>
<body>
Receiver
<button onclick="connect()">Connect</button>
<video id="videoElement"></video>
</body>
<script src="https://cdn.jsdelivr.net/npm/socket.io-client#2/dist/socket.io.js"></script>
<script src="stream.js"></script>
<script>
var mediaStream = new MediaStream();
</script>
<script>
function connect() {
var socket = io.connect('http://localhost/user');
var stream;
var data;
socket.on('connect', function () {
console.log('connected');
stream = ss.createStream();
ss(socket).emit('file', stream);
stream.on('data', (chunk) => {
console.log('data in stream', chunk);
if (!data) {
data = chunk;
}
data += chunk;
// The data received received here is application/octet-stream and its the data from a webcam from another client side. Now I want this data to show in the video tag so that this user can see the live stream of another user's video
document.getElementById('videoElement').src = window.URL.createObjectURL(new Blob([data]));
});
stream.on('finish', () => {
const blob = new Blob([data], {type: 'video/webm;codecs=vp9'});
var reader = new FileReader();
reader.readAsDataURL(blob);
reader.onloadend = function () {
var base64data = reader.result;
// console.log(base64data);
//
// const ele = document.createElement('img');
//
// ele.src = base64data;
//
// document.getElementsByTagName('body')[0].appendChild(ele);
};
console.log('stream has finished', blob);
});
});
}
</script>
</html>

Express / torrent-stream: writing a file from stream, sending socket to client with url but client cannot find file

I'm working on a personal project which basically takes a magnet link, starts downloading the file and then renders the video in the torrent in the browser. I'm using an npm module called torrent-stream to do most of this. Once I create the readable stream and begin writing the file I want to send a socket message to the client with the video url so that the client can render a html5 video element and begin streaming the video.
The problem I am having is that once the client renders the video element and tries to find the source mp4 I get a 404 error not found on the video file. Any advice on this would be highly appreciated mates. :)
Controller function:
uploadVideo: function (req, res) {
var torrentStream = require('torrent-stream');
var mkdirp = require('mkdirp');
var rootPath = process.cwd();
var magnetLink = req.param('magnet_link');
var fs = require('fs');
var engine = torrentStream(magnetLink);
engine.on('ready', function() {
engine.files.forEach(function(file) {
var fileName = file.name;
var filePath = file.path;
console.log(fileName + ' - ' + filePath);
var stream = file.createReadStream();
mkdirp(rootPath + '/assets/videos/' + fileName, function (err) {
if (err) {
console.log(err);
} else {
var videoPath = rootPath + '/assets/videos/' + fileName + '/video.mp4';
var writer = fs.createWriteStream(videoPath);
var videoSent = false;
stream.on('data', function (data) {
writer.write(data);
if (!videoSent) {
fs.exists(videoPath, function(exists) {
if (exists) {
sails.sockets.broadcast(req.param('room'), 'video_ready', {videoPath: '/videos/' + fileName + '/video.mp4'});
videoSent = true;
}
});
}
});
// stream is readable stream to containing the file content
}
});
});
});
res.json({status: 'downloading'});
}
Client javascript:
$(document).ready(function () {
io.socket.on('connect', function () {
console.log('mrah');
io.socket.get('/join', {roomName: 'cowboybebop'});
io.socket.on('message', function (data) {
console.log(data);
});
io.socket.on('video_ready', function (data) {
var video = $('<video width="320" height="240" controls>\
<source src="' + data.videoPath + '" type="video/mp4">\
Your browser does not support the video tag.\
</video>');
$('body').append(video);
});
});
$('form').submit(function (e) {
e.preventDefault();
var formData = $(this).serialize();
$.ajax({
url: '/upload-torrent',
method: 'POST',
data: formData
}).success(function (data) {
console.log(data);
}).error(function (err) {
console.log(err);
});
});
});
Form:
<form action="/upload-torrent" method="POST">
<input name="magnet_link" type="text" value="magnet:?xt=urn:btih:565DB305A27FFB321FCC7B064AFD7BD73AEDDA2B&dn=bbb_sunflower_1080p_60fps_normal.mp4&tr=udp%3a%2f%2ftracker.openbittorrent.com%3a80%2fannounce&tr=udp%3a%2f%2ftracker.publicbt.com%3a80%2fannounce&ws=http%3a%2f%2fdistribution.bbb3d.renderfarming.net%2fvideo%2fmp4%2fbbb_sunflower_1080p_60fps_normal.mp4"/>
<input type="hidden" name="room" value="cowboybebop">
<input type="submit" value="Link Torrent">
You may be interested in Torrent Stream Server. It a server that downloads and streams video at the same time, so you can watch the video without fully downloading it. It's based on the same torrent-stream library which you are exploring and has functionally you are trying to implement, so it may be a good reference for you.
Also, I suggest taking a look at webtorrent. It's a nice torrent library that works in both: NodeJs & browser and has streaming support. It sounds great, but from my experience, it doesn't have very good support in the browser.

Resources