Hulu Plugin Development Thread - Developers only!

  Thread Rating:
  • 0 Votes - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Post Reply
n9mjg Offline
Junior Member
Posts: 3
Joined: Jan 2009
Reputation: 0
Post: #11
Cre8tvDstruct0n Wrote:WIll Apple tv work with this gnash plugin?

In order to get this working on Apple TV, someone with an Intel based Mac running OS X 10.4 would have to compile gnash and all of its dependencies and than place the entire thing into a package installer or tar archive. Either way it's a daunting task and would be a very large install.
find quote
rectalogic Offline
Junior Member
Posts: 30
Joined: Feb 2009
Reputation: 0
Post: #12
rwparris2 Wrote:Edit2: Won't work if I point it at a local sec.swf, either. DecryptPid.swf opens in a small window, and I get no output. I let it sit ~15 minutes before closing it.

Your gnash is probably not console mode - i.e. it's a GUI app and so has no stdout. You can use dumpbin to check the subsystem of your gnash, probably needs to be Windows CUI and not Windows GUI
http://support.microsoft.com/kb/177429

See if you have dump-gnash, it may be CUI.
find quote
mgandalf Offline
Junior Member
Posts: 2
Joined: Feb 2009
Reputation: 0
Post: #13
Regarding missing season episodes due to how the plugin grabs the rss feed which seems to only have 20 entries, I've created a short script to parse out all the episodes based on a given season. I'm not at all familiar with Python, so I wrote this as a proof of concept in Perl.

Maybe someone can adapt this to Python?

Code:
#!/usr/bin/perl

package main;

$Version  = 'Hulu Episode Grabber v0.1';

# Hulu Episide Grabber
#
# Written by Mark R. Buechler 02/23/09
# License: GPL2

sub usage
  {
    die <<"EndUsage";
$Version

Usage:
     -show <id>    : Hulu show_id.
     -season <num> : Season number.

EndUsage
  }

use LWP::Simple;
use Getopt::Long;
use strict;

main();

sub getArgs {
        my $showid;
        my $season;

        my $p = new Getopt::Long::Parser;

        if (!$p->getoptions('show=s'    => \$showid,
                            'season=i'  => \$season)) {
                print "Invalid parameters given.\n";
                usage();
        }

        if (!$showid || !$season) {
                print "Please specify -show & -season.\n";
                usage();
        }

        return($showid, $season);
}

sub main {
        my($showid, $season) = getArgs();

        my $html = parseAjax($showid, $season);

        my $parser = new HuluParser;

        $parser->parse($html);
}

sub parseAjax {
        my $showid = shift;
        my $season = shift;
        my $parsed;
        my $id;

        my $url = "http://www.hulu.com/videos/season_expander?".
          "order=acce&page=1&season_number=$season&show_id=$showid";

        my $ajax;
        if (!defined($ajax = LWP::Simple::get($url))) {
                return undef;
        }

        foreach my $line (split(/\n/, $ajax)) {
                if ($line =~ /^new\sInsertion.After\(.*\sid=\\\"(.*)\\\"\s/) {
                        $id = $1;
                } elsif ($line =~ /^new\sInsertion.Before\(\"$id\",\s\"(.*)\"\)\;/) {
                        $parsed = $1;
                }
        }

        $parsed =~ s/\\074/\</g;
        $parsed =~ s/\\076/\>/g;
        $parsed =~ s/\\"/\"/g;
        $parsed =~ s/\\'/\'/g;

        return $parsed;
}

package HuluParser;
use base qw(HTML::Parser);

my $EPISODE_NUM = 0;
my $EPISODE_NAM = 0;

sub text {
        my $self = shift;
        my $origtext = shift;
        my $is_cdata = shift;

        if ($EPISODE_NUM) {
                print "$origtext: ";
        } elsif ($EPISODE_NAM) {
                print "$origtext\n";
        }
}

sub start {
        my $self = shift;
        my $tag = shift;
        my $attr = shift;
        my $attrseq = shift;
        my $origtext = shift;

        if ($tag eq 'td') {
                foreach my $_attr (keys %{$attr}) {
                        if (($_attr eq 'class') && ($$attr{$_attr} eq 'c0')) {
                                $EPISODE_NUM = 1;
                        }
                }
        } elsif ($tag eq 'a') {
                foreach my $_attr (keys %{$attr}) {
                        if (($_attr eq 'href') &&
                            ($$attr{$_attr} =~ /^http\:\/\/www.hulu.com\/watch/)) {
                                $EPISODE_NAM = 1;
                        }
                }
        }
}

sub end {
        my $self = shift;
        my $tag = shift;
        my $origtext = shift;

        $EPISODE_NUM = 0;
        $EPISODE_NAM = 0;
}

- Mark.
(This post was last modified: 2009-02-23 21:57 by mgandalf.)
find quote
djrobd Offline
Junior Member
Posts: 1
Joined: Feb 2009
Reputation: 0
Post: #14
rwparris2 Wrote:anyone have it working on windows? linux seems to be fine but with windows everytime it gets stuck at 'checking security of host hulu.com'

the command I'm using is:

Code:
gnash "C:\Program Files\XBMC\plugins\video\Hulu\resources\lib\DecryptPid.swf" --render-mode 0 --once --verbose --param FlashVars=pid=fe5a45ce4f6c6c3b8975463d33a311d57c453b6db8e65d9f727d05a673f90472~e​1f809cf7aa007607812588899aededbd98782aaae43d55a8e17b11582905932>"C:\Program Files\XBMC\plugins\video\Hulu\resources\lib\output.txt"
the > just puts stdout in a txt file, it makes no difference whether it is there or not.
[/code]

The options for the win32 binary of gnash are different than the linux version.

The command to use should be:

Code:
gnash "C:\Program Files\XBMC\plugins\video\Hulu\resources\lib\DecryptPid.swf" -r 0 -1 -v -P FlashVars=pid=fe5a45ce4f6c6c3b8975463d33a311d57c453b6db8e65d9f727d05a673f90472~e​1f809cf7aa007607812588899aededbd98782aaae43d55a8e17b11582905932

The only problem I have is that I don't know python enough to make these changes to the plugin itself. Hopefully you can figure it out for us windows users.
find quote
rwparris2 Offline
Team-XBMC Python Developer
Posts: 1,341
Joined: Jan 2008
Reputation: 2
Location: US
Post: #15
djrobd Wrote:The options for the win32 binary of gnash are different than the linux version.

The command to use should be:

Code:
gnash "C:\Program Files\XBMC\plugins\video\Hulu\resources\lib\DecryptPid.swf" -r 0 -1 -v -P FlashVars=pid=fe5a45ce4f6c6c3b8975463d33a311d57c453b6db8e65d9f727d05a673f90472~e​1f809cf7aa007607812588899aededbd98782aaae43d55a8e17b11582905932
The only problem I have is that I don't know python enough to make these changes to the plugin itself. Hopefully you can figure it out for us windows users.

I'll test that string when I get home. Checking platforms is easy.

Edit -- sorry for being so lazy, I'm sure this info is easily available from gnash's site...

Always read the XBMC online-manual, FAQ and search and search the forum before posting.
For troubleshooting and bug reporting please read how to submit a proper bug report.

If you're interested in writing addons for xbmc, read docs and how-to for plugins and scripts ||| http://code.google.com/p/xbmc-addons/
(This post was last modified: 2009-02-24 03:43 by rwparris2.)
find quote
rectalogic Offline
Junior Member
Posts: 30
Joined: Feb 2009
Reputation: 0
Post: #16
rwparris2 Wrote:I'll test that string when I get home.

Those options work on Mac/Linux too I believe. I just used the long options because they are more readable and I didn't know they weren't supported on Windows.


Here is a patch to use the short options:

Code:
diff --git a/resources/lib/decswf.py b/resources/lib/decswf.py
index eec6eff..cbcb56a 100644
--- a/resources/lib/decswf.py
+++ b/resources/lib/decswf.py
@@ -10,7 +10,7 @@ PID_RE = re.compile("hulupid=(.*)")
def hulu_decrypt(pid):
     gnash = common.settings['gnash_path']
     swf = os.path.join(os.path.dirname(os.path.realpath(__file__)), "DecryptPid.swf")
-    args = [gnash, "--render-mode", "0", "--once", "--verbose", "--param", "FlashVars=pid=%s" % pid, swf]
+    args = [gnash, "-r", "0", "-1", "-v", "-P", "FlashVars=pid=%s" % pid, swf]
     output = subprocess.Popen(args, executable=gnash, bufsize=1, universal_newlines=True, stdout=subprocess.PIPE).communicate()[0]
     match = PID_RE.search(output)
     if match:
find quote
ptaylor Offline
Junior Member
Posts: 16
Joined: Feb 2009
Reputation: 0
Post: #17
mgandalf Wrote:Regarding missing season episodes due to how the plugin grabs the rss feed which seems to only have 20 entries, I've created a short script to parse out all the episodes based on a given season. I'm not at all familiar with Python, so I wrote this as a proof of concept in Perl.

Maybe someone can adapt this to Python?

Mark,

A few weeks ago I ran into this same issue and learned enough python to write the below code (with bits of the original python function in-tact). I posted it in the other thread, but I don't think it has been implemented in the plug-in yet (or perhaps rwparris2 has another method in mind to take care of this issue).

PHP Code:
def addEpisodeListself ):
        
#initialize variables
        
p=re.compile('(\d+)')#gets last number from "season ##"
        
currentSeason=p.findall(common.args.name)[0]
        
        
html common.getHTML(common.args.url)
        
# Get Ajax URL for all seasons
        
ajaxMatches re.finditer('http://www.hulu.com/videos/season_expander',html)
        
        for 
match in ajaxMatches:
            
endPos html.find("&quot;",match.start())
            
nextUrl html[match.start():endPos].replace("&amp;","&")
            
            
# Determine Season
            
startPos nextUrl.find('season_number=')
            
seasonNum nextUrl[(startPos+14):(startPos+16)].replace('&','')
            
            if 
seasonNum == currentSeason:
                
#Read the Ajax for this season
                
ajax common.getHTML(nextUrl)
                
ajax ajax.replace('\\"','"').replace('\\076','>').replace('\\074','<')
            
                
soup BeautifulSoup(ajax)
                
episodeData soup.findAll("td", { "class" re.compile('^c[0-13-6]') })    
                
                for 
episode in episodeData:
                    if 
str(episode).find('class="c0"') > -1:   # Episode Number
                        
episodeNum episode.renderContents()
                    
elif str(episode).find('class="c1"') > -1# Link and Title
                        
episodeLink episode.renderContents()
                        
tmp BeautifulSoup(episodeLink).find('a')
                        
url tmp['href'].split('#')[0]
                        
episodeName tmp.renderContents()
                    
elif str(episode).find('class="c3"') > -1# Duration
                        
episodeDuration episode.renderContents()
                        
duration=episodeDuration.split(':')
                        
duration=(int(duration[0])*60)+int(duration[1])
                    
elif str(episode).find('class="c4"') > -1# Air Date
                        
airdate episode.renderContents()
                    
elif str(episode).find('class="c6"') > -1# Queue and icon link?
                        
if len(seasonNum)<2:seasonNum='0'+seasonNum
                        
if len(episodeNum)<2:episodeNum='0'+episodeNum
                        name 
's'+seasonNum+'e'+episodeNum+' '+episodeName
                        thumb 
''
                        
plot ''
                        
common.addDirectory(nameurl,'TV_play'thumbthumbcommon.args.fanartplot'genre'

If you put that into the _tv.py file, replacing the existing addEpisodeList, set the "flat_season" variable in your settings.xml to default of 1, and delete the _ty.pyo file (which will automatically be re-gen'ed next time you run the Hulu plug-in), then this will do what you are looking for.

Oh - Caveats of this code:
1. No thumbnails of the episodes themselves
2. No plot descriptions
3. It was written by someone completely new to Python, so I'm sure there are more pythony ways to do things.
(This post was last modified: 2009-02-24 04:17 by ptaylor.)
find quote
TheB Offline
Junior Member
Posts: 3
Joined: Feb 2009
Reputation: 0
Post: #18
I think there may be issues in the current SVN win32 builds with popen (see the bottom of this thread)

To get around this I edited my decswf.py to look like this:
PHP Code:
#!/usr/bin/python

import os
import re
import subprocess
import common
import string

PID_RE 
re.compile("hulupid=(.*)")

def hulu_decrypt(pid):
    
gnash common.settings['gnash_path']
    
swf os.path.join(os.path.dirname(os.path.realpath(__file__)), "DecryptPid.swf")
    
#args = [gnash, "--render-mode", "0", "--once", "--verbose", "--param", "FlashVars=pid=%s" % pid, swf]
    #output = subprocess.Popen(args, executable=None, bufsize=1, universal_newlines=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0]
    
    
txtout os.path.join(os.path.dirname(os.path.realpath(__file__)), 'tmp.txt')
    
os.system(gnash+'gnash.exe -r 0 -1 -v -P '+"FlashVars=pid=%s" pid+' "'+swf+'" > "'+txtout+'"')
    
output string.join(open(txtout'r').readlines(), "\n")
    
    
match PID_RE.search(output)
    if 
match:
        return 
match.group(1)
    else:
        return 
None 

Now I am getting the decoded pid but the script is still failing with
Code:
start of HULU plugin
19:12:52 T:2548 M:271749120  NOTICE:
19:12:52 T:2548 M:271749120  NOTICE: HULU--> common.args.mode -- > RSS_play
19:12:52 T:2548 M:271749120  NOTICE:
19:12:52 T:2548 M:270704640  NOTICE: http://www.hulu.com/watch/58680/the-fabulous-baker-boys-calling-him-out#http%3A%2F%2Fwww.hulu.com%2Ffeed%2Frecent%2Fmovies.rss%3Frd%3D0
19:12:52 T:2548 M:270704640  NOTICE:
19:12:52 T:2548 M:270704640  NOTICE: HULU --> common :: getHTML :: url = http://www.hulu.com/watch/58680/the-fabulous-baker-boys-calling-him-out#http%3A%2F%2Fwww.hulu.com%2Ffeed%2Frecent%2Fmovies.rss%3Frd%3D0
19:12:52 T:2548 M:270704640  NOTICE:
19:12:52 T:2548 M:271323136  NOTICE: HULU --> common :: getHTML :: url = http://r.hulu.com/videos?content_id=14263288
19:12:52 T:2548 M:271323136  NOTICE:
19:12:55 T:2548 M:270995456  NOTICE: HULU --> SMILURL: http://releasegeo.hulu.com/content.select?pid=c8aa0b0a43c3fc1b979eee8b1cbf594cb5c427c26aa83c7773b737b8e5f98​2b2~6d579943a5d66e396bb5aa24ed17f22524ddd479c804355983585f42d49bdfb8
&mbr=true&format=smil
19:12:55 T:2548 M:270995456  NOTICE:
19:12:55 T:2548 M:270995456  NOTICE: HULU --> common :: getHTML :: url = http://releasegeo.hulu.com/content.select?pid=c8aa0b0a43c3fc1b979eee8b1cbf594cb5c427c26aa83c7773b737b8e5f98​2b2~6d579943a5d66e396bb5aa24ed17f22524ddd479c804355983585f42d49bdfb8
&mbr=true&format=smil
19:12:55 T:2548 M:270995456  NOTICE:
19:12:56 T:2548 M:271454208  NOTICE: Traceback (most recent call last):
19:12:56 T:2548 M:271454208  NOTICE:   File "C:\Documents and Settings\Ryan\Application Data\XBMC\plugins\video\Hulu\default.py", line 60, in ?
19:12:56 T:2548 M:271454208  NOTICE:
19:12:56 T:2548 M:271454208  NOTICE: modes ( )
19:12:56 T:2548 M:271450112  NOTICE:   File "C:\Documents and Settings\Ryan\Application Data\XBMC\plugins\video\Hulu\default.py", line 36, in modes
19:12:56 T:2548 M:271450112  NOTICE:
19:12:56 T:2548 M:271450112  NOTICE: stream_media.Main()
19:12:56 T:2548 M:271446016  NOTICE:   File "C:\Documents and Settings\Ryan\Application Data\XBMC\plugins\video\Hulu\resources\lib\stream_hulu.py", line 14, in __init__
19:12:56 T:2548 M:271446016  NOTICE:
19:12:56 T:2548 M:271446016  NOTICE: self.play()
19:12:56 T:2548 M:271441920  NOTICE:   File "C:\Documents and Settings\Ryan\Application Data\XBMC\plugins\video\Hulu\resources\lib\stream_hulu.py", line 33, in play
19:12:56 T:2548 M:271441920  NOTICE:
19:12:56 T:2548 M:271441920  NOTICE: smilXML=common.getHTML(smilURL)
19:12:56 T:2548 M:271437824  NOTICE:   File "C:\Documents and Settings\Ryan\Application Data\XBMC\plugins\video\Hulu\resources\lib\common.py", line 168, in getHTML
19:12:56 T:2548 M:271433728  NOTICE:
19:12:56 T:2548 M:271433728  NOTICE: response=usock.read()
19:12:56 T:2548 M:271409152  NOTICE:   File "special:\\xbmc\system\python\python24.zlib\socket.py", line 285, in read
19:12:56 T:2548 M:271384576  NOTICE:   File "special:\\xbmc\system\python\python24.zlib\httplib.py", line 478, in read
19:12:56 T:2548 M:271384576  NOTICE: TypeError
19:12:56 T:2548 M:271384576  NOTICE: :
19:12:56 T:2548 M:271380480  NOTICE: unsupported operand type(s) for -=: 'str' and 'int'
19:12:56 T:2548 M:271380480  NOTICE:
19:12:56 T:2548 M:271380480   ERROR: Scriptresult: Error
19:12:58 T:1608 M:273436672   ERROR: DIRECTORY::CDirectory::GetDirectory - Error getting plugin://video/Hulu/?url="http%3A%2F%2Fwww.hulu.com%2Fwatch%2F58680%2Fthe-fabulous-baker-boys-calling-him-out%23http%253A%252F%252Fwww.hulu.com%252Ffeed%252Frecent%252Fmovies.rss%253Frd%​253D0"&mode="RSS_play"&name="The+Fabulous+Baker+Boys%3A+Calling+Him+Out"&fanart="http%3A%2F%2Fassets.hulu.com%2Fshows%2Fkey_art_the_fabulous_baker_boys.jpg"&plot="Susie+points+out+all+of+Jacks+flaws."&genre="HD Gallery"
19:12:58 T:1608 M:273436672   ERROR: CGUIMediaWindow::GetDirectory(plugin://video/Hulu/?url="http%3A%2F%2Fwww.hulu.com%2Fwatch%2F58680%2Fthe-fabulous-baker-boys-calling-him-out%23http%253A%252F%252Fwww.hulu.com%252Ffeed%252Frecent%252Fmovies.rss%253Frd%​253D0"&mode="RSS_play"&name="The+Fabulous+Baker+Boys%3A+Calling+Him+Out"&fanart="http%3A%2F%2Fassets.hulu.com%2Fshows%2Fkey_art_the_fabulous_baker_boys.jpg"&plot="Susie+points+out+all+of+Jacks+flaws."&genre="HD Gallery") failed

Anyone know whats wrong?

BTW: I'm currently using ikons Rev18025 SVN build
find quote
jonm42 Offline
Senior Member
Posts: 177
Joined: Feb 2009
Reputation: 0
Location: Portland, OR
Post: #19
I'll take a stab at this tomorrow -- I was just mucking in this area today. (I'm referring to the episode scan, not the popen on windows; didn't refresh before posting Sad).
(This post was last modified: 2009-02-24 18:33 by jonm42.)
find quote
rwparris2 Offline
Team-XBMC Python Developer
Posts: 1,341
Joined: Jan 2008
Reputation: 2
Location: US
Post: #20
TheB Wrote:I think there may be issues in the current SVN win32 builds with popen (see the bottom of this thread)

yep, popen is broken on win32 for xbmc. Sad

I can't get gnash to work on my system, and after a long day at work I don't want to play with it, but this is what I have so far, and I believe it would work if gnash was...

Code:
#!/usr/bin/python

import os
import re
import subprocess
import common

PID_RE = re.compile("hulupid=(.*)")

def hulu_decrypt(pid):
    gnash = common.settings['gnash_path']
    currentPath = os.path.dirname(os.path.realpath(__file__))
    swf = os.path.join(currentPath, "DecryptPid.swf")
    if os.environ.get( 'OS', 'xbox' ) == 'win32':
        sysargs = '"%s" -r 0 -1 -v -P FlashVars=pid=%s' % (swf, pid)
        outputFilePath = os.path.join(currentPath,'output.txt')
        os.system('"%sgnash" %s>"%s"' %(gnash,sysargs,outputFilePath))
        outputFile = open(outputFilePath,'r')
        output = outputFile.read()
        outputFile.close()
        os.remove(outputFilePath)
    else:
        args = [gnash, "--render-mode", "0", "--once", "--verbose", "--param", "FlashVars=pid=%s" % pid, swf]
        output = subprocess.Popen(args, executable=gnash, bufsize=1, universal_newlines=True, stdout=subprocess.PIPE).communicate()[0]
    print output
    match = PID_RE.search(output)
    if match:
        return match.group(1)

Always read the XBMC online-manual, FAQ and search and search the forum before posting.
For troubleshooting and bug reporting please read how to submit a proper bug report.

If you're interested in writing addons for xbmc, read docs and how-to for plugins and scripts ||| http://code.google.com/p/xbmc-addons/
find quote
Post Reply