import groovy.util.XmlParser import org.serviio.library.metadata.* import org.serviio.library.online.* import groovy.json.JsonSlurper /******************************************************************** * CBS.com plugin for Serviio (US ONLY) * Player/clip logic based on CBS Plugin for Plex * * * Version: * V1: - x x, xxx - Initial Release by Mike_Metro * V2: - Aug 35, 2012 - updated to reflect new feed url, updated by: X S Inattar * V3: - Apr 13, 2013 - New carousel link, PID not available in the carousel * V4: - Apr 17, 2013 - New carousel link and new json format * V5: - Apr 26, 2013 - New carousel link and versioning * V6: - May 12. 2013 - Better error handler, implemented maxItemsToRetrieve, fixed bug on show with only one quality * V7: - May 16. 2013 - Unlimited feeds=100 feeds * Must be installed as a WebResource * Only available in US * Sample URLs: http://www.cbs.com/shows/csi/video/ ********************************************************************/ class CBS extends WebResourceUrlExtractor { final VALID_FEED_URL = '^(?:http://)?(?:www\\.)?cbs\\.com/shows/(.*?)/video(?:/)?$' //final SHOW_LIST = 'http://www.cbs.com/carousels/videosBySection/%s/0/15/' // V3 //final SHOW_LIST = 'http://www.cbs.com/carousels/videosBySection/%s/offset/0/limit/15/' // V4 //final SHOW_LIST = 'http://www.cbs.com/carousels/videosBySection/%s/offset/0/limit/15/xs/0' // V5 final SHOW_LIST = 'http://www.cbs.com/carousels/videosBySection/%s/offset/0/limit/%s/xs/0' // V6 final PRE_SMIL_URL ='http://www.cbs.com%s' final SMIL_URL ='http://link.theplatform.com/s/dJ5BDC/%s?format=SMIL&Tracking=true&mbr=true' int getVersion() { return 7 } int getExtractItemsTimeout(){ return 30 } WebResourceContainer errorHandlerWRC(String e){ List items = [] println e log(e) items << new WebResourceItem(title: e, additionalInfo: ['url':'http://error','thumbnailUrl':'http://fake.jpg']) WebResourceContainer wrc = new WebResourceContainer(title: "Error", items: items) return wrc } void errorHandler(String e){ println e log(e) return } String getExtractorName() { return 'CBS.com' } boolean extractorMatches(URL feedUrl) { return feedUrl ==~ VALID_FEED_URL } WebResourceContainer extractItems(URL resourceUrl, int maxItemsToRetrieve) { List items = [] Date releaseDate def json def i=0 def pageContent = resourceUrl.getText() def jsMatcher = pageContent =~ "id-carousel-(.*)\" class" //V4 if (jsMatcher.count <= 0) { return errorHandlerWRC("CBS: Carousel ID not found") } def section = jsMatcher[0][1] def nameMatcher = resourceUrl.toString() =~ VALID_FEED_URL if (nameMatcher.count <= 0) { errorHandlerWRC("CBS: Cannot find show title") } def showTitle = nameMatcher[0][1] //Added V7, check for unlimited sources if (maxItemsToRetrieve <= -1) { maxItemsToRetrieve=100 } //modified in V6, added maxItemsToRetrieve def contentUrl = new URL(String.format(SHOW_LIST, section, maxItemsToRetrieve)) try{ json = new JsonSlurper().parseText(contentUrl.getText()) }catch (FileNotFoundException e){ errorHandlerWRC("CBS: Carousel page not found") } //doesn't work, need to be fixed /*json.itemList.find { items << new WebResourceItem(title: title, releaseDate: new Date(airdate.toLong()), additionalInfo: [ID:url, thumbnailUrl: thumb.large]) itemsAdded++ if (maxItemsToRetrieve != -1 && itemsAdded >= maxItemsToRetrieve) return true return false }*/ //working try{ //V6 changed result.total to result.size for (i=0;i additionalInfo = new HashMap(); //thumbnailUrl=json.result.data[i].thumb.large additionalInfo.put("url",json.result.data[i].url) additionalInfo.put("thumbnailUrl",json.result.data[i].thumb.large) //removed in V4 //airdate is available again, no need to parse //jsMatcher = thumbnailUrl =~ "([0-9]{4})/([0-9]{2})/([0-9]{2})" //releaseDate= Date.parse("yyyy/MM/dd", jsMatcher[0][0]) releaseDate=Date.parse("MM/dd/yy",json.result.data[i].airdate) items << new WebResourceItem(title: json.result.data[i].title,releaseDate: releaseDate,additionalInfo: additionalInfo) } }catch(e){ errorHandlerWRC("CBS: Json Error parsing" & e) } return new WebResourceContainer(title: showTitle, items: items) } ContentURLContainer extractUrl(WebResourceItem item, PreferredQuality requestedQuality) { //added in V6 if (item.additionalInfo.url.contains("http://error")){ def rtmpUrl = "rtsp://error" def secCode = "abcdefghi" def cacheKey = "http://lastitem_" + secCode def expiresImmediately = false return new ContentURLContainer( contentUrl: rtmpUrl, thumbnailUrl: item.additionalInfo.thumbnailUrl,cacheKey: cacheKey, expiresImmediately: expiresImmediately, live: true) } //edited in V3 def pageContent = new URL(String.format(PRE_SMIL_URL , item.additionalInfo.url)).getText() def PID = pageContent =~ "video.settings.pid = '(.*)';" if (PID.count <= 0) { errorHandler("CBS: PID not found") return null } def smil = new URL(String.format(SMIL_URL, PID[0][1])).getText() // xml parser/slurper had issues was decoding encoded characters & etc. // updated by: X S Inattar // updated on: August 25, 2012 // begin // def stream_source_matcher = smil =~ 'meta base="(.*?)"' if (stream_source_matcher.count <= 0) { errorHandler("CBS: Cannot find stream source") return null } def stream_source = stream_source_matcher[0][1] def vidMatcher = smil =~ 'video src="(.*?)".*?system-bitrate="(.*?)"' if (vidMatcher.count <= 0) { errorHandler("CBS: Cannot find stream bitrate") return null } def player def formats = [:] for (i in 0.. b.compareTo(a) }] as Comparator formats = formats.sort(comparator) def keys = formats.keySet().toList() //V6 formats.count was always null, change to keys.size if (requestedQuality == PreferredQuality.HIGH || keys.size == 1) { player = formats[keys[0]] } else if (requestedQuality == PreferredQuality.MEDIUM || keys.size == 2) { player = formats[keys[1]] } else if (requestedQuality == PreferredQuality.LOW) { player = formats[keys[2]] } // updated by: X S Inattar // updated on: August 25, 2012 // begin // //def clip = [] def clip = '' if (player.contains('.mp4')) { player = player.replace('.mp4', '') //clip = player.split(';') //clip = 'mp4:' + clip[4] clip = 'mp4:' + player } else if (player.contains('.flv')) { player = player.replace('.flv', '') //clip = player.split(';') //clip = clip[4] clip = player } //def contentUrl = "${player} playpath=${clip}" def contentUrl = "${stream_source} playpath=${clip}" // end def cacheKey = "CBS_${item.additionalInfo.PID}_${requestedQuality}" return new ContentURLContainer(contentUrl: contentUrl, thumbnailUrl: item.additionalInfo.thumbnailUrl, expiresImmediately: true, cacheKey : cacheKey) } static void main(args) { CBS extractor = new CBS() //for testing //http://www.cbs.com/shows/big_bang_theory/video/ //http://www.cbs.com/shows/csi/video/ //http://www.cbs.com/shows/48_hours/video/ WebResourceContainer container = extractor.extractItems( new URL("http://www.cbs.com/shows/48_hours/video/"), 5) if ( container ){ container.getItems().each { ContentURLContainer result = extractor.extractUrl(it, PreferredQuality.MEDIUM) println result } } } }