(After Effects) Change text content over the time - text

I would like to put the title + artist name when the song is playing, but I don't know how to achieve this, because with almost 30-35 songs, it would be long and boring to create a specific text layer for each song.
If there is a trick to achieve this quickly.

You can achieve this via scripting.
I have these two scripts that should do what you want. I wrote them some time ago. Maybe you need to do some adjustments.
This one adds several text layers from a csv file.
https://github.com/fabiantheblind/after-effects-script-snippets/blob/master/comp_with_text.jsx
This one should add one text layer with the source text set to the content of a csv.
https://github.com/fabiantheblind/after-effects-script-snippets/blob/master/text_to_comp.jsx
This is a minimal example to add a text layer with sourceText
/**
* main function
*/
var main = function() {
var txt = ['Hello - World', 'dog -cat', 'foo - bah']; // the text to add
app.beginUndoGroup('add source text'); // open a undo group
var curComp = app.project.activeItem; // get the current comp
// check if the curent active item is a comp
if (!curComp || !(curComp instanceof CompItem)) {
alert('noComp');
return;
// end if no comp is active
}
var txtLayer = curComp.layers.addText('titles'); // add a text layer
var counter = 0; // the time to add a keyframe to (in seconds)
// loop the text
for (var i = 0; i < txt.length; i++) {
var curFrame = (counter / curComp.frameRate); // calc time for each frame
$.writeln(curFrame);
// add a keyframe with the text as value every frame
txtLayer.text.sourceText.setValueAtTime(curFrame, txt[i]);
counter++; // increase the time by one
}
app.endUndoGroup();
};
main();

Related

PDFKit split text into two equal columns while using for loop

Im trying to use PDFKit to generate a simple pdf, for the most part the pdf works but albeit in a very non useful way, what i have is a deck building API that takes in a number of cards, each of these objects i want to export to a pdf, its as simple as displaying their name, but as it is, the pdf only renders one card at a time, and only on one line, what id like to happen is to get it to split the text into columns so itd look similar to this.
column 1 | column 2
c1 c8
c2 c9
c3 c10
c4 c(n)
here is my code so far,
module.exports = asyncHandler(async (req, res, next) => {
try {
// find the deck
const deck = await Deck.findById(req.params.deckId);
// need to sort cards by name
await deck.cards.sort((a, b) => {
if (a.name < b.name) {
return -1;
} else if (a.name > b.name) {
return 1;
} else {
return 0;
}
});
// Create a new PDF document
const doc = new PDFDocument();
// Pipe its output somewhere, like to a file or HTTP response
doc.pipe(
fs.createWriteStream(
`${__dirname}/../../public/pdf/${deck.deck_name}.pdf`
)
);
// Embed a font, set the font size, and render some text
doc.fontSize(25).text(`${deck.deck_name} Deck List`, {
align: "center",
underline: true,
underlineColor: "#000000",
underlineThickness: 2,
});
// We need to create two columns for the cards
// The first column will be the card name
// The second column will continue the cards listed
const section = doc.struct("P");
doc.addStructure(section);
for (const card of deck.cards) {
doc.text(`${card.name}`, {
color: "#000000",
fontSize: 10,
columns: 2,
columnGap: 10,
continued: true,
});
}
section.end();
// finalize the PDF and end the response
doc.end();
res.status(200).json({ message: "PDF generated successfully" });
} catch (error) {
console.error(error);
res.status(500).json({
success: false,
message: `Server Error - ${error.message}`,
});
}
});
At Present this does generate a column order like i want, however theres and extreme caveat to this solution and that is, if the card text isnt very long, the next card will start on that same line, it'd be useful if i could find a way to make the text take up the full width of that row, but i havent seen anything to do that with.
I think the problem is that you're relying on PDFKit's text "flow" API/logic, and you're having problems when two cards are not big enough to flow across your columns and you get two cards in one column.
I'd say that what you really want is to create a table—based on your initial text sample.
PDFKit doesn't have a table API (yet), so you'll have to make one up for yourself.
Here's an approach where you figure out the dimensions of things:
the page size
the size of your cells of text (either manually choose for yourself, or use PDFKit to tell you how big some piece of text is)
margins
Then you use those sizes to calculate how many rows and columns of your text can fit on your page.
Finally you iterate of over columns then rows for each page, writing text into those column-by-row "coordinates" (which I track through "offsets" and use to calculate the final "position").
const PDFDocument = require('pdfkit');
const fs = require('fs');
// Create mock-up Cards for OP
const cards = [];
for (let i = 0; i < 100; i++) {
cards.push(`Card ${i + 1}`);
}
// Set a sensible starting point for each page
const originX = 50;
const originY = 50;
const doc = new PDFDocument({ size: 'LETTER' });
// Define row height and column widths, based on font size; either manually,
// or use commented-out heightOf and widthOf methods to dynamically pick sizes
doc.fontSize(24);
const rowH = 50; // doc.heightOfString(cards[cards.length - 1]);
const colW = 150; // doc.widthOfString(cards[cards.length - 1]); // because the last card is the "longest" piece of text
// Margins aren't really discussed in the documentation; I can ignore the top and left margin by
// placing the text at (0,0), but I cannot write below the bottom margin
const pageH = doc.page.height;
const rowsPerPage = parseInt((pageH - originY - doc.page.margins.bottom) / rowH);
const colsPerPage = 2;
var cardIdx = 0;
while (cardIdx < cards.length) {
var colOffset = 0;
while (colOffset < colsPerPage) {
const posX = originX + (colOffset * colW);
var rowOffset = 0;
while (rowOffset < rowsPerPage) {
const posY = originY + (rowOffset * rowH);
doc.text(cards[cardIdx], posX, posY);
cardIdx += 1;
rowOffset += 1;
}
colOffset += 1;
}
// This is hacky, but PDFKit adds a page by default so the loop doesn't 100% control when a page is added;
// this prevents an empty trailing page from being added
if (cardIdx < cards.length) {
doc.addPage();
}
}
// Finalize PDF file
doc.pipe(fs.createWriteStream('output.pdf'));
doc.end();
When I run that I get a PDF with 4 pages that looks like this:
Changing colW = 250 and colsPerPage = 3:

Photoshop: How to handle numbering a large amount of disorganized items?

Suppose I have a large campsite like "seating" chart with several hundred lots sectioned off and outlined in photoshop. (each lot is roughly a square) Every lot needs to be numbered in photoshop and also editable in the future in case of changes. The lots are scattered and curve around the landscape so entering text in one layer seems out since for example lot 27 with be on the right and rotate 20 degrees to match the lot and yet lot 185 might be way over on the left at a far different angle.
Is there an elegant way to do this or at least quickly import a large number sequence that places one number per layer so I can grab them and orient them to their corresponding lot quickly instead of typing out and then positioning ever number individually? I'm having trouble thinking up an elegant/fast way to handle this in Photoshop...
Edit 1 - picture: http://i.imgur.com/UT3DRBi.jpg
You can do it with Extendscript. I am not the best of Extendscript programmers, but the following script will ask you for the number of text labels you want and add that many numbers on sepearate layers. Of course, you can diddle around with the font, colour, position, size etc. but it should get you started.
Here is an example - I turned off layers 4 and 5 so you can see each number is on a new layer.
Here it asks how many numbers you want:
// Setchell - AddNumbers - Adobe Photoshop Script
// Description: Asks user for number of numbers to add, each in own layer
// Version: 0.1
// Author: Mark Setchell (mark#thesetchells.com)
// Web: http://www.thesetchells.com
// ============================================================================
// Installation:
// 1. Place script in 'C:\Program Files\Adobe\Adobe Photoshop CS#\Presets\Scripts\'
// 2. Restart Photoshop
// 3. Choose File > Scripts > AddNumbers
// ============================================================================
// enable double-clicking from Mac Finder or Windows Explorer
// this command only works in Photoshop CS2 and higher
#target photoshop
// bring application forward for double-click events
app.bringToFront();
///////////////////////////////////////////////////////////////////////////////
// AddNumbers
///////////////////////////////////////////////////////////////////////////////
function AddNumbers() {
// Change Debug=1 for extra debugging messages
var Debug=1;
// Get user to enter common stem for JPEG names
var dialog = new Window('dialog', 'Setchell - AddNumbers');
dialog.size = {width:500, height:100};
dialog.stem = dialog.add('edittext',undefined, '<Enter ending number>');
dialog.stem.size = {width:400,height:25};
dialog.stem.value = true;
dialog.stem.buildBtn = dialog.add('button', undefined,'OK', {name:'ok'});
dialog.show();
// Pick up what user entered - just digits
var limit=dialog.stem.text.match(/\d+/);
// Debug
if(Debug)alert(limit);
var cnt;
var n=0;
var nPer=10;
var deltaX=app.activeDocument.width/nPer;
var deltaY=app.activeDocument.height/nPer;
var tX=0;
var tY=deltaY;
app.preferences.typeUnits = TypeUnits.POINTS;
for(cnt=1;cnt<=limit;cnt++){
// Adds a new layer to the active document and stores it in a variable named “myTextLayer”.
var myTextLayer = app.activeDocument.artLayers.add();
// Changes myTextLayer from normal to a text layer.
myTextLayer.kind = LayerKind.TEXT;
// Gets a reference to the textItem property of myTextLayer.
var myText = myTextLayer.textItem;
// sets the font size of the text to 16.
myText.size = 16;
// Sets the contents of the textItem.
myText.contents = cnt;
// Position the label - could be improved :-)
tX=n*deltaX;
myText.position = new Array(tX, tY);
n++;
if(n==nPer){
tY+=deltaY;
n=0;
}
}
return;
}
///////////////////////////////////////////////////////////////////////////////
// isCorrectVersion - check for Adobe Photoshop CS2 (v9) or higher
///////////////////////////////////////////////////////////////////////////////
function isCorrectVersion() {
if (parseInt(version, 10) >= 9) {
return true;
}
else {
alert('This script requires Adobe Photoshop CS2 or higher.', 'Wrong Version', false);
return false;
}
}
///////////////////////////////////////////////////////////////////////////////
// showError - display error message if something goes wrong
///////////////////////////////////////////////////////////////////////////////
function showError(err) {
if (confirm('An unknown error has occurred.\n' +
'Would you like to see more information?', true, 'Unknown Error')) {
alert(err + ': on line ' + err.line, 'Script Error', true);
}
}
// test initial conditions prior to running main function
if (isCorrectVersion()) {
// Save current RulerUnits to restore when we have finished
var savedRulerUnits = app.preferences.rulerUnits;
// Set RulerUnits to PIXELS
app.preferences.rulerUnits = Units.PIXELS;
try {
AddNumbers();
}
catch(e) {
// don't report error on user cancel
if (e.number != 8007) {
showError(e);
}
}
// Restore RulerUnits to whatever they were when we started
app.preferences.rulerUnits = savedRulerUnits;
}

Google Apps Script - Exporting events from Google Sheets to Google Calendar - how can I stop it from removing my spreadsheet formulas?

I've been trying to create a code that takes info from a Google Spreadsheet, and creates Google Calendar events. I'm new to this, so bear with my lack of in-depth coding knowledge!
I initially used this post to create a code:
Create Google Calendar Events from Spreadsheet but prevent duplicates
I then worked out that it was timing out due to the number of rows on the spreadsheet, and wasn't creating eventIDs to avoid the duplicates. I got an answer here to work that out!
Google Script that creates Google Calendar events from a Google Spreadsheet - "Exceeded maximum execution time"
And now I've realised that it's over-writing the formulas, I have in the spreadsheet, auto-completing into each row, as follows:
Row 12 - =if(E4="","",E4+1) // Row 13 - =if(C4="","",C4+1) // Row 18 - =if(B4="","","WHC - "&B4) // Row 19 - =if(B4="","","Docs - "&B4)
Does anyone have any idea how I can stop it doing this?
/**
* Adds a custom menu to the active spreadsheet, containing a single menu item
* for invoking the exportEvents() function.
* The onOpen() function, when defined, is automatically invoked whenever the
* spreadsheet is opened.
* For more information on using the Spreadsheet API, see
* https://developers.google.com/apps-script/service_spreadsheet
*/
function onOpen() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var entries = [{
name : "Export WHCs",
functionName : "exportWHCs"
},
{
name : "Export Docs",
functionName : "exportDocs"
}];
sheet.addMenu("Calendar Actions", entries);
};
/**
* Export events from spreadsheet to calendar
*/
function exportWHCs() {
// check if the script runs for the first time or not,
// if so, create the trigger and PropertiesService.getScriptProperties() the script will use
// a start index and a total counter for processed items
// else continue the task
if(PropertiesService.getScriptProperties().getKeys().length==0){
PropertiesService.getScriptProperties().setProperties({'itemsprocessed':0});
ScriptApp.newTrigger('exportWHCs').timeBased().everyMinutes(5).create();
}
// initialize all variables when we start a new task, "notFinished" is the main loop condition
var itemsProcessed = Number(PropertiesService.getScriptProperties().getProperty('itemsprocessed'));
var startTime = new Date().getTime();
var sheet = SpreadsheetApp.getActiveSheet();
var headerRows = 4; // Number of rows of header info (to skip)
var range = sheet.getDataRange();
var data = range.getValues();
var calId = "flightcentre.com.au_pma5g2rd5cft4lird345j7pke8#group.calendar.google.com";
var cal = CalendarApp.getCalendarById(calId);
for (i in data) {
if (i < headerRows) continue; // Skip header row(s)
var row = data[i];
var date = new Date(row[12]); // First column
var title = row[18]; // Second column
var tstart = new Date(row[15]);
tstart.setDate(date.getDate());
tstart.setMonth(date.getMonth());
tstart.setYear(date.getYear());
var tstop = new Date(row[16]);
tstop.setDate(date.getDate());
tstop.setMonth(date.getMonth());
tstop.setYear(date.getYear());
var id = row[17]; // Sixth column == eventId
// Check if event already exists, update it if it does
try {
var event = cal.getEventSeriesById(id);
}
catch (e) {
// do nothing - we just want to avoid the exception when event doesn't exist
}
if (!event) {
//cal.createEvent(title, new Date("March 3, 2010 08:00:00"), new Date("March 3, 2010 09:00:00"));
var newEvent = cal.createEvent(title, tstart, tstop).addEmailReminder(5).getId();
row[17] = newEvent; // Update the data array with event ID
}
else {
event.setTitle(title);
}
if(new Date().getTime()-startTime > 240000){ // if > 4 minutes
var processed = i+1;// save usefull variable
PropertiesService.getScriptProperties().setProperties({'itemsprocessed':processed});
range.setValues(data);
MailApp.sendEmail(Session.getEffectiveUser().getEmail(),'progress sheet to cal','item processed : '+processed);
return;
}
debugger;
}
// Record all event IDs to spreadsheet
range.setValues(data);
}
/**
* Export events from spreadsheet to calendar
*/
function exportDocs() {
// check if the script runs for the first time or not,
// if so, create the trigger and PropertiesService.getScriptProperties() the script will use
// a start index and a total counter for processed items
// else continue the task
if(PropertiesService.getScriptProperties().getKeys().length==0){
PropertiesService.getScriptProperties().setProperties({'itemsprocessed':0});
ScriptApp.newTrigger('exportDocs').timeBased().everyMinutes(5).create();
}
// initialize all variables when we start a new task, "notFinished" is the main loop condition
var itemsProcessed = Number(PropertiesService.getScriptProperties().getProperty('itemsprocessed'));
var startTime = new Date().getTime();
var sheet = SpreadsheetApp.getActiveSheet();
var headerRows = 4; // Number of rows of header info (to skip)
var range = sheet.getDataRange();
var data = range.getValues();
var calId = "flightcentre.com.au_pma5g2rd5cft4lird345j7pke8#group.calendar.google.com";
var cal = CalendarApp.getCalendarById(calId);
for (i in data) {
if (i < headerRows) continue; // Skip header row(s)
var row = data[i];
var date = new Date(row[13]); // First column
var title = row[19]; // Second column
var tstart = new Date(row[15]);
tstart.setDate(date.getDate());
tstart.setMonth(date.getMonth());
tstart.setYear(date.getYear());
var tstop = new Date(row[16]);
tstop.setDate(date.getDate());
tstop.setMonth(date.getMonth());
tstop.setYear(date.getYear());
var id = row[20]; // Sixth column == eventId
// Check if event already exists, update it if it does
try {
var event = cal.getEventSeriesById(id);
}
catch (e) {
// do nothing - we just want to avoid the exception when event doesn't exist
}
if (!event) {
//cal.createEvent(title, new Date("March 3, 2010 08:00:00"), new Date("March 3, 2010 09:00:00"));
var newEvent = cal.createEvent(title, tstart, tstop).addEmailReminder(5).getId();
row[20] = newEvent; // Update the data array with event ID
}
else {
event.setTitle(title);
}
if(new Date().getTime()-startTime > 240000){ // if > 4 minutes
var processed = i+1;// save usefull variable
PropertiesService.getScriptProperties().setProperties({'itemsprocessed':processed});
range.setValues(data);
MailApp.sendEmail(Session.getEffectiveUser().getEmail(),'progress sheet to cal','item processed : '+processed);
return;
}
debugger;
}
// Record all event IDs to spreadsheet
range.setValues(data);
}
You have to ways to solve that problem.
First possibility : update your sheet with array data only on columns that have no formulas, proceeding as in this other post but in your case (with multiple columns to skip) it will rapidly become tricky
Second possibility : (the one I would personally choose because I 'm not a "formula fan") is to do what your formulas do in the script itself, ie translate the formulas into array level operations.
following your example =if(E4="","",E4+1) would become something like data[n][4]=data[n][4]==''?'':data[n+1][4]; if I understood the logic (but I'm not so sure...).
EDIT
There is actually a third solution that is even simpler (go figure why I didn't think about it in the first place...) You could save the ranges that have formulas, for example if col M has formulas you want to keep use :
var formulM = sheet.getRange('G1:G').getFormulas();
and then, at the end of the function (after the global setValues()) rewrite the formulas using :
sheet.getRange('G1:G').setFormulas(formulM);
to restore all the previous formulas... as simple as that, repeat for every column where you need to keep the formulas.

Check if a message has a label using Google Script

At work we have a google script code that is run every 4 hours and check everyone's #folders for unread mail. If unread mail is found it is moved to INBOX and tagged with ##UNREAD.
What i need is a way for the script to check if it already has the ##UNREAD tag and in that case not move to inbox.
This is the code
function process_unread() {
//Define user label
var label = GmailApp.getUserLabelByName("#Josh");
//Define unread label
var unreadlabel = GmailApp.getUserLabelByName("##UNREAD");
if (label) {
var threads = label.getThreads();
for (var i = 0; i < threads.length; i++) {
var thread = threads[i];
if (thread.isUnread()) {
//Remove label
thread.addLabel(unreadlabel);
thread.moveToInbox();
}
}
}
}
How can i only move emails if they do not have the ##UNREAD label?
Here is my attempt:
function process_unread() {
//Define user label
var label = GmailApp.getUserLabelByName("#Josh");
//Define unread label
var unreadlabel = GmailApp.getUserLabelByName("##UNREAD");
if (label) {
var threads = label.getThreads();
for (var i = 0; i < threads.length; i++) {
var thread = threads[i];
var labels = thread.getLabels();
var doesThisThreadHaveTheLabel = false;
for (var i = 0; i < labels.length; i++) {
var thisParticularLabel = labels[i].getName();
Logger.log(labels[i].getName());
if (thisParticularLabel === "##UNREAD") {
var doesThisThreadHaveTheLabel = true;
};
}
if (thread.isUnread() && doesThisThreadHaveTheLabel === false) {
//Remove label
thread.addLabel(unreadlabel);
thread.moveToInbox();
}
}
}
}
Before you move the thread to the inbox, you want to make sure it does NOT have the label. So add another condition to the If check.
if (thread.isUnread() && doesThisThreadHaveTheLabel === false) {
I created a variable: doesThisThreadHaveTheLabel that will either have a true or false value. It's default gets set to false before every for loop.
var doesThisThreadHaveTheLabel = false;
for (var i = 0; i < labels.length; i++) {
You can debug the code to check it:
In the above picture, you see an icon of a bug in the menu. Before you click that, first click the drop down menu just to the right of the bug, and choose the name of the function to run. Also, add a breakpoint to the code. In that picture you'll see a red dot in the line numbers in the code editor. That's where the code will stop.
I added the label #josh to one of the emails in my account, so the variable label has an object in it. But, I don't have any emails with a label ##UNREAD , so you'll notice that in the list of variables, the variable unreadlabel has a value of null.
In that picture, the code is suspended on the line, 269. I can step in to the next line of code by clicking the step in icon. Hover over the icon to get a context help to pop up.
I stepped line by line further, and retrieved the label that was put into the variable "ThisParticularLabel". You can see in the window, that it has a value of #Josh.
I stepped through that code, and it ended after the main for loop had run once. I also ran that code by itself without debugging it, and it ran in:
Execution succeeded [0.246 seconds total runtime]
You need to debug your code, and see what it is doing on every line, and know what every single variable has for a value, and how the conditional statements are working.
Script will not finish with two for loops both using variable i as i will be set to 0 continually by the inner for loop and will not increment. Switch inner loop to j variable and script will finish.

recognize multple lines on info.selectionText from Context Menu

My extension adds a context menu whenever a user selects some text on the page.
Then, using info.selectionText, I use the selected text on a function executed whenever the user selects one of the items from my context menu. (from http://code.google.com/chrome/extensions/contextMenus.html)
So far, all works ok.
Now, I got this cool request from one of the extension users, to execute that same function once per line of the selected text.
A user would select, for example, 3 lines of text, and my function would be called 3 times, once per line, with the corresponding line of text.
I haven't been able to split the info.selectionText so far, in order to recognize each line...
info.selectionText returns a single line of text, and could not find a way to split it.
Anyone knows if there's a way to do so? is there any "hidden" character to use for the split?
Thanks in advance... in case you're interested, here's the link to the extension
https://chrome.google.com/webstore/detail/aagminaekdpcfimcbhknlgjmpnnnmooo
Ok, as OnClickData's selectionText is only ever going to be text you'll never be able to do it using this approach.
What I would do then is inject a content script into each page and use something similar to the below example (as inspired by reading this SO post - get selected text's html in div)
You could still use the context menu OnClickData hook like you do now but when you receive it instead of reading selectionText you use the event notification to then trigger your context script to read the selection using x.Selector.getSelected() instead. That should give you what you want. The text stays selected in your extension after using the context menu so you should have no problem reading the selected text.
if (!window.x) {
x = {};
}
// https://stackoverflow.com/questions/5669448/get-selected-texts-html-in-div
x.Selector = {};
x.Selector.getSelected = function() {
var html = "";
if (typeof window.getSelection != "undefined") {
var sel = window.getSelection();
if (sel.rangeCount) {
var container = document.createElement("div");
for (var i = 0, len = sel.rangeCount; i < len; ++i) {
container.appendChild(sel.getRangeAt(i).cloneContents());
}
html = container.innerHTML;
}
} else if (typeof document.selection != "undefined") {
if (document.selection.type == "Text") {
html = document.selection.createRange().htmlText;
}
}
return html;
}
$(document).ready(function() {
$(document).bind("mouseup", function() {
var mytext = x.Selector.getSelected();
alert(mytext);
console.log(mytext);
});
});​
http://jsfiddle.net/richhollis/vfBGJ/4/
See also: Chrome Extension: how to capture selected text and send to a web service

Resources