]> www.vanbest.org Git - tv_grab_nl_java/commitdiff
Working on new RTL interface
authorJan-Pascal van Best <janpascal@vanbest.org>
Tue, 14 Jan 2014 19:55:48 +0000 (20:55 +0100)
committerJan-Pascal van Best <janpascal@vanbest.org>
Tue, 14 Jan 2014 19:55:48 +0000 (20:55 +0100)
src/main/java/org/vanbest/xmltv/RTL.java

index b8924367ad93f2e3e0a063861b6d735061b25057..53da69db539a16dd8fba536ed7ee45cdaf604f7e 100644 (file)
-package org.vanbest.xmltv;\r
-\r
-import java.io.BufferedOutputStream;\r
-import java.io.FileNotFoundException;\r
-import java.io.FileOutputStream;\r
-import java.io.FileWriter;\r
-import java.io.PrintWriter;\r
-import java.io.StringWriter;\r
-import java.net.MalformedURLException;\r
-import java.net.URL;\r
-import java.sql.SQLException;\r
-import java.text.SimpleDateFormat;\r
-import java.util.ArrayList;\r
-import java.util.Calendar;\r
-import java.util.Collections;\r
-import java.util.Comparator;\r
-import java.util.Date;\r
-import java.util.HashMap;\r
-import java.util.LinkedList;\r
-import java.util.List;\r
-import java.util.Map;\r
-import javax.xml.parsers.DocumentBuilderFactory;\r
-import javax.xml.stream.XMLOutputFactory;\r
-import javax.xml.stream.XMLStreamWriter;\r
-import javax.xml.transform.Transformer;\r
-import javax.xml.transform.TransformerFactory;\r
-import javax.xml.transform.dom.DOMSource;\r
-import javax.xml.transform.stream.StreamResult;\r
-import javax.xml.transform.TransformerException;\r
-import javax.xml.transform.TransformerConfigurationException;\r
-\r
-import net.sf.json.JSONArray;\r
-import net.sf.json.JSONObject;\r
-\r
-import org.apache.commons.lang.StringUtils;\r
-import org.apache.log4j.Level;\r
-import org.apache.log4j.Logger;\r
-import org.w3c.dom.DOMException;\r
-import org.w3c.dom.Document;\r
-import org.w3c.dom.Element;\r
-import org.w3c.dom.Node;\r
-import org.w3c.dom.NodeList;\r
-\r
-public class RTL extends AbstractEPGSource implements EPGSource {\r
-\r
-       private static final String programme_url = "http://www.rtl.nl/active/epg_data/dag_data/";\r
-       private static final String detail_url = "http://www.rtl.nl/active/epg_data/uitzending_data/";\r
-       private static final String icon_url = "http://www.rtl.nl/service/gids/components/vaste_componenten/";\r
-       private static final int MAX_PROGRAMMES_PER_DAY = 9999;\r
-       public static final String NAME = "rtl.nl";\r
-       static Logger logger = Logger.getLogger(RTL.class);\r
-\r
-       String[] xmlKeys = { "zendernr", "pgmsoort", "genre", "bijvnwlanden",\r
-                       "ondertiteling", "begintijd", "titel", "site_path", "wwwadres",\r
-                       "presentatie", "omroep", "eindtijd", "inhoud", "tt_inhoud",\r
-                       "alginhoud", "afl_titel", "kijkwijzer" };\r
-       Map<String, Integer> xmlKeyMap = new HashMap<String, Integer>();\r
-\r
-       static boolean debug = false;\r
-       PrintWriter debugWriter;\r
-\r
-       class RTLException extends Exception {\r
-               public RTLException(String s) {\r
-                       super(s);\r
-               }\r
-       }\r
-\r
-       class DateStatus {\r
-               Date programDate;\r
-               Calendar prevStartTime = null;\r
-               final static int START_TIME = 1;\r
-               final static int END_TIME = 2;\r
-\r
-               public DateStatus(Date date) {\r
-                       reset(date);\r
-               }\r
-\r
-               public void reset(Date date) {\r
-                       this.programDate = date;\r
-                       this.prevStartTime = null;\r
-               }\r
-       }\r
-\r
-       class DescStatus {\r
-               String inhoud;\r
-               String alginhoud;\r
-               String tt_inhoud;\r
-       }\r
-\r
-       public RTL(int sourceId, Config config) {\r
-               super(sourceId, config);\r
-               if (debug) {\r
-                       for (int i = 0; i < xmlKeys.length; i++) {\r
-                               xmlKeyMap.put(xmlKeys[i], i);\r
-                       }\r
-               }\r
-       }\r
-\r
-       public String getName() {\r
-               return NAME;\r
-       }\r
-\r
-        private String xmlToString(Document doc) {\r
-                try {\r
-                  Transformer transformer = TransformerFactory.newInstance().newTransformer();\r
-                  StringWriter stw = new StringWriter();  \r
-                  transformer.transform(new DOMSource(doc), new StreamResult(stw));  \r
-                  return stw.toString();\r
-                } catch (TransformerConfigurationException e) {\r
-                  logger.debug("Cannot convert XML Document to String, e");\r
-                  return "";\r
-                } catch (TransformerException e) {\r
-                  logger.debug("Cannot convert XML Document to String, e");\r
-                  return "";\r
-                }\r
-        }\r
-\r
-       private Document fetchXML(URL url) throws Exception {\r
-               Document xml = null;\r
-               boolean done = false;\r
-               logger.debug("Fetching XML from URL "+url);\r
-               for (int count = 0; !done; count++) {\r
-                       Thread.sleep(config.niceMilliseconds*(1<<count));\r
-                       try {\r
-                               xml = DocumentBuilderFactory.newInstance()\r
-                                       .newDocumentBuilder().parse(url.openStream());\r
-                               done = true;\r
-                       } catch (Exception e) {\r
-                                       if (!config.quiet) {\r
-                                               logger.warn("Error fetching from url " + url + ", count="\r
-                                                               + count);\r
-                                       }\r
-                                       if (count >= MAX_FETCH_TRIES) {\r
-                                               stats.fetchErrors++;\r
-                                               logger.debug("Error getting data from url", e);\r
-                                               throw new Exception("Error getting data from url "\r
-                                                               + url, e);\r
-                                       }\r
-                      }\r
-                }\r
-               logger.debug(xmlToString(xml));\r
-                return xml;\r
-        }\r
-\r
-       public List<Channel> getChannels() {\r
-               List<Channel> result = new ArrayList<Channel>(10);\r
-\r
-               URL url = null;\r
-               try {\r
-                       url = new URL(programme_url + "1");\r
-               } catch (MalformedURLException e) {\r
-                       logger.error("Exception creating RTL channel list url", e);\r
-               }\r
-               Document xml = null;\r
-               try {\r
-                       xml = fetchXML(url);\r
-               } catch (Exception e) {\r
-                       logger.error("Exception reading RTL channel listing from " + url\r
-                                       + " and transforming to XML", e);\r
-                        return result;\r
-               }\r
-               Element root = xml.getDocumentElement();\r
-               String json = root.getTextContent();\r
-               JSONObject o = JSONObject.fromObject(json);\r
-               for (Object k : o.keySet()) {\r
-                       JSONArray j = (JSONArray) o.get(k);\r
-                       String id = genericChannelId(k.toString());\r
-                       String name = (String) j.get(0);\r
-                        String icon = icon_url + id + ".gif";\r
-                       Channel c = Channel.getChannel(getId(), id, name);\r
-                        c.addIcon(icon);\r
-                       result.add(c);\r
-               }\r
-\r
-               Collections.sort(result, new Comparator<Channel>() {\r
-                       public int compare(Channel o1, Channel o2) {\r
-                               if (o1.source == o2.source) {\r
-                                       int c1 = Integer.parseInt(o1.id);\r
-                                       int c2 = Integer.parseInt(o2.id);\r
-                                       return (c1 == c2 ? 0 : ((c1 < c2) ? -1 : 1));\r
-                               } else {\r
-                                       return o1.source < o2.source ? -1 : 1;\r
-                               }\r
-                       }\r
-               });\r
-               return result;\r
-       }\r
-\r
-       private String genericChannelId(String jsonid) {\r
-               return jsonid.replaceAll("^Z", ""); // remove initial Z\r
-       }\r
-\r
-       /*\r
-        * <?xml version="1.0" encoding="iso-8859-1" ?> <uitzending_data>\r
-        * <uitzending_data_item> <zendernr>5</zendernr>\r
-        * <pgmsoort>Realityserie</pgmsoort> <genre>Amusement</genre>\r
-        * <bijvnwlanden></bijvnwlanden> <ondertiteling></ondertiteling>\r
-        * <begintijd>05:00</begintijd> <titel>Marriage Under Construction</titel>\r
-        * <site_path>0</site_path> <wwwadres></wwwadres>\r
-        * <presentatie></presentatie> <omroep></omroep> <eindtijd>06:00</eindtijd>\r
-        * <inhoud></inhoud> <tt_inhoud>Een jong stel wordt gevolgd bij het zoeken\r
-        * naar, en vervolgens verbouwen en inrichten van, hun eerste huis. Dit\r
-        * verloopt uiteraard niet zonder slag of stoot.</tt_inhoud> <alginhoud>Een\r
-        * jong stel wordt gevolgd bij het zoeken naar, en vervolgens verbouwen en\r
-        * inrichten van, hun eerste huis. Dit verloopt uiteraard niet zonder slag\r
-        * of stoot.</alginhoud> <afl_titel></afl_titel> <kijkwijzer></kijkwijzer>\r
-        * </uitzending_data_item> </uitzending_data>\r
-        */\r
-       private void fetchDetail(Programme prog, DateStatus dateStatus, String id)\r
-                       throws Exception {\r
-               URL url = detailUrl(id);\r
-               Document xml = fetchXML(url);\r
-               Element root = xml.getDocumentElement();\r
-               if (root.hasAttributes()) {\r
-                       logger.warn("Unknown attributes for RTL detail root node");\r
-               }\r
-               NodeList nodes = root.getChildNodes();\r
-               DescStatus descStatus = new DescStatus();\r
-               for (int i = 0; i < nodes.getLength(); i++) {\r
-                       Node n = nodes.item(i);\r
-                       if (!n.getNodeName().equals("uitzending_data_item")) {\r
-                               logger.warn("Ignoring RTL detail, tag " + n.getNodeName()\r
-                                               + ", full xml:");\r
-                               logger.debug(xmlToString(xml));\r
-                               continue;\r
-                       }\r
-                       // we have a uitzending_data_item node\r
-                       NodeList subnodes = n.getChildNodes();\r
-                       String[] result = new String[xmlKeys.length];\r
-                       for (int j = 0; j < subnodes.getLength(); j++) {\r
-                               try {\r
-                                       if (debug) {\r
-                                               Node sub = subnodes.item(j);\r
-                                               String key = ((Element) sub).getTagName();\r
-                                               int index = xmlKeyMap.get(key);\r
-                                               String value = "\""\r
-                                                               + sub.getTextContent().replaceAll("\\s", " ")\r
-                                                               + "\"";\r
-                                               result[index] = value;\r
-                                       }\r
-                                       handleNode(prog, dateStatus, descStatus, subnodes.item(j));\r
-                               } catch (RTLException e) {\r
-                                       logger.debug(xmlToString(xml), e);\r
-                                       continue;\r
-                               }\r
-                       }\r
-                       if (debug) {\r
-                               for (int k = 0; k < result.length; k++) {\r
-                                       debugWriter.print(result[k]);\r
-                                       debugWriter.print(",");\r
-                               }\r
-                               debugWriter.println();\r
-                       }\r
-               }\r
-               StringBuilder description = new StringBuilder();\r
-               if (descStatus.alginhoud != null)\r
-                       description.append(descStatus.alginhoud);\r
-               if (descStatus.inhoud != null) {\r
-                       if (description.length() != 0) {\r
-                               description.append("<p>");\r
-                       }\r
-                       description.append(descStatus.inhoud);\r
-               }\r
-               if (description.length() == 0 && descStatus.tt_inhoud != null) {\r
-                       // only use tt_inhoud if the other two are both empty, since it is\r
-                       // almost\r
-                       // always a summary of those fields and others such as <presenter>\r
-                       description.append(descStatus.tt_inhoud);\r
-               }\r
-               prog.addDescription(description.toString());\r
-       }\r
-\r
-       private void handleNode(Programme prog, DateStatus dateStatus,\r
-                       DescStatus descStatus, Node n) throws RTLException, DOMException,\r
-                       SQLException {\r
-               if (n.getNodeType() != Node.ELEMENT_NODE) {\r
-                       throw new RTLException("Ignoring non-element node "\r
-                                       + n.getNodeName());\r
-               }\r
-               if (n.hasAttributes()) {\r
-                       throw new RTLException("Unknown attributes for RTL detail node "\r
-                                       + n.getNodeName());\r
-               }\r
-               if (n.hasChildNodes()) {\r
-                       NodeList list = n.getChildNodes();\r
-                       for (int i = 0; i < list.getLength(); i++) {\r
-                               if (list.item(i).getNodeType() == Node.ELEMENT_NODE) {\r
-                                       throw new RTLException("RTL detail node " + n.getNodeName()\r
-                                                       + " has unexpected child element "\r
-                                                       + list.item(i).getNodeName());\r
-                               }\r
-                       }\r
-               }\r
-               Element e = (Element) n;\r
-               String tag = e.getTagName();\r
-\r
-               if (e.getTextContent().isEmpty()) {\r
-                       return;\r
-               }\r
-               if (tag.equals("genre")) {\r
-                       prog.addCategory(config.translateCategory(e.getTextContent()));\r
-               } else if (tag.equals("eindtijd")) {\r
-                       prog.endTime = parseTime(e.getTextContent(), dateStatus,\r
-                                       DateStatus.END_TIME);\r
-               } else if (tag.equals("omroep")) {\r
-               } else if (tag.equals("kijkwijzer")) {\r
-                       logger.trace(prog.toString());\r
-                       logger.trace("Kijkwijzer: \"" + e.getTextContent() + "\"");\r
-                       String kijkwijzer = e.getTextContent();\r
-                       List<String> list = parseKijkwijzer(kijkwijzer);\r
-                       if (config.joinKijkwijzerRatings) {\r
-                               // mythtv doesn't understand multiple <rating> tags\r
-                               prog.addRating("kijkwijzer", StringUtils.join(list, ","));\r
-                       } else {\r
-                               for (String rating : list) {\r
-                                       prog.addRating("kijkwijzer", rating);\r
-                               }\r
-                       }\r
-                       logger.trace("Kijkwijzer: \"" + StringUtils.join(list, ",") + "\"");\r
-                       // TODO add kijkwijzer icons?\r
-               } else if (tag.equals("presentatie")) {\r
-                       // A; A en B; A, B, C en D\r
-                       String[] presentatoren = e.getTextContent().split(", | en ");\r
-                       for (String pres : presentatoren) {\r
-                               prog.addPresenter(pres);\r
-                       }\r
-               } else if (tag.equals("wwwadres")) {\r
-                       prog.addUrl(e.getTextContent());\r
-               } else if (tag.equals("alginhoud")) {\r
-                       descStatus.alginhoud = e.getTextContent();\r
-               } else if (tag.equals("inhoud")) {\r
-                       descStatus.inhoud = e.getTextContent();\r
-               } else if (tag.equals("tt_inhoud")) {\r
-                       descStatus.tt_inhoud = e.getTextContent();\r
-                       // ignore, is summary of other fields\r
-               } else if (tag.equals("zendernr")) {\r
-               } else if (tag.equals("titel")) {\r
-               } else if (tag.equals("bijvnwlanden")) {\r
-               } else if (tag.equals("afl_titel")) {\r
-                       prog.addSecondaryTitle(e.getTextContent());\r
-               } else if (tag.equals("site_path")) {\r
-               } else if (tag.equals("ondertiteling")) {\r
-                       if (e.getTextContent().equals("J")) {\r
-                               prog.addSubtitle("teletext");\r
-                       } else {\r
-                               throw new RTLException("Ignoring unknown value \""\r
-                                               + n.getTextContent() + "\" for tag ondertiteling");\r
-                       }\r
-               } else if (tag.equals("begintijd")) {\r
-               } else if (tag.equals("pgmsoort")) {\r
-               } else {\r
-                       throw new RTLException("Ignoring unknown tag " + n.getNodeName()\r
-                                       + ", content: \"" + e.getTextContent() + "\"");\r
-               }\r
-               // prog.endTime = parseTime(date, root.)\r
-       }\r
-\r
-       @Override\r
-       public List<Programme> getProgrammes(List<Channel> channels, int day)\r
-                       throws Exception {\r
-               List<Programme> result = new LinkedList<Programme>();\r
-               Map<String, Channel> channelMap = new HashMap<String, Channel>();\r
-               for (Channel c : channels) {\r
-                       if (c.enabled && c.source == getId())\r
-                               channelMap.put(c.id, c);\r
-               }\r
-               URL url = programmeUrl(day);\r
-               Document xml = fetchXML(url);\r
-               Element root = xml.getDocumentElement();\r
-               Date date = new SimpleDateFormat("yyyy-MM-dd").parse(root\r
-                               .getAttribute("date"));\r
-               DateStatus dateStatus = new DateStatus(date);\r
-               // System.out.println("date: " + date);\r
-               String json = root.getTextContent();\r
-               // System.out.println("json: " + json);\r
-               JSONObject o = JSONObject.fromObject(json);\r
-               String prevChannel = null;\r
-               for (Object k : o.keySet()) {\r
-                       String id = genericChannelId(k.toString());\r
-                       if (!channelMap.containsKey(id)) {\r
-                               // if (!config.quiet)\r
-                               // System.out.println("Skipping programmes for channel " + id);\r
-                               continue;\r
-                       }\r
-                       if (!id.equals(prevChannel)) {\r
-                               dateStatus.reset(date);\r
-                               prevChannel = id;\r
-                       }\r
-                       JSONArray j = (JSONArray) o.get(k);\r
-                       // System.out.println(k.toString()+": "+j.toString());\r
-                       // System.out.println("Channel name:" + j.get(0));\r
-                       for (int i = 1; i < j.size() && i < MAX_PROGRAMMES_PER_DAY; i++) {\r
-                               JSONArray p = (JSONArray) j.get(i);\r
-                               String starttime = p.getString(0);\r
-                               String title = p.getString(1);\r
-                               String programme_id = p.getString(2);\r
-                               String genre_id = p.getString(3); // 1 = amusement, etc\r
-                               String quark2 = p.getString(4); // 0 of 1, movie flag?\r
-                               if (debug)\r
-                                       debugWriter.print("\"" + id + "\",\"" + starttime + "\",\""\r
-                                                       + title + "\",\"" + genre_id + "\",\"" + quark2\r
-                                                       + "\",");\r
-                               Programme prog = cache.get(getId(), programme_id);\r
-                               if (prog == null) {\r
-                                       stats.cacheMisses++;\r
-                                       prog = new Programme();\r
-                                       prog.addTitle(title);\r
-                                       prog.startTime = parseTime(starttime, dateStatus,\r
-                                                       DateStatus.START_TIME);\r
-                                       prog.channel = channelMap.get(id).getXmltvChannelId();\r
-                                       if (config.fetchDetails) {\r
-                                               fetchDetail(prog, dateStatus, programme_id);\r
-                                       }\r
-                                       cache.put(getId(), programme_id, prog);\r
-                               } else {\r
-                                       stats.cacheHits++;\r
-                               }\r
-                               result.add(prog);\r
-                       }\r
-               }\r
-               return result;\r
-       }\r
-\r
-       // Assumption: programmes are more-or-less in ascending order by start time\r
-       private Date parseTime(String time, DateStatus status, int mode) {\r
-               Calendar result = Calendar.getInstance();\r
-               result.setTime(status.programDate);\r
-               String[] parts = time.split(":");\r
-               if (parts.length != 2) {\r
-                       if (!config.quiet)\r
-                               logger.debug("Wrong time format " + time);\r
-                       // ignore\r
-               }\r
-               result.set(Calendar.HOUR_OF_DAY, Integer.parseInt(parts[0]));\r
-               result.set(Calendar.MINUTE, Integer.parseInt(parts[1]));\r
-               Calendar prev = status.prevStartTime;\r
-               // Check if the start time of a new program is at most one hour before\r
-               // the start time of\r
-               // the previous one. End time of a program should be at or after the\r
-               // start time of the\r
-               // program. Else, assume it's on the next day.\r
-               if (prev != null) {\r
-                       if (mode == DateStatus.START_TIME) {\r
-                               prev.add(Calendar.HOUR_OF_DAY, -1);\r
-                       }\r
-                       if (result.before(prev)) {\r
-                               result.add(Calendar.DAY_OF_MONTH, 1);\r
-                       }\r
-               }\r
-               if (mode == DateStatus.START_TIME) {\r
-                       status.prevStartTime = result;\r
-               }\r
-               return result.getTime();\r
-       }\r
-\r
-       private static URL programmeUrl(int day) throws MalformedURLException {\r
-               return new URL(programme_url + day);\r
-       }\r
-\r
-       private static URL detailUrl(String id) throws Exception {\r
-               return new URL(detail_url + id);\r
-       }\r
-\r
-       /**\r
-        * @param args\r
-        * @throws FileNotFoundException\r
-        */\r
-       public static void main(String[] args) throws FileNotFoundException {\r
-               debug = true;\r
-               Logger.getRootLogger().setLevel(Level.TRACE);\r
-\r
-               Config config = Config.getDefaultConfig();\r
-               config.niceMilliseconds = 50;\r
-               RTL rtl = new RTL(2, config);\r
-               if (debug) {\r
-                       rtl.cache.clear();\r
-                       logger.info("Writing CSV to rtl.csv");\r
-                       rtl.debugWriter = new PrintWriter(new BufferedOutputStream(\r
-                                       new FileOutputStream("rtl.csv")));\r
-                       rtl.debugWriter\r
-                                       .print("\"zender\",\"starttime\",\"title\",\"quark1\",\"quark2\",");\r
-                       for (int k = 0; k < rtl.xmlKeys.length; k++) {\r
-                               rtl.debugWriter.print(rtl.xmlKeys[k]);\r
-                               rtl.debugWriter.print(",");\r
-                       }\r
-                       rtl.debugWriter.println();\r
-               }\r
-\r
-               try {\r
-                       List<Channel> channels = rtl.getChannels();\r
-                       logger.info("Channels: " + channels);\r
-                       XMLStreamWriter writer = XMLOutputFactory.newInstance()\r
-                                       .createXMLStreamWriter(new FileWriter("rtl.xml"));\r
-                       writer.writeStartDocument();\r
-                       writer.writeCharacters("\n");\r
-                       writer.writeDTD("<!DOCTYPE tv SYSTEM \"xmltv.dtd\">");\r
-                       writer.writeCharacters("\n");\r
-                       writer.writeStartElement("tv");\r
-                       for (Channel c : channels) {\r
-                               c.serialize(writer, true);\r
-                       }\r
-                       writer.flush();\r
-                       // List<Programme> programmes =\r
-                       // rtl.getProgrammes(channels.subList(6, 9), 0);\r
-                       for (int day = 0; day < 10; day++) {\r
-                               List<Programme> programmes = rtl.getProgrammes(channels, day);\r
-                               for (Programme p : programmes) {\r
-                                       p.serialize(writer);\r
-                               }\r
-                       }\r
-                       writer.writeEndElement();\r
-                       writer.writeEndDocument();\r
-                       writer.flush();\r
-                       if (!config.quiet) {\r
-                               EPGSource.Stats stats = rtl.getStats();\r
-                               logger.info("Number of programmes from cache: "\r
-                                               + stats.cacheHits);\r
-                               logger.info("Number of programmes fetched: "\r
-                                               + stats.cacheMisses);\r
-                               logger.info("Number of fetch errors: " + stats.fetchErrors);\r
-                       }\r
-                       if (debug) {\r
-                               rtl.debugWriter.flush();\r
-                               rtl.debugWriter.close();\r
-                       }\r
-                       rtl.close();\r
-               } catch (Exception e) {\r
-                       // TODO Auto-generated catch block\r
-                       logger.debug("Exception in RTL.main()", e);\r
-               }\r
-       }\r
-\r
-}\r
+package org.vanbest.xmltv;
+
+import java.io.BufferedOutputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.sql.SQLException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamWriter;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerConfigurationException;
+
+import net.sf.json.JSONArray;
+import net.sf.json.JSONObject;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.w3c.dom.DOMException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+public class RTL extends AbstractEPGSource implements EPGSource {
+
+    private static final String programme_base_url = "http://www.rtl.nl/system/s4m/tvguide/guide_for_one_day.xml";
+
+    private static final String icon_url = "http://www.rtl.nl/service/gids/components/vaste_componenten/";
+    private static final int MAX_PROGRAMMES_PER_DAY = 9999;
+    public static final String NAME = "rtl.nl";
+    static Logger logger = Logger.getLogger(RTL.class);
+
+    static boolean debug = false;
+    PrintWriter debugWriter;
+
+    private Map<String,JSONObject> abstracts = new HashMap<String,JSONObject>();
+    private Map<String,JSONObject> episodes = new HashMap<String,JSONObject>();
+    private Map<String,JSONObject> seasons = new HashMap<String,JSONObject>();
+
+    class RTLException extends Exception {
+        public RTLException(String s) {
+            super(s);
+        }
+    }
+
+    public RTL(int sourceId, Config config) {
+        super(sourceId, config);
+    }
+
+    public String getName() {
+        return NAME;
+    }
+
+    // http://www.rtl.nl/system/s4m/tvguide/guide_for_one_day.xml?output=json&days_ahead=7&days_back=1&station=ALL
+    // Note: station parameter is either ALL or one of RTL4,RTL5,RTL7,RTL8
+    private static URL programmeUrl(int days_back, int days_ahead) throws MalformedURLException {
+        return new URL(programme_base_url + "?output=json&days_ahead="+days_ahead+"&days_back="+days_back+"&station=ALL");
+    }
+
+    public List<Channel> getChannels() {
+        Map<String,Channel> channels = new HashMap<String,Channel>(5);
+        JSONObject o;
+        try {
+            URL url = programmeUrl(0, 0);
+            o = fetchJSON(url);
+        } catch (Exception e) {
+            logger.error("Error fetching channels for source "+getName(), e);
+            return new ArrayList<Channel>();
+        }
+        JSONArray schedules = o.getJSONArray("schedule");
+        for (int i=0; i<schedules.size(); i++) {
+            JSONObject schedule = schedules.getJSONObject(i);
+            String channel = schedule.getString("station");
+            if(!channels.containsKey(channel)) {
+                Channel c = Channel.getChannel(getId(), channel, channel);
+                // TODO: channel icon
+                channels.put(channel, c);
+            }
+        }
+        List<Channel> result = new ArrayList<Channel>(10);
+        return new ArrayList<Channel>(channels.values());
+    }
+
+    @Override
+    List<String> parseKijkwijzer(String s) {
+       List<String> result = new ArrayList<String>();
+       for (int i = 0; i < s.length(); i++) {
+        }
+        return result;
+    }
+
+    Programme createProgramme(JSONObject schedule, Map<String,Channel> channelMap) {
+        Programme prog = new Programme();
+        prog.startTime = new Date(1000L*schedule.getLong("unixtime"));
+        prog.channel = channelMap.get(schedule.getString("station")).getXmltvChannelId();
+        if(schedule.getBoolean("rerun")) prog.setPreviouslyShown();
+
+        String abstractKey = schedule.optString("abstract_key");
+        if(abstractKey!=null) {
+            JSONObject abstrac = abstracts.get(abstractKey);
+            prog.addTitle(abstrac.getString("name"));
+        }
+        String episodeKey = schedule.optString("episode_key");
+        if(episodeKey!=null) {
+            JSONObject episode = episodes.get(episodeKey);
+            String s = episode.optString("name");
+            if(s!=null && !s.isEmpty()) prog.addSecondaryTitle(s);
+            s = episode.optString("episode_number");
+            if(s!=null && !s.isEmpty()) prog.addEpisode(s, "onscreen");
+            s = episode.optString("synopsis");
+            if(s!=null && !s.isEmpty()) prog.addDescription(s);
+            String kijkwijzer = episode.optString("nicam");
+            if (kijkwijzer != null && !kijkwijzer.isEmpty()) {
+                logger.debug("kijkwijzer: "+kijkwijzer);
+                prog.addRating("kijkwijzer", kijkwijzer);
+                /*
+                List<String> list = parseKijkwijzer(kijkwijzer);
+                if (config.joinKijkwijzerRatings) {
+                    // mythtv doesn't understand multiple <rating> tags
+                    prog.addRating("kijkwijzer", StringUtils.join(list, ","));
+                } else {
+                    for (String rating : list) {
+                        prog.addRating("kijkwijzer", rating);
+                    }
+                }
+                */
+            }
+        }
+        String seasonKey = schedule.optString("season_key");
+        if(seasonKey!=null) {
+            JSONObject season = seasons.get(seasonKey);
+            // ignored
+            // season_number
+            // name
+        }
+        return prog;
+    }
+
+    void parseLibrary(JSONArray library) {
+        if(library.size()!=1) {
+            logger.warn("RTL library array size is not equals to one!");
+        }
+        JSONObject lib = library.getJSONObject(0);
+        // library: abstracts
+        JSONArray abstractArray = lib.getJSONArray("abstracts");
+        for(int i=0; i<abstractArray.size(); i++) {
+            JSONObject abstrac = abstractArray.getJSONObject(i);
+            abstracts.put(abstrac.getString("abstract_key"), abstrac);
+        }
+        // library: seasons
+        JSONArray seasonArray = lib.getJSONArray("seasons");
+        for(int i=0; i<seasonArray.size(); i++) {
+            JSONObject season = seasonArray.getJSONObject(i);
+            seasons.put(season.getString("season_key"), season);
+        }
+        // library: episodes
+        JSONArray episodeArray = lib.getJSONArray("episodes");
+        for(int i=0; i<episodeArray.size(); i++) {
+            JSONObject episode = episodeArray.getJSONObject(i);
+            episodes.put(episode.getString("episode_key"), episode);
+        }
+    }
+
+    @Override
+    public List<Programme> getProgrammes(List<Channel> channels, int day)
+                    throws Exception {
+        List<Programme> result = new LinkedList<Programme>();
+        Map<String, Channel> channelMap = new HashMap<String, Channel>();
+        for (Channel c : channels) {
+            if (c.enabled && c.source == getId())
+                channelMap.put(c.id, c);
+        }
+        URL url = programmeUrl(0, day);
+        JSONObject json = fetchJSON(url);
+
+        // First parse the library
+        JSONArray library = json.getJSONArray("library");
+        parseLibrary(library);
+
+        // Then the schedules
+        JSONArray schedules = json.getJSONArray("schedule");
+        for (int i=0; i<schedules.size(); i++) {
+            JSONObject schedule = schedules.getJSONObject(i);
+
+            // Skip programme if station not in channel list
+            String station=schedule.getString("station");
+            if (!channelMap.containsKey(station)) {
+                if (!config.quiet)
+                   logger.info("Skipping programmes for channel " + station);
+                continue;
+            }
+            result.add(createProgramme(schedule, channelMap));
+        }
+
+        return result;
+    }
+
+    /**
+     * @param args
+     * @throws FileNotFoundException
+     */
+    public static void main(String[] args) throws FileNotFoundException {
+            debug = true;
+            Logger.getRootLogger().setLevel(Level.TRACE);
+
+            Config config = Config.getDefaultConfig();
+            config.niceMilliseconds = 50;
+            RTL rtl = new RTL(2, config);
+            if (debug) {
+                    rtl.cache.clear();
+                    logger.info("Writing CSV to rtl.csv");
+                    rtl.debugWriter = new PrintWriter(new BufferedOutputStream(
+                                    new FileOutputStream("rtl.csv")));
+                    rtl.debugWriter
+                                    .print("\"zender\",\"starttime\",\"title\",\"quark1\",\"quark2\",");
+                    /*
+                    for (int k = 0; k < rtl.xmlKeys.length; k++) {
+                            rtl.debugWriter.print(rtl.xmlKeys[k]);
+                            rtl.debugWriter.print(",");
+                    }
+                    */
+                    rtl.debugWriter.println();
+            }
+
+            try {
+                    List<Channel> channels = rtl.getChannels();
+                    logger.info("Channels: " + channels);
+                    XMLStreamWriter writer = XMLOutputFactory.newInstance()
+                                    .createXMLStreamWriter(new FileWriter("rtl.xml"));
+                    writer.writeStartDocument();
+                    writer.writeCharacters("\n");
+                    writer.writeDTD("<!DOCTYPE tv SYSTEM \"xmltv.dtd\">");
+                    writer.writeCharacters("\n");
+                    writer.writeStartElement("tv");
+                    for (Channel c : channels) {
+                            c.serialize(writer, true);
+                    }
+                    writer.flush();
+                    // List<Programme> programmes =
+                    // rtl.getProgrammes(channels.subList(6, 9), 0);
+                    for (int day = 0; day < 10; day++) {
+                            List<Programme> programmes = rtl.getProgrammes(channels, day);
+                            for (Programme p : programmes) {
+                                    p.serialize(writer);
+                            }
+                    }
+                    writer.writeEndElement();
+                    writer.writeEndDocument();
+                    writer.flush();
+                    if (!config.quiet) {
+                            EPGSource.Stats stats = rtl.getStats();
+                            logger.info("Number of programmes from cache: "
+                                            + stats.cacheHits);
+                            logger.info("Number of programmes fetched: "
+                                            + stats.cacheMisses);
+                            logger.info("Number of fetch errors: " + stats.fetchErrors);
+                    }
+                    if (debug) {
+                            rtl.debugWriter.flush();
+                            rtl.debugWriter.close();
+                    }
+                    rtl.close();
+            } catch (Exception e) {
+                    // TODO Auto-generated catch block
+                    logger.debug("Exception in RTL.main()", e);
+            }
+    }
+
+}