import java.text.SimpleDateFormat;
import org.serviio.library.metadata.*
import org.serviio.library.online.*
import org.apache.commons.lang.*

/**
 * SvtPlay.se content URL extractor plugin. 
 * 
 * ##############################################
 * Resource URL instructions
 * ##############################################
 * 
 * This is a plugin of the type "Web Resource" ("Webbresurs").
 * 
 * Find URLs for categories by navigating to your desired category from http://www.svtplay.se/kategorier
 * Right click in the "Senaste program" button and copy the shortcut to the clipboard. For documentaries
 * ths URL is http://www.svtplay.se/kategorier/dokumentar?tab=senasteprogram&sida=1 
 *
 * As of version 1.1.12 of the plugin, you no longer need to alter the "sida" parameter to increase 
 * the number of shows returned by the plugin. The plugin uses the "max items to retrieve" property 
 * value set in Serviio when you register the content feed. If no max value is set, a maximum of 40 shows 
 * will be returned.
 *
 * You can also add individual shows, links to which are found at:
 * http://www.svtplay.se/program
 * For example, the show "En pilgrims död" has the feed URL
 * http://www.svtplay.se/en-pilgrims-dod?tab=episodes&sida=1
 *
 * If you want to get only the episodes of the shows (and not the related clips) you do not need to 
 * modify the URL:s which you have found as per the above instructions.
 * 
 * If you DO want to include the clips, you need to add the request parameter serviioclips=1 to the URL.
 * If you do NOT want to include the shows, you need to add the request parameter serviioepisodes=0 to the URL.
 * PLEASE NOTE: Neither of these request parameters will actually be passed to the SvtPlay servers. They 
 * will only be used internally by the plugin and then stripped from the resource URL.
 * 
 * Example. To get shows AND clips of "En pilgrims död", modify the URL so it becomes:
 * http://www.svtplay.se/en-pilgrims-dod?tab=episodes&sida=1&serviioclips=1
 *
 * If you want one feed to display ONLY the shows and another feed to display ONLY the clips,
 * use these two feed urls:
 * 1) http://www.svtplay.se/en-pilgrims-dod?tab=episodes&sida=1
 * 2) http://www.svtplay.se/en-pilgrims-dod?tab=episodes&sida=1&serviioclips=1&serviioepisodes=0
 *
 * Live feeds are included since version 1.1.6.
 * They can sometimes be included in certain category pages (such as news).
 * To add a feed with ONLY live shows, use this:
 * http://www.svtplay.se/?tab=live&sida=1
 * or this:
 * http://www.svtplay.se/kanaler
 * As usual, increase the trailing number to get more (multiple of 8) shows.
 *
 * NOTE for live shows and Serviio 1.1: The serviio command for remuxing of live HLS shows is broken.
 * So to play live HLS streams from SvtPlay you will need an ffmpeg wrapper removing the "-bsf:v h264_mp4toannexb" option.
 * Or edit your renderer profile to force transcoding of live h264 content.
 * See: http://forum.serviio.org/viewtopic.php?f=20&t=8062&start=140#p63488
 *
 * ##############################################
 * Version history
 * ##############################################
 *
 * @version 1.1.13 
 *
 * Fixed in version 1.1: Properly parse publishing date.
 * Fixed in version 1.1: Return standard url from Pirate API even if not matching any of the quality criteria. (dynamic)
 * Fixed in version 1.1.2: Handle bug in the pirateplay API which sometimes returns the wrong stream urls bundled with the correct ones.
 *                         Added new getVersion() method to conform to updated plugin API
 *                         Restructured these instructions to remove focus from the pre Serviio 1.1 challenges.
 *                         VERSION = 2
 * 
 * Fixed in version 1.1.3: Remove dependency on Pirate API when m3u8 stream url can be parsed by plugin.
 *                         Avoid returning feed items pointing to the starting page of shows.
 *                         VERSION = 3
 *
 * Fixed in version 1.1.4: Now also returns clips.
 *                         VERSION = 4
 *
 * Fixed in version 1.1.5: Added support for user preference regarding shows vs clips, depending on URL request parameters
 *                         VERSION = 5
 *
 * Fixed in version 1.1.6: Added support for live broadcasts
 *                         VERSION = 6
 *
 * Fixed in version 1.1.7: Typo in 1.1.6 made parsing crash when clips where included by serviioclips request parameter option
 *                         VERSION = 7
 *
 * Fixed in version 1.1.8: Added support for live channels at http://www.svtplay.se/kanaler
 *                         VERSION = 8
 *
 * Fixed in version 1.1.9: Changed the name of the class and the filename to make it shorter and more readable in some Serviio 
 *                         management clients running on small screens.
 *                         NOTE: Please remember to delete the previous file when upgrading so they don't sit side by side!
 *                         VERSION = 9
 *
 * Fixed in version 1.1.10: Adapted regular expression for finding shows to the new data-tabname "program" instead of "episodes".
 *
 * Fixed in version 1.1.11: Adapted regular expression for finding shows to the new data-tabname "senasteprogram" as well as "program" and "episodes".
 *
 * Fixed in version 1.1.12: Adapted script for new ajax retrieval model when requesting more than 8 shows / clips. 
 *                          Fixed target tabname for clips (altered from "clips" to "klipp")
 *                          allowed playlink target to be either "playLink" och "playAltLink".
 *
 * Fixed in version 1.1.13: Introduced more resolutions to the quality priority list.
 *                          VERSION = 13
 *
 * Fixed in version 1.1.14: Increased the max items to retrieve to 100 when set to infinite.
 *                          VERSION = 14
 *
 * TODO: Add support for rss files which are still sparsely used at SVT.
 *
 * ##############################################
 * Credits
 * ############################################## 
 *
 * @author Otto Dandenell 
 * 
 * The plugin re-uses some of the extraction methods from the original svtplay implementation by Tomas Falemo
 * It relies on the Pirateplay API to get http-based stream urls, see http://pirateplay.se/
 *
 * ##############################################
 * Special instructions for serviio versions older 
 * than 1.1
 * ##############################################
 *
 * Most shows (e.g. the local news show ABC) are delivered as Akamai hls streams.
 * The way serviio 1.0.1 or below tried to find out media information for those streams was broken.
 * To get the content to work properly, use one of the  FFMpeg Wrappers (.bat on Windows or .sh on Linux)
 * provided in the svtplay-extras.zip archive.
 * Put the wrapper in your lib directory, and add a line to the file at bin/ServiioService.exe.vmoptions:
 * The new line should be:
 * -Dffmpeg.location=/path/to/serviio/lib/FFMPegwrapper.sh
 * On my Windows box:
 * -Dffmpeg.location="C:\Program Files\Serviio\lib\FFMPegWrapper.bat"
 * Edit a line at the top of the wrapper to properly set the path to the actual FFMPeg exe.
 *
 * NOTE: When upgrading Serviio in minor versions pre 1.1 (e.g. from 1.0.0 to 1.0.1) the ServiioService.exe.vmoptions
 * file will be overwritten in the upgrade process. Thus you will need to add the -Dffmpeg.location option again
 * after an upgrade.
 *
 * The plugin uses the org.apache.commons.lang.StringEscapeUtils class to convert escaped html entities
 * found in scraped web pages into proper characters. That library was be added in the Serviio 1.1
 * distribution. For older distributions, the jar file (included in the svtplay-extras.zip archive) will have to be added 
 * to the lib directory.
 *
 */
class SvtPlay extends org.serviio.library.online.WebResourceUrlExtractor {
    final VALID_RESOURCE_URL = '^http(s)?://www.svtplay.se/.*$'

    final int VERSION = 14;
    
    private static SimpleDateFormat df    = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mmz" )
    
    private static SimpleDateFormat dfHourMinute = new SimpleDateFormat( "HH:mm" )

    String getExtractorName() {
        return getClass().getName()
    }

    int getVersion() {
        return VERSION
    }
    
    boolean extractorMatches(URL resourceUrl) {
        return resourceUrl ==~ VALID_RESOURCE_URL
    }

    WebResourceContainer extractItems(URL resourceUrl, int maxItemsToRetrieve) {
        log( getExtractorName() + ': extracting items for url: ' +  resourceUrl + ' , maxItemsToRetrieve: ' + maxItemsToRetrieve)
        def resourceHtml
        
        /* Find out if the resourceUrl is configured to fetch clips or shows or both. */
        def matcherServiioClips = (resourceUrl =~ '.*(?:[?|&]serviioclips=)([^&$]*)')
        def matcherServiioEpisodes = (resourceUrl =~ '.*(?:[?|&]serviioepisodes=)([^&$]*)')
        
        def blnIncludeServiioClips = false
        def blnIncludeServiioEpisodes = true
        def blnIncludeServiioLive = true
        
        if (matcherServiioClips != null && matcherServiioClips.size() > 0)
        {
            switch (matcherServiioClips[0][1])
            {
                case "1":
                case "true":
                case "yes":
                    blnIncludeServiioClips = true
                    break
                default:
                    blnIncludeServiioClips = false
            }
        }
        if (matcherServiioEpisodes != null && matcherServiioEpisodes.size() > 0)
        {
            switch (matcherServiioEpisodes[0][1])
            {
                case "1":
                case "true":
                case "yes":
                    blnIncludeServiioEpisodes = true
                    break
                default:
                    blnIncludeServiioEpisodes = false
            }
        }
        
        /* Remove serviio request parameters */
        def properResourceUrl = (resourceUrl =~ '(?:serviioclips=[^&]*&?)').replaceAll('')
        properResourceUrl = (properResourceUrl =~ '(?:serviioepisodes=[^&]*&?)').replaceAll('')
        
        
        /* Remove trailing ampersands and question marks. */
        while (properResourceUrl ==~ /.*[\?&]$/)
        {
            properResourceUrl = (properResourceUrl =~ /[\?&](?:$)/).replaceAll('')
        }
                
        def url = new URL(''+properResourceUrl)
        
        
        def connection = url.openConnection()
        if(connection.responseCode == 200){
            resourceHtml = connection.content.text
        }
        else{
            return null
        }
                
        // find out the title
        def title
        //<title>Barn | SVT Play</title>
        def matcherTitle = (resourceHtml =~ '<title>(.*?)</title>')
        if(matcherTitle.size() > 0)
        {
            title = StringEscapeUtils.unescapeHtml(matcherTitle[0][1])
            //println 'title - ' + title
        }
        
        // Find a category thumbnail by matching known categories
        def thumbnailUrl
        if (resourceUrl ==~ '.*/kategorier/barn.*$')
        {
            thumbnailUrl = "http://www.svtplay.se/public/images/barn.png"
        }
        else if (resourceUrl ==~ '.*/kategorier/dokumentar.*$')
        {
            thumbnailUrl = "http://www.svtplay.se/public/images/dokumentar.png"
        }
        else if (resourceUrl ==~ '.*/kategorier/filmochdrama.*$')
        {
            thumbnailUrl = "http://www.svtplay.se/public/images/filmochdrama.png"
        }
        else if (resourceUrl ==~ '.*/kategorier/kulturochnoje.*$')
        {
            thumbnailUrl = "http://www.svtplay.se/public/images/kulturochnoje.png"
        }
        else if (resourceUrl ==~ '.*/kategorier/nyheter.*$')
        {
            thumbnailUrl = "http://www.svtplay.se/public/images/nyheter.png"
        }
        else if (resourceUrl ==~ '.*/kategorier/samhalleochfakta.*$')
        {
            thumbnailUrl = "http://www.svtplay.se/public/images/samhalleochfakta.png"
        }
        else if (resourceUrl ==~ '.*/kategorier/sport.*$')
        {
            thumbnailUrl = "http://www.svtplay.se/public/images/sport.png"
        }
        else if (resourceUrl ==~ '.*/kategorier/os.*$')
        {
            thumbnailUrl = "http://www.svtplay.se/public/images/os.png"
        }
        else if (resourceUrl ==~ '.*/kategorier/oppetarkiv.*$')
        {
            thumbnailUrl = "http://www.svtplay.se/public/images/oppetarkiv.png"
        }
        else if (resourceUrl ==~ '.*/kategorier/live.*$')
        {
            thumbnailUrl = "http://www.svtplay.se/public/images/live.png"
        }
        else if (resourceUrl ==~ '^http(s)*://www.svtplay.se/program$')
        {
            // All programs. Use SVT Play logo.
            //<img alt="SVT Play" src="/public/2012.65/images/svt-play-2x.png">
            def matcherLogo = (resourceHtml =~ '<img alt="SVT Play" src="([^\\"]*?)">')
            if(matcherTitle.size() > 0)
            {
                thumbnailUrl = "http://www.svtplay.se" + matcherTitle[0][1]
            }
        }
        else
        {
            // Individual show page.
            // TODO
        }
        // println 'thumbnailUrl - ' + thumbnailUrl
        
        List<WebResourceItem> items = [] 
        // Parse items from markup, return only max items
        /*
              <article class="svtUnit svtNth-4 svtMediaBlock playPositionRelative playJsInfo-Core playJsInfo-Hover playIEFixedHeight"
                 data-title="ABC - 9/11 18:10"
                 data-description=""
                 data-length="5 min"
                 data-available="5 dagar kvar"
                 data-broadcasted="fre 9 nov"
                 data-published=""
                 data-broadcastStartTime="">
                        <div class="playDisplayTable">
                <a href="/video/574063/9-11-18-10" class="playLink playBoxWithClickArea playIELinkFix">
                    <div>
                        <div class="svtMBFig-L-O-O-O svtMediaBlockFig-L playJsInfo-ImageContainer playPositionRelative">
                            <img class="playGridThumbnail" alt="ABC - 9/11 18:10" src="http://www.svt.se/cachable_image/1352482140000/svts/article574062.svt/ALTERNATES/medium/default_title"/>
                                                    </div>
                        <span class="playDisplayTouch-N-I-I-I playJsInfo-Open playIconInline playIcon-ArrowDown playFloatRight"></span>
                        <h5 class="playGridHeadline">
                                                            9/11 18:10
                                                                                </h5>
                                                    <p class="svtXColorWhite">Sändes:
                                <time datetime="2012-11-09T18:10+01:00">
                                fre 9 nov
                                </time>
                            </p>
                                                                    </div>
                </a>
                <a href="?" class="playClickArea playJsInfo-Open">
                    <span class="playIconInline playIcon-ArrowDown svtHide-Gte-S"></span>
                </a>
            </div>
        </article>        
        */
        
        // Episodes or clips or both? They are encapsulated by divs with the data-tabname attribute.
        def strServiioEpisodesHTML = ""
        if (blnIncludeServiioEpisodes)
        {
            // Include episodes
            def episodesMatcher =  resourceHtml =~ '(?s)<div [^>]*data-tabname="(?:senasteprogram|program|episodes)"[^>]*?>.*?(?:<div [^>]*data-tabname=|</body>)'
            if (episodesMatcher != null && episodesMatcher.getCount() > 0)
            {
                strServiioEpisodesHTML = episodesMatcher[0]
            }                        
        }
       // println 'strServiioEpisodesHTML: ' + strServiioEpisodesHTML
        
        def strServiioClipsHTML = ""
        if (blnIncludeServiioClips)
        {
            // Include episodes
            def clipsMatcher =  resourceHtml =~ '(?s)<div [^>]*data-tabname="(?:clips|klipp)"[^>]*?>.*?(?:<div [^>]*data-tabname=|</body>)'
            if (clipsMatcher != null && clipsMatcher.getCount() > 0)
            {
                strServiioClipsHTML = clipsMatcher[0]
            }
        }
        //println 'strServiioClipsHTML: ' + strServiioClipsHTML

        def strServiioLiveHTML = ""
        if (blnIncludeServiioLive)
        {
            // Include episodes
            def liveMatcher =  resourceHtml =~ '(?s)<div [^>]*data-tabname=\"live\"[^>]*?>.*?(?:<div [^>]*data-tabname=|</body>)'
            if (liveMatcher != null && liveMatcher.getCount() > 0)
            {
                strServiioLiveHTML = liveMatcher[0]
            }
            
          //  println 'strServiioLiveHTML: ' + strServiioLiveHTML         
        }
        
        def itemsAdded = 0;

        // We put live feeds at the top, clips at the bottom.
        // Live feeds have two special patterns.
        if (blnIncludeServiioLive)
        {
            // Hashmap to avoid including the same channel / show twice
            HashMap liveMap = new HashMap()
            
            
            // Channels at http://www.svtplay.se/kanaler
            def liveKanalerContainerMatcher = resourceHtml =~'(?s)<ul [^>]*data-player=\"player\"(.*?)</ul>'
            if (liveKanalerContainerMatcher != null && liveKanalerContainerMatcher.getCount() > 0)
            {
                
                def liveKanalerMatcher = liveKanalerContainerMatcher[0][1] =~ '(?s)<a [^>]*href=\"(/kanaler/[^\"]*)\"[^>]*>(.*?)</a>'    
                //println 'liveKanalerMatcher.getCount(): ' + liveKanalerMatcher.getCount()
                if (liveKanalerMatcher != null && liveKanalerMatcher.getCount() > 0)
                {
                    //println 'liveKanalerMatcher.getCount(): ' + liveKanalerMatcher.getCount()
                    for (int i = 0; i < liveKanalerMatcher.getCount(); i++)
                    {
                        def strKanalPath = liveKanalerMatcher[i][1]
                        strKanalPath = (strKanalPath =~ '([?]type=embed$)').replaceAll('')
                        //println i + ': ' + strKanalPath
                        
                        if (!liveMap.containsKey(strKanalPath))
                        {
                            def videoArticleUrl = 'http://www.svtplay.se' + strKanalPath
                            //println 'videoArticleUrl ' + i + ': ' + videoArticleUrl
    
                            def strKanalHtml = liveKanalerMatcher[i]
                            /*
<a href="/kanaler/svt1" class="svtJsTab playJsShareTab playJsZapper-Channel" data-tab="1" data-thumbnail="/public/images/channels/backgrounds/svt1-background.jpg" data-jsonhref="/kanaler/svt1" data-channel="svt1" title="SVT1 | SVT Play">
                        <div class="playChannelLogoContainer">
                            <img class="svtHide-Lte-S playChannelMenuLogo" alt="SVT1" src="/public/images/channels/svt1.png" />
                            <img class="svtHide-Gte-M playChannelMenuLogo" alt="SVT1" src="/public/images/channels/svt1-small_2x.png" />
                        </div>
                        <div class="svtHide-Lte-S playChannelMenuDashboard playJsSchedule-Active" data-channeldashboard="svt1">
                            <span class="playChannelMenuTitle">Cimarron strip: Sound of a drum</span>
                            <div class="playMenuProgressbar playJsSchedule-ProgressBar" role="progressbar" aria-valuetext="11%" aria-valuemin="0" aria-valuemax="100" aria-valuenow="11" >
    <div class="playMenuProgressbarValue playJsSchedule-Progress"
             style="width:11%;"
             data-programpercent="11"
             data-servertime="1360158821394"
             data-starttime="1360158300000"
             data-endtime="1360162800000">
    </div>
</div>

                        </div>
                    </a>                            
                            */
                            // Title
                            def liveTitle = "";
                            def liveTitleMatcher = strKanalHtml =~ '<span class=\"playChannelMenuTitle\">(.*?)</span>'
                            if (liveTitleMatcher != null && liveTitleMatcher.getCount() > 0)
                                liveTitle = org.apache.commons.lang.StringEscapeUtils.unescapeHtml(liveTitleMatcher[0][1])
                            //println 'Title ' + i + ': ' + liveTitle
                            
                            // Channel name
                            def channelName = ""
                            def channelLogo
                            def liveChannelLogoMatcher = strKanalHtml =~ '<img class=\"[^\"]*playChannelMenuLogo[^\"]*\"[^>"]*?alt="([^\"]*)"[^>"]*?src="([^\"]*)"'
                            if (liveChannelLogoMatcher != null && liveChannelLogoMatcher.getCount() > 0)
                            {
                                channelName = org.apache.commons.lang.StringEscapeUtils.unescapeHtml(liveChannelLogoMatcher[0][1])
                                channelLogo = liveChannelLogoMatcher[0][2]
                            }
                            //println 'channelName ' + i + ': ' + channelName
                            //println 'channelLogo ' + i + ': ' + channelLogo
                                                        
                            // Thumbnail
                            def videoArticleThumbnailUrl
                            def videoArticleThumbnailUrlMatcher = strKanalHtml =~ '(?s)<a [^>]*href=\"/kanaler/[^\"]*\"[^>]*?data-thumbnail=\"([^\"]*)\"'    
                            if (videoArticleThumbnailUrlMatcher != null && videoArticleThumbnailUrlMatcher.getCount() > 0)
                            {
                                videoArticleThumbnailUrl = videoArticleThumbnailUrlMatcher[0][1]
                            }
                            //println 'videoArticleThumbnailUrl ' + i + ': ' + videoArticleThumbnailUrl
                            
                            // Starttime + endtime
                            /*
                             data-starttime="1360158300000"
                             data-endtime="1360162800000">
                            */
                            Date startTime = new Date()
                            Date endTime = new Date()
                            def startTimeMatcher = strKanalHtml =~ '(?s)<div class=\"playMenuProgressbarValue[^>]*?data-starttime=\"([^\"]*)\"[^>]*?data-endtime=\"([^\"]*)\"'
                            def startTimeMillisecs = ""
                            def endTimeMillisecs = ""
                            if (startTimeMatcher != null && startTimeMatcher.getCount() > 0)
                            {
                                startTimeMillisecs = startTimeMatcher[0][1]
                                endTimeMillisecs = startTimeMatcher[0][2]
                                //println 'startTimeMillisecs ' + i + ': ' + startTimeMillisecs
                                //println 'endTimeMillisecs ' + i + ': ' + endTimeMillisecs                                                                
                                try
                                {
                                    startTime.setTime(Long.parseLong(startTimeMillisecs, 10))
                                    //println 'startTime ' + i + ': ' + startTime
                                }
                                catch(Exception ex)
                                {
                                     log('ERROR:  Could not parse start time of live show (startTimeMillisecs: ' + startTimeMillisecs + '): ' + ex.getMessage())
                                     //println 'ERROR:  Could not parse start time of live show (startTimeMillisecs: ' + startTimeMillisecs + '): ' + ex.getMessage()
                                }
                                try
                                {
                                    endTime.setTime(Long.parseLong(endTimeMillisecs, 10))
                                    //println 'endTime ' + i + ': ' + endTime
                                }
                                catch(Exception ex)
                                {
                                     log('ERROR:  Could not parse end time of live show (endTimeMillisecs: ' + endTimeMillisecs + '): ' + ex.getMessage())
                                     //println 'ERROR:  Could not parse start time of live show (endTimeMillisecs: ' + endTimeMillisecs + '): ' + ex.getMessage()
                                }                               
                            }
                            
                            liveTitle = dfHourMinute.format(startTime) + ' - ' + dfHourMinute.format(endTime) + ': ' + liveTitle
                            if (channelName != null && !channelName.equals(""))
                            {
                                liveTitle = channelName + ' ' + liveTitle
                            }
                                                        
                            WebResourceItem item = new WebResourceItem(title: liveTitle, additionalInfo: ['videoArticleUrl': videoArticleUrl, 'videoArticleThumbnailUrl': videoArticleThumbnailUrl, 'live': 'true'])
                            item.releaseDate = startTime                        
                            items << item
                            itemsAdded++             
                        }
                    }
                }
            }
            if (!strServiioLiveHTML.equals(""))
            {            
                def liveArticleMatches = strServiioLiveHTML =~ '(?s)<a href=\"([^\"]*?)\"[^>]*>[^<>]*<article[^>]*?>.*?</article>[^<]*</a>'
                for(int i = 0; i < liveArticleMatches.getCount() && (maxItemsToRetrieve == -1 || itemsAdded < maxItemsToRetrieve) ; i++ ) {
                    def strLiveArticlePath = liveArticleMatches[i][1]
                    strLiveArticlePath = (strLiveArticlePath =~ '([?]type=embed$)').replaceAll('')
                    
                    if (!liveMap.containsKey(strLiveArticlePath))
                    {
                        // Flag will be set to true if we can determine that this show is live NOW.
                        def isBroadcastLive = false
                        def willBroadcastLiveLater = false
        
                        /*
                        Broadcast dates
                        <span class="playBroadcastTime">
                            <time datetime="2013-01-28T13:49+01:00">13.49</time> -
                            <time datetime="2013-01-28T17:00+01:00">17.00</time>
                        </span>
        
                        */
                        def broadcastTimeMatcher = liveArticleMatches[i] =~ '(?s)<[^>]*?class=\"playBroadcastTime[ \"]*?[^>]*>.*?<time datetime=\"([^\"]*?)\">.*?<time datetime=\"([^\"]*?)\">.*?(?:</span>|</div)>'
        
                        def startTime
                        def endTime
                        
                        if (broadcastTimeMatcher != null && broadcastTimeMatcher.getCount() > 0)
                        {
                            def strStartTime = broadcastTimeMatcher[0][1]
                        
                            try
                            {
                                startTime = GetValidDate(strStartTime)
                            }
                            catch(Exception ex)
                            {
                                 log('ERROR:  Could not parse start time of live show (strStartTime: ' + strStartTime + '): ' + ex.getMessage())
                                // println 'ERROR:  Could not parse start time of live show (strStartTime: ' + strStartTime + '): ' + ex.getMessage()
                            }
        
                            def strEndTime = broadcastTimeMatcher[0][2]
                            try
                            {
                                endTime = GetValidDate(strEndTime)
                            }
                            catch(Exception ex)
                            {
                                 log('ERROR:  Could not parse end time of live show (strEndTime: ' + strEndTime + '): ' + ex.getMessage())
                                 //println 'ERROR:  Could not parse end time of live show (strEndTime: ' + strEndTime + '): ' + ex.getMessage()
                            }
                            Date now = new Date()
                            
                            if (now.after(startTime) && now.before(endTime)) {
                                isBroadcastLive = true
                            }
                            else if (now.before(startTime)) {
                                willBroadcastLiveLater = true
                            }
                        }
        //                println 'startTime : ' + startTime
        //                println 'endTime : ' + endTime
                        
                        
                        if (!isBroadcastLive)
                        {
                            // Try a different matcher
                            isBroadcastLive = strServiioLiveHTML ==~ '<img class=\"playBroadcastLiveIcon\"'                    
                        }
                        
                        if (isBroadcastLive || willBroadcastLiveLater)
                        {
        //                    println 'FOUND ONGOING LIVE SHOW AT INDEX ' + i
        
                            def videoArticleUrl = 'http://www.svtplay.se' + strLiveArticlePath
        //                    println '    LIVE videoArticleUrl: ' + videoArticleUrl
                            def liveTitle = ""
                            /* <h5>SVT Forum - 28/1 09.00</h5> */
                            if (willBroadcastLiveLater) {
                                /* <div class="playBroadcastTitle">Sverige idag - 28/1 17.30</div> */
                                def titleMatcher = liveArticleMatches[i] =~ '<div class=\"playBroadcastTitle\">(.*?)</div>'
                                if (titleMatcher != null && titleMatcher.getCount() > 0) {
                                    liveTitle = org.apache.commons.lang.StringEscapeUtils.unescapeHtml(titleMatcher[0][1])
                                }
                            }
                            else if (isBroadcastLive) {
                                def titleMatcher = liveArticleMatches[i] =~ '<h5>(.*?)</h5>'
                                if (titleMatcher != null && titleMatcher.getCount() > 0) {
                                    liveTitle = org.apache.commons.lang.StringEscapeUtils.unescapeHtml(titleMatcher[0][1])
                                }
                            }
        //                    println 'liveTitle: ' + liveTitle
                            
                            // Try parsing the thumbnail
                            //<img  class="svtMediaBlockFig-L playBroadcastThumbnail" alt="SVT Forum - 28/1 09.00" src="http://www.svt.se/cachable_image/1338457984000/svts/article105942.svt/ALTERNATES/small/2112215-T.jpg"/>
                            def videoArticleThumbnailUrl = ""
                            def thumbnailMatcher = liveArticleMatches[i] =~ '(?s)<img[ ]*class=\"[^\"]*?playBroadcastThumbnail[^\"]*?\"[^>]*?src=\"([^\"]*?)\"'
                            if (thumbnailMatcher != null && thumbnailMatcher.getCount() > 0)
                            {
                                videoArticleThumbnailUrl = thumbnailMatcher[0][1]
                                log(' videoArticleThumbnailUrl: ' + videoArticleThumbnailUrl)
                            }
                            
                            WebResourceItem item = new WebResourceItem(title: liveTitle, additionalInfo: ['videoArticleUrl': videoArticleUrl, 'videoArticleThumbnailUrl': videoArticleThumbnailUrl, 'live': 'true'])
                            item.releaseDate = startTime                        
                            items << item
                            itemsAdded++             
                             
                        }
                
        //                println 'liveArticleMatches['+ i+ '] isBroadcastLive: ' + isBroadcastLive
                    }
                }
            }
        }
        // We put live feeds at the top, clips at the bottom.
        def strNonLiveFeedHTML = strServiioEpisodesHTML + strServiioClipsHTML        
        
        def articleMatches = strNonLiveFeedHTML =~ '(?s)<article [^>]*?>.*?</article>'

        // jämför om maxItemsToRetrieve är större än 8 och articleMatches.getCount() är lika med 8. Hämta isåfall nya artikellistor via ajax. 
        if (maxItemsToRetrieve == -1 || maxItemsToRetrieve > articleMatches.getCount())
        {
            def antal = maxItemsToRetrieve
            if (antal == -1)
                antal = 100
                
            // Episodes
            if (blnIncludeServiioEpisodes)
            {
                def ajaxEpisodesMatcher =  strServiioEpisodesHTML =~ '(?s)data-baseurl=\"([^\"]*?)\".*?class=\"[^\"]*playShowMoreButton[^\"]*\"'
                if (ajaxEpisodesMatcher != null && ajaxEpisodesMatcher.getCount() > 0)
                {
                    def ajaxBaseUrl = ajaxEpisodesMatcher[0][1]
                    //println 'ajaxBaseUrl: ' + ajaxBaseUrl
                    def strAjaxEpisodesUrl = 'http://www.svtplay.se' + ajaxBaseUrl.replace("&amp;", "&") + 'sida=1&antal=' + antal
                    //println 'strAjaxEpisodesUrl: ' + strAjaxEpisodesUrl

                    def ajaxEpisodesUrl = new URL(''+strAjaxEpisodesUrl)
                                        
                    def ajaxEpisodesConnection = ajaxEpisodesUrl.openConnection()
                    if(ajaxEpisodesConnection.responseCode == 200){
                        strServiioEpisodesHTML = ajaxEpisodesConnection.content.text
                    }                                
                }
            }
            
            // Clips
            if (blnIncludeServiioClips)
            {
                def ajaxClipsMatcher =  strServiioClipsHTML =~ '(?s)data-baseurl=\"([^\"]*?)\".*?class=\"[^\"]*playShowMoreButton[^\"]*\"'
                if (ajaxClipsMatcher != null && ajaxClipsMatcher.getCount() > 0)
                {
                    def ajaxBaseUrlClips = ajaxClipsMatcher[0][1]
                    //println 'ajaxBaseUrlClips: ' + ajaxBaseUrlClips
                    def strAjaxClipsUrl = 'http://www.svtplay.se' + ajaxBaseUrlClips.replace("&amp;", "&") + 'sida=1&antal=' + antal
                    //println 'strAjaxClipsUrl: ' + strAjaxClipsUrl

                    def ajaxClipsUrl = new URL(''+strAjaxClipsUrl)
                                        
                    def ajaxClipsConnection = ajaxClipsUrl.openConnection()
                    if(ajaxClipsConnection.responseCode == 200){
                        strServiioClipsHTML = ajaxClipsConnection.content.text
                    }                                
                }
            }
            
            // Refresh the HTML used for article selection
            strNonLiveFeedHTML = strServiioEpisodesHTML + strServiioClipsHTML
            // Refresh the matches
            articleMatches = strNonLiveFeedHTML =~ '(?s)<article [^>]*?>.*?</article>'
        }
        for( int i = 0; i < articleMatches.getCount() && (maxItemsToRetrieve == -1 || itemsAdded < maxItemsToRetrieve) ; i++ ) {
            //println 'articleMatches['+ i+ ']: ' + articleMatches[i]            
            // Find out if this has a playlink
//            def playlinkMatcher = articleMatches[i][0] =~ '(?s).*data-title="([^"]*?)".*?<a href="([^"]*?)" class="playLink'
            def playlinkMatcher = articleMatches[i] =~ '(?s)data-title=\"([^\"]*?)\".*?<a href=\"([^\"]*?)\" class=\"(?:playLink|playAltLink)'
            if (playlinkMatcher != null)
            {
                def webResourceItemTitle
                String strReleaseDate
                Date releaseDate
                def videoArticleThumbnailUrl
                def videoArticleUrl         
                
                def validDate = false       
                
                if (playlinkMatcher.getCount() > 0)
                {
                    // Try parsing the releas date
                    // <time datetime="2012-11-09T18:10+01:00">
                    log(' Found matching playlink')
                    def timeMatcher = articleMatches[i] =~ '(?s)<time datetime=\"([^\"]*?)\"'
                    if (timeMatcher != null)
                    {
                        if (timeMatcher.getCount() > 0)
                        {
                            strReleaseDate = timeMatcher[0][1]
                            // println 'strRelease: ' + strReleaseDate
                            try
                            {
                                releaseDate = GetValidDate(strReleaseDate)
                                validDate = true
                                //log(' releaseDate: ' + releaseDate)
                                // println ' releaseDate: ' + releaseDate
                            }
                            catch(Exception ex)
                            {
                                 log('ERROR:  Could not parse release date (strReleaseDate: ' + strReleaseDate + ')')
                                // println 'Error parsing date: ' + ex.getMessage()
                            }
                        }
                    }

                    // Try parsing the thumbnail
                    //<img class="playGridThumbnail" src="http://www.svt.se/cachable_image/1352482140000/svts/article574062.svt/ALTERNATES/medium/default_title"/>
                    def thumbnailMatcher = articleMatches[i] =~ '(?s)<img class=\"playGridThumbnail\" [^>]*?src=\"([^\"]*?)\"'
                    if (thumbnailMatcher != null)
                    {
                        if (thumbnailMatcher.getCount() > 0)
                        {
                            videoArticleThumbnailUrl = thumbnailMatcher[0][1]
                            log(' videoArticleThumbnailUrl: ' + videoArticleThumbnailUrl)
                        }
                    }
                    
                    webResourceItemTitle = org.apache.commons.lang.StringEscapeUtils.unescapeHtml(playlinkMatcher[0][1])
                    videoArticleUrl = 'http://www.svtplay.se' + playlinkMatcher[0][2]
                    log(' webResourceItemTitle: ' + webResourceItemTitle)
                    //println 'webResourceItemTitle: ' + webResourceItemTitle
                    log(' videoArticleUrl: ' + videoArticleUrl)
 
                    WebResourceItem item = new WebResourceItem(title: webResourceItemTitle, additionalInfo: ['videoArticleUrl': videoArticleUrl, 'videoArticleThumbnailUrl': videoArticleThumbnailUrl, 'live': 'false'])
                    if (validDate)
                    {
                        item.releaseDate = releaseDate                        
                    }
                    items << item
                    itemsAdded++             
                }
            }
        }
        
        def webResourceContainer = new WebResourceContainer()
        webResourceContainer.title = title
        webResourceContainer.thumbnailUrl = thumbnailUrl
        webResourceContainer.items = items
        
        log( getExtractorName() + ': finished extracting items for url: ' +  resourceUrl + ' . Found ' + itemsAdded + ' items' )
        return webResourceContainer
        
    }

    ContentURLContainer extractUrl(WebResourceItem item, PreferredQuality requestedQuality) {
    
        log ( getExtractorName() + ': extracting Url for WebResourceItem at ' + item.getAdditionalInfo()['videoArticleUrl'])
        // Get the html page which displays the video widget
        def videoArticleUrl = new URL(item.getAdditionalInfo()['videoArticleUrl'])
        def videoArticleContentHtml
        def connection = videoArticleUrl.openConnection()
        if(connection.responseCode == 200){
            videoArticleContentHtml = connection.content.text
        }
        else{
            return null
        }
        
        // Parse the json resource url from the data-json attribute.
        def videoJsonMatcher = (videoArticleContentHtml =~ '(?s)<a [^>]*?id=\"player\"[^>]*?data-json-href=\"([^\\"]*?)\"')
        def videoJsonPath
        def videoJsonUrl
        if (videoJsonMatcher != null && videoJsonMatcher.size() > 0)
        {
            videoJsonUrl = new URL('http://www.svtplay.se' + videoJsonMatcher[0][1] + '?output=json')
        }
        else
        {
            //<a id="player" class="svtplayer playJsRemotePlayer playSvtplayer" data-id="547626"
            def videoArticleMatcher = (videoArticleContentHtml =~ '<a id="player" [^>]*? data-id="([^\\"]*?)"')
            def videoArticleId
            assert videoArticleMatcher != null
            if(videoArticleMatcher.size() > 0)
            {
                videoArticleId = videoArticleMatcher[0][1]
                log(' videoArticleId - ' + videoArticleId)
    //            println ' videoArticleId - ' + videoArticleId
            }
            else
            {
            
                log(' No video article id or json href found')
    //            println ' No video article id found'
                return null
            }
            videoJsonUrl = new URL('http://www.svtplay.se/video/' + videoArticleId + '?output=json')
        }
        log('videoJsonUrl: ' + videoJsonUrl)
        //println 'videoJsonUrl: ' + videoJsonUrl
        def videoJsonContent
        def connectionJson = videoJsonUrl.openConnection()
        if(connectionJson.responseCode == 200){
            videoJsonContent = connectionJson.content.text
        }
        else{
            return null
        }
        log(' videoJsonContent: ' + videoJsonContent)
//        println ' videoJsonContent: ' + videoJsonContent

        // Find the url of the script from which we can parse the swfUrl
        // <script type="text/javascript" src="/public/2012.65/javascripts/script-built.js"></script>
        def swfUrl
        def scriptPathMatcher = (videoArticleContentHtml =~ '<script [^>]* src="([^\\"]*?\\/script-built\\.js)"')
        def scriptPath
        if(scriptPathMatcher != null && scriptPathMatcher.size() > 0)
        {
            scriptPath = scriptPathMatcher[0][1]
            log(' js scriptPath: ' + scriptPath)
            def scriptContent
            URL scriptUrl = new URL('http://www.svtplay.se' + scriptPath)
            def scriptConnection = scriptUrl.openConnection()
            if(scriptConnection.responseCode == 200){
                scriptContent = scriptConnection.content.text
                // svtplayer.SVTPlayer.SWF_PATH="/statiskt/swf/video/";svtplayer.SVTPlayer.SPRITE_PATH="/statiskt/skins/svt/css/img/svtplayer/";svtplayer.SVTPlayer.PLAYER_FILE="svtplayer-2012.48.swf";svtplayer.SVTPlayer.BUILD_VERSION="2012.48";    
                def swfUrlMatcher = scriptContent =~ 'svtplayer\\.SVTPlayer\\.PLAYER_SWF="([^\\"]*?)"'
                if(swfUrlMatcher != null && swfUrlMatcher.size() > 0)
                {
                    swfUrl = 'http://www.svtplay.se' + swfUrlMatcher[0][1] 
                    log(' swfUrl: ' + swfUrl)
                }
            }
        }
        else
        {
           log( ' Found no path to js script')
        }        

        def contentUrl = GetBestMatch(videoJsonContent, requestedQuality, videoArticleUrl)  
        if (contentUrl == null)
            return null
                              
//        if (swfUrl != null && contentUrl.startsWith('rtmp')) contentUrl = '"' + contentUrl + ' swfUrl=' + swfUrl + ' swfVfy=1 pageUrl=' + videoArticleUrl + '"'
        if (swfUrl != null && contentUrl.startsWith('rtmp')) contentUrl = contentUrl + ' swfUrl=' + swfUrl + ' swfVfy=1'

        def thumbnail = item.getAdditionalInfo()['videoArticleThumbnailUrl']
        
        def blnIsLive = false
        def strIsLive = item.getAdditionalInfo()['live']
        if (strIsLive != null && strIsLive.equals("true"))
            blnIsLive = true
        
//        println 'blnIsLive: ' + blnIsLive
        
        ContentURLContainer container =  new ContentURLContainer(fileType: MediaFileType.VIDEO, contentUrl: contentUrl, thumbnailUrl: thumbnail, live: blnIsLive)
        if (blnIsLive) {
            // Set a cache key and mark this as expired on its startTime
            def strCacheKey = "" + videoArticleUrl + '_' + requestedQuality
//            println 'strCacheKey: ' + strCacheKey
            container.cacheKey = strCacheKey
            container.expiresOn = item.releaseDate
        }
        return container
    }

    
    String GetBestMatch(String jsonContent, PreferredQuality requestedQuality, URL videoUrl){
        String match = null
        String[] priorityList
        if(requestedQuality == PreferredQuality.HIGH){
            priorityList = ['e','d','c','b','a']
        }else{
            if(requestedQuality == PreferredQuality.MEDIUM){
                priorityList = ['c','b','d','a','e']
            }else{
                // LOW
                priorityList = ['a','b','c','d','e']
            }
        }       
        
        // example of match: rtmp://fl11.c91005.cdn.qbrick.com/91005/_definst_/kluster/20110922/PG-1146034-001A-MITTINATUREN2-02-mp4-e-v1.mp4 
        priorityList.each{
            if(match == null){
                def matcher = (jsonContent =~ '.*"url"\\:"(rtmp.*-'+it+'-v1.*?)","bitrate"\\:')
                if(matcher.size() > 0)
                {
                    match = matcher[0][1]
                    log(' found qbrick rtmp url. Best match for requested quality: ' + match)
                }
            }
        }
        
        if (match == null)
        {
            // This might be an AKAMAI video.
            // {"url":"http://svtplay1n-f.akamaihd.net/z/world/open/20121113/1308899-062A_ABC/REGIONALA_NYHET-062A-abc-4fc14b301c7a9fe6_,900,320,420,620,1660,2760,.mp4.csmil/manifest.f4m","bitrate":0,"playerType":"flash"}
            // {"url":"http://svtplay9i-f.akamaihd.net/i/world/open/20121230/1130774-003A/OLOF_PALME-003A-13e3178289b3be47_,900,320,420,620,1660,2760,.mp4.csmil/master.m3u8","bitrate":0,"playerType":"ios"}
            
                // We prefer the hls stream (ios) because ffmpeg has support for it and it is never encrypted
                def httpMatcher = (jsonContent =~ '\\{"url"\\:[^\\"]?"(http[^\\"]*?)","bitrate"[^}]*?"playerType"\\:"ios"\\}')
                // the flash version is more likely to be unplayable by ffmpeg, but we can try it.
                if(httpMatcher == null || httpMatcher.size() == 0)
                    httpMatcher = (jsonContent =~ '\\{"url"\\:[^\\"]?"(http[^\\"]*?)","bitrate"[^}]*?"playerType"\\:"flash"\\}')
                if(httpMatcher.size() > 0)
                {

                    String[] priorityListHttp
                    if(requestedQuality == PreferredQuality.HIGH){
                        priorityListHttp = ['1280x720','1024x576','768x432','704x396','576x324','512x288','480x270','320x180']
                    }else{
                        if(requestedQuality == PreferredQuality.MEDIUM){
                            priorityListHttp = ['768x432','704x396','576x324','1024x576','512x288','480x270','1280x720','320x180']
                        }else{
                            // LOW
                            priorityListHttp = ['320x180','480x270','512x288','576x324','704x396','768x432','1024x576', '1280x720']
                        }
                    }       

                    def httpMatch = httpMatcher[0][1]
//                    println 'httpMatch: ' + httpMatch
                    
                    if (httpMatch.contains("m3u8"))
                    {
//                        println 'httpMatch är en m3u8-fil'
                        def m3u8URL = new URL(httpMatch)
                        def m3u8Connection = m3u8URL.openConnection()
                        if (m3u8Connection.responseCode == 200){
                            def  m3u8Content = m3u8Connection.content.text
                            //println 'm3u8Content: ' + m3u8Content
                            List m3u8Lines = m3u8Content.readLines()
                            priorityListHttp.each{
                                
                                if(match == null){
                                    for(int index = 0; index < m3u8Lines.size() - 1; index++)
                                    {
//                                        println '      ' + index + ': ' + m3u8Lines.get(index);
                                        def httpm3u8QualityMatcher = (m3u8Lines.get(index) =~ '#EXT-X-STREAM-INF\\:.*,RESOLUTION=' + it +',')
                                        if(httpm3u8QualityMatcher.size() > 0 )
                                        {
                                            match = m3u8Lines.get(index+1)
                                            
                                            // clean up ugly stuff in the url
                                            match = (match =~ /\\?null=&/).replaceAll('')
                                            match = (match =~ /&id=$/).replaceAll('')
                                            log(' found m3u8 http stream url. Best match for requested quality: ' + match)
//                                            println ' found m3u8 http stream url. Best match for requested quality: ' + match
                                        }
                                    }
                                }                                    
                            }
                        }
                        
                    }
                    
                    if (match == null)
                    {
                        // Använd Pirate API
                    
                        // En bugg i pirateplay API:t gör att vi behöver filtrera bort ovidkommande klipp.
                        // Här hittar vi en mall för vad vi ska leta efter bland de strömmar som pirateplay föreslår
                        def httpMatchIntro = (httpMatch =~ /manifest\.f4m$/).replaceAll('')
                        httpMatchIntro = (httpMatchIntro =~ /master\.m3u8$/).replaceAll('')
                        //println 'httpMatchIntro: ' + httpMatchIntro
                    
                    
                        // Get the url from the pirateplayer API
                        def pirateURL = new URL('http://pirateplay.se/api/get_streams.js?url=' + java.net.URLEncoder.encode(videoUrl.toString()))
                        // println 'pirateURL: ' + pirateURL
                     
                        def pirateContent
                           
                        def pirateConnection = pirateURL.openConnection()
                        if (pirateConnection.responseCode == 200){
                            pirateContent = pirateConnection.content.text    
                            //println 'pirateContent: ' + pirateContent
                                            
                            priorityListHttp.each{
                                if(match == null){
                                    def httpPirateMatcher = (pirateContent =~ '"url"\\:[^\\"]?"(http[^\\"]*?)"[^\\}]*?"quality": "' + it +'"')
                                    if(httpPirateMatcher.size() > 0)
                                    {
                                        for(int i=0; i < httpPirateMatcher.size(); i++)
                                        {
                                            //println 'httpPirateMatcher[i][1]: ' + httpPirateMatcher[i][1]
                                            if (httpMatchIntro.length() > 0 && httpPirateMatcher[i][1].startsWith(httpMatchIntro))
                                            {
                                                match = httpPirateMatcher[i][1]
                                                
                                                // clean up ugly stuff in the url
                                                match = (match =~ /\\?null=&/).replaceAll('')
                                                match = (match =~ /&id=$/).replaceAll('')
                                                log(' found pirateAPI http stream url based matching the svtplay json url entry. Best match for requested quality: ' + match)
                                                //println ' found pirateAPI http url based on svtplay json. Best match for requested quality: ' + match
                                             }
                                        }
                                    }
                                }
                            }
                            
                            if(match == null){
                                priorityListHttp.each{
                                    if(match == null){
                                        def httpPirateMatcher = (pirateContent =~ '"url"\\:[^\\"]?"(http[^\\"]*?)"[^\\}]*?"quality": "' + it +'"')
                                        if(httpPirateMatcher.size() > 0)
                                        {
                                            match = httpPirateMatcher[0][1]
                                            
                                            // clean up ugly stuff in the url
                                            match = (match =~ /\\?null=&/).replaceAll('')
                                            match = (match =~ /&id=$/).replaceAll('')
                                            log(' found http pirateAPI stream url NOT based on svtplay json entry. Best match for requested quality: ' + match)
                                            //println ' found http pirate stream url NOT based on svtplay json entry. Best match for requested quality: ' + match
                                        }
                                    }
                                }
                            }
    
                            if (match == null) {
                                // The stream might be dynamic. Just grab the first one.
                                def defaultMatcher = (pirateContent =~ '"url"\\:[^\\"]?"(http[^\\"]*?)"')
                                if(defaultMatcher.size() > 0)
                                {
                                    match = defaultMatcher[0][1]
                                    
                                    // clean up ugly stuff in the url
    //                                match = (match =~ /\\?null=&/).replaceAll('')
    //                                match = (match =~ /&id=$/).replaceAll('')
                                    log(' found default http stream url, not matched for quality: ' + match)
                                    // Nota bene: this is most likely a f4m manifest, which is not playable in ffmpeg.
                                    // Need to work on hds decoding?
                                    // Ticket #1964 has been opened with ffmpeg!: 
                                    // https://ffmpeg.org/trac/ffmpeg/ticket/1964
                                }
    
                            }
                        }
                    }
                }
        }
        
        return match
    }

    Date GetValidDate(strMarkupDate)
    {
        // Reformat date from html markup to parsable date string
        //NOTE: SimpleDateFormat uses GMT[-+]hh:mm for the TZ which breaks
        //things a bit.  Before we go on we have to repair this.

        //this is zero time so we need to add that TZ indicator for 
        if ( strMarkupDate.endsWith( "Z" ) ) {
            strMarkupDate = strMarkupDate.substring( 0, strMarkupDate.length() - 1) + "GMT-00:00";
        } else {
            int inset = 6;
        
            String s0 = strMarkupDate.substring( 0, strMarkupDate.length() - inset );
            String s1 = strMarkupDate.substring( strMarkupDate.length() - inset, strMarkupDate.length() );

            strMarkupDate = s0 + "GMT" + s1;
        }
        
        // println 're-structured strRelease: ' + strReleaseDate
        
        return df.parse( strMarkupDate );
    }
    
    static main(args) {
        // this is just to test
//        def TestUrl = new URL("http://www.svtplay.se/kategorier/barn?tab=episodes&sida=1")
//        def TestUrlHttp = new URL("http://www.svtplay.se/abc")
//        def TestUrlHttp = new URL("http://www.svtplay.se/sang-i-stammor")
        def TestUrlHttp = new URL("http://www.svtplay.se/kategorier/dokumentar?tab=senasteprogram")
//        def TestUrlHttp = new URL("http://www.svtplay.se/?tab=news&sida=5")
//        def TestUrlHttp = new URL("http://www.svtplay.se/palme")
//        def TestUrlHttp = new URL("http://www.svtplay.se/kategorier/filmochdrama?tab=senasteprogram&sida=1")
//        def TestUrlHttp = new URL("http://www.svtplay.se/kategorier/nyheter")
//        def TestUrlHttp = new URL("http://www.svtplay.se/kategorier/filmochdrama?tab=episodes&sida=3")
//        def TestUrlHttp = new URL("http://www.svtplay.se/kategorier/nyheter?serviioclips=1&serviioepisodes=0")
//          def TestUrlHttp = new URL("http://www.svtplay.se/kategorier/nyheter?tab=senasteprogram&sida=1")
//        def TestUrlHttp = new URL("http://www.svtplay.se/palme?serviioclips=1")
        
//        def TestUrlLive = new URL("http://www.svtplay.se/?tab=live&sida=5")
        def TestUrlLive = new URL("http://www.svtplay.se/kanaler")
        def TestUrlRtmp = new URL("http://www.svtplay.se/homeland")
        
        SvtPlay extractor = new SvtPlay()
        println "PluginName               : " + extractor.getExtractorName();
        println "Plugin version           : " + extractor.getVersion();
        println "TestMatch HTTP           : " + extractor.extractorMatches(TestUrlHttp);
        println "TestMatch Live           : " + extractor.extractorMatches(TestUrlLive);
        println "TestMatch Rtmp           : " + extractor.extractorMatches(TestUrlRtmp);
    
        println ''
        
        assert extractor.extractorMatches( TestUrlHttp )
        assert extractor.extractorMatches( TestUrlLive )
        assert extractor.extractorMatches( TestUrlRtmp )
        assert !extractor.extractorMatches( new URL("http://google.com/feeds/api/standardfeeds/top_rated?time=today") )
        
        WebResourceContainer containerLive = extractor.extractItems(TestUrlLive, 24);

        ContentURLContainer result1Live = null
        ContentURLContainer result2Live = null
        ContentURLContainer result3Live = null
        if (containerLive.getItems().size() > 0)
        {
            println ' containerLive.getItems()[0].releaseDate: ' + containerLive.getItems()[0].releaseDate
            result1Live = extractor.extractUrl(containerLive.getItems()[0], PreferredQuality.LOW)

            for (int index = 0; index < containerLive.getItems().size(); index++)
            {
                println 'http item #' + index + ': ' + containerLive.getItems()[index].title + ', videoArticleUrl: ' + containerLive.getItems()[index].getAdditionalInfo()['videoArticleUrl']
            }
            result2Live = extractor.extractUrl(containerLive.getItems()[1], PreferredQuality.MEDIUM)
            result3Live = extractor.extractUrl(containerLive.getItems()[2], PreferredQuality.HIGH)
        }
//        ContentURLContainer result1Live = extractor.extractUrl(containerLive.getItems()[0], PreferredQuality.LOW)
        println "Result low Live: $result1Live"
        println "Result medium Live: $result2Live"
        println "Result high Live: $result3Live"

        println ''
        println 'Calling extractor.extractItems on TestUrlHttp: ' + TestUrlHttp

        WebResourceContainer containerHttp = extractor.extractItems(TestUrlHttp, -1);

        ContentURLContainer result1Http = null
         if (containerHttp.getItems().size() > 0)
         {
            println ' containerHttp.getItems()[0].releaseDate: ' + containerHttp.getItems()[0].releaseDate
            result1Http = extractor.extractUrl(containerHttp.getItems()[0], PreferredQuality.LOW)

            for (int index = 0; index < containerHttp.getItems().size(); index++)
            {
                println 'http item #' + index + ': ' + containerHttp.getItems()[index].title + ', videoArticleUrl: ' + containerHttp.getItems()[index].getAdditionalInfo()['videoArticleUrl']
            }
         }
//        ContentURLContainer result1Http = extractor.extractUrl(containerHttp.getItems()[0], PreferredQuality.LOW)
        ContentURLContainer result2Http = extractor.extractUrl(containerHttp.getItems()[1], PreferredQuality.MEDIUM)
        ContentURLContainer result3Http = extractor.extractUrl(containerHttp.getItems()[2], PreferredQuality.HIGH)
        println "Result low HTTP: $result1Http"
        println "Result medium HTTP: $result2Http"
        println "Result high HTTP: $result3Http"

        println ''

        WebResourceContainer containerRtmp = extractor.extractItems(TestUrlRtmp, 3)

        if (containerRtmp.getItems().size() > 0)
        {
            ContentURLContainer result1Rtmp = extractor.extractUrl(containerRtmp.getItems()[0], PreferredQuality.LOW)
            println "Result low Rtmp: $result1Rtmp"
        }
        else
        {
            println 'No qbrick items found!'
        }
        if (containerRtmp.getItems().size() > 1)
        {
            ContentURLContainer result2Rtmp = extractor.extractUrl(containerRtmp.getItems()[1], PreferredQuality.MEDIUM)
            println "Result medium Rtmp: $result2Rtmp"
        }
        if (containerRtmp.getItems().size() > 2)
        {
            ContentURLContainer result3Rtmp = extractor.extractUrl(containerRtmp.getItems()[2], PreferredQuality.HIGH)
            println "Result high Rtmp: $result3Rtmp"
        }
    }
}