FrameScrub Flash Extension

Justin | Animate/Flash,animation,extensions | Wednesday, April 4th, 2012

Introducing FrameScrub, the lip-syncing tool for those who want to go panel-less.

How it Works

It works in most ways like the selection tool: you can click to select, double-click to edit, but you can also scrub-slide through graphic symbol frames.

You can even scrub through loop settings (using the Alt/Opt key).

Features

  • Click and drag to update the first frame that is displayed.
  • Hold SHIFT to cycle through frames faster.
  • Hold ALT (OPT on Mac) to cycle through loop settings (Loop, Play Once, Single Frame).
  • Hold CTRL (CMD on Mac) to temporarily revert to the Selection Tool.

Download

(compatibility: CS3 and newer, including CC and CC 2014, Flash 8 & MX 2004 untested)
FrameScrub.zip

Installation

Open the MXP file in Extension Manager, follow the steps, and restart Flash. If you don’t see the FrameScrub in your toolbar (behind the selection tool), you can add it by accessing Edit (Flash on Mac) > Customize Tools Panel… Select a tool position, locate FrameScrub in the list on the left and use the right-facing arrow button to add FrameScrub to the selected tool position.

Update (11/29/12): I didn’t realize until someone pointed it out, but there’s no direct way to add a keyboard shortcut to a custom tool. To help with this, I’ve add a Command to the extension that will activate FrameScrub. A keyboard shortcut can then be added to that Command.

Other Lip Sync Tools from Ajar Productions

Get updates from Ajar Productions

Splittext_id_400px

Sign up today and get the InDesign Split Text premium extension for free!

Unsubscribe at any time. Powered by ConvertKit

42 thoughts on “FrameScrub Flash Extension”

  1. Excellent idea. Thanks !

  2. Justin says:

    Thanks for your comment, Nicolas! Much appreciated.

  3. Good idea, thanks. I will give a link to our artist.

  4. Justin says:

    Cool, elmortem. Thanks for sharing the link!

  5. raph says:

    Hi! I cant seem to get this to run, after installing it via extensions manager how do I activate it?
    Thanks

  6. Justin says:

    Hi raph, there’s an extra step installing a tool. I’ve add an ‘Installation’ heading above that should help.

  7. Corey says:

    Just tried to install this in Flash 8 & recieved
    “This extension requires the following products:
    Flash 9 or greater
    The extension will not be installed”

    …which sucks because this thing looks super helpful!

  8. Nicolas says:

    Seriously, why keep on using Flash 8? For the AS2? upgrade to F9 at the very least, it’s compatible with many more extensions since the migration to AS3.

  9. Corey says:

    Because that’s the only version I’ve found that doesn’t screw up my workflow.

  10. Justin says:

    Hi Corey,
    I updated the minimum version. Try downloading and installing now…

  11. Corey says:

    Justin, that worked like a charm sir. Thank you so much, what an amazing time saver this is gonna be!

  12. Justin says:

    Glad to hear it, Corey!

  13. themartist says:

    Is it CS6 compliant? Doesn’t seem to work but this would ROCK if it did.

  14. themartist says:

    Sorry, it does work on CS6… absolutely AWESOME addition to my workflow… wow!

  15. Justin says:

    Hey, glad to hear it, themartist! Thanks for the update!

  16. This is a pretty cool tool. Very Maya. You may also be interested to see a tool I just put up on my blog, also for making it easier to set the “first frame”, but using a moderately complex system of keyboard shortcuts:

    http://blog.pinkandaint.com/start-frame-madness/

  17. Justin says:

    Cool stuff, David!

  18. Hmmm. In CS4 it works but the display doesn’t update until after I release the mouse button. That is, I can’t see what the art looks like until I stop scrubbing. Have you run into this problem? It makes the tool much less useful that I had initially hoped.

    By the way, in my previous comment I didn’t state it very well. What I meant is that setting up the keyboard shortcuts is moderately complex, but once they’re in place it’s a super easy and intuitive system and it greatly speeds up the process of setting the start frame (though you probably intuited that if you read my blog post).

  19. Justin says:

    Hi, David. The display refreshing was the hardest problem of the extension. I made sure that was working on my versions (including CS4) before releasing. I’ve only got Macs these days though, do you have a Windows machine?

  20. Yeah, I use Windows. Believe me, I know what you mean about display issues like this. In fact, I think I’ve run into this same one before, perhaps when I was trying to develop a stage rotation (like an animation disc) tool. I don’t remember if I ever solved it. Maybe I’ll take a look at your code and see if I can figure anything out to get it to display properly in Windows CS4.

  21. Okay, now I remember when I ran into this bug before. It was introduced in the 10.0.2 update for CS4. I got around it with a minor kludge to deselect and reselect the current selection, which had the effect of correctly updating the display. Anyway, here’s the updated jsfl file, not displaying correctly in CS4 for Windows:
    [moved to next comment -jp]

  22. Oops, sorry about the formatting. I’ll try that again:

    /*
    * @author Justin Putney
    * @website http://ajarproductions.com
    * @version 0.0.2
    *
    * Updated 6/5/2012 by David Hernston (http://www.pinkandaint.com) to fix a display bug in
    * Flash CS4 for Windows
    
    */
    
    
    var didDrag = false;
    var startPt = new Object();
    var theTool;
    var dragInt = 6;
    var anchorFrame;
    var loopArr = ["loop", "play once", "single frame"];
    var anchorLoop;
    var FL_VERSION = getFlashAuthVersion();
    var NEEDS_REFRESH = (FL_VERSION > 9);
    var SPACEBAR = 32;
    
    function configureTool() {
    	theTool = fl.tools.activeTool;
    	theTool.setToolName("FrameScub");
    	theTool.setIcon("FrameScrub.png");
    	theTool.setMenuString("FrameScrub Tool");
    	theTool.setToolTip("FrameScrub Tool");
    	//theTool.setOptionsFile( "FrameScrub.xml" );
    	
    	///////////////////////////////////////////
    	// shape PI
    	theTool.setPI( "movie" );
    }
    
    
    function notifySettingsChanged() {
    	theTool = fl.tools.activeTool;
    }
    
    
    function setCursor() {
    	fl.tools.setCursor( 1 );
    }
    
    function activate() {
    	theTool = fl.tools.activeTool;
    }
    
    /*function keyDown() {
    	var pressed = fl.tools.getKeyDown();
    	if(!didDrag && pressed == SPACEBAR){
    		fl.selectTool("hand");
    	}
    }
    
    function keyUP() {
    	fl.selectTool("FrameScub");
    }*/
    
    function deactivate() {
    }
    
    function mouseDown() {
    	fl.tools.setCursor( 4 );
    	// start drawing of object
    	if(fl.tools.altIsDown) fl.drawingLayer.beginDraw(true);
    	
    	// set the flag if the cursor moves "enough"
    	didDrag = false;
    	anchorFrame = 0; //failsafe
    	anchorLoop = 0;	//failsafe
    	startPt = fl.tools.penDownLoc;
    	var sel = fl.getDocumentDOM().selection;
    	var tSel = sel[0];
    	if (tSel == '[object SymbolInstance]') {
    		if (tSel.symbolType == 'graphic') {
    			anchorFrame = tSel.firstFrame;
    			switch(tSel.loop){
    				case loopArr[0]:
    					anchorLoop = 0;
    					break;
    				case loopArr[1]:
    					anchorLoop = 1;
    					break;
    				case loopArr[2]:
    					anchorLoop = 2;
    					break;
    				default:
    					anchorLoop = 0;
    			}
    		
    		} else if (tSel.symbolType == 'movie clip'){
    				var confirmed = confirm('The selected instance of ' + tSel.libraryItem.name  + ' is a movieclip. In order to use FrameScrub, the instance must be converted to a graphic. \r\rWould you like to convert it to a graphic?');
    					if(confirmed){ 
    						tSel.symbolType = 'graphic'; 
    						//reset selected item
    						//tSel = dom.selection[i];
    					}
    		}
    	}
    
    }
    
    
    function transformPoint( pt ) {
    	var mat = fl.getDocumentDOM().viewMatrix;
    	var x = pt.x*mat.a + pt.y*mat.c + mat.tx;
    	var y = pt.x*mat.b + pt.y*mat.d + mat.ty;
    	pt.x = x;
    	pt.y = y;
    	return pt;
    }
    
    function mouseMove(mouseLoc) {
    	// only calculate an object if user drags the mouse
    	if (fl.tools.mouseIsDown) {
    		fl.tools.setCursor( 4 );
    		// check how much the mouse has moved since the pen went down
    		var pt1 = startPt;
    		var pt2 = mouseLoc;
    		var dx = pt2.x - pt1.x;
    		var adx = (dx > 0) ? dx : -dx;
    		var dragAmt = dragInt;
    		
    		// constrain with the shift key
    		if (fl.tools.shiftIsDown) {
    			dragAmt = dragInt * .2;
    		}
    		
    		if (adx > dragInt) {
    			didDrag = true;
    			if(fl.tools.altIsDown) {
    				dragAmt = dragInt * 2;
    				updateLoopSetting(Math.round(dx/dragAmt), mouseLoc);
    				return; //exit before altering firstFrame
    			}
    			updateSelection(Math.round(dx/dragAmt));
    		}
    	}
    }
    
    function mouseUp() {
    	// end the drawing
    	fl.drawingLayer.endDraw();
    	fl.drawingLayer.beginDraw();
    	fl.drawingLayer.endDraw();
    
    	if (!didDrag && fl.documents.length > 0) {
    	var pt1 = transformPoint(startPt);
    	fl.getDocumentDOM().mouseClick({x:pt1.x, y:pt1.y}, fl.tools.shiftIsDown, false);
    	}
    	fl.tools.setCursor( 1 );
    	//refresh view
    	if(NEEDS_REFRESH) {
    		var dom = fl.getDocumentDOM();
    		var sel = dom.selection;
    		//drawing a selectionrect is one of the few things that works
    		dom.selectNone();
    		dom.setSelectionRect({left:0, top:0, right:1, bottom:1});
    		dom.selection = sel;
    	}
    }
    
    
    function mouseDoubleClick() { 
    	var pt1 = startPt; //transformPoint(startPt);
    	fl.getDocumentDOM().mouseDblClk({x:pt1.x, y:pt1.y}, fl.tools.altIsDown, fl.tools.shiftIsDown, false)
    }
    
    function updateLoopSetting(delta, mouseLoc) {
    	var newLoop = anchorLoop + delta;
    	if(newLoop > 2) newLoop = 2;
    	if(newLoop < 0) newLoop = 0;
    	var dom = fl.getDocumentDOM();
    	var sel = dom.selection;
    	var tSel = sel[0];
    	//dom.selectNone();
    	if (tSel == '[object SymbolInstance]' && tSel.symbolType == 'graphic') {
    		var loopStr = loopArr[newLoop];
    		tSel.loop = loopStr;
    		fl.drawingLayer.beginFrame();
    		var pt1 = mouseLoc;
    		pt1.y += 20; //move down from cursor
    		pt1.x -= 10;
    		fl.drawingLayer.moveTo(pt1.x, pt1.y);
    		if(loopStr === 'loop') {
    			var pt2 = {y: pt1.y, x: pt1.x + 20};
    			var pt3 = {y: pt2.y, x: pt2.x + 20};
    			fl.drawingLayer.curveTo( pt1.x+10, pt1.y-10, pt2.x, pt2.y);
    			fl.drawingLayer.curveTo( pt2.x+10, pt2.y-10, pt3.x, pt3.y);
    			fl.drawingLayer.curveTo( pt3.x-10, pt3.y+10, pt2.x, pt2.y);
    			fl.drawingLayer.curveTo( pt2.x-10, pt2.y+10, pt1.x, pt1.y);
    		} else if(loopStr === 'play once'){
    			var pt2 = {y: pt1.y, x: pt1.x + 30};
    			var pt3 = {y: pt2.y-7, x: pt2.x-7};
    			var pt4 = {y: pt2.y+7, x: pt3.x};
    			var pt5 = {y: pt1.y - 15, x: pt2.x};
    			var pt6 = {y: pt1.y + 15, x: pt2.x};
    			fl.drawingLayer.lineTo(pt2.x, pt2.y);
    			fl.drawingLayer.lineTo(pt3.x, pt3.y);
    			fl.drawingLayer.moveTo(pt2.x, pt2.y);
    			fl.drawingLayer.lineTo(pt4.x, pt4.y);
    			fl.drawingLayer.moveTo(pt5.x, pt5.y);
    			fl.drawingLayer.lineTo(pt6.x, pt6.y);
    		
    		} else if(loopStr === 'single frame'){
    			var pt2 = {y: pt1.y, x: pt1.x + 10};
    			var pt3 = {y: pt2.y+20, x: pt2.x};
    			var pt4 = {y: pt3.y, x: pt1.x};
    			fl.drawingLayer.lineTo(pt2.x, pt2.y);
    			fl.drawingLayer.lineTo(pt3.x, pt3.y);
    			fl.drawingLayer.lineTo(pt4.x, pt4.y);
    			fl.drawingLayer.lineTo(pt1.x, pt1.y);
    		}
    		
    		fl.drawingLayer.endFrame();
    	}
    	//dom.selection = sel;
    	dom.livePreview = true;
    }
    
    function updateSelection(delta) 
    {
    	var dom = fl.getDocumentDOM();
    	var sel = dom.selection;
    	var len = sel.length;
    	/*var i;
    	for(i=0; i<len; i++){*/
    	if(len == 1) {
    		var tSel = sel[0];
    		if(tSel == '[object SymbolInstance]'){
    			if(tSel.symbolType == 'graphic') {
    				var maxFrame = tSel.libraryItem.timeline.frameCount-1;
    				if(anchorFrame == undefined) anchorFrame = tSel.firstFrame;
    				var newFrame = anchorFrame + delta;
    				if(newFrame  maxFrame) newFrame = maxFrame;
    				tSel.firstFrame = newFrame;
    				dom.livePreview = true;
    				updateDisplay()
    			} 
    		}
    	}
    	
    }
    
    function updateDisplay()
    {
    	// Update the display
    	// This is just a workaround for a display bug introduced in the 10.0.2 update
    	// It's only needed for Flash CS4, perhaps only in Windows.
    	var doc = fl.getDocumentDOM();
    	var selection = doc.selection;
    	doc.setSelectionRect({left:-2000, top:-2000, right:-1990, bottom:-1990});	
    	doc.selection = selection;
    }
    
    function getFlashAuthVersion(){
    	return parseInt(fl.version.split(' ').pop().split(',')[0]);
    }
    
  23. Oops again. In my first attemp to post the new code, it should have read “NOW displaying correctly”, not “NOT displaying correctly”. Sorry for taking up so much space in your comments :/

  24. Justin says:

    Thanks, David! I’ve used code like this in past extensions…not sure why it wasn’t in my release version of this one. I’ll incorporate it in the next release.

  25. Zardo says:

    Hey Justin, this looks like a great extension. I am having a similar problem to earlier commenter Corey. Attempting to install in any version of flash CS4 or earlier gives the message “This extension requires the following products: Flash MX 2004 or greater The extension will not be installed.”

    Does seem to install fine into CS5 and 5.5, although I still use 8 for a variety of reasons.

    Would love to be able to make use of this & appreciate any insight you have.

  26. Zardo says:

    Apologies, the issue was not with your extension. Feel free to delete my earlier post. And thanks a million for a very useful tool.

  27. Justin says:

    No worries, Zardo. Glad you were able to get it working!

  28. Justin says:

    Hi David,
    I was able to post an update. As you can see, I already had similar code in the mouseUp function, just not in the live update. Please try version 0.0.2 and let me know if it works as expected on your system. Thanks!

  29. Wip says:

    Amazing work! Thanks a lot for this.
    I was looking to bind a keyboard shortcut to it. Not super necessary I know, but just wondering if it’s possible.

  30. Justin says:

    Thanks, Wip! You can customize your keyboard shortcuts to add one to the tool, but it will merely select the tool. To achieve this kind of functionality with shortcuts alone, you might look at David’s link from above.

  31. Wip says:

    Hey Justin, Thanks for that. I should’ve formulated my question differently though 🙂
    While customising shortcuts, I can normally find extensions in the lists (commands), but can’t find this one anywhere, not even in the tools dropdown. I’m probably blind, any chance you know where it is?

  32. Justin says:

    Hi Wip. No prob. At the Commands menu (I know confusingly named) toward the top, choose Tools as shown here.

  33. Mike Milo says:

    Hey Justin, any idea why this wouldn’t work in Flash CC 2015?

    1. Justin says:

      Hi Mike, are you having trouble installing it or running it?

      1. Mike Milo says:

        Both I guess… It just doesn’t come up. It’s not available at all in the toolbar, or the extensions or the Commands. I assume you’re aware that there is no Extensions Manager any longer so I have been using the utility the Adobe Flash team provided and it claims its installed the tool but it doesn’t seem to be anywhere in my UI.

      2. Justin says:

        There may be installation challenges with this version. Is this after the Animate name change?

        You can run the following as JSFL to see if the FrameScrub files exist in the Tools directory:
        fl.trace(FLfile.listFolder(fl.configURI + 'Tools/'));

      3. Mike Milo says:

        No this is just the standard 2015 install (although it doesn’t work in Animate either, but then no extensions do yet). Not familiar with JSFL stuff. I’m just a poor animator! How do execute something like that to see?

  34. Justin says:

    Create a new JavaScript Flash (JFSL) File, paste the text in and hit the run button (looks like a play button).

  35. daniel zwiercan says:

    Hi Justin, great Extension.
    In the extension list it says “free” but in the licencing agreement it talks about a fee and legal agreement and stuff… what is the deal?
    Can I use this at work, like at a commercial animation studio?
    Is the freeware, creative commons?

    1. Justin says:

      Hi Daniel,
      That’s my standard EULA for both free and paid extensions. FrameScrub is totally free to use and can be applied to commercial projects.

      1. daniel zwiercan says:

        Great, Thanks for all the great work.

Leave a Reply

Your email address will not be published. Required fields are marked *