Project

General

Profile

RE: HTS-Protokoll » htsp-xmltv.py

htsp-xmltv - Ulrich Buck, 2015-03-31 13:32

 
1
#!/usr/bin/env python
2
# coding=utf-8
3
#
4
# Copyright (C) 2012 Adam Sutton <[email protected]>
5
# Modified Feb 2015 by ulibuck to generate xmltv data
6
#
7
# This program is free software: you can redistribute it and/or modify
8
# it under the terms of the GNU General Public License as published by
9
# the Free Software Foundation, version 3 of the License.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
#
19
"""
20
Connect to a HTSP server and generate xmltv data -- channels and epg entries -- from asynchronous initialSync
21
"""
22

    
23
# System imports
24
import os, sys, pprint, time
25
from optparse import OptionParser
26
from operator import itemgetter, attrgetter
27

    
28
# TVH imports
29
from tvh.htsp import HTSPClient
30
import tvh.log as log
31
  
32
# encode xml escape characters
33
def encodeXMLText(text):
34
  text = text.replace("&", "&amp;")
35
  text = text.replace("\"", "&quot;")
36
  text = text.replace("'", "&apos;")
37
  text = text.replace("<", "&lt;")
38
  text = text.replace(">", "&gt;")
39
  return text
40

    
41
# define EPG genre names
42
def genre_names ( mcat, scat ):
43
  epg_genre_names = [
44
  [ "Undefined content" ],
45
  
46
  [ "Movie / Drama",
47
    "Detective / Thriller",
48
    "Adventure / Western / War",
49
    "Science fiction / Fantasy / Horror",
50
    "Comedy",
51
    "Soap / Melodrama / Folkloric",
52
    "Romance",
53
    "Serious / Classical / Religious / Historical movie / Drama",
54
    "Adult movie / Drama" ],
55

    
56
  [ "News / Current affairs",
57
    "News / Weather report",
58
    "News magazine",
59
    "Documentary",
60
    "Discussion / Interview / Debate" ],
61

    
62
  [ "Show / Game show",
63
    "Game show / Quiz / Contest",
64
    "Variety show",
65
    "Talk show" ],
66

    
67
  [ "Sports",
68
    "Special events (Olympic Games, World Cup, etc.)",
69
    "Sports magazines",
70
    "Football / Soccer",
71
    "Tennis / Squash",
72
    "Team sports (excluding football)",
73
    "Athletics",
74
    "Motor sport",
75
    "Water sport",
76
    "Winter sports",
77
    "Equestrian",
78
    "Martial sports" ],
79

    
80
  [ "Children's / Youth programmes",
81
    "Pre-school children's programmes",
82
    "Entertainment programmes for 6 to 14",
83
    "Entertainment programmes for 10 to 16",
84
    "Informational / Educational / School programmes",
85
    "Cartoons / Puppets" ],
86

    
87
  [ "Music / Ballet / Dance",
88
    "Rock / Pop",
89
    "Serious music / Classical music",
90
    "Folk / Traditional music",
91
    "Jazz",
92
    "Musical / Opera",
93
    "Ballet" ],
94

    
95
  [ "Arts / Culture (without music)",
96
    "Performing arts",
97
    "Fine arts",
98
    "Religion",
99
    "Popular culture / Traditional arts",
100
    "Literature",
101
    "Film / Cinema",
102
    "Experimental film / Video",
103
    "Broadcasting / Press",
104
    "New media",
105
    "Arts / culture magazines",
106
    "Fashion" ],
107

    
108
  [ "Social / Political issues / Economics",
109
    "Magazines / Reports / Documentary",
110
    "Economics / Social advisory",
111
    "Remarkable people" ],
112

    
113
  [ "Education / Science / Factual topics",
114
    "Nature / Animals / Environment",
115
    "Technology / Natural sciences",
116
    "Medicine / Physiology / Psychology",
117
    "Foreign countries / Expeditions",
118
    "Social / Spiritual sciences",
119
    "Further education",
120
    "Languages" ],
121

    
122
  [ "Leisure hobbies",
123
    "Tourism / Travel",
124
    "Handicraft",
125
    "Motoring",
126
    "Fitness and health",
127
    "Cooking",
128
    "Advertisement / Shopping",
129
    "Gardening" ]
130
  ]
131
  if len(epg_genre_names) <= mcat : mcat = 0
132
  if len(epg_genre_names[mcat]) <= scat: scat = 0
133
  return epg_genre_names[mcat][scat]
134

    
135
# Formatiere Datum und Uhrzeit für die Ausgabe
136
def datumzeit (Zeit):
137
  Wochentage = ["Mo","Di","Mi","Do","Fr","Sa","So"]
138
  Monatsnamen = ["Jan","Feb","Mrz","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Dez"]
139
  Wochentag = Wochentage[Zeit.tm_wday]
140
  Monatstag = Zeit.tm_mday
141
  Monat = Monatsnamen [Zeit.tm_mon-1]
142
  return '%s %02d %s %s' % (Wochentag, Monatstag, Monat, time.strftime('%Y %H:%M:%S',Zeit))
143

    
144
try:
145
  
146
  # Command line
147
  optp = OptionParser()
148
  optp.add_option('-a', '--host', default='localhost',
149
                  help='Specify HTSP server hostname [localhost]')
150
  optp.add_option('-o', '--port', default=9982, type='int',
151
                  help='Specify HTSP server port [9982]')
152
  optp.add_option('-u', '--user', default=None,
153
                  help='Specify HTSP authentication username [None]')
154
  optp.add_option('-p', '--passwd', default=None,
155
                  help='Specify HTSP authentication password [None]')
156
  optp.add_option('-e', '--epg', default=True, action='store_false',
157
                  help='Exclude EPG data [Include]')
158
  optp.add_option('-t', '--update', default=None, type='int',
159
                  help='Specify when to receive updates from [None]')
160
  xnam = '%s.xml' % (os.path.splitext(__file__)[0])
161
  optp.add_option('-x', '--xnam', default=None,
162
                  help='Specify xml output file name [%s]' % (xnam))
163
  (opts, args) = optp.parse_args()
164

    
165
  # Connect
166
  htsp = HTSPClient((opts.host, opts.port))
167
  msg  = htsp.hello()
168
  sernam = msg['servername']
169
  server = msg['serverversion']
170
  log.info('%s connected to [%s] / %s [%s] / HTSP v%d' % (htsp._name, opts.host, sernam, server, htsp._version))
171

    
172
  # Authenticate
173
  if opts.user:
174
    htsp.authenticate(opts.user, opts.passwd)
175
    log.info('authenticated as %s' % opts.user)
176

    
177
  # Enable async
178
  args = {}
179
  if opts.epg:
180
    args['epg']   = 1
181
  if opts.update != None:
182
    args['lastUpdate'] = opts.update
183
  htsp.enableAsyncMetadata(args)
184
  # log.info('generating xmltv -- channels and epg entries -- from asynchronous initialSync')
185
  
186
  # Process messages
187
  chanNum={}
188
  chanAdd=[]
189
  evenAdd=[]
190
  while True:
191
    msg = htsp.recv()
192
    if 'method' in msg:
193
      if msg['method'] == 'channelAdd':
194
        chanNum[msg['channelId']]=msg['channelNumber']
195
        chanAdd.append(msg)    # store 'channelAdd' message
196
      elif msg['method'] == 'eventAdd':
197
        msg['channelNumber']=chanNum[msg['channelId']]
198
        evenAdd.append(msg)    # store 'eventAdd' message
199
      elif msg['method'] == 'initialSyncCompleted':
200
        break
201
  # log.info('initialSync completed -- %d channels and %d events' % (len(chanAdd), len(evenAdd)))
202

    
203
  # determine the local time offset to UTC
204
  etm = int(time.time())    # seconds since the epoch
205
  nlc = time.localtime(etm)    # convert to struct_time in local time
206
  ngm = time.gmtime(etm)    # convert to struct_time in UTC
207
  # convert back to seconds since the epoch
208
  # - interpret struct_time in UTC in local time using mktime()
209
  # - use daylight savings flag from local time
210
  egm = time.mktime((ngm[0],ngm[1],ngm[2],ngm[3],ngm[4],ngm[5],ngm[6],ngm[7],nlc[8]))
211
  edf = etm - egm    # local time offset to UTC in seconds = difference of the two times 
212
  sdf = 1
213
  if edf < 0: sdf = -1    # sign of the time difference
214
  hrh = int(sdf*edf/36)    # time difference in hours times hundred
215
  mts = int((hrh%100)*3/5)    # convert fraction of hour to minutes
216
  hrmt = '%+05d' % (sdf*(int(hrh/100)*100 + mts))    # UTC offset in the form +HHMM or -HHMM
217

    
218
  # generate xmltv
219
  if opts.xnam != None:
220
    xnam = opts.xnam
221
  file = open (xnam, 'w')
222
  gnam = os.path.basename(__file__)
223
  file.write('<?xml version="1.0" encoding="UTF-8"?>\n')
224
  file.write('<!DOCTYPE tv SYSTEM "xmltv.dtd">\n')
225
  out = '<tv date="%s %s" source-info-name="%s [%s]" generator-info-name="%s">\n' \
226
        % (time.strftime('%Y%m%d%H%M%S'), hrmt, sernam, server, gnam)
227
  file.write(out)
228
  chanAddSorted = sorted(chanAdd, key=itemgetter('channelNumber'))
229
  for msg in chanAddSorted:
230
    out = '  <channel id="%s">\n' % (msg['channelNumber'])
231
    file.write(out)
232
    out = '    <display-name>%s</display-name>\n' % (encodeXMLText(msg['channelName']))
233
    file.write(out)
234
    if 'channelIcon' in msg:
235
      out = '    <icon src="%s"/>\n' % (msg['channelIcon'])
236
      file.write(out)
237
    out = '  </channel>\n'
238
    file.write(out)
239
  evenAddSorted = sorted(evenAdd, key=itemgetter('channelNumber', 'start'))
240
  for msg in evenAddSorted:
241
    sta = time.localtime(msg['start'])
242
    sto = time.localtime(msg['stop'])
243
    out = '  <programme channel="%s" start="%s %s" stop="%s %s">\n' \
244
          % (msg['channelNumber'], time.strftime('%Y%m%d%H%M%S',sta), hrmt, time.strftime('%Y%m%d%H%M%S',sto), hrmt)
245
    file.write(out)
246
    if 'title' in msg:
247
      out = '    <title>%s</title>\n' % (encodeXMLText(msg['title']))
248
    else:
249
      out = '    <title>no title</title>\n'
250
    file.write(out)
251
    if 'description' in msg:
252
      out = '    <desc>%s</desc>\n' % (encodeXMLText(msg['description']))
253
      file.write(out)
254
    if 'summary' in msg:
255
      out = '    <sub-title>%s</sub-title>\n' % (encodeXMLText(msg['summary']))
256
      file.write(out)
257
    if 'contentType' in msg:
258
      bits = '{0:08b}'.format(msg['contentType'])
259
      mcat = int(bits[:4],2)    # top 4 bits has major category
260
      scat = int(bits[4:],2)    # bottom 4 bits has sub-category
261
      out = '    <category>%s</category>\n' % (encodeXMLText(genre_names(mcat,scat)))
262
      file.write(out)
263
    out = '  </programme>\n'
264
    file.write(out)
265
  out = '</tv>\n'
266
  file.write(out)
267
  file.close()
268
  now = time.localtime()
269
  sys.stdout.write('%s  %s -- %d channels and %d epg entries\n' % (datumzeit(now), xnam, len(chanAdd), len(evenAdd)))
270

    
271
except KeyboardInterrupt: pass
272
except Exception, e:
273
  log.error(e)
274
  sys.exit(1) 
275

    
276
# ############################################################################
277
# Editor Configuration
278
#
279
# vim:sts=2:ts=2:sw=2:et
280
# ############################################################################
(2-2/2)