I have a feature for users to be able to export the database information. But if the user choose all options to export it takes 1 minute or more to download the .csv file. I'm only including 1 part of the if statement, where I'm pulling in all the data.
here it is:
function exportTheData() {
//get the data for data array
if(exportVolumeData == 1) {
for(j=0; j<plantData1.length; j++) {
i = plantData["MergeKey_lvl00"].indexOf(plantData1["MergeKey_lvl00"][j]);
data.push(plantData["PlantName"][i]);
if(statesExport == 1) {
countyindex = counties["CountyId"].indexOf(plantData["LocationId"][i]);
stateid = counties["StateId"][countyindex];
statename = states["StateName"][states["StateId"].indexOf(stateid)];
data.push(statename);
}
if(countyExport == 1) {
countyindex = counties["CountyId"].indexOf(plantData["LocationId"][i]);
countyname = counties["CountyName"][countyindex];
data.push(countyname);
}
if(basinsExport == 1) {
countyindex = counties["CountyId"].indexOf(plantData["LocationId"][i]);
subbasinid = counties["SubBasinId"][countyindex];
subbasinindex = basinSub["SubBasinId"].indexOf(subbasinid);
basinid = basinSub["BasinId"][subbasinindex];
basinindex = basin["BasinId"].indexOf(basinid);
basinname = basin["BasinName"][basinindex];
data.push(basinname);
}
if(subBasinsExport == 1) {
countyindex = counties["CountyId"].indexOf(plantData["LocationId"][i]);
subbasinid = counties["SubBasinId"][countyindex];
subbasinindex = basinSub["SubBasinId"].indexOf(subbasinid);
subbasinname = basinSub["SubBasinName"][subbasinindex];
data.push(subbasinname);
}
if(paddsExport == 1) {
countyindex = counties["CountyId"].indexOf(plantData["LocationId"][i]);
subpaddid = counties["SubPaddId"][countyindex];
subpaddindex = paddSub["SubPaddId"].indexOf(subpaddid);
paddid = paddSub["PaddId"][subpaddindex];
paddindex = padd["PaddId"].indexOf(paddid);
paddname = padd["PaddName"][paddindex];
data.push(paddname);
}
if(subPaddsExport == 1) {
countyindex = counties["CountyId"].indexOf(plantData["LocationId"][i]);
subpaddid = counties["SubPaddId"][countyindex];
subpaddname = paddSub["SubPaddName"][paddSub["SubPaddId"].indexOf(subpaddid)];
data.push(subpaddname);
}
if(fullNameExport == 1) {
companyindex = getCompanyInfo["MergeKey_lvl00"].indexOf(plantData["OperatorId"][i]);
fullname = getCompanyInfo["FullName"][companyindex];
data.push(fullname);
}
if(shortNameExport == 1) {
companyindex = getCompanyInfo["MergeKey_lvl00"].indexOf(plantData["OperatorId"][i]);
shortname = getCompanyInfo["ShortName"][companyindex];
data.push(shortname);
}
if(tickerExport == 1) {
companyindex = getCompanyInfo["MergeKey_lvl00"].indexOf(plantData["OperatorId"][i]);
ticker = getCompanyInfo["Ticker"][companyindex];
data.push(ticker);
}
volumeindex = plantData1["MergeKey_lvl00"].indexOf(plantData["MergeKey_lvl00"][i]);
startdate = plantData1["MonthStartDate"][volumeindex];
volumetypeindex = plantData2["VolumeTypeId"].indexOf(plantData1["VolumeTypeId"][j]);
volumetype = plantData2["VolumeType"][volumetypeindex];
volumeunit = plantData2["Unit"][volumetypeindex];
volume = plantData1["Volume"][volumeindex];
data.push(startdate);
data.push(volumetype);
data.push(volumeunit);
data.push(volume);
}
/* * Convert our data to CSV string */
var CSVString = prepCSVRow(titles, titles.length, '');
CSVString = prepCSVRow(data, titles.length, CSVString);
/* * Make CSV downloadable*/
var downloadLink = document.createElement("a");
var blob = new Blob(["\ufeff", CSVString]);
var url = URL.createObjectURL(blob);
downloadLink.href = url;
downloadLink.download = "data.csv";
/** Actually download CSV */
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
}
Here is where I convert the data array to csv string:
function prepCSVRow(arr, columnCount, initial) {
var row = ''; // this will hold data
var delimeter = ','; // data slice separator, in excel it's `;`, in usual CSv it's `,`
var newLine = '\r\n'; // newline separator for CSV row
/* * Convert [1,2,3,4] into [[1,2], [3,4]] while count is 2
* #param _arr {Array} - the actual array to split
* #param _count {Number} - the amount to split
* return {Array} - splitted array */
function splitArray(_arr, _count) {
var splitted = [];
var result = [];
_arr.forEach(function(item, idx) {
if ((idx + 1) % _count === 0) {
splitted.push('"' + item + '"');
result.push(splitted);
splitted = [];
} else {
splitted.push('"' + item + '"');
}
});
return result;
}
var plainArr = splitArray(arr, columnCount);
// don't know how to explain this
// you just have to like follow the code
// and you understand, it's pretty simple
// it converts `['a', 'b', 'c']` to `a,b,c` string
plainArr.forEach(function(arrItem) {
arrItem.forEach(function(item, idx) {
row += item + ((idx + 1) === arrItem.length ? '' : delimeter);
});
row += newLine;
});
return initial + row;
}
Any idea on how I could speed this up? We have over 6,000 rows of data in the database. Thanks!
Changing the data.push.. to data[data.length] = .. has made the function a lot faster. Also, I created variables for countyindex and companyindex instead of calling it multiple times in the same function.
Also, cleaning up the last part of the function really helped:
/* * Convert our data to CSV string */
var CSVString = prepCSVRow(titles, titles.length);
CSVString += prepCSVRow(data, titles.length);
/* * Make CSV downloadable*/
var downloadLink = document.createElement('a'),
blob = new Blob(['\ufeff', CSVString]);
downloadLink.href = URL.createObjectURL(blob);
downloadLink.download = 'data.csv';
/** Actually download CSV */
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
Related
I am learning Javascript and this is my first time working with Google Sheets Apps Script. What I am doing is taking a large JSON file and importing it into my sheet. After that I am populating a few hundred properties based on the key:value found in the JSON.
This is how it kinda works right now:
Go to first column and first row of my sheet.
Get the name (property name).
Search the JSON for the key and then grab the value.
Update a neighbor cell with the value found in the JSON.
Right now it all works the only issue is it seems to be pretty slow. It takes about .5-1 second per lookup and when I have 200+ properties it just seems slow. This might just be a limitation or it might be my logic.
My sheet can be found here: https://docs.google.com/spreadsheets/d/1tt3eh1RjL_CbUIaPzj10DbocgyDC0iNRIba2B4YTGgg/edit#gid=0
My function that does everything:
function parse() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var range = sheet.getRange(2,1);
var range1 = sheet.getRange("A2");
var cell = range.getCell(1, 1);
var event_line = cell.getValue();
var tmp = event_line.split(". ");
var col_number = tmp[0];
var event_name = tmp[1];
event_json = get_json_from_cell(col_number);
const obj = JSON.parse(event_json);
var traits = obj.context.traits;
var properties = obj.properties;
//Get the range for the section where properties are
var traits_range = sheet.getRange("contextTraits");
var allprop = sheet.getRange("testAll");
var alllen = allprop.getNumRows();
var length = traits_range.getNumRows();
for (var i = 1; i < length; i++) {
var cell = traits_range.getCell(i, 1);
var req = traits_range.getCell(i, 4).getValue();
var trait = cell.getValue();
var result = traits[trait];
var result_cell = traits_range.getCell(i, 3);
if (result == undefined) {
if (req == "y"){
result = "MISSING REQ";
result_cell.setBackground("red");
} else {
result = "MISSING";
result_cell.setBackground("green");
}
} else {
result_cell.setBackground("blue");
}
result_cell.setValue(result);
Logger.log(result);
}
for (var i = 1; i < alllen; i++) {
var cell = allprop.getCell(i,1);
var req = allprop.getCell(i, 4).getValue();
var prop = cell.getValue();
var result = properties[prop];
var result_cell = allprop.getCell(i, 3);
if (result == undefined) {
if (req == "y"){
result = "MISSING REQ";
result_cell.setBackground("red");
} else {
result = "MISSING";
result_cell.setBackground("green");
}
} else {
result_cell.setBackground("blue");
}
result_cell.setValue(result);
}
Logger.log(result);
}
I want to export the current google sheet data into pdf page but I do want a page break at row 10 so that any data after row 10 goes on to the next page. This means there will be multiple pages for more data. I do not want to print all data on one page. The final pdf file should have multiple pages for data that crosses row 10 in the google sheet
var saveToRootFolder = false
function onOpen() {
SpreadsheetApp.getUi()
.createAddonMenu()
.addItem('Export all sheets', 'exportAsPDF')
.addItem('Export all sheets as separate files', 'exportAllSheetsAsSeparatePDFs')
.addItem('Export current sheet', 'exportCurrentSheetAsPDF')
.addItem('Export selected area', 'exportPartAsPDF')
.addItem('Export predefined area', 'exportNamedRangesAsPDF')
.addToUi()
}
function _exportBlob(blob, fileName, spreadsheet) {
blob = blob.setName(SpreadsheetApp.getActiveSheet().getRange('E4:G4').getValue())
var folder = saveToRootFolder ? DriveApp : DriveApp.getFileById(spreadsheet.getId()).getParents().next()
var pdfFile = folder.createFile(blob)
var folder = DriveApp.getFolderById("FOLDER_ID");
pdfFile.moveTo(folder);
// Display a modal dialog box with custom HtmlService content.
const htmlOutput = HtmlService
.createHtmlOutput('<p>Click to open ' + fileName + '</p>')
.setWidth(300)
.setHeight(80)
SpreadsheetApp.getUi().showModalDialog(htmlOutput, 'Export Successful')
}
function exportAsPDF() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet()
var blob = _getAsBlob(spreadsheet.getUrl())
_exportBlob(blob, spreadsheet.getName(), spreadsheet)
}
function _getAsBlob(url, sheet, range) {
var rangeParam = ''
var sheetParam = ''
if (range) {
rangeParam =
'&r1=' + (range.getRow() - 1)
+ '&r2=' + range.getLastRow()
+ '&c1=' + (range.getColumn() - 1)
+ '&c2=' + range.getLastColumn()
}
if (sheet) {
sheetParam = '&gid=' + sheet.getSheetId()
}
// A credit to https://gist.github.com/Spencer-Easton/78f9867a691e549c9c70
// these parameters are reverse-engineered (not officially documented by Google)
// they may break overtime.
var exportUrl = url.replace(/\/edit.*$/, '')
+ '/export?exportFormat=pdf&format=pdf'
+ '&size=A4'
+ '&portrait=true'
+ '&fitw=true'
+ '&top_margin=0.2'
+ '&bottom_margin=0.2'
+ '&left_margin=0.2'
+ '&right_margin=0.1'
+ '&scale=3'
+ '&sheetnames=false&printtitle=false'
+ '&pagenum=UNDEFINED' // change it to CENTER to print page numbers
+ '&gridlines=true'
+ '&fzr=FALSE'
+ sheetParam
+ rangeParam
Logger.log('exportUrl=' + exportUrl)
var response
var i = 0
for (; i < 5; i += 1) {
response = UrlFetchApp.fetch(exportUrl, {
muteHttpExceptions: true,
headers: {
Authorization: 'Bearer ' + ScriptApp.getOAuthToken(),
},
})
if (response.getResponseCode() === 429) {
// printing too fast, retrying
Utilities.sleep(3000)
} else {
break
}
}
if (i === 5) {
throw new Error('Printing failed. Too many sheets to print.')
}
return response.getBlob()
}
function exportAllSheetsAsSeparatePDFs() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet()
var files = []
var folder = saveToRootFolder ? DriveApp : DriveApp.getFileById(spreadsheet.getId()).getParents().next()
spreadsheet.getSheets().forEach(function (sheet) {
spreadsheet.setActiveSheet(sheet)
var blob = _getAsBlob(spreadsheet.getUrl(), sheet)
var fileName = sheet.getName()
blob = blob.setName(fileName)
var pdfFile = folder.createFile(blob)
files.push({
url: pdfFile.getUrl(),
name: fileName,
})
})
const htmlOutput = HtmlService
.createHtmlOutput('<p>Click to open PDF files</p>'
+ '<ul>'
+ files.reduce(function (prev, file) {
prev += '<li>' + file.name + '</li>'
return prev
}, '')
+ '</ul>')
.setWidth(300)
.setHeight(150)
SpreadsheetApp.getUi().showModalDialog(htmlOutput, 'Export Successful')
}
function exportCurrentSheetAsPDF() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet()
var currentSheet = SpreadsheetApp.getActiveSheet()
var blob = _getAsBlob(spreadsheet.getUrl(), currentSheet)
_exportBlob(blob, currentSheet.getName(), spreadsheet)
}
function exportPartAsPDF(predefinedRanges) {
var ui = SpreadsheetApp.getUi()
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet()
var selectedRanges
var fileSuffix
if (predefinedRanges) {
selectedRanges = predefinedRanges
fileSuffix = '-predefined'
} else {
var activeRangeList = spreadsheet.getActiveRangeList()
if (!activeRangeList) {
ui.alert('Please select at least one range to export')
return
}
selectedRanges = activeRangeList.getRanges()
fileSuffix = '-selected'
}
if (selectedRanges.length === 1) {
// special export with formatting
var currentSheet = selectedRanges[0].getSheet()
var blob = _getAsBlob(spreadsheet.getUrl(), currentSheet, selectedRanges[0])
var fileName = spreadsheet.getName() + fileSuffix
_exportBlob(blob, fileName, spreadsheet)
return
}
var tempSpreadsheet = SpreadsheetApp.create(spreadsheet.getName() + fileSuffix)
if (!saveToRootFolder) {
DriveApp.getFileById(tempSpreadsheet.getId()).moveTo(DriveApp.getFileById(spreadsheet.getId()).getParents().next())
}
var tempSheets = tempSpreadsheet.getSheets()
var sheet1 = tempSheets.length > 0 ? tempSheets[0] : undefined
SpreadsheetApp.setActiveSpreadsheet(tempSpreadsheet)
tempSpreadsheet.setSpreadsheetTimeZone(spreadsheet.getSpreadsheetTimeZone())
tempSpreadsheet.setSpreadsheetLocale(spreadsheet.getSpreadsheetLocale())
for (var i = 0; i < selectedRanges.length; i++) {
var selectedRange = selectedRanges[i]
var originalSheet = selectedRange.getSheet()
var originalSheetName = originalSheet.getName()
var destSheet = tempSpreadsheet.getSheetByName(originalSheetName)
if (!destSheet) {
destSheet = tempSpreadsheet.insertSheet(originalSheetName)
}
Logger.log('a1notation=' + selectedRange.getA1Notation())
var destRange = destSheet.getRange(selectedRange.getA1Notation())
destRange.setValues(selectedRange.getValues())
destRange.setTextStyles(selectedRange.getTextStyles())
destRange.setBackgrounds(selectedRange.getBackgrounds())
destRange.setFontColors(selectedRange.getFontColors())
destRange.setFontFamilies(selectedRange.getFontFamilies())
destRange.setFontLines(selectedRange.getFontLines())
destRange.setFontStyles(selectedRange.getFontStyles())
destRange.setFontWeights(selectedRange.getFontWeights())
destRange.setHorizontalAlignments(selectedRange.getHorizontalAlignments())
destRange.setNumberFormats(selectedRange.getNumberFormats())
destRange.setTextDirections(selectedRange.getTextDirections())
destRange.setTextRotations(selectedRange.getTextRotations())
destRange.setVerticalAlignments(selectedRange.getVerticalAlignments())
destRange.setWrapStrategies(selectedRange.getWrapStrategies())
}
// remove empty Sheet1
if (sheet1) {
Logger.log('lastcol = ' + sheet1.getLastColumn() + ',lastrow=' + sheet1.getLastRow())
if (sheet1 && sheet1.getLastColumn() === 0 && sheet1.getLastRow() === 0) {
tempSpreadsheet.deleteSheet(sheet1)
}
}
exportAsPDF()
SpreadsheetApp.setActiveSpreadsheet(spreadsheet)
DriveApp.getFileById(tempSpreadsheet.getId()).setTrashed(true)
}
function exportNamedRangesAsPDF() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet()
var allNamedRanges = spreadsheet.getNamedRanges()
var toPrintNamedRanges = []
for (var i = 0; i < allNamedRanges.length; i++) {
var namedRange = allNamedRanges[i]
if (/^print_area_.*$/.test(namedRange.getName())) {
Logger.log('found named range ' + namedRange.getName())
toPrintNamedRanges.push(namedRange.getRange())
}
}
if (toPrintNamedRanges.length === 0) {
SpreadsheetApp.getUi().alert('No print areas found. Please add at least one \'print_area_1\' named range in the menu Data > Named ranges.')
return
} else {
toPrintNamedRanges.sort(function (a, b) {
return a.getSheet().getIndex() - b.getSheet().getIndex()
})
exportPartAsPDF(toPrintNamedRanges)
}
}
I developed an example script to test the approach described in my comment. I'll leave it here for future reference, but feel free to leave a comment if you have any doubt about it. This is the final script:
function exportRowsToPDF() {
var importantRows = [2, 13, 14, 15, 19]; // Exported rows
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var originalSheet = SpreadsheetApp.getActiveSheet();
var sheets = spreadsheet.getSheets();
var tempSheet = spreadsheet.insertSheet("TEMP", 10);
var maxColumn = originalSheet.getLastColumn();
for (var i = 0; i < importantRows.length; i++) {
tempSheet.appendRow(originalSheet.getRange(importantRows[i], 1, 1, maxColumn)
.getValues()[0]);
}
for (var i = 0; i < sheets.length; i++) {
if (sheets[i].getSheetName() !== "TEMP") {
sheets[i].hideSheet();
}
}
DriveApp.createFile(spreadsheet.getBlob().setName("Exported Sheet"));
for (var i = 0; i < sheets.length; i++) {
sheets[i].showSheet();
}
spreadsheet.deleteSheet(tempSheet);
}
You can modify the script to export any particular rows, but I used five of them for testing. This script will create a new sheet on your spreadsheet, and it will copy any relevant row there. Then, it will hide every sheet minus the newly created one. After that it will export the spreadsheet as a PDF blob directly to your Drive. Finally, it will reset the spreadsheet to the original state (by removing the created sheet and unhiding every other sheet). This code uses the same methods as your example, but write to me if you want to clarify any point.
My problem:
I'm trying to merge multiple blob audio files to a single blob and download it on the page.
What I tried:
I tried to concatenate the Audio blobs in the following ways:
Method - 1:
const url = window.URL.createObjectURL(new Blob(fullBlobArray), {
type: 'audio/*'
});
const a = document.createElement("a");
document.body.appendChild(a);
a.style = "display: none";
a.href = url;
a.download = "testing.wav";
a.click();
URL.revokeObjectURL(url);
a.remove();
Method - 2 (Using - ConcatenateBlobs.js plugin - ConcatenateJS)
ConcatenateBlobs(fullBlobArray, 'audio/wav', function (fullBlob) {
const url = window.URL.createObjectURL(fullBlob);
const a = document.createElement("a");
document.body.appendChild(a);
a.style = "display: none";
a.href = url;
a.download = "testing.wav";
a.click();
URL.revokeObjectURL(url);
a.remove();
//Close the window if it downloaded.
window.close();
Output is explained below:
If you have the following audio blobs:
[audio1, audio2, audio3]
Then, after downloading from the above code, only the Audio from the first file (i.e. audio1 ) is getting played. But the file size of the full blob is the total size of audio1 + audio2 + audio3
I couldn't figure out where I went wrong. Kindly help me in this to get rid of this problem.
Finally, found a solution!!!
Thanks for this StackOverflow article. Highly appreciated for the efforts for this article.
Thanks for the commenting out (#Bergi, #Zac, #Peter Krebs in the comments) that we need to Format the blob according to WAV Format
For merging multiple WAV files into a single file and below is the code:
wav_merger.js
var _index;
function readFileAsync(blob) {
return new Promise((resolve, reject) => {
let reader = new FileReader();
reader.addEventListener("loadend", function () {
resolve(reader.result);
});
reader.onerror = reject;
reader.readAsArrayBuffer(blob);
})
}
function getBufferFromBlobs(blobArray) {
return new Promise((resolve, reject) => {
var _arrBytes = [];
var _promises = [];
if (blobArray.length > 0) {
$.each(blobArray, function (index, blob) {
_index = index;
var dfd = $.Deferred();
readFileAsync(blob).then(function (byteArray) {
_arrBytes.push(byteArray);
dfd.resolve();
});
_promises.push(dfd);
});
$.when.apply($, _promises).done(function () {
var _blob = combineWavsBuffers(_arrBytes);
resolve(_blob);
});
}
});
}
function loadWav(blobArray) {
return getBufferFromBlobs(blobArray);
debugger;
// .then(function (bufferArray) {
// return combineWavsBuffers(bufferArray); //Combine original wav buffer and play
//});
}
function combineWavsBuffers(bufferArray) {
if (bufferArray.length > 0) {
var _bufferLengths = bufferArray.map(buffer => buffer.byteLength);
// Getting sum of numbers
var _totalBufferLength = _bufferLengths.reduce(function (a, b) {
return a + b;
}, 0);
var tmp = new Uint8Array(_totalBufferLength);
//Get buffer1 audio data to create the new combined wav
var audioData = getAudioData.WavHeader.readHeader(new DataView(bufferArray[0]));
var _bufferLength = 0;
$.each(bufferArray, function (index, buffer) {
//Combine array bytes of original wavs buffers.
tmp.set(new Uint8Array(buffer), _bufferLength);
_bufferLength+= buffer.byteLength;
});
//Send combined buffer and send audio data to create the audio data of combined
var arrBytesFinal = getWavBytes(tmp, {
isFloat: false, // floating point or 16-bit integer
numChannels: audioData.channels,
sampleRate: audioData.sampleRate,
});
//Create a Blob as Base64 Raw data with audio/wav type
return new Blob([arrBytesFinal], { type: 'audio/wav; codecs=MS_PCM' });
}
return null;
}
//Combine two audio .wav buffers.
function combineWavsBuffers1(buffer1, buffer2) {
//Combine array bytes of original wavs buffers
var tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength);
tmp.set(new Uint8Array(buffer1), 0);
tmp.set(new Uint8Array(buffer2), buffer1.byteLength);
//Get buffer1 audio data to create the new combined wav
var audioData = getAudioData.WavHeader.readHeader(new DataView(buffer1));
console.log('Audio Data: ', audioData);
//Send combined buffer and send audio data to create the audio data of combined
var arrBytesFinal = getWavBytes(tmp, {
isFloat: false, // floating point or 16-bit integer
numChannels: audioData.channels,
sampleRate: audioData.sampleRate,
});
//Create a Blob as Base64 Raw data with audio/wav type
return new Blob([arrBytesFinal], { type: 'audio/wav; codecs=MS_PCM' });
}
//Other functions //////////////////////////////////////////////////////////////
// Returns Uint8Array of WAV bytes
function getWavBytes(buffer, options) {
const type = options.isFloat ? Float32Array : Uint16Array
const numFrames = buffer.byteLength / type.BYTES_PER_ELEMENT
const headerBytes = getWavHeader(Object.assign({}, options, { numFrames }))
const wavBytes = new Uint8Array(headerBytes.length + buffer.byteLength);
// prepend header, then add pcmBytes
wavBytes.set(headerBytes, 0)
wavBytes.set(new Uint8Array(buffer), headerBytes.length)
return wavBytes
}
// adapted from https://gist.github.com/also/900023
// returns Uint8Array of WAV header bytes
function getWavHeader(options) {
const numFrames = options.numFrames
const numChannels = options.numChannels || 2
const sampleRate = options.sampleRate || 44100
const bytesPerSample = options.isFloat ? 4 : 2
const format = options.isFloat ? 3 : 1
const blockAlign = numChannels * bytesPerSample
const byteRate = sampleRate * blockAlign
const dataSize = numFrames * blockAlign
const buffer = new ArrayBuffer(44)
const dv = new DataView(buffer)
let p = 0
function writeString(s) {
for (let i = 0; i < s.length; i++) {
dv.setUint8(p + i, s.charCodeAt(i))
}
p += s.length
}
function writeUint32(d) {
dv.setUint32(p, d, true)
p += 4
}
function writeUint16(d) {
dv.setUint16(p, d, true)
p += 2
}
writeString('RIFF') // ChunkID
writeUint32(dataSize + 36) // ChunkSize
writeString('WAVE') // Format
writeString('fmt ') // Subchunk1ID
writeUint32(16) // Subchunk1Size
writeUint16(format) // AudioFormat
writeUint16(numChannels) // NumChannels
writeUint32(sampleRate) // SampleRate
writeUint32(byteRate) // ByteRate
writeUint16(blockAlign) // BlockAlign
writeUint16(bytesPerSample * 8) // BitsPerSample
writeString('data') // Subchunk2ID
writeUint32(dataSize) // Subchunk2Size
return new Uint8Array(buffer)
}
function getAudioData() {
function WavHeader() {
this.dataOffset = 0;
this.dataLen = 0;
this.channels = 0;
this.sampleRate = 0;
}
function fourccToInt(fourcc) {
return fourcc.charCodeAt(0) << 24 | fourcc.charCodeAt(1) << 16 | fourcc.charCodeAt(2) << 8 | fourcc.charCodeAt(3);
}
WavHeader.RIFF = fourccToInt("RIFF");
WavHeader.WAVE = fourccToInt("WAVE");
WavHeader.fmt_ = fourccToInt("fmt ");
WavHeader.data = fourccToInt("data");
WavHeader.readHeader = function (dataView) {
var w = new WavHeader();
var header = dataView.getUint32(0, false);
if (WavHeader.RIFF != header) {
return;
}
var fileLen = dataView.getUint32(4, true);
if (WavHeader.WAVE != dataView.getUint32(8, false)) {
return;
}
if (WavHeader.fmt_ != dataView.getUint32(12, false)) {
return;
}
var fmtLen = dataView.getUint32(16, true);
var pos = 16 + 4;
switch (fmtLen) {
case 16:
case 18:
w.channels = dataView.getUint16(pos + 2, true);
w.sampleRate = dataView.getUint32(pos + 4, true);
break;
default:
throw 'extended fmt chunk not implemented';
}
pos += fmtLen;
var data = WavHeader.data;
var len = 0;
while (data != header) {
header = dataView.getUint32(pos, false);
len = dataView.getUint32(pos + 4, true);
if (data == header) {
break;
}
pos += (len + 8);
}
w.dataLen = len;
w.dataOffset = pos + 8;
return w;
};
getAudioData.WavHeader = WavHeader;
}
getAudioData();
custom_script.js
getBufferFromBlobs(fullBlobArray).then(function (singleBlob) {
const url = window.URL.createObjectURL(singleBlob);
const a = document.createElement("a");
document.body.appendChild(a);
a.style = "display: none";
a.href = url;
a.download = "testing.wav";
a.click();
URL.revokeObjectURL(url);
a.remove();
});
Have the same problem, thank #Vikash to bring it here. I'm using ConcatenateBlobs.js to concat wav blobs and it seems only working on Chrome. Your solution is great but the source is a bit long, so I tried to fix ConcatenateBlobs.js base on the fact that file length in the header need to be fixed. Luckily, it works:
function ConcatenateBlobs(blobs, type, callback) {
var buffers = [];
var index = 0;
function readAsArrayBuffer() {
if (!blobs[index]) {
return concatenateBuffers();
}
var reader = new FileReader();
reader.onload = function(event) {
buffers.push(event.target.result);
index++;
readAsArrayBuffer();
};
reader.readAsArrayBuffer(blobs[index]);
}
readAsArrayBuffer();
function audioLengthTo32Bit(n) {
n = Math.floor(n);
var b1 = n & 255;
var b2 = (n >> 8) & 255;
var b3 = (n >> 16) & 255;
var b4 = (n >> 24) & 255;
return [b1, b2, b3, b4];
}
function concatenateBuffers() {
var byteLength = 0;
buffers.forEach(function(buffer) {
byteLength += buffer.byteLength;
});
var tmp = new Uint8Array(byteLength);
var lastOffset = 0;
var newData;
buffers.forEach(function(buffer) {
if (type=='audio/wav' && lastOffset > 0) newData = new Uint8Array(buffer, 44);
else newData = new Uint8Array(buffer);
tmp.set(newData, lastOffset);
lastOffset += newData.length;
});
if (type=='audio/wav') {
tmp.set(audioLengthTo32Bit(lastOffset - 8), 4);
tmp.set(audioLengthTo32Bit(lastOffset - 44), 40); // update audio length in the header
}
var blob = new Blob([tmp.buffer], {
type: type
});
callback(blob);
}
}
I'm trying to convert an AudioBuffer into a wav file that I can download.
I tried 2 methods:
The first one, I record all the sounds going threw a mediaRecorder and do this:
App.model.mediaRecorder.ondataavailable = function(evt) {
// push each chunk (blobs) in an array
//console.log(evt.data)
App.model.chunks.push(evt.data);
};
App.model.mediaRecorder.onstop = function(evt) {
// Make blob out of our blobs, and open it.
var blob = new Blob(App.model.chunks, { 'type' : 'audio/wav; codecs=opus' });
createDownloadLink(blob);
};
I create a chunk table containing blobs and then create a new Blob with these chunks. Then in the function "createDownloadLink()" I create an audio node and a download link:
function createDownloadLink(blob) {
var url = URL.createObjectURL(blob);
var li = document.createElement('li');
var au = document.createElement('audio');
li.className = "recordedElement";
var hf = document.createElement('a');
li.style.textDecoration ="none";
au.controls = true;
au.src = url;
hf.href = url;
hf.download = 'myrecording' + App.model.countRecordings + ".wav";
hf.innerHTML = hf.download;
li.appendChild(au);
li.appendChild(hf);
recordingslist.appendChild(li);
}
The audio node is created and I can listen to the sound that I recorded so everything seems to work. But when I download the file it can't be read by any player. I think it's because it's not encoded in WAV so it's not understand.
The second method is the same than above except for the "createDownloadLink()" function.
function createDownloadLink(blob) {
var reader = new FileReader();
reader.readAsArrayBuffer(blob);
App.model.sourceBuffer = App.model.audioCtx.createBufferSource();
reader.onloadend = function()
{
App.model.recordBuffer = reader.result;
App.model.audioCtx.decodeAudioData(App.model.recordBuffer, function(decodedData)
{
App.model.sourceBuffer.buffer = decodedData;
})
}
Here I get an AudioBuffer of the sounds I recorded, but I didn't find how to convert it into a WAV file...
Can you use a variation of this?
https://gist.github.com/asanoboy/3979747
Maybe something like this?
var wav = createWavFromBuffer(convertBlock(decodedData), 44100);
// Then call wav.getBuffer or wav.getWavInt16Array() for the WAV-RIFF formatted data
The other functions here:
class Wav {
constructor(opt_params) {
this._sampleRate = opt_params && opt_params.sampleRate ? opt_params.sampleRate : 44100;
this._channels = opt_params && opt_params.channels ? opt_params.channels : 2;
this._eof = true;
this._bufferNeedle = 0;
this._buffer;
}
setBuffer(buffer) {
this._buffer = this.getWavInt16Array(buffer);
this._bufferNeedle = 0;
this._internalBuffer = '';
this._hasOutputHeader = false;
this._eof = false;
}
getBuffer(len) {
var rt;
if( this._bufferNeedle + len >= this._buffer.length ){
rt = new Int16Array(this._buffer.length - this._bufferNeedle);
this._eof = true;
}
else {
rt = new Int16Array(len);
}
for(var i=0; i<rt.length; i++){
rt[i] = this._buffer[i+this._bufferNeedle];
}
this._bufferNeedle += rt.length;
return rt.buffer;
}
eof() {
return this._eof;
}
getWavInt16Array(buffer) {
var intBuffer = new Int16Array(buffer.length + 23), tmp;
intBuffer[0] = 0x4952; // "RI"
intBuffer[1] = 0x4646; // "FF"
intBuffer[2] = (2*buffer.length + 15) & 0x0000ffff; // RIFF size
intBuffer[3] = ((2*buffer.length + 15) & 0xffff0000) >> 16; // RIFF size
intBuffer[4] = 0x4157; // "WA"
intBuffer[5] = 0x4556; // "VE"
intBuffer[6] = 0x6d66; // "fm"
intBuffer[7] = 0x2074; // "t "
intBuffer[8] = 0x0012; // fmt chunksize: 18
intBuffer[9] = 0x0000; //
intBuffer[10] = 0x0001; // format tag : 1
intBuffer[11] = this._channels; // channels: 2
intBuffer[12] = this._sampleRate & 0x0000ffff; // sample per sec
intBuffer[13] = (this._sampleRate & 0xffff0000) >> 16; // sample per sec
intBuffer[14] = (2*this._channels*this._sampleRate) & 0x0000ffff; // byte per sec
intBuffer[15] = ((2*this._channels*this._sampleRate) & 0xffff0000) >> 16; // byte per sec
intBuffer[16] = 0x0004; // block align
intBuffer[17] = 0x0010; // bit per sample
intBuffer[18] = 0x0000; // cb size
intBuffer[19] = 0x6164; // "da"
intBuffer[20] = 0x6174; // "ta"
intBuffer[21] = (2*buffer.length) & 0x0000ffff; // data size[byte]
intBuffer[22] = ((2*buffer.length) & 0xffff0000) >> 16; // data size[byte]
for (var i = 0; i < buffer.length; i++) {
tmp = buffer[i];
if (tmp >= 1) {
intBuffer[i+23] = (1 << 15) - 1;
}
else if (tmp <= -1) {
intBuffer[i+23] = -(1 << 15);
}
else {
intBuffer[i+23] = Math.round(tmp * (1 << 15));
}
}
return intBuffer;
}
}
// factory
function createWavFromBuffer(buffer, sampleRate) {
var wav = new Wav({
sampleRate: sampleRate,
channels: 1
});
wav.setBuffer(buffer);
return wav;
}
// ArrayBuffer -> Float32Array
var convertBlock = function(buffer) {
var incomingData = new Uint8Array(buffer);
var i, l = incomingData.length;
var outputData = new Float32Array(incomingData.length);
for (i = 0; i < l; i++) {
outputData[i] = (incomingData[i] - 128) / 128.0;
}
return outputData;
}
I have Json data and i need convert json data to Excel file using javascript,
Reference URL : http://jsfiddle.net/hybrid13i/JXrwM/
i am using this code:
function JSONToTSVConvertor(JSONData, ReportTitle, ShowLabel, myTemplateName){
//If JSONData is not an object then JSON.parse will parse the JSON string in an Object
var arrData = typeof JSONData != 'object' ? JSON.parse(JSONData) : JSONData;
var TSV = '';
//Set Report title in first row or line
//TSV += ReportTitle + '\r\n\n';
//This condition will generate the Label/Header
if (ShowLabel) {
var row = "";
//This loop will extract the label from 1st index of on array
for (var index in arrData[0]) {
//Now convert each value to string and tab-seprated
row += index + ' ';
}
row = row.slice(0, -1);
//append Label row with line break
TSV += row + '\r\n';
}
//1st loop is to extract each row
for (var i = 0; i < arrData.length; i++) {
var row = "";
//2nd loop will extract each column and convert it in string tab-seprated
for (var index in arrData[i]) {
row += '"' + arrData[i][index] + '" ';
}
row.slice(0, row.length - 1);
//add a line break after each row
TSV += row + '\r\n';
}
if (TSV == '') {
alert("Invalid data");
return;
}
var blob = new Blob([TSV], {type: "data:text/tsv;charset=utf-8"});
//Generate a file name
var fileName = myTemplateName;
//this will remove the blank-spaces from the title and replace it with an underscore
fileName += ReportTitle.replace(/ /g,"_");
saveAs(blob, ""+fileName+".tsv");
}
this sample code work to csv and tsv format. and i need to Excel format i don't think any idea please help me.
pls suggest some example code.
Thanks...
I have used the following code Javascript JSON to Excel or CSV file download
change file extension only (reports.xlsx or reports.CSV)
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.13.1/xlsx.full.min.js"></script>
<script>
function ExportData()
{
filename='reports.xlsx';
data=[{Market: "IN", New Arrivals: "6", Upcoming Appointments: "2", Pending - 1st Attempt: "4"},
{Market: "KS/MO", New Arrivals: "4", Upcoming Appointments: "4", Pending - 1st Attempt: "2"},
{Market: "KS/MO", New Arrivals: "4", Upcoming Appointments: "4", Pending - 1st Attempt: "2"},
{Market: "KS/MO", New Arrivals: "4", Upcoming Appointments: "4", Pending - 1st Attempt: "2"}]
var ws = XLSX.utils.json_to_sheet(data);
var wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "People");
XLSX.writeFile(wb,filename);
}
</script>
CSV, as said, is excel file itself. But, in many locales, csv generated by the script above is opened incorrectly, where excel puts everything into 1 cell. Small modification of original script helps: just replace header with "sep=,".
var CSV = 'sep=,' + '\r\n\n';
Working example with change here: https://jsfiddle.net/1ecj1rtz/.
Spent some time figuring this out, and therefore answering old thread to help others save some time.
I've created a class to export json data to excel file. I'll be happy if some productive edit is made in my code.
Just add the class in your JS library and call:
var myTestXML = new myExcelXML(myJsonArray);
myTestXML.downLoad();
My myExcelXML Class:
let myExcelXML = (function() {
let Workbook, WorkbookStart = '<?xml version="1.0"?><ss:Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:html="http://www.w3.org/TR/REC-html40">';
const WorkbookEnd = '</ss:Workbook>';
let fs, SheetName = 'SHEET 1',
styleID = 1, columnWidth = 80,
fileName = "Employee_List", uri, link;
class myExcelXML {
constructor(o) {
let respArray = JSON.parse(o);
let finalDataArray = [];
for (let i = 0; i < respArray.length; i++) {
finalDataArray.push(flatten(respArray[i]));
}
let s = JSON.stringify(finalDataArray);
fs = s.replace(/&/gi, '&');
}
downLoad() {
const Worksheet = myXMLWorkSheet(SheetName, fs);
WorkbookStart += myXMLStyles(styleID);
Workbook = WorkbookStart + Worksheet + WorkbookEnd;
uri = 'data:text/xls;charset=utf-8,' + encodeURIComponent(Workbook);
link = document.createElement("a");
link.href = uri;
link.style = "visibility:hidden";
link.download = fileName + ".xls";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
get fileName() {
return fileName;
}
set fileName(n) {
fileName = n;
}
get SheetName() {
return SheetName;
}
set SheetName(n) {
SheetName = n;
}
get styleID() {
return styleID;
}
set styleID(n) {
styleID = n;
}
}
const myXMLStyles = function(id) {
let Styles = '<ss:Styles><ss:Style ss:ID="' + id + '"><ss:Font ss:Bold="1"/></ss:Style></ss:Styles>';
return Styles;
}
const myXMLWorkSheet = function(name, o) {
const Table = myXMLTable(o);
let WorksheetStart = '<ss:Worksheet ss:Name="' + name + '">';
const WorksheetEnd = '</ss:Worksheet>';
return WorksheetStart + Table + WorksheetEnd;
}
const myXMLTable = function(o) {
let TableStart = '<ss:Table>';
const TableEnd = '</ss:Table>';
const tableData = JSON.parse(o);
if (tableData.length > 0) {
const columnHeader = Object.keys(tableData[0]);
let rowData;
for (let i = 0; i < columnHeader.length; i++) {
TableStart += myXMLColumn(columnWidth);
}
for (let j = 0; j < tableData.length; j++) {
rowData += myXMLRow(tableData[j], columnHeader);
}
TableStart += myXMLHead(1, columnHeader);
TableStart += rowData;
}
return TableStart + TableEnd;
}
const myXMLColumn = function(w) {
return '<ss:Column ss:AutoFitWidth="0" ss:Width="' + w + '"/>';
}
const myXMLHead = function(id, h) {
let HeadStart = '<ss:Row ss:StyleID="' + id + '">';
const HeadEnd = '</ss:Row>';
for (let i = 0; i < h.length; i++) {
const Cell = myXMLCell(h[i].toUpperCase());
HeadStart += Cell;
}
return HeadStart + HeadEnd;
}
const myXMLRow = function(r, h) {
let RowStart = '<ss:Row>';
const RowEnd = '</ss:Row>';
for (let i = 0; i < h.length; i++) {
const Cell = myXMLCell(r[h[i]]);
RowStart += Cell;
}
return RowStart + RowEnd;
}
const myXMLCell = function(n) {
let CellStart = '<ss:Cell>';
const CellEnd = '</ss:Cell>';
const Data = myXMLData(n);
CellStart += Data;
return CellStart + CellEnd;
}
const myXMLData = function(d) {
let DataStart = '<ss:Data ss:Type="String">';
const DataEnd = '</ss:Data>';
return DataStart + d + DataEnd;
}
const flatten = function(obj) {
var obj1 = JSON.parse(JSON.stringify(obj));
const obj2 = JSON.parse(JSON.stringify(obj));
if (typeof obj === 'object') {
for (var k1 in obj2) {
if (obj2.hasOwnProperty(k1)) {
if (typeof obj2[k1] === 'object' && obj2[k1] !== null) {
delete obj1[k1]
for (var k2 in obj2[k1]) {
if (obj2[k1].hasOwnProperty(k2)) {
obj1[k1 + '-' + k2] = obj2[k1][k2];
}
}
}
}
}
var hasObject = false;
for (var key in obj1) {
if (obj1.hasOwnProperty(key)) {
if (typeof obj1[key] === 'object' && obj1[key] !== null) {
hasObject = true;
}
}
}
if (hasObject) {
return flatten(obj1);
} else {
return obj1;
}
} else {
return obj1;
}
}
return myExcelXML;
})();
I know its a little late to answer but I have found an nice angular library that does all the hard work it self.
GITHUB: ngJsonExportExcel
Library Direct Download : Download
Filesaver JS : Download
How to use?
Include the module in you app
var myapp = angular.module('myapp', ['ngJsonExportExcel'])
Add a export button and use the ng-json-export-excel directive and pass data into the directive
ng-json-export-excel : it is the directive name
data : it is the data that will be exported (JSON)
report-fields :
pass the column name and the keys that are present in your JSON e.g.
customer_name": "Customer Name"
HTML
<button ng-json-export-excel data="dataList" report-fields="{'uesr.username': 'Heder 1', keyjson2: 'Header 2', keyjson3: 'Head 3'}" filename =" 'export-excel' " separator="," class="css-class"></button>
Excel is a very complex format with many versions. If you really need to do this I would investigate some of the JavaScript libraries that others have written. Do a Google search for "javascript excel writer" to see some examples.
This code snippet is using node.js with the excel4node and express modules in order to convert JSON data to an Excel file and send it to the client, using Javascript.
const xl = require('excel4node');
const express = require('express');
const app = express();
var json = [{"Vehicle":"BMW","Date":"30, Jul 2013 09:24 AM","Location":"Hauz Khas, Enclave, New Delhi, Delhi, India","Speed":42},{"Vehicle":"Honda CBR","Date":"30, Jul 2013 12:00 AM","Location":"Military Road, West Bengal 734013, India","Speed":0},{"Vehicle":"Supra","Date":"30, Jul 2013 07:53 AM","Location":"Sec-45, St. Angel's School, Gurgaon, Haryana, India","Speed":58},{"Vehicle":"Land Cruiser","Date":"30, Jul 2013 09:35 AM","Location":"DLF Phase I, Marble Market, Gurgaon, Haryana, India","Speed":83},{"Vehicle":"Suzuki Swift","Date":"30, Jul 2013 12:02 AM","Location":"Behind Central Bank RO, Ram Krishna Rd by-lane, Siliguri, West Bengal, India","Speed":0},{"Vehicle":"Honda Civic","Date":"30, Jul 2013 12:00 AM","Location":"Behind Central Bank RO, Ram Krishna Rd by-lane, Siliguri, West Bengal, India","Speed":0},{"Vehicle":"Honda Accord","Date":"30, Jul 2013 11:05 AM","Location":"DLF Phase IV, Super Mart 1, Gurgaon, Haryana, India","Speed":71}]
const createSheet = () => {
return new Promise(resolve => {
// setup workbook and sheet
var wb = new xl.Workbook();
var ws = wb.addWorksheet('Sheet');
// Add a title row
ws.cell(1, 1)
.string('Vehicle')
ws.cell(1, 2)
.string('Date')
ws.cell(1, 3)
.string('Location')
ws.cell(1, 4)
.string('Speed')
// add data from json
for (let i = 0; i < json.length; i++) {
let row = i + 2
ws.cell(row, 1)
.string(json[i].Vehicle)
ws.cell(row, 2)
.date(json[i].Date)
ws.cell(row, 3)
.string(json[i].Location)
ws.cell(row, 4)
.number(json[i].Speed)
}
resolve( wb )
})
}
app.get('/excel', function (req, res) {
createSheet().then( file => {
file.write('ExcelFile.xlsx', res);
})
});
app.listen(3040, function () {
console.log('Excel app listening on port 3040');
});