import groovy.json.JsonSlurper
import org.serviio.library.metadata.*
import org.serviio.library.online.*
import org.serviio.util.*
/**
* WebResource extractor plugin for tunein.com.
*
* @author Petr Nejedly
*
*/
class TuneIn extends WebResourceUrlExtractor {
final VALID_FEED_URL = '^(?:https?://)?(?:www\\.)?tunein\\.com(/radio/|/search/?\\?query=).+'
String getExtractorName() {
return 'Tune In'
}
boolean extractorMatches(URL feedUrl) {
return feedUrl ==~ VALID_FEED_URL
}
int getVersion() {
10
}
WebResourceContainer extractItems(URL resourceUrl, int maxItems) {
String html = resourceUrl.getText()
def titleMatcher = html =~ '(?s)
(.+?)<'
String pageTitle = titleMatcher[0][1].trim()
println "pagetitle = $pageTitle"
List items = []
def stationMatcher = html =~ '(?s)"GuideId":.*?"Title":"(.*?)".*?"Url":"(.*?)"'
def itemsAdded = 0;
for( int i = 0; i < stationMatcher.size() && (maxItems == -1 || itemsAdded < maxItems); i++ ) {
String stationTitle = stationMatcher[i][1].trim()
String stationUrl = stationMatcher[i][2].trim()
if (!stationUrl.contains("-s")) {
continue
}
WebResourceItem item = new WebResourceItem(title: stationTitle, additionalInfo: ['stationUrl':stationUrl])
items << item
itemsAdded++
}
return new WebResourceContainer(title: pageTitle, items: items)
}
ContentURLContainer extractUrl(WebResourceItem item, PreferredQuality requestedQuality) {
String stationTitle = item.title
String stationUrl = item.getAdditionalInfo()['stationUrl']
assert stationUrl != null
String playerHtml = new URL("http://tunein.com$stationUrl").getText()
def jsonMatcher = playerHtml =~ '(?s)TuneIn\\.payload =(.*?)TuneIn\\.'
String jsonCode = jsonMatcher[0][1].trim()
def json = new JsonSlurper().parseText(jsonCode)
Map jsonResult = (Map) json;
Map broadcastJson = jsonResult['Station']['broadcast']
// thumbnail
String thumbnailUrl = broadcastJson['SquareLogo']
// streams, get required quality
String streamUrl = getStreamUrl(jsonResult)
if(streamUrl == null || streamUrl == "") {
// try to load external stream URLs
String extStreamsUrl = broadcastJson['StreamUrl']
if(extStreamsUrl != null && extStreamsUrl != "") {
if(!extStreamsUrl.startsWith("http")) extStreamsUrl = "http:" + extStreamsUrl
println "extstreamsUrl = $extStreamsUrl"
String streamsDef = new URL(extStreamsUrl).getText()
def jsonMatcher2 = streamsDef =~ '(?s)(\\{.*?\\}\\].*\\})'
String jsonCode2 = jsonMatcher2[0][1].trim()
def json2 = new JsonSlurper().parseText(jsonCode2)
Map jsonResult2 = (Map) json2;
streamUrl = getStreamUrl(jsonResult2)
}
}
if(streamUrl == null || streamUrl == "") {
log('No suitable streams found')
return null
}
return new ContentURLContainer(fileType: MediaFileType.AUDIO, contentUrl: streamUrl, thumbnailUrl: thumbnailUrl, live: true)
}
private String getStreamUrl(Map jsonResult) {
List streams = jsonResult['Streams']
// only get streams that are represented as playlists and are live streams
streams = streams.findAll { it -> it['Type'] == 'Live' && ['MP3','Windows','AAC','Flash'].contains(it['MediaType']) && it['Url'].indexOf('adType') == -1 }
if( streams.size() > 0 ) {
// ignore quality, audio bitrate is too low to make any difference, deliver the best available
streams = streams.sort { it -> it['Bandwidth'].toInteger() }
Map selectedStream = streams.last()
String streamUrl = selectedStream['Url']
boolean hasPlaylist = Boolean.valueOf (selectedStream['HasPlaylist'])
if(hasPlaylist && streamUrl.startsWith("http")) {
return getUrlFromPlaylist(new URL(streamUrl))
} else {
return streamUrl
}
} else {
return null
}
}
/**
* Supports m3u playlists ATM
*/
protected getUrlFromPlaylist(URL playlistUrl) {
assert playlistUrl != null
String playlist = playlistUrl.getText()
if(playlist.toLowerCase().startsWith(" -1 || url.indexOf('.pls') > -1) {
return getUrlFromPlaylist(new URL(url))
}
return url
}
}
static void main(args) {
// this is just to test
TuneIn extractor = new TuneIn()
assert extractor.extractorMatches( new URL("http://tunein.com/radio/London-United-Kingdom-r100780/?qlc=1") )
assert !extractor.extractorMatches( new URL("http://google.com/feeds/api/standardfeeds/top_rated?time=today") )
//WebResourceContainer container = extractor.extractItems( new URL("http://tunein.com/radio/London-United-Kingdom-r100780/?qlc=1"), -1) // with embedded streams
//WebResourceContainer container = extractor.extractItems( new URL("http://tunein.com/radio/search/05301/"), -1) // with external streams
//WebResourceContainer container = extractor.extractItems( new URL("http://tunein.com/search/?query=kiss"), -1)
//WebResourceContainer container = extractor.extractItems( new URL("http://tunein.com/search/?query=kiss%20fm%20955"), -1)
WebResourceContainer container = extractor.extractItems( new URL("http://tunein.com/search/?query=doowop"), -1)
//println container
container.getItems().each {
ContentURLContainer result = extractor.extractUrl(it, PreferredQuality.MEDIUM)
println result
}
}
}