Java tutorial
package org.vanbest.xmltv; /* Copyright (c) 2013 Jan-Pascal van Best <janpascal@vanbest.org> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. The full license text can be found in the LICENSE file. */ import java.io.BufferedReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URL; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamWriter; import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import net.sf.json.JSON; import net.sf.json.JSONArray; import net.sf.json.JSONObject; public class Horizon extends AbstractEPGSource implements EPGSource { static String channels_url = "https://www.horizon.tv/oesp/api/NL/nld/web/channels/"; static String listings_url = "https://www.horizon.tv/oesp/api/NL/nld/web/listings"; private static final int MAX_DAYS_AHEAD_SUPPORTED_BY_HORIZON = 7; public final static String NAME = "horizon.tv"; static Logger logger = Logger.getLogger(Horizon.class); public Horizon(Config config) { super(config); } public String getName() { return NAME; } public static URL programmeUrl(Channel channel, int day) throws Exception { StringBuilder s = new StringBuilder(listings_url); s.append("?byStationId="); s.append(channel.id); s.append("&sort=startTime&range=1-100"); Calendar startTime = Calendar.getInstance(); startTime.set(Calendar.HOUR_OF_DAY, 0); startTime.set(Calendar.MINUTE, 0); startTime.set(Calendar.SECOND, 0); startTime.set(Calendar.MILLISECOND, 0); startTime.add(Calendar.DAY_OF_MONTH, day); Calendar endTime = (Calendar) startTime.clone(); endTime.add(Calendar.DAY_OF_MONTH, 1); s.append("&byStartTime="); s.append(startTime.getTimeInMillis()); s.append("~"); s.append(endTime.getTimeInMillis()); return new URL(s.toString()); } /* * (non-Javadoc) * * @see org.vanbest.xmltv.EPGSource#getChannels() */ @Override public List<Channel> getChannels() { List<Channel> result = new ArrayList<Channel>(10); URL url = null; try { url = new URL(channels_url); } catch (MalformedURLException e) { logger.error("Exception creating horizon channel list url", e); } JSONObject channels; try { channels = fetchJSON(url); } catch (Exception e1) { // TODO Auto-generated catch block e1.printStackTrace(); return result; } logger.debug("horizon channels json: " + channels.toString()); int numChannels = Integer.parseInt(channels.getString("totalResults")); JSONArray jsonArray = channels.getJSONArray("channels"); for (int i = 0; i < jsonArray.size(); i++) { JSONObject zender = jsonArray.getJSONObject(i); // System.out.println( "id: " + zender.getString("id")); // System.out.println( "name: " + zender.getString("name")); JSONArray stationSchedules = zender.getJSONArray("stationSchedules"); assert (1 == stationSchedules.size()); JSONObject firstSchedule = stationSchedules.getJSONObject(0); JSONObject station = firstSchedule.getJSONObject("station"); logger.debug("firstschedule: " + firstSchedule.toString()); long horizonId = station.getLong("id"); String name = station.getString("title"); // Use the largest available station logo as icon JSONArray images = station.getJSONArray("images"); String icon = ""; int maxSize = 0; for (int j = 0; j < images.size(); j++) { JSONObject image = images.getJSONObject(j); if (image.getString("assetType").startsWith("station-logo") && image.getInt("width") > maxSize) { icon = image.getString("url"); maxSize = image.getInt("width"); } } String xmltv = name.replaceAll("[^a-zA-Z0-9]", "") + "." + getName(); Channel c = Channel.getChannel(getName(), Long.toString(horizonId), xmltv, name); //Channel c = new HorizonChannel(getId(), horizonId, name); c.addIcon(icon); result.add(c); } return result; } /* * (non-Javadoc) * * @see org.vanbest.xmltv.EPGSource#getProgrammes(java.util.List, int, * boolean) */ @Override public List<Programme> getProgrammes(List<Channel> channels, int day) throws Exception { List<Programme> result = new ArrayList<Programme>(); if (day > MAX_DAYS_AHEAD_SUPPORTED_BY_HORIZON) { return result; // empty list } for (Channel c : channels) { URL url = programmeUrl(c, day); logger.debug("Programme url:" + url); JSONObject jsonObject = fetchJSON(url); logger.debug(jsonObject.toString()); JSONArray listings = jsonObject.getJSONArray("listings"); for (int i = 0; i < listings.size(); i++) { JSONObject listing = listings.getJSONObject(i); Programme p = programmeFromJSON(listing, config.fetchDetails); p.channel = c.getXmltvChannelId(); result.add(p); } } return result; } /* * {"id":"crid:~~2F~~2Feventis.nl~~2F00000000-0000-1000-0004-00000189B7F0-imi:001017B90000FCD2", * "countryCode":"NL", * "languageCode":"nld", * "deviceCode":"web", * "locationId":"15332128", * "startTime":1362399000000, * "endTime":1362399300000, * "stationId":"28070126", * "imi":"imi:001017B90000FCD2", * "program":{"id":"crid:~~2F~~2Feventis.nl~~2F00000000-0000-1000-0004-00000189B7F0", * "mediaGroupId":"crid:~~2F~~2Feventis.nl~~2F00000000-0000-1000-0008-000000007784", * "title":"NOS Sportjournaal (Ned1) - 13:10", * "secondaryTitle":"NOS Sportjournaal", * "description":"Aandacht voor het actuele sportnieuws.", * "shortDescription":"Aandacht voor het actuele sportnieuws.", * "longDescription":"Aandacht voor het actuele sportnieuws.", * "countryCode":"NL", * "languageCode":"nld", * "deviceCode":"web", * "medium":"TV", * "categories":[{"id":"13946291","title":"sports","scheme":"urn:tva:metadata:cs:UPCEventGenreCS:2009"}, * {"id":"13946352","title":"sports/sports","scheme":"urn:tva:metadata:cs:UPCEventGenreCS:2009"}], * "mediaType":"Episode", * "isAdult":false, * "seriesEpisodeNumber":"50108216", * "cast":[], * "directors":[], * "videos":[], * "images":[{"assetType":"tva-boxcover","width":180,"height":260,"url":"https://www.horizon.tv/static-images/926/511/36654624.p.jpg"}, * {"assetType":"boxart-xlarge","width":210,"height":303,"url":"https://www.horizon.tv/static-images/926/511/36654624.p_210x303_34273348255.jpg"}, * {"assetType":"boxart-large","width":180,"height":260,"url":"https://www.horizon.tv/static-images/926/511/36654624.p_180x260_34273348256.jpg"}, * {"assetType":"boxart-medium","width":110,"height":159,"url":"https://www.horizon.tv/static-images/926/511/36654624.p_110x159_34273348257.jpg"}, * {"assetType":"boxart-small","width":75,"height":108,"url":"https://www.horizon.tv/static-images/926/511/36654624.p_75x108_34273348258.jpg"}]}} */ private Programme programmeFromJSON(JSONObject json, boolean fetchDetails) throws Exception { String id = json.getString("id"); Programme result = cache.get(getName(), id); boolean cached = (result != null); boolean doNotCache = false; if (result == null) { stats.cacheMisses++; result = new Programme(); result.startTime = new Date(json.getLong("startTime")); result.endTime = new Date(json.getLong("endTime")); JSONObject prog = json.getJSONObject("program"); // Sometimes the JSON doesnt contains a prog item if (prog == null || prog.isNullObject()) return result; String title = null; if (prog.has("title")) { title = prog.getString("title"); } String secondary = null; if (prog.has("secondaryTitle")) { secondary = prog.getString("secondaryTitle"); if (secondary.contains("Zal snel bekend")) secondary = null; } if (title != null && secondary != null && title.contains(secondary)) { title = secondary; secondary = null; } if (title != null && !title.isEmpty()) { result.addTitle(title); if (secondary != null && !secondary.isEmpty()) { result.addSecondaryTitle(secondary); } } else { doNotCache = true; if (secondary != null && !secondary.isEmpty()) { result.addTitle(secondary); } } String description = null; if (prog.has("longDescription")) description = prog.getString("longDescription"); if (description == null || description.isEmpty()) { if (prog.has("description")) description = prog.getString("description"); } if (description == null || description.isEmpty()) { if (prog.has("shortDescription")) description = prog.getString("shortDescription"); } if (description != null && !description.isEmpty()) { result.addDescription(description); } else { doNotCache = true; } if (prog.has("cast")) { JSONArray cast = prog.getJSONArray("cast"); for (int j = 0; j < cast.size(); j++) { result.addActor(cast.getString(j)); } } if (prog.has("directors")) { JSONArray directors = prog.getJSONArray("directors"); for (int j = 0; j < directors.size(); j++) { result.addDirector(directors.getString(j)); } } if (prog.has("categories")) { JSONArray categories = prog.getJSONArray("categories"); for (int j = 0; j < categories.size(); j++) { String cat = categories.getJSONObject(j).getString("title"); if (!cat.contains("/")) { // Remove things like "drama/drama" and subcategories result.addCategory(config.translateCategory(cat)); } } } if (prog.has("seriesEpisodeNumber")) { String episode = prog.getString("seriesEpisodeNumber"); result.addEpisode(episode, "onscreen"); } /* Disabled, contains disinformation if (prog.has("parentalRating")){ String rating = prog.getString("parentalRating"); result.addRating("kijkwijzer", "Afgeraden voor kinderen jonger dan "+rating+" jaar"); } */ /* // TODO add icon */ } else { // System.out.println("From cache: " + // programme.getString("titel")); stats.cacheHits++; } // TODO other fields if (!cached && !doNotCache) { cache.put(getName(), id, result); } logger.debug(result); return result; } /** * @param args */ public static void main(String[] args) { Config config = Config.getDefaultConfig(); Horizon horizon = new Horizon(config); horizon.clearCache(); try { List<Channel> channels = horizon.getChannels(); System.out.println("Channels: " + channels); XMLStreamWriter writer = XMLOutputFactory.newInstance() .createXMLStreamWriter(new FileWriter("horizon.xml")); writer.writeStartDocument(); writer.writeCharacters("\n"); writer.writeDTD("<!DOCTYPE tv SYSTEM \"xmltv.dtd\">"); writer.writeCharacters("\n"); writer.writeStartElement("tv"); // List<Channel> my_channels = channels; List<Channel> my_channels = channels.subList(0, 5); for (Channel c : my_channels) { c.serialize(writer, true); } writer.flush(); for (int day = 0; day < 5; day++) { List<Programme> programmes = horizon.getProgrammes(my_channels, day); for (Programme p : programmes) { p.serialize(writer); } } writer.writeEndElement(); writer.writeEndDocument(); writer.flush(); if (!config.quiet) { EPGSource.Stats stats = horizon.getStats(); System.out.println("Number of programmes from cache: " + stats.cacheHits); System.out.println("Number of programmes fetched: " + stats.cacheMisses); System.out.println("Number of fetch errors: " + stats.fetchErrors); } horizon.close(); } catch (Exception e) { logger.error("Error in horizon testing", e); } } }