Google AdWords script issue - google-ads-api
I am trying to set up an AdWords script for the first time but cannot get it to function properly. It is essentially supposed to crawl all of our clients' AdWords accounts, send all of the account information to a Google Sheet, and send me an email to let me know if there are any anomalies detected in any of the accounts. I have not had any luck with getting the info to send to the Google sheet, let a lone an email notification. This is the primary error I'm currently getting when I preview the script: TypeError: Cannot call method "getValue" of null. (line 510)
Here is the Google resource page (https://developers.google.com/adwords/scripts/docs/solutions/mccapp-account-anomaly-detector) for the script and the actual script itself that I'm using is below.
Any recommendations on how to get this to function properly would be greatly appreciated. Thank you!
// Copyright 2017, Google Inc. All Rights Reserved.
//
// 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.
/**
* #name MCC Account Anomaly Detector
*
* #fileoverview The MCC Account Anomaly Detector alerts the advertiser whenever
* one or more accounts in a group of advertiser accounts under an MCC account
* is suddenly behaving too differently from what's historically observed. See
* https://developers.google.com/adwords/scripts/docs/solutions/mccapp-account-anomaly-detector
* for more details.
*
* #author AdWords Scripts Team [adwords-scripts#googlegroups.com]
*
* #version 1.4
*
* #changelog
* - version 1.4
* - Added conversions to tracked statistics.
* - version 1.3.2
* - Added validation for external spreadsheet setup.
* - version 1.3.1
* - Improvements to time zone handling.
* - version 1.3
* - Cleanup the script a bit for easier debugging and maintenance.
* - version 1.2
* - Added AdWords API report version.
* - version 1.1
* - Fix the script to work in accounts where there is no stats.
* - version 1.0
* - Released initial version.
*/
var SPREADSHEET_URL = 'https://docs.google.com/a/altitudemarketing.com/spreadsheets/d/1ELWZPcGLqf7n9GDnTx5o7xWOFZHVbgaLakeXAu5NY-E/edit?usp=sharing';
var CONFIG = {
// Uncomment below to include an account label filter
// ACCOUNT_LABEL: 'High Spend Accounts'
};
var CONST = {
FIRST_DATA_ROW: 12,
FIRST_DATA_COLUMN: 2,
MCC_CHILD_ACCOUNT_LIMIT: 50,
TOTAL_DATA_COLUMNS: 9
};
var STATS = {
'NumOfColumns': 4,
'Impressions':
{'Column': 3, 'Color': 'red', 'AlertRange': 'impressions_alert'},
'Clicks': {'Column': 4, 'Color': 'orange', 'AlertRange': 'clicks_alert'},
'Conversions':
{'Column': 5, 'Color': 'dark yellow 2', 'AlertRange': 'conversions_alert'},
'Cost': {'Column': 6, 'Color': 'yellow', 'AlertRange': 'cost_alert'}
};
var DAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday',
'Saturday', 'Sunday'];
/**
* Configuration to be used for running reports.
*/
var REPORTING_OPTIONS = {
// Comment out the following line to default to the latest reporting version.
apiVersion: 'v201605'
};
function main() {
var account;
var alertText = [];
Logger.log('Using spreadsheet - %s.', SPREADSHEET_URL);
var spreadsheet = validateAndGetSpreadsheet(SPREADSHEET_URL);
spreadsheet.setSpreadsheetTimeZone(AdWordsApp.currentAccount().getTimeZone());
var dataRow = CONST.FIRST_DATA_ROW;
SheetUtil.setupData(spreadsheet);
Logger.log('MCC account: ' + mccManager.mccAccount().getCustomerId());
while (account = mccManager.next()) {
Logger.log('Processing account ' + account.getCustomerId());
alertText.push(processAccount(account, spreadsheet, dataRow));
dataRow++;
}
sendEmail(mccManager.mccAccount(), alertText, spreadsheet);
}
/**
* For each of Impressions, Clicks, Conversions, and Cost, check to see if the
* values are out of range. If they are, and no alert has been set in the
* spreadsheet, then 1) Add text to the email, and 2) Add coloring to the cells
* corresponding to the statistic.
*
* #return {string} the next piece of the alert text to include in the email.
*/
function processAccount(account, spreadsheet, startingRow) {
var sheet = spreadsheet.getSheets()[0];
var thresholds = SheetUtil.thresholds();
var today = AdWordsApp.report(SheetUtil.getTodayQuery(), REPORTING_OPTIONS);
var past = AdWordsApp.report(SheetUtil.getPastQuery(), REPORTING_OPTIONS);
var hours = SheetUtil.hourOfDay();
var todayStats = accumulateRows(today.rows(), hours, 1); // just one week
var pastStats = accumulateRows(past.rows(), hours, SheetUtil.weeksToAvg());
var alertText = ['Account ' + account.getCustomerId()];
var validWhite = ['', 'white', '#ffffff']; // these all count as white
// Colors cells that need alerting, and adds text to the alert email body.
function generateAlert(field, emailAlertText) {
// There are 2 cells to check, for Today's value and Past value
var bgRange = [
sheet.getRange(startingRow, STATS[field].Column, 1, 1),
sheet.getRange(startingRow, STATS[field].Column + STATS.NumOfColumns,
1, 1)
];
var bg = [bgRange[0].getBackground(), bgRange[1].getBackground()];
// If both backgrounds are white, change background Colors
// and update most recent alert time.
if ((-1 != validWhite.indexOf(bg[0])) &&
(-1 != validWhite.indexOf(bg[1]))) {
bgRange[0].setBackground([[STATS[field]['Color']]]);
bgRange[1].setBackground([[STATS[field]['Color']]]);
spreadsheet.getRangeByName(STATS[field]['AlertRange']).
setValue('Alert at ' + hours + ':00');
alertText.push(emailAlertText);
}
}
if (thresholds.Impressions &&
todayStats.Impressions < pastStats.Impressions * thresholds.Impressions) {
generateAlert('Impressions',
' Impressions are too low: ' + todayStats.Impressions +
' Impressions by ' + hours + ':00, expecting at least ' +
parseInt(pastStats.Impressions * thresholds.Impressions));
}
if (thresholds.Clicks &&
todayStats.Clicks < (pastStats.Clicks * thresholds.Clicks).toFixed(1)) {
generateAlert('Clicks',
' Clicks are too low: ' + todayStats.Clicks +
' Clicks by ' + hours + ':00, expecting at least ' +
(pastStats.Clicks * thresholds.Clicks).toFixed(1));
}
if (thresholds.Conversions &&
todayStats.Conversions <
(pastStats.Conversions * thresholds.Conversions).toFixed(1)) {
generateAlert(
'Conversions',
' Conversions are too low: ' + todayStats.Conversions +
' Conversions by ' + hours + ':00, expecting at least ' +
(pastStats.Conversions * thresholds.Conversions).toFixed(1));
}
if (thresholds.Cost &&
todayStats.Cost > (pastStats.Cost * thresholds.Cost).toFixed(2)) {
generateAlert(
'Cost',
' Cost is too high: ' + todayStats.Cost + ' ' +
account.getCurrencyCode() + ' by ' + hours +
':00, expecting at most ' +
(pastStats.Cost * thresholds.Cost).toFixed(2));
}
// If no alerts were triggered, we will have only the heading text. Remove it.
if (alertText.length == 1) {
alertText = [];
}
var dataRows = [[
account.getCustomerId(), todayStats.Impressions, todayStats.Clicks,
todayStats.Conversions, todayStats.Cost, pastStats.Impressions.toFixed(0),
pastStats.Clicks.toFixed(1), pastStats.Conversions.toFixed(1),
pastStats.Cost.toFixed(2)
]];
sheet.getRange(startingRow, CONST.FIRST_DATA_COLUMN,
1, CONST.TOTAL_DATA_COLUMNS).setValues(dataRows);
return alertText;
}
var SheetUtil = (function() {
var thresholds = {};
var upToHour = 1; // default
var weeks = 26; // default
var todayQuery = '';
var pastQuery = '';
var setupData = function(spreadsheet) {
Logger.log('Running setupData');
spreadsheet.getRangeByName('date').setValue(new Date());
spreadsheet.getRangeByName('account_id').setValue(
mccManager.mccAccount().getCustomerId());
var getThresholdFor = function(field) {
thresholds[field] = parseField(spreadsheet.
getRangeByName(field).getValue());
};
getThresholdFor('Impressions');
getThresholdFor('Clicks');
getThresholdFor('Conversions');
getThresholdFor('Cost');
var now = new Date();
// Basic reporting statistics are usually available with no more than a 3-hour
// delay.
var upTo = new Date(now.getTime() - 3 * 3600 * 1000);
upToHour = parseInt(getDateStringInTimeZone('h', upTo));
spreadsheet.getRangeByName('timestamp').setValue(
DAYS[getDateStringInTimeZone('u', now)] + ', ' + upToHour + ':00');
if (upToHour == 1) {
// First run of the day, clear existing alerts.
spreadsheet.getRangeByName(STATS['Clicks']['AlertRange']).clearContent();
spreadsheet.getRangeByName(STATS['Impressions']['AlertRange']).
clearContent();
spreadsheet.getRangeByName(STATS['Conversions']['AlertRange'])
.clearContent();
spreadsheet.getRangeByName(STATS['Cost']['AlertRange']).clearContent();
// Reset background and font Colors for all data rows.
var bg = [];
var ft = [];
var bg_single = [
'white', 'white', 'white', 'white', 'white', 'white', 'white', 'white',
'white'
];
var ft_single = [
'black', 'black', 'black', 'black', 'black', 'black', 'black', 'black',
'black'
];
// Construct a 50-row array of colors to set.
for (var a = 0; a < CONST.MCC_CHILD_ACCOUNT_LIMIT; ++a) {
bg.push(bg_single);
ft.push(ft_single);
}
var dataRegion = spreadsheet.getSheets()[0].getRange(
CONST.FIRST_DATA_ROW, CONST.FIRST_DATA_COLUMN,
CONST.MCC_CHILD_ACCOUNT_LIMIT, CONST.TOTAL_DATA_COLUMNS);
dataRegion.setBackgrounds(bg);
dataRegion.setFontColors(ft);
}
var weeksStr = spreadsheet.getRangeByName('weeks').getValue();
weeks = parseInt(weeksStr.substring(0, weeksStr.indexOf(' ')));
var dateRangeToCheck = getDateStringInPast(0, upTo);
var dateRangeToEnd = getDateStringInPast(1, upTo);
var dateRangeToStart = getDateStringInPast(1 + weeks * 7, upTo);
var fields = 'HourOfDay, DayOfWeek, Clicks, Impressions, Conversions, Cost';
todayQuery = 'SELECT ' + fields +
' FROM ACCOUNT_PERFORMANCE_REPORT DURING ' + dateRangeToCheck + ',' +
dateRangeToCheck;
pastQuery = 'SELECT ' + fields +
' FROM ACCOUNT_PERFORMANCE_REPORT WHERE DayOfWeek=' +
DAYS[getDateStringInTimeZone('u', now)].toUpperCase() +
' DURING ' + dateRangeToStart + ',' + dateRangeToEnd;
};
var getThresholds = function() { return thresholds; };
var getHourOfDay = function() { return upToHour; };
var getWeeksToAvg = function() { return weeks; };
var getPastQuery = function() { return pastQuery; };
var getTodayQuery = function() { return todayQuery; };
// The SheetUtil public interface.
return {
setupData: setupData,
thresholds: getThresholds,
hourOfDay: getHourOfDay,
weeksToAvg: getWeeksToAvg,
getPastQuery: getPastQuery,
getTodayQuery: getTodayQuery
};
})();
function sendEmail(account, alertTextArray, spreadsheet) {
var bodyText = '';
alertTextArray.forEach(function(alertText) {
// When zero alerts, this is an empty array, which we don't want to add.
if (alertText.length == 0) { return }
bodyText += alertText.join('\n') + '\n\n';
});
bodyText = bodyText.trim();
var email = spreadsheet.getRangeByName('email').getValue();
if (bodyText.length > 0 && email && email.length > 0 &&
email != 'foo#example.com') {
Logger.log('Sending Email');
MailApp.sendEmail(email,
'AdWords Account ' + account.getCustomerId() + ' misbehaved.',
'Your account ' + account.getCustomerId() +
' is not performing as expected today: \n\n' +
bodyText + '\n\n' +
'Log into AdWords and take a look: ' +
'adwords.google.com\n\nAlerts dashboard: ' +
SPREADSHEET_URL);
}
else if (bodyText.length == 0) {
Logger.log('No alerts triggered. No email being sent.');
}
}
function toFloat(value) {
value = value.toString().replace(/,/g, '');
return parseFloat(value);
}
function parseField(value) {
if (value == 'No alert') {
return null;
} else {
return toFloat(value);
}
}
function accumulateRows(rows, hours, weeks) {
var result = {Clicks: 0, Impressions: 0, Conversions: 0, Cost: 0};
while (rows.hasNext()) {
var row = rows.next();
var hour = row['HourOfDay'];
if (hour < hours) {
result = addRow(row, result, 1 / weeks);
}
}
return result;
}
function addRow(row, previous, coefficient) {
if (!coefficient) {
coefficient = 1;
}
if (!row) {
row = {Clicks: 0, Impressions: 0, Conversions: 0, Cost: 0};
}
if (!previous) {
previous = {Clicks: 0, Impressions: 0, Conversions: 0, Cost: 0};
}
return {
Clicks: parseInt(row['Clicks']) * coefficient + previous.Clicks,
Impressions:
parseInt(row['Impressions']) * coefficient + previous.Impressions,
Conversions:
parseInt(row['Conversions']) * coefficient + previous.Conversions,
Cost: toFloat(row['Cost']) * coefficient + previous.Cost
};
}
function checkInRange(today, yesterday, coefficient, field) {
var yesterdayValue = yesterday[field] * coefficient;
if (today[field] > yesterdayValue * 2) {
Logger.log('' + field + ' too much');
} else if (today[field] < yesterdayValue / 2) {
Logger.log('' + field + ' too little');
}
}
/**
* Produces a formatted string representing a date in the past of a given date.
*
* #param {number} numDays The number of days in the past.
* #param {date} date A date object. Defaults to the current date.
* #return {string} A formatted string in the past of the given date.
*/
function getDateStringInPast(numDays, date) {
date = date || new Date();
var MILLIS_PER_DAY = 1000 * 60 * 60 * 24;
var past = new Date(date.getTime() - numDays * MILLIS_PER_DAY);
return getDateStringInTimeZone('yyyyMMdd', past);
}
/**
* Produces a formatted string representing a given date in a given time zone.
*
* #param {string} format A format specifier for the string to be produced.
* #param {date} date A date object. Defaults to the current date.
* #param {string} timeZone A time zone. Defaults to the account's time zone.
* #return {string} A formatted string of the given date in the given time zone.
*/
function getDateStringInTimeZone(format, date, timeZone) {
date = date || new Date();
timeZone = timeZone || AdWordsApp.currentAccount().getTimeZone();
return Utilities.formatDate(date, timeZone, format);
}
/**
* Module that deals with fetching and iterating through multiple accounts.
*
* #return {object} callable functions corresponding to the available
* actions. Specifically, it currently supports next, current, mccAccount.
*/
var mccManager = (function() {
var accountIterator;
var mccAccount;
var currentAccount;
// Private one-time init function.
var init = function() {
var accountSelector = MccApp.accounts();
// Use this to limit the accounts that are being selected in the report.
if (CONFIG.ACCOUNT_LABEL) {
accountSelector.withCondition("LabelNames CONTAINS '" +
CONFIG.ACCOUNT_LABEL + "'");
}
accountSelector.withLimit(CONST.MCC_CHILD_ACCOUNT_LIMIT);
accountIterator = accountSelector.get();
mccAccount = AdWordsApp.currentAccount(); // save the mccAccount
currentAccount = AdWordsApp.currentAccount();
};
/**
* After calling this, AdWordsApp will have the next account selected.
* If there are no more accounts to process, re-selects the original
* MCC account.
*
* #return {AdWordsApp.Account} The account that has been selected.
*/
var getNextAccount = function() {
if (accountIterator.hasNext()) {
currentAccount = accountIterator.next();
MccApp.select(currentAccount);
return currentAccount;
}
else {
MccApp.select(mccAccount);
return null;
}
};
/**
* Returns the currently selected account. This is cached for performance.
*
* #return {AdWords.Account} The currently selected account.
*/
var getCurrentAccount = function() {
return currentAccount;
};
/**
* Returns the original MCC account.
*
* #return {AdWords.Account} The original account that was selected.
*/
var getMccAccount = function() {
return mccAccount;
};
// Set up internal variables; called only once, here.
init();
// Expose the external interface.
return {
next: getNextAccount,
current: getCurrentAccount,
mccAccount: getMccAccount
};
})();
/**
* Validates the provided spreadsheet URL and email address
* to make sure that they're set up properly. Throws a descriptive error message
* if validation fails.
*
* #param {string} spreadsheeturl The URL of the spreadsheet to open.
* #return {Spreadsheet} The spreadsheet object itself, fetched from the URL.
* #throws {Error} If the spreadsheet URL or email hasn't been set
*/
function validateAndGetSpreadsheet(spreadsheeturl) {
if (spreadsheeturl == 'YOUR_SPREADSHEET_URL') {
throw new Error('Please specify a valid Spreadsheet URL. You can find' +
' a link to a template in the associated guide for this script.');
}
var spreadsheet = SpreadsheetApp.openByUrl(spreadsheeturl);
var email = spreadsheet.getRangeByName('email').getValue();
if ('foo#example.com' == email) {
throw new Error('Please either set a custom email address in the' +
' spreadsheet, or set the email field in the spreadsheet to blank' +
' to send no email.');
}
return spreadsheet;
}
Related
Is there a way to average only some rows of data in a sheet
I have several sheets that import various scores based on file reviews for different areas. I want to calculate an office average for those offices who have had more than one review in each period, but there's no way to tell ahead of time which offices are going to have more than one, so in each list there could be office 1 score office 2 score office 2 score office 3 score Etc. Is there a way to automate this, eg find duplicates and average, or do I have to look through after the imports and do it by hand? Cheers :) Meg
You can use the query function in Sheets. Put this in cell E1: =query(A:C,"select B,avg(C) where B is not null group by B label avg(C) 'Office average' ",1)
function getDataSubset() { const ss = SpreadsheetApp.getActiveSpreadsheet() const ssId = ss.getId(); const sheet = ss.getSheetByName('contact') const sheetName = sheet.getName() const lastRow = sheet.getLastRow(); const lastCol = sheet.getLastColumn(); // let theQuery = "SELECT * WHERE job ='a job'" // works let theQuery = "SELECT A, B WHERE E ='a job' AND G > 30" //works // with header row - result is an array of objects if header row is specified // const a1Range = sheet.getDataRange().getA1Notation(); // let result = Utils.gvizQuery( // ssId // YOUR_SPREADSHEET_ID // ,theQuery // ,sheetName // can be a number (the sheetId), or the name of the sheet; if not needed, but headers are, pass in undefined // ,a1Range // specify range, ex: `A2:O` // ,1 // HEADER_ROW_INDEX_IF_NEEDED> - always a number // ); // no header row - result is an array of arrays const a1Range = sheet.getRange(2,1,lastRow,lastCol).getA1Notation(); let result = Utils.gvizQuery( ssId // YOUR_SPREADSHEET_ID ,theQuery ,sheetName // can be a number (the sheetId), or the name of the sheet; if not needed, but headers are, pass in undefined ,a1Range // specify range, ex: `A2:O` // HEADER_ROW_INDEX_IF_NEEDED> - always a number ); console.log( JSON.stringify(result) ); } /** * https://stackoverflow.com/questions/51327982/how-to-use-google-sheets-query-or-google-visualization-api-from-apps-script/51328419#51328419 */ (function(context) { const Utils = (context.Utils || (context.Utils = {})); /** * Queries a spreadsheet using Google Visualization API's Datasoure Url. * * #param {String} ssId Spreadsheet ID. * #param {String} query Query string. * #param {String|Number} sheetId Sheet Id (gid if number, name if string). [OPTIONAL] * #param {String} range Range [OPTIONAL] * #param {Number} headers Header rows. [OPTIONAL] */ Utils.gvizQuery = function(ssId, query, sheetId, range, headers) { var response = JSON.parse( UrlFetchApp .fetch( Utilities.formatString( "https://docs.google.com/spreadsheets/d/%s/gviz/tq?tq=%s%s%s%s", ssId, encodeURIComponent(query), (typeof sheetId === "number") ? "&gid=" + sheetId : (typeof sheetId === "string") ? "&sheet=" + sheetId : "", (typeof range === "string") ? "&range=" + range : "", "&headers=" + ((typeof headers === "number" && headers > 0) ? headers : "0") ), { "headers":{ "Authorization":"Bearer " + ScriptApp.getOAuthToken() } } ) .getContentText() .replace("/*O_o*/\n", "") // remove JSONP wrapper .replace(/(google\.visualization\.Query\.setResponse\()|(\);)/gm, "") // remove JSONP wrapper ), table = response.table, rows; if (typeof headers === "number") { rows = table.rows.map(function(row) { return table.cols.reduce( function(acc, col, colIndex) { acc[col.label] = row.c[colIndex] && row.c[colIndex].v; return acc; }, {} ); }); } else { rows = table.rows.map(function(row) { return row.c.reduce( function(acc, col) { acc.push(col && col.v); return acc; }, [] ); }); } return rows; }; Object.freeze(Utils); })(this);
Is there a way to generate an SRT file (or similar) using Google Cloud Speech?
In order to generate subtitles for my videos, I converted them to audio files and used the Cloud Speech-to-Text. It works, but it only generates transcriptions, whereas what I need is a *.srt/*.vtt/similar file. What I need is what YouTube does: to generate transcriptions and sync them with the video, like a subtitle format, ie.: transcriptions with the times when captions should appear. Although I could upload them to YouTube and then download their auto-generated captions, it doesn't seem very correct. Is there a way to generate an SRT file (or similar) using Google Cloud Speech?
There's no way really to do this directly from the Speech-to-Text API. What you could try to do is some post-processing on the speech recognition result. For example, here's a request to the REST API using a model meant to transcribe video, with a public google-provided sample file: curl -s -H "Content-Type: application/json" \ -H "Authorization: Bearer "$(gcloud auth application-default print-access-token) \ https://speech.googleapis.com/v1p1beta1/speech:longrunningrecognize \ --data "{ 'config': { 'encoding': 'LINEAR16', 'sampleRateHertz': 16000, 'languageCode': 'en-US', 'enableWordTimeOffsets': true, 'enableAutomaticPunctuation': true, 'model': 'video' }, 'audio': { 'uri':'gs://cloud-samples-tests/speech/Google_Gnome.wav' } }" The above uses asynchronous recognition (speech:longrunningrecognize), which is more fitting for larger files. Enabling punctuation ('enableAutomaticPunctuation': true) in combination with the start and end times of words ('enableWordTimeOffsets': true) near the start and end of each sentence (which you'd also have to convert from nanos to timestamps) could allow you to provide a text file in the srt format. You would probably also have to include some rules about the maximum length of a sentence appearing on the screen at any given time. The above should not be too difficult to implement, however, there's a strong possibility that you would still encounter timing/synchronization issues.
There is no way to do it using Google Cloud itself buy as suggested you may post-process the result. In this file I have made a quick code that kind of does the job. You may want to adapt it to your needs: function convertGSTTToSRT(string) { var obj = JSON.parse(string); var i = 1; var result = '' for (const line of obj.response.results) { result += i++; result += '\n' var word = line.alternatives[0].words[0] var time = convertSecondStringToRealtime(word.startTime); result += formatTime(time) + ' --> ' var word = line.alternatives[0].words[line.alternatives[0].words.length - 1] time = convertSecondStringToRealtime(word.endTime); result += formatTime(time) + '\n' result += line.alternatives[0].transcript + '\n\n' } return result; } function formatTime(time) { return String(time.hours).padStart(2, '0')+ ':' + String(time.minutes).padStart(2, '0') + ':' + String(time.seconds).padStart(2, '0') + ',000'; } function convertSecondStringToRealtime(string) { var seconds = string.substring(0, string.length - 1); var hours = Math.floor(seconds / 3600); var minutes = Math.floor(seconds % 3600 / 60); seconds = Math.floor(seconds % 3600 % 60); return { hours, minutes, seconds } }
here is the code I used import math import json import datetime def to_hms(s): m, s = divmod(s, 60) h, m = divmod(m, 60) return '{}:{:0>2}:{:0>2}'.format(h, m, s) def srt_generation(filepath, filename): filename = 'DL_BIRTHDAY' with open('{}{}.json'.format(filepath, filename), 'r') as file: data = file.read() results = json.loads(data)['response']['annotationResults'][0]['speechTranscriptions'] processed_results = [] counter = 1 lines = [] wordlist = [] for transcription in results: alternative = transcription['alternatives'][0] if alternative.has_key('transcript'): # print(counter) # lines.append(counter) tsc = alternative['transcript'] stime = alternative['words'][0]['startTime'].replace('s','').split('.') etime = alternative['words'][-1]['endTime'].replace('s','').split('.') if(len(stime) == 1): stime.append('000') if(len(etime) == 1): etime.append('000') lines.append('{}\n{},{} --> {},{}\n{}\n\n\n'.format(counter, to_hms(int(stime[0])), stime[1], to_hms(int(etime[0])), etime[1],tsc.encode('ascii', 'ignore'))) counter = counter+1 wordlist.extend(alternative['words']) srtfile = open('{}{}.srt'.format(filepath, filename), 'wr') srtfile.writelines(lines) srtfile.close() ## Now generate 3 seconds duration chunks of those words. lines = [] counter = 1 strtime =0 entime = 0 words = [] standardDuration = 3 srtcounter = 1 for word in wordlist: stime = word['startTime'].replace('s','').split('.') etime = word['endTime'].replace('s','').split('.') if(len(stime) == 1): stime.append('000 ') if(len(etime) == 1): etime.append('000') if(counter == 1): strtime = '{},{}'.format(stime[0], stime[1]) entime = '{},{}'.format(etime[0], etime[1]) words.append(word['word']) else: tempstmime = int(stime[0]) tempentime = int(etime[0]) stimearr = strtime.split(',') etimearr = entime.split(',') if(tempentime - int(strtime.split(',')[0]) > standardDuration ): transcript = ' '.join(words) lines.append('{}\n{},{} --> {},{}\n{}\n\n\n'.format(srtcounter, to_hms(int(stimearr[0])), stimearr[1], to_hms(int(etimearr[0])), etimearr[1],transcript.encode('ascii', 'ignore'))) srtcounter = srtcounter+1 words = [] strtime = '{},{}'.format(stime[0], stime[1]) entime = '{},{}'.format(etime[0], etime[1]) words.append(' ') words.append(word['word']) else: words.append(' ') words.append(word['word']) entime = '{},{}'.format(etime[0], etime[1]) counter = counter +1 if(len(words) > 0): tscp = ' '.join(words) stimearr = strtime.split(',') etimearr = entime.split(',') lines.append('{}\n{},{} --> {},{}\n{}\n\n\n'.format(srtcounter, to_hms(int(stimearr[0])), stimearr[1], to_hms(int(etimearr[0])), etimearr[1],tscp.encode('ascii', 'ignore'))) srtfile = open('{}{}_3_Sec_Custom.srt'.format(filepath, filename), 'wr') srtfile.writelines(lines) srtfile.close()
Use this request parameter "enable_word_time_offsets: True" to get the time stamps for the word groups. Then create an srt programmatically.
If you require a *.vtt file, here is a snippet to convert the API response received from GCP speech-to-text client into a valid *.vtt. Some answers above are for *.srt so sharing this here. const client = new speech.SpeechClient(); const [response] = await client.recognize(request); createVTT(response); function createVTT(response) { const wordsArray = response.results[0].alternatives[0].words; let VTT = ''; let buffer = []; const phraseLength = 10; let startPointer = '00:00:00'; let endPointer = '00:00:00'; VTT += 'WEBVTT\n\n'; wordsArray.forEach((wordItem) => { const { startTime, endTime, word } = wordItem; const start = startTime.seconds; const end = endTime.seconds; if (buffer.length === 0) { // first word of the phrase startPointer = secondsToFormat(start); } if (buffer.length < phraseLength) { buffer.push(word); } if (buffer.length === phraseLength) { endPointer = secondsToFormat(end); const phrase = buffer.join(' '); VTT += `${startPointer + ' --> ' + endPointer}\n`; VTT += `${phrase}\n\n`; buffer = []; } }); if (buffer.length) { // handle the left over buffer items const lastItem = wordsArray[wordsArray.length - 1]; const end = lastItem.endTime.seconds; endPointer = secondsToFormat(end); const phrase = buffer.join(' '); VTT += `${startPointer + ' --> ' + endPointer}\n`; VTT += `${phrase}\n\n`; } return VTT; } function secondsToFormat(seconds) { const timeHours = Math.floor(seconds / 3600) .toString() .padStart(2, '0'); const timeMinutes = Math.floor(seconds / 60) .toString() .padStart(2, '0'); const timeSeconds = (seconds % 60).toString().padStart(2, '0'); const formattedTime = timeHours + ':' + timeMinutes + ':' + timeSeconds + '.000'; return formattedTime; } Note: enableWordTimeOffsets: true must be set but that's already answered above. This answer is for people who want .vtt copy. Hope this was helpful to someone :)
How to get back to previous state after applying a matrix on a image?
I am using Fabricjs where I am applying some image filters using their convolute. But it is very slower on image size more than 200KB. Hence, I decided to write my own logic to apply filters and then to call canvas.renderAll() to update the canvas from the image canvas. I am able to apply emboss using kernel matrix: [ -2, -1, 0, -1, 1, 1, 0, 1, 2 ] But How to get back to original state of image ,In other words , do I need to apply any other kernel/matrix?
First of all write your own filter class: (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; fabric.Image.filters.MyFilter = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** #lends fabric.Image.filters.MyFilter.prototype */ { /** * Filter type * #param {String} type * #default */ type: 'MyFilter', /** * Constructor */ initialize: function(options) { options = options || { }; }, /** * Applies filter to canvas element * #param {Object} canvasEl Canvas element to apply filter to */ applyTo: function(canvasEl) { var context = canvasEl.getContext('2d'), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, for (var i = 0, len = data.length; i < len; i += 4) { data[i] = 255 - data[i]; data[i + 1] = 255 - data[i + 1]; data[i + 2] = 255 - data[i + 2]; } context.putImageData(imageData, 0, 0); }, /** * Returns object representation of an instance * #return {Object} Object representation of an instance */ toObject: function() { return extend(this.callSuper('toObject'), { }); } }); /** * Returns filter instance from an object representation * #static * #param {Object} object Object to create an instance from * #return {fabric.Image.filters.RemoveWhite} Instance of fabric.Image.filters.MyFilter */ fabric.Image.filters.MyFilter.fromObject = function(object) { return new fabric.Image.filters.MyFilter(object); }; })(typeof exports !== 'undefined' ? exports : this); Second, apply the filter with your normal fabricjs logic: myimage.filters[0] = new fabric.Image.filters.MyFilter; myimage.applyFilters(canvas.renderAll.bind(canvas)); When you want to go back to normal image do: myimage.filters[0] = null; myimage.applyFilters(canvas.renderAll.bind(canvas)); Some considerations: 1) if the code you posted is your filter, it looks like an invert colors filter available at: fabric.Image.filters.Invert() 2) if you really want to invert the formula again, you just need to run the filter again if you want to use a custom function. if a = 255 - a, doing again a = 255 - a will revert to a original value. 3) some filter are not invertible, and if they are is better to store a copy of the image other than going back pixel by pixel.
Multiple criteria timestamp
I am using... function onEdit() { var s = SpreadsheetApp.getActiveSheet(); var r = s.getActiveCell(); var time = new Date(+new Date + (1000 * 60 * 60 * 24 * 7)); time = Utilities.formatDate(time, "GMT-08:00", "MM/dd/yyyy"); if( r.getColumn() == 2 ) { //checks the column var nextCell = r.offset(0, 6); if( nextCell.getValue() === '' ) //is empty? nextCell.setValue(time); } } ...to add a timestamp+7 days to column H. In addition to this timestamp feature... (could be another script) When column D value = "Questions/Waiting for Info" and then changes to anything else. and When column E value = "Preliminary, Less than 25kW" or "Preliminary, Less than 25kW" and then changes to anything except for "Preliminary, Less than 25kW" or "Preliminary, Less than 25kW". we want to start the timestamp+7 days over again. It appears that this might not work with onEdit as there are some values that must be read prior to the edit. Not sure how to do this. Thanks Correct, this is impossible using onEdit without copying the original data somewhere else first. A complicated solution out of my expertise at the moment.
Maybe try something like this function onEdit(e) { var d = new Date(new Date() + (1000 * 60 * 60 * 24 * 7)); var time = Utilities.formatDate(d, "GMT-08:00", "MM/dd/yyyy"), ind = [2, 4].indexOf(e.range.columnStart), off; if (ind == 0) { off = 6; } else if (ind == 1 && e.value !== "Questions/Waiting for Info") { off = 4; } e.range.offset(0, off).setValue(time) }
Printing in Openlayers 3 (pdf)
I have made a printing tools for openlayers 3 which prints in PDF format. Here is my code to print in pdf. var dims = { a0: [1189, 841], a1: [841, 594], a2: [594, 420], a3: [420, 297], a4: [297, 210], a5: [210, 148] }; var exportElement = document.getElementById('export-pdf'); exportElement.addEventListener('click', function(e) { if (exportElement.className.indexOf('disabled') > -1) { return; } exportElement.className += ' disabled'; var format = document.getElementById('format').value; var resolution = document.getElementById('resolution').value; var buttonLabelElement = document.getElementById('button-label'); var label = buttonLabelElement.innerText; var dim = dims[format]; var width = Math.round(dim[0] * resolution / 25.4); var height = Math.round(dim[1] * resolution / 25.4); var size = /** #type {ol.Size} */ (map.getSize()); var extent = map.getView().calculateExtent(size); map.once('postcompose', function(event) { //var tileQueue = map.getTileQueue(); // To prevent potential unexpected division-by-zero // behaviour, tileTotalCount must be larger than 0. //var tileTotalCount = tileQueue.getCount() || 1; var interval; interval = setInterval(function() { //var tileCount = tileQueue.getCount(); //var ratio = 1 - tileCount / tileTotalCount; //buttonLabelElement.innerText = ' ' + (100 * ratio).toFixed(1) + '%'; //if (ratio == 1 && !tileQueue.getTilesLoading()) { clearInterval(interval); buttonLabelElement.innerText = label; var canvas = event.context.canvas; var data = canvas.toDataURL('image/jpeg'); var pdf = new jsPDF('landscape', undefined, format); pdf.addImage(data, 'JPEG', 0, 0, dim[0], dim[1]); pdf.save('map.pdf'); map.setSize(size); map.getView().fitExtent(extent, size); map.renderSync(); exportElement.className = exportElement.className.replace(' disabled', ''); // } }, 100); }); map.setSize([width, height]); map.getView().fitExtent(extent, /** #type {ol.Size} */ (map.getSize())); map.renderSync(); }, false); I can print in PDF when I have only OSM Layer but when I add local layers from my geoserver I can't print anything and the whole application is freezed. Can anyone tell me what am I doing wrong here? I am using jspdf to print pdf. AJ
Your problem is that you load imagery from other domains, and haven't configured them for CORS. See https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image for a description on cross origin image use. In order to get data out of the canvas, all images put into it must be from the same domain or transmitted with the appropriate Access-Control-Allow-Origin header. I would investigate how to set up your server to serve the map imagery with those headers. You should also take a look at the crossOrigin option on your ol3 sources.
There is few solutions for CORS. Very simple solution is to proxy OSM requests through your backend server (user <-> backend <-> OSM), but then we have little more server load.