Archive for February 25th, 2008

I looked for a nice clean class implementation in PHP for embedding videos from youtube and such and could not find anything that was nice enough. So here is my take on embedding videos in php.
As the embed code is reconstructed it should be safe enough put probably some more checks need to be done after extracting the id to see there is nothing hostile there.

Configuration:
This class requires the SpyC library to read the cobnfiguration file. The library is assumed to be in the SITEBASE/include/yaml/ directory.

PHP:
  1. define('SITEBASE', '/var/www/mysite'); // path to the root of the site (not forcefully public)
  2. define('VIDEO_EMBED_CONFIG_FILE', SITEBASE.'/config/video_embed.yaml'); //path of video embed config file
  3. define('DEBUG', true); //to activate debug mode and false for production usage. it will write to a log file when something goes wrong but should not produce exceptions in production enviroment

USAGE
note: The embed code may either be embed or url

PHP:
  1. $embed='http://www.youtube.com/watch?v=h2EUW_rgDVo';
  2. $videoEmbed = new VideoEmbed($embed); //optional width and height may be passed to the constructor
  3. print($videoEmbed->embed);
  4. $videoEmbed->width = 240; // resize
  5. $videoEmbed->height = 120;
  6. print($videoEmbed->embed); // resized video
  7. print($videoEmbed->thumb); // get thumb url

the other public properties are: ->id, ->type, ->url, ->width and ->height
note that magic getters and setters are used to make ->id, ->type, ->url read only

TODO: thumbnails should be cached locally
TODO: Create unit tests (for the moment test_VideoEmbed(); does some testing)

The video services are configured in the configuration file (video_embed.yaml), form:

---
embedTemplate: default embed template
defaultWidth: default width
defaultHeight: default height
services:
    servicename:
        urlPattern: pattern to distinguis between services
        embedUrlTemplate: template (used with sprintf) for construvting the player url
        thumbnailUrlTemplate: template to find thumnbail by video ID (used with sprintf)
        thumbnailUrlExtractPattern: if present the thumbnailUrlTemplate is assumed to be a text resource, and this is a regexp to extract the thumnbail from it
        extractPattern: regexp pattern to extract
        apiUrl: api url (not used for the currently supported services)
        defaultWidth: service default width
        defaultHeight: service default height
		embedTemplate: specific embed template (not used for the current supported services)

Example configuration file for google video youtube and dailymotion (if you configure it for other services please post the config)... :)

---
embedTemplate: <object width="%2$s" height="%3$s" ><param name="movie" value="%1$s"></param><param name="wmode" value="transparent"></param><embed src="%1$s" type="application/x-shockwave-flash" wmode="transparent" width="%2$s" height="%3$s"></embed></object>
defaultWidth: 425
defaultHeight: 350
services:
    youtube:
        urlPattern: youtube.com
        embedUrlTemplate: http://www.youtube.com/v/%1$s&rel=1
        thumbnailUrlTemplate: http://i.ytimg.com/vi/%1$s/default.jpg
        extractPattern: /youtube\.com\/(v\/|watch\?v=)([\w\-]+)/
        apiUrl: http://www.youtube.com/api2_rest
        defaultWidth: 425
        defaultHeight: 350
    google:
        urlPattern: video.google
        extractPattern: /docid=([^&]*)/i
        embedUrlTemplate: http://video.google.com/googleplayer.swf?docId=%1$s
        thumbnailUrlTemplate: http://video.google.com/videofeed?docid=%s
        thumbnailUrlExtractPattern: '/

And here is the code :Complete class code with readme and configuration file

The code referes to a debug function, you can use:

PHP:
  1. function debug_log($msg, $file = "debug")
  2. {
  3.     $dbg = "";
  4.     if (SITE != '[PROD]') {
  5.         $bts = debug_backtrace();
  6.         foreach($bts as $bt) {
  7.             $path = str_replace(SITEBASE, '', $bt ['file']);
  8.             $dbg .= $path . " line " . $bt['line'] . " (function " . $bt['function'] . ")\n";
  9.         }
  10.         $handle = fopen(SITEBASE . "/../log/{$file}.log", "a");
  11.         fwrite($handle, strftime("%Y-%m-%d %H:%M:%S  ") . $dbg . $msg . "\n------------------\n");
  12.         fclose($handle);
  13.     }
  14. }

Click to continue reading

There are several Ruby on Rails plugins for tagging. The first one was acts_as_taggable by DHH. It was simple and limited in functionality, so it was forked several times. The most popular version of these forks is acts_as_taggable_on_steroids, which is pretty performant, has tag cloud calculations and offers extras such as tests.

However, it's not possible to have several sets of tags on the same object. For example, it can be useful to separate skills and interests for users. acts_as_taggable_on implements this lacking functionality.

Cool, but how do we use it ?

First, install it like any rails plugin :

script/plugin install http://svn.intridea.com/svn/public/acts_as_taggable_on/

Then, generate the migration for creating the new SQL tables for the tags (and play this migration) :

script/generate acts_as_taggable_on_migration
rake db:migrate

The last step of the installation is to declare the User class as taggable :

RUBY:
  1. class User <ActiveRecord::Base
  2.   acts_as_taggable_on :skills, :interest
  3.   # ...
  4. end

Done ? OK, let's test it in script/console:

RUBY:
  1. >> joe = User.new(:login => 'Joe')
  2. # => #<user id: nil, login: "joe">
  3.  
  4. >> joe.skill_list
  5. # => []
  6. >> joe.skill_list = "ruby, rails, optimization"
  7. # => "ruby, rails, optimization"
  8. >> joe.skill_list
  9. # => ["ruby", "rails", "optimization"]
  10.  
  11. >> joe.interest_list = "procrastinate, humour"
  12. # => "procrastinate, humour"
  13. >> joe.interest_list
  14. # => ["procrastinate", "humour"]
  15.  
  16. >> joe.save
  17. # => true
  18.  
  19. >> User.find_tagged_with("rails")
  20. # => [#</user><user id: 1, login: "joe">]
  21. >> User.find_tagged_with("rails", :on => :interests)
  22. # => []

With these methods, you should be able to have tags on many models on your app, with several sets if you want. The next big step is tag clouds, because of the lacking documentation. I think an example is welcomed.

So, the first thing is finding tags with their counts :

RUBY:
  1. # app/controllers/clouds_controller.rb
  2. class CloudsController <ApplicationController
  3.   def skills
  4.     @tags = User.skill_counts
  5.     @levels = (1 .. 5).map { |i| "level-#{i}" }
  6.   end
  7. end

Then, we can show them with the tag_cloud helper:

HTML:
  1. # app/views/clouds/skills.html.erb
  2. <% if @tags.empty? -%>
  3.   <p>No tags :/</p>
  4. <% else -%>
  5.   <ul id="tag-cloud">
  6.     <% tag_cloud(@tags, @levels) do |tag,level| -%>
  7.       <li class="<%= level %>"><%=h tag %>
  8.     <% end -%>
  9.   </li></ul>
  10. <% end -%>

Go on http://my-web-site/clouds/skills and have a glance on the tag clouds.

The last caveat is tag clouds with tags from all the models and sets. For this, we need to dive into acts_as_taggable_on and play with ActiveRecord:

RUBY:
  1. class CloudsController <ApplicationController
  2.   def index
  3.     @tags = Tag.find(:all,
  4.       :select => "#{Tag.table_name}.id, #{Tag.table_name}.name, COUNT(*) AS count",
  5.       :joins  => "LEFT OUTER JOIN #{Tagging.table_name} ON #{Tag.table_name}.id = #{Tagging.table_name}.tag_id",
  6.       :group  => "#{Tag.table_name}.id, #{Tag.table_name}.name HAVING COUNT(*)> 0",
  7.       :order  => "count DESC",
  8.       :limit  => 30
  9.     ).sort_by(&:name)
  10.     @levels = (1 .. 5).map { |i| "level-#{i}" }
  11.   end
  12. end

The app/views/clouds/all.html.erb view is the same as skills.html.erb. Enjoy it on http://my-web-site/clouds/ :-)

Click to continue reading

Creative Commons License
This work is licensed under a Creative Commons Attribution 2.0 License.