|
1 /* |
|
2 * PremiereEpg plugin to VDR (C++) |
|
3 * |
|
4 * (C) 2005 Stefan Huelswitt <s.huelswitt@gmx.de> |
|
5 * |
|
6 * This code is base on the commandline tool premiereepg2vdr |
|
7 * (C) 2004-2005 by Axel Katzur software@katzur.de |
|
8 * but has been rewritten from scratch |
|
9 * |
|
10 * This code is free software; you can redistribute it and/or |
|
11 * modify it under the terms of the GNU General Public License |
|
12 * as published by the Free Software Foundation; either version 2 |
|
13 * of the License, or (at your option) any later version. |
|
14 * |
|
15 * This code is distributed in the hope that it will be useful, |
|
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
18 * GNU General Public License for more details. |
|
19 * |
|
20 * You should have received a copy of the GNU General Public License |
|
21 * along with this program; if not, write to the Free Software |
|
22 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
|
23 * Or, point your browser to http://www.gnu.org/copyleft/gpl.html |
|
24 */ |
|
25 |
|
26 #include <vdr/plugin.h> |
|
27 #include <vdr/filter.h> |
|
28 #include <vdr/epg.h> |
|
29 #include <vdr/channels.h> |
|
30 #include <vdr/dvbdevice.h> |
|
31 #include <libsi/section.h> |
|
32 #include <libsi/descriptor.h> |
|
33 |
|
34 //#define DEBUG |
|
35 //#define DEBUG2 |
|
36 |
|
37 #ifdef DEBUG |
|
38 #define d(x) { (x); } |
|
39 #else |
|
40 #define d(x) ; |
|
41 #endif |
|
42 #ifdef DEBUG2 |
|
43 #define d2(x) { (x); } |
|
44 #else |
|
45 #define d2(x) ; |
|
46 #endif |
|
47 |
|
48 #define PMT_SCAN_TIMEOUT 10 // seconds |
|
49 #define PMT_SCAN_IDLE 300 // seconds |
|
50 |
|
51 static const char *VERSION = "0.0.1"; |
|
52 static const char *DESCRIPTION = "Parse extended Premiere EPG data"; |
|
53 |
|
54 // --- CIT --------------------------------------------------------------------- |
|
55 |
|
56 namespace SI { |
|
57 |
|
58 #define CIT_LEN 17 |
|
59 |
|
60 struct cit { |
|
61 u_char table_id :8; |
|
62 #if BYTE_ORDER == BIG_ENDIAN |
|
63 u_char section_syntax_indicator :1; |
|
64 u_char :3; |
|
65 u_char section_length_hi :4; |
|
66 #else |
|
67 u_char section_length_hi :4; |
|
68 u_char :3; |
|
69 u_char section_syntax_indicator :1; |
|
70 #endif |
|
71 u_char section_length_lo :8; |
|
72 u_char service_id_hi :8; |
|
73 u_char service_id_lo :8; |
|
74 #if BYTE_ORDER == BIG_ENDIAN |
|
75 u_char :2; |
|
76 u_char version_number :5; |
|
77 u_char current_next_indicator :1; |
|
78 #else |
|
79 u_char current_next_indicator :1; |
|
80 u_char version_number :5; |
|
81 u_char :2; |
|
82 #endif |
|
83 u_char section_number :8; |
|
84 u_char last_section_number :8; |
|
85 u_char content_id_hi_hi :8; |
|
86 u_char content_id_hi_lo :8; |
|
87 u_char content_id_lo_hi :8; |
|
88 u_char content_id_lo_lo :8; |
|
89 u_char duration_h :8; |
|
90 u_char duration_m :8; |
|
91 u_char duration_s :8; |
|
92 #if BYTE_ORDER == BIG_ENDIAN |
|
93 u_char reserved :4; |
|
94 u_char descriptors_loop_length_hi :4; |
|
95 #else |
|
96 u_char descriptors_loop_length_hi :4; |
|
97 u_char reserved :4; |
|
98 #endif |
|
99 u_char descriptors_loop_length_lo :8; |
|
100 }; |
|
101 |
|
102 class CIT : public NumberedSection { |
|
103 public: |
|
104 CIT(const unsigned char *data, bool doCopy=true) : NumberedSection(data, doCopy) {} |
|
105 CIT() {} |
|
106 int getContentId(void) const; |
|
107 time_t getDuration(void) const; |
|
108 DescriptorLoop eventDescriptors; |
|
109 protected: |
|
110 virtual void Parse(void); |
|
111 private: |
|
112 const cit *s; |
|
113 }; |
|
114 |
|
115 int CIT::getContentId(void) const { |
|
116 return (HILO(s->content_id_hi)<<16) | (HILO(s->content_id_lo)); |
|
117 } |
|
118 |
|
119 time_t CIT::getDuration(void) const { |
|
120 return DVBTime::getDuration(s->duration_h,s->duration_m,s->duration_s); |
|
121 } |
|
122 |
|
123 void CIT::Parse(void) { |
|
124 unsigned int offset=0; |
|
125 data.setPointerAndOffset<const cit>(s, offset); |
|
126 eventDescriptors.setData(data+offset,HILO(s->descriptors_loop_length)); |
|
127 } |
|
128 |
|
129 } // end of namespace |
|
130 |
|
131 // --- cDescrF2 ---------------------------------------------------------------- |
|
132 |
|
133 class cDescrF2 { |
|
134 private: |
|
135 SI::Descriptor *d; |
|
136 SI::CharArray data; |
|
137 int idx, loop, nloop, index; |
|
138 public: |
|
139 cDescrF2(SI::Descriptor *D); |
|
140 int TransportStreamId(void) { return data.TwoBytes(2); } |
|
141 int OrgNetworkId(void) { return data.TwoBytes(4); } |
|
142 int ServiceId(void) { return data.TwoBytes(6); } |
|
143 void Start(void); |
|
144 bool Next(void); |
|
145 time_t StartTime(void); |
|
146 int Index(void) { return index; } |
|
147 }; |
|
148 |
|
149 cDescrF2::cDescrF2(SI::Descriptor *D) |
|
150 { |
|
151 d=D; |
|
152 data=d->getData(); |
|
153 Start(); |
|
154 } |
|
155 |
|
156 void cDescrF2::Start(void) |
|
157 { |
|
158 idx=8; loop=0; nloop=-3; index=-1; |
|
159 } |
|
160 |
|
161 bool cDescrF2::Next(void) |
|
162 { |
|
163 loop+=3; |
|
164 if(loop>=nloop) { |
|
165 idx+=nloop+3; |
|
166 if(idx>=d->getLength()) return false; |
|
167 loop=0; nloop=data[idx+2]; |
|
168 } |
|
169 index++; |
|
170 return true; |
|
171 } |
|
172 |
|
173 time_t cDescrF2::StartTime(void) |
|
174 { |
|
175 int off=idx+3+loop; |
|
176 return SI::DVBTime::getTime(data[idx+0],data[idx+1],data[off+0],data[off+1],data[off+2]); |
|
177 } |
|
178 |
|
179 // --- cFilterPremiereEpg ------------------------------------------------------ |
|
180 |
|
181 class cFilterPremiereEpg : public cFilter { |
|
182 private: |
|
183 int pmtpid, pmtidx, pmtnext; |
|
184 // |
|
185 void NextPmt(void); |
|
186 protected: |
|
187 virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length); |
|
188 public: |
|
189 cFilterPremiereEpg(void); |
|
190 virtual void SetStatus(bool On); |
|
191 void Trigger(void); |
|
192 }; |
|
193 |
|
194 cFilterPremiereEpg::cFilterPremiereEpg(void) |
|
195 { |
|
196 Trigger(); |
|
197 Set(0x00,0x00); |
|
198 } |
|
199 |
|
200 void cFilterPremiereEpg::Trigger(void) |
|
201 { |
|
202 d(printf("trigger\n")) |
|
203 pmtpid=0; pmtidx=0; pmtnext=0; |
|
204 } |
|
205 |
|
206 void cFilterPremiereEpg::SetStatus(bool On) |
|
207 { |
|
208 d(printf("setstatus %d\n",On)) |
|
209 cFilter::SetStatus(On); |
|
210 Trigger(); |
|
211 } |
|
212 |
|
213 void cFilterPremiereEpg::NextPmt(void) |
|
214 { |
|
215 Del(pmtpid,0x02); |
|
216 pmtpid=0; |
|
217 pmtidx++; |
|
218 d(printf("PMT next\n")) |
|
219 } |
|
220 |
|
221 void cFilterPremiereEpg::Process(u_short Pid, u_char Tid, const u_char *Data, int Length) |
|
222 { |
|
223 if(Pid==0 && Tid==SI::TableIdPAT) { |
|
224 int now=time(0); |
|
225 if(!pmtnext || now>pmtnext) { |
|
226 if(pmtpid) NextPmt(); |
|
227 if(!pmtpid) { |
|
228 SI::PAT pat(Data,false); |
|
229 if(pat.CheckCRCAndParse()) { |
|
230 SI::PAT::Association assoc; |
|
231 int idx=0; |
|
232 for(SI::Loop::Iterator it; pat.associationLoop.getNext(assoc,it);) { |
|
233 if(!assoc.isNITPid()) { |
|
234 if(idx++==pmtidx) { |
|
235 pmtpid=assoc.getPid(); |
|
236 Add(pmtpid,0x02); |
|
237 pmtnext=now+PMT_SCAN_TIMEOUT; |
|
238 d(printf("PMT pid now 0x%04x (idx=%d)\n",pmtpid,pmtidx)) |
|
239 break; |
|
240 } |
|
241 } |
|
242 } |
|
243 if(!pmtpid) { |
|
244 pmtidx=0; |
|
245 pmtnext=now+PMT_SCAN_IDLE; |
|
246 d(printf("PMT scan idle\n")) |
|
247 } |
|
248 } |
|
249 } |
|
250 } |
|
251 } |
|
252 else if(pmtpid>0 && Pid==pmtpid && Tid==SI::TableIdPMT && Source() && Transponder()) { |
|
253 SI::PMT pmt(Data,false); |
|
254 if(pmt.CheckCRCAndParse()) { |
|
255 SI::PMT::Stream stream; |
|
256 for(SI::Loop::Iterator it; pmt.streamLoop.getNext(stream,it); ) { |
|
257 if(stream.getStreamType()==0x05) { |
|
258 SI::CharArray data=stream.getData(); |
|
259 if((data[1]&0xE0)==0xE0 && (data[3]&0xF0)==0xF0) { |
|
260 bool prvData=false, usrData=false; |
|
261 SI::Descriptor *d; |
|
262 for(SI::Loop::Iterator it; (d=stream.streamDescriptors.getNext(it)); ) { |
|
263 switch(d->getDescriptorTag()) { |
|
264 case SI::PrivateDataSpecifierDescriptorTag: |
|
265 d(printf("prv: %d %08x\n",d->getLength(),d->getData().FourBytes(2))) |
|
266 if(d->getLength()==6 && d->getData().FourBytes(2)==0x000000be) |
|
267 prvData=true; |
|
268 break; |
|
269 case 0x90: |
|
270 d(printf("usr: %d %08x\n",d->getLength(),d->getData().FourBytes(2))) |
|
271 if(d->getLength()==6 && d->getData().FourBytes(2)==0x0000ffff) |
|
272 usrData=true; |
|
273 break; |
|
274 default: |
|
275 break; |
|
276 } |
|
277 delete d; |
|
278 } |
|
279 if(prvData && usrData) { |
|
280 int pid=stream.getPid(); |
|
281 d(printf("found citpid 0x%04x",pid)) |
|
282 if(!Matches(pid,0xA0)) { |
|
283 Add(pid,0xA0); |
|
284 d(printf(" (added)")) |
|
285 } |
|
286 d(printf("\n")) |
|
287 } |
|
288 } |
|
289 } |
|
290 } |
|
291 NextPmt(); pmtnext=0; |
|
292 } |
|
293 } |
|
294 else if(Tid==0xA0 && Source()) { |
|
295 SI::CIT cit(Data,false); |
|
296 if(cit.CheckCRCAndParse()) { |
|
297 cSchedulesLock SchedulesLock(true,10); |
|
298 cSchedules *Schedules=(cSchedules *)cSchedules::Schedules(SchedulesLock); |
|
299 if(Schedules) { |
|
300 int nCount=0; |
|
301 time_t firstTime=0; |
|
302 SI::Descriptor *d; |
|
303 int LanguagePreferenceShort=-1; |
|
304 int LanguagePreferenceExt=-1; |
|
305 bool UseExtendedEventDescriptor=false; |
|
306 SI::ExtendedEventDescriptors *ExtendedEventDescriptors=0; |
|
307 SI::ShortEventDescriptor *ShortEventDescriptor=0; |
|
308 for(SI::Loop::Iterator it; (d=cit.eventDescriptors.getNext(it)); ) { |
|
309 switch(d->getDescriptorTag()) { |
|
310 case 0xF2: |
|
311 if(nCount>=0) { |
|
312 nCount++; |
|
313 cDescrF2 f2(d); |
|
314 if(f2.Next()) { |
|
315 if(nCount==1) firstTime=f2.StartTime(); |
|
316 else { |
|
317 time_t time=f2.StartTime(); |
|
318 if(firstTime<time-5*50 || firstTime>time+5*60) |
|
319 nCount=-1; |
|
320 } |
|
321 } |
|
322 } |
|
323 break; |
|
324 case SI::ExtendedEventDescriptorTag: |
|
325 { |
|
326 SI::ExtendedEventDescriptor *eed=(SI::ExtendedEventDescriptor *)d; |
|
327 if(I18nIsPreferredLanguage(Setup.EPGLanguages,I18nLanguageIndex(eed->languageCode), LanguagePreferenceExt) || !ExtendedEventDescriptors) { |
|
328 delete ExtendedEventDescriptors; |
|
329 ExtendedEventDescriptors=new SI::ExtendedEventDescriptors; |
|
330 UseExtendedEventDescriptor=true; |
|
331 } |
|
332 if(UseExtendedEventDescriptor) { |
|
333 ExtendedEventDescriptors->Add(eed); |
|
334 d=NULL; // so that it is not deleted |
|
335 } |
|
336 if(eed->getDescriptorNumber()==eed->getLastDescriptorNumber()) |
|
337 UseExtendedEventDescriptor=false; |
|
338 } |
|
339 break; |
|
340 case SI::ShortEventDescriptorTag: |
|
341 { |
|
342 SI::ShortEventDescriptor *sed=(SI::ShortEventDescriptor *)d; |
|
343 if(I18nIsPreferredLanguage(Setup.EPGLanguages,I18nLanguageIndex(sed->languageCode), LanguagePreferenceShort) || !ShortEventDescriptor) { |
|
344 delete ShortEventDescriptor; |
|
345 ShortEventDescriptor=sed; |
|
346 d=NULL; // so that it is not deleted |
|
347 } |
|
348 } |
|
349 break; |
|
350 default: |
|
351 break; |
|
352 } |
|
353 delete d; |
|
354 } |
|
355 |
|
356 bool Modified=false; |
|
357 int optCount=0; |
|
358 for(SI::Loop::Iterator it; (d=cit.eventDescriptors.getNext(it)); ) { |
|
359 if(d->getDescriptorTag()==0xF2) { |
|
360 optCount++; |
|
361 |
|
362 cDescrF2 f2(d); |
|
363 tChannelID channelID(Source(),f2.OrgNetworkId(),f2.TransportStreamId(),f2.ServiceId()); |
|
364 cChannel *channel=Channels.GetByChannelID(channelID,true); |
|
365 if(!channel) continue; |
|
366 |
|
367 cSchedule *pSchedule=(cSchedule *)Schedules->GetSchedule(channelID); |
|
368 if(!pSchedule) { |
|
369 pSchedule=new cSchedule(channelID); |
|
370 Schedules->Add(pSchedule); |
|
371 } |
|
372 |
|
373 for(f2.Start(); f2.Next();) { |
|
374 u_int16_t EventId=(cit.getContentId()<<4) | f2.Index(); |
|
375 time_t StartTime=f2.StartTime(); |
|
376 |
|
377 bool isOpt=false; |
|
378 if(f2.Index()==0 && nCount>1) isOpt=true; |
|
379 |
|
380 d2(printf("ch=%s id=%04x/%08x %d opt=%d/%d st=%s ",*channelID.ToString(),EventId,cit.getContentId(),f2.Index(),isOpt,optCount,stripspace(ctime(&StartTime)))) |
|
381 if(StartTime+cit.getDuration()+Setup.EPGLinger*60<time(0)) { |
|
382 d2(printf("(old)\n")) |
|
383 continue; |
|
384 } |
|
385 |
|
386 cEvent *pEvent=(cEvent *)pSchedule->GetEvent(EventId,StartTime); |
|
387 if(!pEvent) { |
|
388 d2(printf("(new)\n")) |
|
389 pEvent=pSchedule->AddEvent(new cEvent(channelID,EventId)); |
|
390 if(!pEvent) continue; |
|
391 } |
|
392 else { |
|
393 d2(printf("(upd)\n")) |
|
394 pEvent->SetSeen(); |
|
395 if(pEvent->TableID()==0x00) continue; |
|
396 if(Tid==pEvent->TableID() && pEvent->Version()==cit.getVersionNumber()) continue; |
|
397 } |
|
398 pEvent->SetEventID(EventId); |
|
399 pEvent->SetTableID(Tid); |
|
400 pEvent->SetVersion(cit.getVersionNumber()); |
|
401 pEvent->SetStartTime(StartTime); |
|
402 pEvent->SetDuration(cit.getDuration()); |
|
403 |
|
404 if(ShortEventDescriptor) { |
|
405 char buffer[256+32]; |
|
406 ShortEventDescriptor->name.getText(buffer,sizeof(buffer)-32); |
|
407 if(isOpt) { |
|
408 char o[32]; |
|
409 snprintf(o,sizeof(o)," (Option %d)",optCount); |
|
410 strcat(buffer,o); |
|
411 } |
|
412 pEvent->SetTitle(buffer); |
|
413 pEvent->SetShortText(ShortEventDescriptor->text.getText(buffer,sizeof(buffer))); |
|
414 } |
|
415 if(ExtendedEventDescriptors) { |
|
416 char buffer[ExtendedEventDescriptors->getMaximumTextLength(": ")+1]; |
|
417 pEvent->SetDescription(ExtendedEventDescriptors->getText(buffer,sizeof(buffer),": ")); |
|
418 } |
|
419 |
|
420 pEvent->SetComponents(NULL); |
|
421 pEvent->FixEpgBugs(); |
|
422 Modified=true; |
|
423 } |
|
424 if(Modified) { |
|
425 pSchedule->Sort(); |
|
426 Schedules->SetModified(pSchedule); |
|
427 } |
|
428 } |
|
429 delete d; |
|
430 } |
|
431 delete ExtendedEventDescriptors; |
|
432 delete ShortEventDescriptor; |
|
433 } |
|
434 } |
|
435 } |
|
436 } |
|
437 |
|
438 // --- cPluginPremiereEpg ------------------------------------------------------ |
|
439 |
|
440 class cPluginPremiereEpg : public cPlugin { |
|
441 private: |
|
442 struct { |
|
443 cFilterPremiereEpg *filter; |
|
444 cDevice *device; |
|
445 } epg[MAXDVBDEVICES]; |
|
446 public: |
|
447 cPluginPremiereEpg(void); |
|
448 virtual const char *Version(void) { return VERSION; } |
|
449 virtual const char *Description(void) { return DESCRIPTION; } |
|
450 virtual bool Start(void); |
|
451 virtual void Stop(void); |
|
452 }; |
|
453 |
|
454 cPluginPremiereEpg::cPluginPremiereEpg(void) |
|
455 { |
|
456 memset(epg,0,sizeof(epg)); |
|
457 } |
|
458 |
|
459 bool cPluginPremiereEpg::Start(void) |
|
460 { |
|
461 for(int i=0; i<MAXDVBDEVICES; i++) { |
|
462 cDevice *dev=cDevice::GetDevice(i); |
|
463 if(dev) { |
|
464 epg[i].device=dev; |
|
465 dev->AttachFilter(epg[i].filter=new cFilterPremiereEpg); |
|
466 isyslog("Attached premiere EPG filter to device %d",i); |
|
467 } |
|
468 } |
|
469 return true; |
|
470 } |
|
471 |
|
472 void cPluginPremiereEpg::Stop(void) |
|
473 { |
|
474 for(int i=0; i<MAXDVBDEVICES; i++) { |
|
475 cDevice *dev=epg[i].device; |
|
476 if(dev) dev->Detach(epg[i].filter); |
|
477 delete epg[i].filter; |
|
478 epg[i].device=0; |
|
479 epg[i].filter=0; |
|
480 } |
|
481 } |
|
482 |
|
483 VDRPLUGINCREATOR(cPluginPremiereEpg); // Don't touch this! |