Index: /trunk/trac-hacks/moviemacro/movie/htdocs/js/flashembed.min.js
===================================================================
--- /trunk/trac-hacks/moviemacro/movie/htdocs/js/flashembed.min.js	(revision 663)
+++ /trunk/trac-hacks/moviemacro/movie/htdocs/js/flashembed.min.js	(revision 663)
@@ -0,0 +1,17 @@
+
+/**
+ * flashembed 0.28. Adobe Flash embedding script
+ * 
+ * http://flowplayer.org/tools/flash-embed.html
+ *
+ * Copyright (c) 2008 Tero Piirainen (tero@flowplayer.org)
+ *
+ * Released under the MIT License:
+ * http://www.opensource.org/licenses/mit-license.php
+ * 
+ * >> Basically you can do anything you want but leave this header as is <<
+ *
+ * Since: 				03/11/2008 
+ * Version: 0.28 		05/26/2008
+ */
+function flashembed(g,h,j){if(typeof g=='string')g=document.getElementById(g);var k={src:'#',width:'100%',height:'100%',version:null,loadEvent:null,onFail:null,expressInstall:null,allowfullscreen:true,allowscriptaccess:'always',quality:'high',bgcolor:'#ffffff',type:'application/x-shockwave-flash',pluginspage:'http://www.adobe.com/go/getflashplayer'};extend(k,h);var l=k.loadEvent;k.loadEvent=null;if(l){if(!g)return;g['on'+l]=function(){return load()}}else{return load()}function extend(a,b){if(b){for(key in b){a[key]=b[key]}}}function load(){var a=getVersion();var b=k.version;var c=k.expressInstall;if(!g)return;if(!b||isSupported(b)){k.onFail=k.version=k.expressInstall=null;g.innerHTML=getHTML();return g.firstChild}else if(k.onFail){var d=k.onFail.call(k,getVersion(),j);if(d)g.innerHTML=d}else if(b&&c&&isSupported([6,65])){extend(k,{src:c});j={MMredirectURL:location.href,MMplayerType:'PlugIn',MMdoctitle:document.title};g.innerHTML=getHTML()}else{if(g.innerHTML.replace(/\s/g,'')!=''){}else{g.innerHTML="<h2>Flash version "+b+" or greater is required</h2>"+"<h3>"+(a[0]>0?"Your version is "+a:"You have no flash plugin installed")+"</h3>"+"<p>Download latest version from <a href='"+k.pluginspage+"'>here</a></p>"}}g['on'+l]=null}function isSupported(a){var b=getVersion();var c=(b[0]>a[0])||(b[0]==a[0]&&b[1]>=a[1]);return c}function getHTML(){var a="";if(typeof j=='function')j=j();if(navigator.plugins&&navigator.mimeTypes&&navigator.mimeTypes.length){a='<embed type="application/x-shockwave-flash" ';extend(k,{name:k.id});for(var b in k){if(k[b]!=null)a+=[b]+'="'+k[b]+'"\n\t'}if(j){a+='flashvars=\'';for(var b in j){a+=[b]+'='+asString(j[b])+'&'}a+='\''}a+='/>'}else{a='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" ';a+='width="'+k.width+'" height="'+k.height+'"';if(document.all&&parseInt(navigator.appVersion)<=6){k.id="_"+(""+Math.random()).substring(5)}if(k.id)a+=' id="'+k.id+'"';a+='>';a+='\n\t<param name="movie" value="'+k.src+'" />';k.id=k.src=k.width=k.height=null;for(var b in k){if(k[b]!=null)a+='\n\t<param name="'+b+'" value="'+k[b]+'" />'}if(j){a+='\n\t<param name="flashvars" value=\'';for(var b in j){a+=[b]+'='+asString(j[b])+'&'}a+='\' />'}a+="</object>"}return a}function getVersion(){var a=[0,0];if(navigator.plugins&&typeof navigator.plugins["Shockwave Flash"]=="object"){var b=navigator.plugins["Shockwave Flash"].description;if(typeof b!="undefined"){b=b.replace(/^.*\s+(\S+\s+\S+$)/,"$1");var c=parseInt(b.replace(/^(.*)\..*$/,"$1"),10);var d=/r/.test(b)?parseInt(b.replace(/^.*r(.*)$/,"$1"),10):0;a=[c,d]}}else if(window.ActiveXObject){try{var f=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7")}catch(e){try{var f=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");a=[6,0];f.AllowScriptAccess="always"}catch(e){if(a[0]==6)return}try{var f=new ActiveXObject("ShockwaveFlash.ShockwaveFlash")}catch(e){}}if(typeof f=="object"){var b=f.GetVariable("$version");if(typeof b!="undefined"){b=b.replace(/^\S+\s+(.*)$/,"$1").split(",");a=[parseInt(b[0],10),parseInt(b[2],10)]}}}return a}function asString(b){switch(typeOf(b)){case'string':return'"'+b.replace(new RegExp('(["\\\\])','g'),'\\$1')+'"';case'array':return'['+map(b,function(a){return asString(a)}).join(',')+']';case'object':var c=[];for(var d in b){c.push('"'+d+'":'+asString(b[d]))}return'{'+c.join(',')+'}'}return String(b).replace(/\s/g," ").replace(/\'/g,"\"")}function typeOf(a){if(a===null||a===undefined)return false;var b=typeof a;return(b=='object'&&a.push)?'array':b}if(window.attachEvent){window.attachEvent("onbeforeunload",function(){__flash_unloadHandler=function(){};__flash_savedUnloadHandler=function(){}})}function map(a,b){var c=[];for(var i in a){c[i]=b(a[i])}return c}flashembed.getVersion=getVersion;flashembed.isSupported=isSupported;return g}if(typeof jQuery=='function'){(function($){$.fn.extend({flashembed:function(a,b){return this.each(function(){new flashembed(this,a,b)})}})})(jQuery)}
Index: /trunk/trac-hacks/moviemacro/movie/htdocs/js/flow.embed.js
===================================================================
--- /trunk/trac-hacks/moviemacro/movie/htdocs/js/flow.embed.js	(revision 663)
+++ /trunk/trac-hacks/moviemacro/movie/htdocs/js/flow.embed.js	(revision 663)
@@ -0,0 +1,79 @@
+
+/**
+ * flowembed 0.10. Flowplayer embedding script
+ * 
+ * http://flowplayer.org/tools/flow-embed.html
+ *
+ * Copyright (c) 2008 Tero Piirainen (tero@flowplayer.org)
+ *
+ * Released under the MIT License:
+ * http://www.opensource.org/licenses/mit-license.php
+ * 
+ * >> Basically you can do anything you want but leave this header as is <<
+ *
+ * Version: 0.10 - 05/19/2008
+ */
+ 
+(function($) {		
+	
+	// jQuery plugin initialization
+	$.fn.extend({
+		flowembed: function(params, config, opts) { 			
+			return this.each(function() {
+				new flowembed($(this), params, config, opts);
+			});
+		}
+	});
+					
+			
+	function flowembed(root, params, config, embedOpts) {
+	
+		var opts = {
+			oneInstance: true,
+			activeClass: 'playing',
+			overlayClass: 'playButton'
+		};	
+		
+		$.extend(opts, embedOpts);
+		var player = null;
+		config = config || {};
+		if (typeof params == 'string') params = {src:params};
+		
+		root.click(function(event) {			
+			
+			// oneInstance previously playing video	
+			if (opts.oneInstance) onClipDone();
+			
+			// save nested HTML content for resuming purposes
+			root.addClass(opts.activeClass).data("html", root.html());
+			
+			// build flowplayer with videoFile supplied in href- attribute
+			config.videoFile = root.attr("href"); 			
+
+			player = flashembed(this, params, {config:config});
+			
+			// disable default behaviour
+			return false;
+			
+		}); 
+
+		// create play button on top of splash image
+		root.append($("<div/>").addClass(opts.overlayClass));
+
+		
+		/* 
+			this function is called by Flowplayer when playback finishes. 
+			it makes currently playing video to oneInstance it's original
+			HTML stage.
+		*/
+		if (opts.oneInstance && !$.isFunction("onClipDone")) {			
+			window.onClipDone = function() {
+				$("." + opts.activeClass).each(function() {
+					$(this).html($(this).data("html")).removeClass(opts.activeClass);	
+				});
+			};			
+		} 
+	}
+
+})(jQuery);
+
Index: /trunk/trac-hacks/moviemacro/movie/__init__.py
===================================================================
--- /trunk/trac-hacks/moviemacro/movie/__init__.py	(revision 663)
+++ /trunk/trac-hacks/moviemacro/movie/__init__.py	(revision 663)
@@ -0,0 +1,1 @@
+from movie import *
Index: /trunk/trac-hacks/moviemacro/movie/macro.py
===================================================================
--- /trunk/trac-hacks/moviemacro/movie/macro.py	(revision 663)
+++ /trunk/trac-hacks/moviemacro/movie/macro.py	(revision 663)
@@ -0,0 +1,238 @@
+""" Movie plugin for Trac.
+    
+    Embeds various online movies.
+"""
+from urlparse import urlparse, urlunparse
+from genshi.builder import tag
+from trac.core import *
+from trac.wiki.api import IWikiMacroProvider, parse_args
+from trac.web.chrome import ITemplateProvider, add_script
+from trac.wiki.macros import WikiMacroBase
+
+
+EMBED_COUNT = '_moviemacro_embed_count'
+
+def get_absolute_url(base, url):
+    """ Generate an absolute url from the url with the special schemes
+        {htdocs,chrome,ticket,wiki,source} simply return the url if given with
+        {http,https,ftp} schemes.
+        
+        Examples:
+            http://example.com/filename.ext
+                ie. http://www.google.com/logo.jpg
+            
+            chrome://site/filename.ext
+            htdocs://img/filename.ext
+                note: `chrome` is an alias for `htdocs`
+            
+            ticket://number/attachment.pdf
+                ie. ticket://123/specification.pdf
+            
+            wiki://WikiWord/attachment.jpg
+            
+            source://changeset/path/filename.ext
+                ie. source://1024/trunk/docs/README
+    """
+    def ujoin(*parts):
+        """ Remove double slashes.
+        """
+        parts = [part.strip('/') for part in parts]
+        return '/' + '/'.join(parts)
+    
+    scheme, netloc, path, query, params, fragment = urlparse(url)
+    
+    if scheme in ('ftp', 'http', 'https'):
+        return url
+    
+    if scheme in ('htdocs', 'chrome'):
+        return ujoin(base, 'chrome', path)
+    
+    if scheme in ('source',):
+        return ujoin(base, 'export', path)
+    
+    if scheme in ('ticket',):
+        return ujoin(base, 'raw-attachment/ticket', path)
+    
+    if scheme in ('wiki',):
+        return ujoin(base, 'raw-attachment/wiki', path)
+    
+    return url
+
+def string_keys(d):
+    """ Convert unicode keys into string keys, suiable for func(**d) use.
+    """
+    sdict = {}
+    for key, value in d.items():
+        sdict[str(key)] = value
+    
+    return sdict
+
+def xform_style(style):
+    """ Convert between a style-string and a style-dictionary.
+    """
+    if isinstance(style, dict):
+        result = '; '.join(['%s: %s' % (k, v) for k, v in style.items()])
+        if result:
+            result += ';'
+    else:
+        result = style.split(';')
+        while '' in result:
+            result.remove('')
+        
+        result = dict((s.strip() for s in i.split(':', 1)) for i in result)
+    
+    return result
+
+def xform_query(query):
+    """ Convert between a query-string and a query-dictionary.
+    """
+    if isinstance(query, dict):
+        result = '&'.join(['%s=%s' % (k, v) for k, v in query.items()])
+    else:
+        result = query.split('&')
+        while '' in result:
+            result.remove('')
+        
+        result = dict((s.strip() for s in i.split('=', 1)) for i in result)
+    
+    return result
+
+
+class MovieMacro(WikiMacroBase):
+    
+    implements(IWikiMacroProvider, ITemplateProvider)
+    
+    # IWikiMacroProvider methods
+    def expand_macro(self, formatter, name, content):
+        args, kwargs = parse_args(content, strict=True)
+        kwargs = string_keys(kwargs)
+        
+        if len(args) >= 1:
+            url = args[0]
+        elif len(args) == 0:
+            raise TracError('URL to a movie at least required.')
+        
+        embed_count = getattr(formatter, EMBED_COUNT, 0)
+        embed_count += 1
+        setattr(formatter, EMBED_COUNT, embed_count)
+        
+        url = get_absolute_url(formatter.href.base, url)
+        src = get_absolute_url(formatter.href.base, 'htdocs://movie/img/black.jpg')
+        
+        scheme, netloc, path, params, query, fragment = urlparse(url)
+        
+        try:
+            style_dict = xform_style(kwargs.get('style', ''))
+        except:
+            raise TracError('Double check the `style` argument.')
+        
+        style = {
+            'display': 'block',
+            'border': style_dict.get('border', 'none'),
+            'margin': style_dict.get('margin', '0 auto'),
+            'clear': 'both'
+        }
+        
+        if netloc == 'www.youtube.com':
+            query_dict = xform_query(query)
+            video = query_dict.get('v')
+            
+            url = urlunparse((scheme, netloc, '/v/%s' % video, '', '', ''))
+            
+            width = kwargs.pop('width', style_dict.get('width', '425px'))
+            height = kwargs.pop('height', style_dict.get('height', '344px'))
+            
+            style.update({
+                'width': width,
+                'height': height,
+            })
+            
+            return tag.object(tag.param(name='movie', value=url),
+                              tag.param(name='allowFullScreen', value='true'),
+                              tag.embed(src=url, type='application/x-shockwave-flash', allowfullscreen='true', width=width, height=height),
+                              style=xform_style(style))
+        
+        if netloc == 'video.google.com':
+            query_dict = xform_query(query)
+            query_dict['hl'] = 'en'
+            query_dict['fs'] = 'true'
+            
+            query = xform_query(query_dict)
+            
+            url = urlunparse((scheme, netloc, '/googleplayer.swf', '', query, ''))
+            
+            width = kwargs.pop('width', style_dict.get('width', '400px'))
+            height = kwargs.pop('height', style_dict.get('height', '326px'))
+            
+            style.update({
+                'width': width,
+                'height': height,
+            })
+            
+            return tag.embed(src=url,
+                             allowFullScreen='true',
+                             allowScriptAccess='always',
+                             type='application/x-shockwave-flash',
+                             style=xform_style(style))
+        
+        if netloc == 'www.metacafe.com':
+            parts = path.split('/')
+            try:
+                path = '/fplayer/%s/%s.swf' % (parts[2], parts[3])
+            except:
+                raise TracError("Non-standard URL, don't know how to process it, file a ticket please.")
+            
+            url = urlunparse((scheme, netloc, path, '', '', ''))
+            
+            width = kwargs.pop('width', style_dict.get('width', '400px'))
+            height = kwargs.pop('height', style_dict.get('height', '345px'))
+            
+            style.update({
+                'width': width,
+                'height': height,
+            })
+            
+            return tag.embed(src=url,
+                             wmode='transparent',
+                             pluginspage='http://www.macromedia.com/go/getflashplayer',
+                             type='application/x-shockwave-flash',
+                             style=xform_style(style))
+        
+        # Local movies.
+        tags = []
+        
+        if embed_count == 1:
+            add_script(formatter.req, 'movie/js/flashembed.min.js')
+            add_script(formatter.req, 'movie/js/flow.embed.js')
+            
+            script = '''
+                $(function() {
+                    $("a.flowplayer").flowembed("%s",  {initialScale:'scale'});		
+                });
+            ''' % get_absolute_url(formatter.href.base, 'htdocs://movie/swf/FlowPlayerDark.swf')
+            
+            tags.append(tag.script(script))
+        
+        width = kwargs.pop('width', style_dict.get('width', '320px'))
+        height = kwargs.pop('height', style_dict.get('height', '320px'))
+        
+        style.update({
+            'width': width,
+            'height': height,
+        })
+        
+        kwargs = {'style': xform_style(style)}
+        
+        tags.append(tag.a(tag.img(src=src, **kwargs), class_='flowplayer', href=url, **kwargs))
+        
+        return ''.join([str(i) for i in tags])
+    
+    # ITemplateProvider methods
+    def get_htdocs_dirs(self):
+        """ Makes the 'htdocs' folder inside the egg available.
+        """
+        from pkg_resources import resource_filename
+        return [('movie', resource_filename('movie', 'htdocs'))]
+    
+    def get_templates_dirs(self):
+        return []  # must return an iterable
Index: /trunk/trac-hacks/moviemacro/setup.py
===================================================================
--- /trunk/trac-hacks/moviemacro/setup.py	(revision 663)
+++ /trunk/trac-hacks/moviemacro/setup.py	(revision 663)
@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+
+from setuptools import setup
+
+description = '''\
+Embeds various online videos into wiki pages.
+Currently supports local files and YouTube, GoogleVideo and MetaCafe movies.
+
+This plugin makes use of the FlowPlayer [1] flash video player.
+If you are in a country where bandwidth comes at a premium considder
+downloading [2] the content and placing it in you htdocs directory,
+then reference it as htdocs://site/filename.flv
+
+[1] http://flowplayer.org/
+[2] http://www.arrakis.es/~rggi3/youtube-dl/
+'''
+
+setup(
+    name = 'MovieMacro',
+    version = '0.1',
+    packages = ['movie'],
+    package_data = {'movie': ['htdocs/img/*.jpg',
+                              'htdocs/js/*.js',
+                              'htdocs/swf/*.swf']},
+    author = 'Louis Cordier',
+    author_email = 'lcordier@gmail.com',
+    description = description,
+    url = 'http://trac-hacks.org/wiki/MovieMacro/',
+    license = 'BSD',
+    keywords = 'trac plugin movie macro',
+    classifiers = ['Framework :: Trac'],
+    entry_points = {'trac.plugins': ['movie.macro = movie.macro']}
+)
Index: /trunk/plugins/oforgeplugin/share/conf/trac.ini.oforge
===================================================================
--- /trunk/plugins/oforgeplugin/share/conf/trac.ini.oforge	(revision 650)
+++ /trunk/plugins/oforgeplugin/share/conf/trac.ini.oforge	(revision 663)
@@ -104,4 +104,5 @@
 tractoc.* = enabled
 includemacro.* = enabled
+movie.* = enabled
 # These 3 modules *only* are used from account manager
 acct_mgr.api.AccountManager = enabled
