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

/**
 * oppetarkiv.se content URL extractor plugin. 
 * 
 * ##############################################
 * Resource URL instructions
 * ##############################################
 * 
 * This is a plugin of the type "Web Resource" ("Webbresurs").
 *
 * ##############################################
 * Version history
 * ##############################################
 *
 * @version 1.0.2
 *
 *
 * ##############################################
 * Credits
 * ############################################## 
 *
 * @author Otto Dandenell 
 * 
 */
class OppetArkiv extends org.serviio.library.online.WebResourceUrlExtractor {
    final VALID_RESOURCE_URL = '^http(s)?://www.oppetarkiv.se/.*$'

    final int VERSION = 2;
    
    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
        // println 'thumbnailUrl - ' + thumbnailUrl
        
        List<WebResourceItem> items = [] 
        // Parse items from markup, return only max items
        /*
            <article class="svtUnit svtNth-1 svtMediaBlock svtMediaBlock-oa svt-margin-bottom-15px">
            <div class="svt-display-table-xs">
                <div class="svt-table-cell-left-xs svtJsClickArea">
                    <figure class="svtMediaBlockFig-M svtMBFig-L-O-O-O svt-text-margin-medium  ">
                        <noscript>
    <img class="oaImg" alt="Glaset i &ouml;rat" src="http://www.svt.se/oppet-arkiv-pub/cachable_image/1366273741000/incoming/article1100192.svt/ALTERNATES/medium/glaset.jpg"/>
</noscript>
<img class="oaImg svtHide-No-Js" data-responsive-maxsize="medium" data-aspect-ratio="16_9" data-imagename="http://www.svt.se/oppet-arkiv-pub/cachable_image/1366273741000/incoming/article1100192.svt/ALTERNATES/medium/glaset.jpg" alt="Glaset i &ouml;rat" src="/public/images/responsiveimage/16_9.png"/>
                                            </figure>
                    <div class="svt-overflow-hidden">
                                                                            <h2 class="svt-heading-xs svt-text-margin-medium"><a title="Glaset i &ouml;rat" class="svtLink-Discreet-THEMED svtJsLoadHref" href="http://www.oppetarkiv.se/video/1069186/">Glaset i &ouml;rat</a></h2>
                                                <p class="svt-text-time svt-color-darkgrey">
                            <time datetime="1974-10-11T23:00+0100">11 oktober 1974</time>
                        </p>
                    </div>
                </div>
                <div class="svt-table-cell-right-xs svtoa-js-load-info-link svt-pointer-cursor svtoa-position-bottom-right-gt-xs svt-padding-top-10px-padding-left-10px" data-source="/video/details/1069186/">
                    <a href="#" class="svtHide-No-Js svtoa-info-button svtIcon-small-right svtoa-icon-info" role="button">
                        <span class="svtVisuallyhidden">Visa mer information</span>
                    </a>
                </div>
            </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 [^>]*class="svt-container-main"[^>]*?>.*'
            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"[^>]*?>.*?(?:<div [^>]*data-tabname=|</body>)'
            if (clipsMatcher != null && clipsMatcher.getCount() > 0)
            {
                strServiioClipsHTML = clipsMatcher[0]
            }
        }
        //println 'strServiioClipsHTML: ' + strServiioClipsHTML

        
        def itemsAdded = 0;

        // We put live feeds at the top, clips at the bottom.
        def strNonLiveFeedHTML = strServiioEpisodesHTML + strServiioClipsHTML        
        
        def 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] =~ '(?s)<h2 [^>]*?><a title=\"([^\"]*?)\" class=\"[^\"]*?svtJsLoadHref\" href=\"([^\"]*?)\".*?</div>'
            def playlinkMatcher = articleMatches[i] =~ '(?s)<h3 [^>]*?><a title=\"([^\"]*?)\" class=\"[^\"]*?svtJsLoadHref\" href=\"([^\"]*?)\".*?</div>'
            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="oaImg" alt="Glaset i &ouml;rat" src="http://www.svt.se/oppet-arkiv-pub/cachable_image/1366273741000/incoming/article1100192.svt/ALTERNATES/medium/glaset.jpg"/>
                    def thumbnailMatcher = articleMatches[i] =~ '(?s)<img class=\"oaImg\" [^>]*?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]
                    videoArticleUrl = playlinkMatcher[0][2]
                    log(' webResourceItemTitle: ' + webResourceItemTitle)
                    //println 'webResourceItemTitle: ' + webResourceItemTitle
                    log(' videoArticleUrl: ' + videoArticleUrl)
                    //println 'videoArticleUrl: ' + videoArticleUrl
                    //println ' videoArticleThumbnailUrl: ' + videoArticleThumbnailUrl
                    
                    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'])
        //println 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.oppetarkiv.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.oppetarkiv.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)
            //println ' js scriptPath: ' + scriptPath
            def scriptContent
            URL scriptUrl = new URL('http://www.oppetarkiv.se' + scriptPath)
            def scriptConnection = scriptUrl.openConnection()
            if(scriptConnection.responseCode == 200){
                scriptContent = scriptConnection.content.text
                //println ' js scriptContent: ' + scriptContent                
                // 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')
           //println  ' 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 =~ /\\?null=$/).replaceAll('')
                                            match = (match =~ /&id=$/).replaceAll('')
                                            match = (match =~ /\?$/).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 TestUrlHttp = new URL("http://www.oppetarkiv.se/etikett/titel/Babels%20hus/")        
        
        OppetArkiv extractor = new OppetArkiv()
        println "PluginName               : " + extractor.getExtractorName();
        println "Plugin version           : " + extractor.getVersion();
        println "TestMatch HTTP           : " + extractor.extractorMatches(TestUrlHttp);
    
        println ''
        
        assert extractor.extractorMatches( TestUrlHttp )
        assert !extractor.extractorMatches( new URL("http://google.com/feeds/api/standardfeeds/top_rated?time=today") )
        
        WebResourceContainer containerHttp = extractor.extractItems(TestUrlHttp, 24);

        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 ''
    }
}