Flash Player version in AWStats - Revised

November 20th, 2008

My first implementation of this idea may have been sufficient for general use. For inclusion in Wordpress however it was problematic at best.
Now that I have revised (and tested) it, I thought I'd share this updated Wordpress plugin.

PHP:
  1. <?php
  2. /*
  3. Plugin Name: Flash version Detection
  4. Plugin URI: http://www.dirty-motherfucker.org/
  5. Description: Detects flash version
  6. Version: 0.1
  7. Author: gencha
  8. Author URI: http://www.dirty-motherfucker.org
  9. */
  10.  
  11. $plugin_root = get_settings('siteurl') . '/wp-content/plugins/'.dirname(plugin_basename(__FILE__));
  12.  
  13. if( preg_match("/(\/\?feed=|\/feed)/i",$_SERVER['REQUEST_URI']) ) {
  14.         // RSS Feeds
  15.         // do nothing
  16. } else {
  17.         add_action('wp_head', 'add_head');
  18. }
  19.  
  20. function add_head() {
  21.     global $plugin_root;
  22.     echo '
  23.     <!-- Flash player version detection -->
  24.     <script src="' . $plugin_root . '/swfobject.js" type="text/javascript"></script>
  25.     <script type="text/javascript">
  26.         window.onload = function() {
  27.             var version = deconcept.SWFObjectUtil.getPlayerVersion();
  28.             if( document.getElementById && version["major"]> 0 ) {
  29.                 var flash_version = version["major"] +"."+ version["minor"] +"."+ version["rev"];
  30.                 document.getElementById("flashversion").innerHTML = "<iframe src=\"' . get_settings("siteurl") . '/flash_player_version.php?version=" + flash_version + "\" style=\"display: none;\"></iframe>";
  31.             }
  32.         }
  33.     </script>
  34.     ';
  35. }
  36. ?>

In this version I put the JavaScript code inline. Although I'm not a big fan of that, it's reasonable in this case and I have all the code in one place. This made it easier to fix the second bug. Which was that the whole concept only worked on the main page. On other pages the browser couldn't find the flash_version.php file. Which resulted in nasty 404 errors. Now I can simply put the blogs base path into the JavaScript code and everything is well.

Furry Blog Logo

November 13th, 2008

I had a little fun with particles in AS3 the past days. I wrote a small piece based on my Processing port framework that traces the paths of particles. The particles pick up color from a given input texture. As they move, they plot their color onto the canvas. Although i added the condition that the color is only plotted if the target pixel is darker than the particle color. This results in a nice glowing effect around the outline of the image.
I built the swf files against Flash Player 10 this time. There is actually no need at all to do so, but i just couldn't resist Vector. Arrays are so dirty :(

Here is the larger version with the complete blog logo. ;)

And here's some source to go with it. Although it'll hardly compile outside of the Processing Port framework ;D But since i moved servers my SVN repository isn't publicly available anymore. So if anyone has any interest in the complete source, i'll wrap it up and put it online ;)

Particle.as

Actionscript:
  1. package org.dirty_dirtymotherfucker.tracer {
  2.   import flash.display.BitmapData;
  3.   import flash.geom.Point;
  4.   import flash.geom.Rectangle;
  5.   import org.dirty_dirtymotherfucker.processing.*;
  6.  
  7.   public class Particle {
  8.    
  9.     public var position_x:Number;
  10.     public var position_y:Number;
  11.     public var direction_x:Number;
  12.     public var direction_y:Number;
  13.     public var color:uint;
  14.     public var life:Number;
  15.     public var source:BitmapData;
  16.     public var randomSource:BitmapData;
  17.    
  18.     public function Particle( source:BitmapData, randomSource:BitmapData, sourceOffset:uint ) {
  19.       this.source = source;
  20.       if( Tracer.USE_PERLIN_NOISE ) {
  21.         this.randomSource = new BitmapData( randomSource.width, 1, false );
  22.         this.randomSource.copyPixels( randomSource, new Rectangle( 0, sourceOffset, randomSource.width, 1 ), new Point( 0, 0 ) );
  23.       }
  24.       birth();
  25.     }
  26.    
  27.     public function birth( ):void {
  28.       life        = Tracer.MAX_PARTICLE_LIFE;
  29.       position_x  = MathHelper.randomRange( -Tracer.CANVAS_SIZE_X, Tracer.CANVAS_SIZE_X );
  30.       position_y  = MathHelper.randomRange( -Tracer.CANVAS_SIZE_Y, Tracer.CANVAS_SIZE_Y );
  31.       direction_x = MathHelper.randomRange( -1.0, 1.0 );
  32.       direction_y = MathHelper.randomRange( -1.0, 1.0 );
  33.       color       = source.getPixel32( position_x, position_y );
  34.     }
  35.    
  36.     public function travel( source:BitmapData, canvas:BitmapData, blend:Number ):void {
  37.       if( Tracer.USE_PERLIN_NOISE ) {
  38.         direction_x += ( ColorHelper.getR( randomSource.getPixel( life * 10, 0 ) ) - 128 ) / 256;
  39.         direction_y += ( ColorHelper.getG( randomSource.getPixel( life * 10, 0 ) ) - 128 ) / 256;
  40.       } else {
  41.         direction_x += MathHelper.randomRange( -0.1, 0.1 );
  42.         direction_y += MathHelper.randomRange( -0.1, 0.1 );
  43.       }
  44.      
  45.       // normalize direction
  46.       var len:Number = Math.sqrt( direction_x * direction_x + direction_y * direction_y );
  47.       direction_x /= len;
  48.       direction_y /= len;
  49.      
  50.       position_x += direction_x;
  51.       position_y += direction_y;
  52.       life -= 0.1;
  53.       //if( 0 == color ) life = 0;
  54.      
  55.       var sourceColor:uint = source.getPixel32( position_x, position_y );
  56.       var canvasColor:uint = canvas.getPixel32( position_x, position_y );
  57.      
  58.       if( sourceColor != canvasColor ) {
  59.         color = ColorHelper.blend( color, sourceColor, blend );
  60.      
  61.         var colorFit:Boolean = ColorHelper.getR( ColorHelper.toGrayScale( color ) )> ColorHelper.getR( ColorHelper.toGrayScale( canvasColor ) );
  62.         if( 0 != ColorHelper.getA( color ) && colorFit ) {
  63.           canvas.setPixel32( position_x, position_y, ColorHelper.blend( color, canvasColor, 25 ) );
  64.           //canvas.setPixel32( position.x, position.y, color );
  65.         }
  66.       }
  67.      
  68.       if( 0> position_x || position_x> Tracer.CANVAS_SIZE_X || 0> position_y || position_y> Tracer.CANVAS_SIZE_Y || 0>= life ) birth();
  69.     }
  70.    
  71.   }
  72.  
  73. }

Tracer.as

Actionscript:
  1. package org.dirty_dirtymotherfucker.tracer {
  2.  
  3.   import flash.display.*;
  4.   import flash.events.*;
  5.   import flash.geom.Matrix;
  6.   import flash.geom.Point;
  7.   import flash.geom.Rectangle;
  8.   import flash.net.URLLoader;
  9.   import flash.net.URLRequest;
  10.  
  11.   import org.dirty_dirtymotherfucker.processing.*;
  12.  
  13.   /**
  14.    * The main implementation of the Tracer project
  15.    */
  16.   public class Tracer extends BlogSprite {
  17.    
  18.     // Main application settings
  19.    
  20.     public static const CANVAS_BACKGROUND_COLOR:uint  = 0xFF000000;
  21.    
  22.     public static const UPDATES_PER_SECOND:uint       = 1200;
  23.    
  24.     public static const ITERATIONS_PER_UPDATE:uint    = 10;
  25.    
  26.     public static const CANVAS_SIZE_X:uint            = 466;// 840;
  27.     public static const CANVAS_SIZE_Y:uint            = 220;
  28.    
  29.     public static const ADDITIVE_BLENDING:Boolean     = true;
  30.     public static const SUBTRACTIVE_BLENDING:Boolean  = false;
  31.     private static const MAX_ITERATIONS:uint          = 0;// 120 * 20;
  32.    
  33.     public static const MAX_PARTICLE_LIFE:Number      = 100;
  34.     public static const NUM_PARTICLES:int             = 1000;
  35.    
  36.     public static const USE_PERLIN_NOISE:Boolean      = false;
  37.    
  38.     private var iterationCount:int = 0;
  39.    
  40.     private var loader:Loader;
  41.     private var source:BitmapData;
  42.     private var randomSeed:BitmapData;
  43.    
  44.     private var particles:Vector.<Particle>;
  45.    
  46.     private var blend:Number;
  47.    
  48.     public function Tracer():void {
  49.       ColorHelper.loadPalette( );
  50.      
  51.       super( CANVAS_SIZE_X, CANVAS_SIZE_Y, CANVAS_BACKGROUND_COLOR );
  52.      
  53.       loader = new Loader();
  54.       //loader.load( new URLRequest( "map.png" ) );
  55.       loader.load( new URLRequest( "http://www.dirty-motherfucker.org/blog/wp-content/uploads/2008/11/motherfucking.png" ) );
  56.      
  57.       loader.contentLoaderInfo.addEventListener( Event.COMPLETE, drawMap );
  58.     }
  59.    
  60.     override protected function onDraw( event:TimerEvent ):void {
  61.       canvas.lock();
  62.       for( var iteration:uint = 0; iteration <ITERATIONS_PER_UPDATE; ++iteration ) {
  63.         // draw
  64.         if( null == loader.content ) break;
  65.         for each( var particle:Particle in particles ) {
  66.           particle.travel( source, canvas, blend );
  67.         }
  68.        
  69.         blend += 0.06;
  70.         if( blend>= 255 ) stopApp();
  71.        
  72.         // cycle limiter
  73.         if( MAX_ITERATIONS> 0 && ++iterationCount> MAX_ITERATIONS ) {
  74.           restart();
  75.         }
  76.       }
  77.       canvas.unlock();
  78.  
  79.     }
  80.    
  81.     override protected function startApp( updatesPerSecond:uint = 0 ):void {
  82.       super.startApp( UPDATES_PER_SECOND );
  83.      
  84.       particles = new Vector.<Particle>();
  85.      
  86.       blend = 10;
  87.      
  88.       iterationCount = 0;
  89.      
  90.       canvas.fillRect( new Rectangle( 0, 0, CANVAS_SIZE_X, CANVAS_SIZE_Y ), CANVAS_BACKGROUND_COLOR );
  91.      
  92.       initContent();
  93.     }
  94.    
  95.     private function drawMap( e:Event ):void {
  96.       loader.removeEventListener( Event.COMPLETE, drawMap );
  97.      
  98.       initContent();
  99.     }
  100.    
  101.     private function initContent():void {
  102.       if( null == loader ) return;
  103.       source = new BitmapData( CANVAS_SIZE_X, CANVAS_SIZE_Y, true, 0x00000000 );
  104.       var matrix:Matrix = new Matrix();
  105.       matrix.translate( 0, CANVAS_SIZE_Y / 2 - loader.height / 2 );
  106.       source.draw( loader, matrix );
  107.      
  108.       if( USE_PERLIN_NOISE ) {
  109.         randomSeed = new BitmapData( MAX_PARTICLE_LIFE * 10, NUM_PARTICLES, false, 0x000000 );
  110.         randomSeed.perlinNoise( MAX_PARTICLE_LIFE * 10, NUM_PARTICLES, 8, Math.random() * uint.MAX_VALUE, true, true, BitmapDataChannel.GREEN | BitmapDataChannel.RED, false );
  111.       }
  112.      
  113.       for( var i:uint = 0; i <NUM_PARTICLES; ++i ) {
  114.         particles.push( new Particle( source, randomSeed, i ) );
  115.       }
  116.      
  117.     }
  118.  
  119.   }
  120.  
  121. }

Including Flash Player version in AWStats

November 12th, 2008

I was interested in what Flash Player version people are using when visiting my site. So i looked around the web for a solution including AWStats. At first i was out of luck until i noticed that AWStats has a discussion forum on sourceforge as well.
Someone there came up with a solution which was almost perfect for me.

First of all you're gonna want to put this in your awstats config file for your site:

CODE:
  1. ExtraSectionName1="Flash Player Version"
  2. ExtraSectionCodeFilter1="200 304"
  3. ExtraSectionCondition1="URL,\/flash_player_version\.php"
  4. ExtraSectionFirstColumnTitle1="Version"
  5. ExtraSectionFirstColumnValues1="QUERY_STRING,version=([^&]+)"
  6. ExtraSectionFirstColumnFormat1="%s"
  7. ExtraSectionStatTypes1=PL
  8. ExtraSectionAddAverageRow1=0
  9. ExtraSectionAddSumRow1=1
  10. MaxNbOfExtra1=100
  11. MinHitExtra1=1

Note that i made some slight adjustments to some strings compared to the original version.
Now you need to add some JavaScript to make appropriate URL requests that AWStats can read from.
Note that this is the original code, not the one i finally ended up with.

JavaScript:
  1. <script language="javascript" type="text/javascript" src="../swfobject/swfobject.js"></script>
  2.  
  3. <script type="text/javascript">
  4. var version = deconcept.SWFObjectUtil.getPlayerVersion();
  5. if (document.getElementById && version["major"]> 0) {
  6. var flash_version = version['major'] +"."+ version['minor'] +"."+ version['rev'];
  7. document.getElementById('flashversion').innerHTML = "<iframe src=\"../version.php?version=" + flash_version + "\" style=\"display: none;\"></iframe>";
  8. }
  9. </script>

As i did not want to just jam this into my theme files, i wrote a little Wordpress plugin to do the above task.

PHP:
  1. <?php
  2. /*
  3. Plugin Name: Flash Player Version Detection
  4. Plugin URI: http://www.dirty-motherfucker.org/
  5. Description: Detects flash version
  6. Version: 0.1
  7. Author: gencha
  8. Author URI: http://www.dirty-motherfucker.org
  9. */
  10.  
  11. $plugin_root = get_settings('siteurl') . '/wp-content/plugins/'.dirname(plugin_basename(__FILE__));
  12.  
  13. if( preg_match("/(\/\?feed=|\/feed)/i",$_SERVER['REQUEST_URI']) ) {
  14.         // RSS Feeds
  15.         // do nothing
  16. } else {
  17.         add_action('wp_head', 'add_head');
  18. }
  19.  
  20. function add_head() {
  21.     global $plugin_root;
  22.     echo '
  23.         <!-- Flash player version detection -->
  24.         <script src="' . $plugin_root . '/swfobject.js" type="text/javascript"></script>
  25.         <script src="' . $plugin_root . '/flash_version.js" type="text/javascript"></script>
  26.     ';
  27. }
  28. ?>

I took some small bits from the KML Flash Embed plugin. This will include the appropriate JavaScript files in your blog. Obviously, if you're not using Wordpress, this is of no use to you. You can just directly include SWFObject in the head section of your site.

Now the important bit is the JavaScript file that actually performs the URL request which AWStats will later see.

JavaScript:
  1. window.onload = function() {
  2.     var version = deconcept.SWFObjectUtil.getPlayerVersion();
  3.     if( document.getElementById && version["major"]> 0 ) {
  4.         var flash_version = version['major'] +"."+ version['minor'] +"."+ version['rev'];
  5.         document.getElementById("flashversion").innerHTML = "<iframe src=\"../flash_player_version.php?version=" + flash_version + "\" style=\"display: none;\"></iframe>";
  6.     }
  7. }

Now the only thing that is left is that you include an empty div tag with the id "flashversion" somewhere in your site. Now THIS i did just jam into my theme :P
Although i am sure there is some hook i can use in the Wordpress plugin architecture. But i'm lazy. So there you have it.

Sketches #5

November 10th, 2008

Postal sketch second round ;)

postal2.png

I hope there'll be more technical content again soon ;)

Sketches #4

October 30th, 2008

A small sketch i drew during a boring meeting. At least something worth to post. Hope you like it.

postal.png

And maybe you noticed that i re-did the theme of the blog. Thanks to Alien^PDX who inspired me to do it with the background image of his own new website. Be sure to check out his website.

Converting SWF animations to video

October 14th, 2008

If you ever tried to convert a SWF animation to a video you know what a pain it can be. Let's first look at the solutions at hand:

  • Exporting a movie from Flash
  • Using a SWF to movie converter
  • Screen capture

Now let's break those down.

  • Exporting a movie from Flash
    - Doesn't play sub-movieclip animations
  • Using a SWF to video converter
    - Some have limited encoding support
    - Some have really bad encoding quality
    - Some have poor playback performance and the result looks shitty
    - Some also don't play sub-movieclip animations
  • Screen capture
    - Suffers from the same encoding and playback problems as converters

So, after trying all of the above on a project at hand i was rather disappointed to see that all the solution i tried were basically shit. The animation at hand had 1024x768 dimensions and only in Flash player 10 it played with reasonable speed. So when i added screen capture into the mix the result looked like crap.

So i rolled my own converter. And the goals were clear:

  • I want to capture every single frame of the animation
  • I want the highest quality possible
  • I want to use the vector scaling of flash to achieve my target size to avoid bitmap scaling later on in the process.

Now what i ended up with is a converter that just writes out every single frame as PNG, no sound, no direct video. But capturing the sound is extremely easy and so is converting a PNG sequence into a movie.

So here is how it's done.
At first i tried to gotoAndStop( ++frame ); in a loop and writing out the frames, but this results in sub-movieclip animations not being played back.
So what you have to do is just play the movie. But you place a listener on it for Event.ENTER_FRAME. If your host application runs at the same frame rate you can also place the listener on the host application (which is what I've done in my reference implementation).

Now in the event listener you just .draw into a BitmapData object, encode it to PNG (using the AsPngEncoder from the AsWing project for example) and then write it to disk.
And this is where you get problems. Cause Flash itself won't let you write to disk. I had 2 solutions to that problem. I used SWFStudio to compile a .exe that could write binaries to disk and i wrote one version in AIR.

Feel free to look at both implementations, but keep in mind that they are basically proof-of-concept, look like shit and are almost unuseable.

The SWFStudio version expects the name of the swf as the first command-line parameter (which can't include spaces). The second and third parameter are the width and height. The last parameter is the number of frames to write.

The AIR version includes a GUI to set the input movie and output folder.

Have fun!

SWFStudio Version
Adobe AIR Version

Setting up Cacti

October 5th, 2008

So in the process of migrating our company network architecture over the paths months, I had the chance to set up Cacti for server monitoring. I followed some documentation and articles on the net but ended up making some stupid mistakes. So, let's start with this.

The host system Cacti will be running on, is Ubuntu Server 8.04.1 with no prior modifications. I just installed all pending updates as of today of course. To reproduce this I'm running it in a VMWare virtual machine (just to make sure I'm not missing something important). The configuration of the systems that are being monitored is really not that important. As long as you can run an SNMP deamon on them.

Which brings me to the first thing I didn't understand from the start. Cacti itself is the tool that is monitoring other servers. Of course you can monitor the machine that Cacti is running on as well, but the main point is, you don't have to install it on every machine that you want to monitor.

We start off by installing MySQL-Server, as the configure process for Cacti can't do that on it's own.

CODE:
  1. sudo apt-get install mysql-server -y

We input a root password and that's that with the MySQL-Server.

Now let's install Cacti itself.

CODE:
  1. sudo apt-get install cacti -y

I configure it to use Apache2. And I use dbconfig-common for database configuration. Give it the MySQL-Server root password and then a password for the Cacti user. And then Cacti is installed.

Now we configure Cacti via the web interface. For the initial setup process you could just use w3m:

CODE:
  1. w3m http://localhost/cacti

But sooner or later you better get something with a GUI ;)

When you get to the login screen, give it the default login admin/admin. After that you have to give the admin account a new password. Congratulations, now Cacti is set up and ready to go.

So, when you click the Graphs tab now, you'll most likely see nothing, or if you already waited for the poller to run for the first time, you see 4 (almost) empty graphs for localhost. These graphs are created by running scripts on the local host. I usually use SNMP to gather the data. So now we create a new device for the local host which is SNMP enabled. But for that, we first need to install the SNMP deamon.

CODE:
  1. sudo apt-get install snmpd -y;
  2. sudo vi /etc/default/snmpd

What we want to change in /etc/default/snmpd is mainly the logging options. SNMDOPTS includes "-Lsd", more suitable would be "-LS 0-5 d". The original setting will log at a debug level and pollute the logfiles. The new one will only log important messages.
At the end of SNMPDOPTS there is also a 127.0.0.1, this is the listening address. To monitor our host system this would be fine. But for additional machines, make sure to either remove it or adjust it to the correct listening address.

CODE:
  1. sudo vi /etc/snmp/snmpd.conf

In this file look for lines starting with com2sec. What you're gonna want to do is, comment out the first line (the one for paranoid settings) and enable the second line, which allows read-only access.
Now restart snmpd:

CODE:
  1. sudo invoke-rc.d snmpd restart

We're not ready to create our new device.
In Cacti, go to the console tab. Select "create Devices" from the list in the middle. Click Add in the upper right corner.
Give the new device a proper name and supply the ip address. If you didn't adjust the ip address in /etc/default/snmpd, then make sure to use 127.0.0.1 here.
As the host template select "Generic SNMP-enabled Host" and at the bottom of the form, select "Version 1" as the SNMP Version.
Now you can select SNMP as the Downed Device Detection as well. And that's it.

Now, let's first add a few more data sources. In the "Add data query" dropdown in the lower right, select "SNMP - Get mounted partitions" then click "Add" on the right. Repeat the process for "SNMP - Get Processor Information".
Now we're ready to create some graphs.

Make sure that all the data queries have already returned successful results. The status will say "Success" and it will signal that some items have been returned. Especially the Processor information can take a while until valid data is returned. You can click the green circle to refresh the data. When you have proper data, click "Create Graphs for this Host" at the top of the page. Add all data queries for "SNMP - Get Mounted Partitions" and "SNMP - Get Processor Information". For "SNMP - Interface Statistics", select the eth interfaces you wanna monitor, then select the graph from the dropdown on the lower right. For example "Traffic (bytes/sec, Total Bandwidth)". You can come back later to this screen to create more interface related graphs. Confirm the next screen with the "Create" button.

Now select the Devices from the menu on the left. Select your newly created device from the list, select "Place on a Tree (default Tree)" from the dropdown on the bottom right and click "Add". Confirm by clicking "yes".

Now go to the "graphs" tab again. As before, you'll most likely see nothing when looking at your new device, just wait for it ;)

That should be sufficient to get you started. Have fun.

Supernova

August 18th, 2008

While working on a reference implementation in Actionscript for a demo effect, I accidentally ended up with this beauty. It's based on the lovely City Traveler algorithm by Jared Tarbell with only a few minor adjustments actually. I thought I'd share nevertheless :) Be sure to stop it at some point, otherwise it will go on and on forever, eating up you cpu time.

As always, I also provide a larger version.

You can also dig out the source from the Processing repository, but it's now buried under the more recent revision of the effect i was actually going for. So if you're interested, better drop me a line ;)