2 * PremiereEpg plugin to VDR (C++)
4 * (C) 2005 Stefan Huelswitt <s.huelswitt@gmx.de>
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
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.
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.
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
26 #include <vdr/plugin.h>
27 #include <vdr/filter.h>
29 #include <vdr/channels.h>
30 #include <vdr/dvbdevice.h>
31 #include <libsi/section.h>
32 #include <libsi/descriptor.h>
43 #define d2(x) { (x); }
48 #define PMT_SCAN_TIMEOUT 10 // seconds
49 #define PMT_SCAN_IDLE 300 // seconds
51 static const char *VERSION = "0.0.1";
52 static const char *DESCRIPTION = "Parse extended Premiere EPG data";
54 // --- CIT ---------------------------------------------------------------------
62 #if BYTE_ORDER == BIG_ENDIAN
63 u_char section_syntax_indicator :1;
65 u_char section_length_hi :4;
67 u_char section_length_hi :4;
69 u_char section_syntax_indicator :1;
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
76 u_char version_number :5;
77 u_char current_next_indicator :1;
79 u_char current_next_indicator :1;
80 u_char version_number :5;
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;
92 #if BYTE_ORDER == BIG_ENDIAN
94 u_char descriptors_loop_length_hi :4;
96 u_char descriptors_loop_length_hi :4;
99 u_char descriptors_loop_length_lo :8;
102 class CIT : public NumberedSection {
104 CIT(const unsigned char *data, bool doCopy=true) : NumberedSection(data, doCopy) {}
106 int getContentId(void) const;
107 time_t getDuration(void) const;
108 DescriptorLoop eventDescriptors;
110 virtual void Parse(void);
115 int CIT::getContentId(void) const {
116 return (HILO(s->content_id_hi)<<16) | (HILO(s->content_id_lo));
119 time_t CIT::getDuration(void) const {
120 return DVBTime::getDuration(s->duration_h,s->duration_m,s->duration_s);
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));
129 } // end of namespace
131 // --- cDescrF2 ----------------------------------------------------------------
137 int idx, loop, nloop, index;
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); }
145 time_t StartTime(void);
146 int Index(void) { return index; }
149 cDescrF2::cDescrF2(SI::Descriptor *D)
156 void cDescrF2::Start(void)
158 idx=8; loop=0; nloop=-3; index=-1;
161 bool cDescrF2::Next(void)
166 if(idx>=d->getLength()) return false;
167 loop=0; nloop=data[idx+2];
173 time_t cDescrF2::StartTime(void)
176 return SI::DVBTime::getTime(data[idx+0],data[idx+1],data[off+0],data[off+1],data[off+2]);
179 // --- cFilterPremiereEpg ------------------------------------------------------
181 class cFilterPremiereEpg : public cFilter {
183 int pmtpid, pmtidx, pmtnext;
187 virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length);
189 cFilterPremiereEpg(void);
190 virtual void SetStatus(bool On);
194 cFilterPremiereEpg::cFilterPremiereEpg(void)
200 void cFilterPremiereEpg::Trigger(void)
202 d(printf("trigger\n"))
203 pmtpid=0; pmtidx=0; pmtnext=0;
206 void cFilterPremiereEpg::SetStatus(bool On)
208 d(printf("setstatus %d\n",On))
209 cFilter::SetStatus(On);
213 void cFilterPremiereEpg::NextPmt(void)
218 d(printf("PMT next\n"))
221 void cFilterPremiereEpg::Process(u_short Pid, u_char Tid, const u_char *Data, int Length)
223 if(Pid==0 && Tid==SI::TableIdPAT) {
225 if(!pmtnext || now>pmtnext) {
226 if(pmtpid) NextPmt();
228 SI::PAT pat(Data,false);
229 if(pat.CheckCRCAndParse()) {
230 SI::PAT::Association assoc;
232 for(SI::Loop::Iterator it; pat.associationLoop.getNext(assoc,it);) {
233 if(!assoc.isNITPid()) {
235 pmtpid=assoc.getPid();
237 pmtnext=now+PMT_SCAN_TIMEOUT;
238 d(printf("PMT pid now 0x%04x (idx=%d)\n",pmtpid,pmtidx))
245 pmtnext=now+PMT_SCAN_IDLE;
246 d(printf("PMT scan idle\n"))
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;
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)
270 d(printf("usr: %d %08x\n",d->getLength(),d->getData().FourBytes(2)))
271 if(d->getLength()==6 && d->getData().FourBytes(2)==0x0000ffff)
279 if(prvData && usrData) {
280 int pid=stream.getPid();
281 d(printf("found citpid 0x%04x",pid))
282 if(!Matches(pid,0xA0)) {
284 d(printf(" (added)"))
291 NextPmt(); pmtnext=0;
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);
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()) {
315 if(nCount==1) firstTime=f2.StartTime();
317 time_t time=f2.StartTime();
318 if(firstTime<time-5*50 || firstTime>time+5*60)
324 case SI::ExtendedEventDescriptorTag:
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;
332 if(UseExtendedEventDescriptor) {
333 ExtendedEventDescriptors->Add(eed);
334 d=NULL; // so that it is not deleted
336 if(eed->getDescriptorNumber()==eed->getLastDescriptorNumber())
337 UseExtendedEventDescriptor=false;
340 case SI::ShortEventDescriptorTag:
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
358 for(SI::Loop::Iterator it; (d=cit.eventDescriptors.getNext(it)); ) {
359 if(d->getDescriptorTag()==0xF2) {
363 tChannelID channelID(Source(),f2.OrgNetworkId(),f2.TransportStreamId(),f2.ServiceId());
364 cChannel *channel=Channels.GetByChannelID(channelID,true);
365 if(!channel) continue;
367 cSchedule *pSchedule=(cSchedule *)Schedules->GetSchedule(channelID);
369 pSchedule=new cSchedule(channelID);
370 Schedules->Add(pSchedule);
373 for(f2.Start(); f2.Next();) {
374 u_int16_t EventId=(cit.getContentId()<<4) | f2.Index();
375 time_t StartTime=f2.StartTime();
378 if(f2.Index()==0 && nCount>1) isOpt=true;
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"))
386 cEvent *pEvent=(cEvent *)pSchedule->GetEvent(EventId,StartTime);
388 d2(printf("(new)\n"))
389 pEvent=pSchedule->AddEvent(new cEvent(channelID,EventId));
390 if(!pEvent) continue;
393 d2(printf("(upd)\n"))
395 if(pEvent->TableID()==0x00) continue;
396 if(Tid==pEvent->TableID() && pEvent->Version()==cit.getVersionNumber()) continue;
398 pEvent->SetEventID(EventId);
399 pEvent->SetTableID(Tid);
400 pEvent->SetVersion(cit.getVersionNumber());
401 pEvent->SetStartTime(StartTime);
402 pEvent->SetDuration(cit.getDuration());
404 if(ShortEventDescriptor) {
406 ShortEventDescriptor->name.getText(buffer,sizeof(buffer)-32);
409 snprintf(o,sizeof(o)," (Option %d)",optCount);
412 pEvent->SetTitle(buffer);
413 pEvent->SetShortText(ShortEventDescriptor->text.getText(buffer,sizeof(buffer)));
415 if(ExtendedEventDescriptors) {
416 char buffer[ExtendedEventDescriptors->getMaximumTextLength(": ")+1];
417 pEvent->SetDescription(ExtendedEventDescriptors->getText(buffer,sizeof(buffer),": "));
420 pEvent->SetComponents(NULL);
421 pEvent->FixEpgBugs();
426 Schedules->SetModified(pSchedule);
431 delete ExtendedEventDescriptors;
432 delete ShortEventDescriptor;
438 // --- cPluginPremiereEpg ------------------------------------------------------
440 class cPluginPremiereEpg : public cPlugin {
443 cFilterPremiereEpg *filter;
445 } epg[MAXDVBDEVICES];
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);
454 cPluginPremiereEpg::cPluginPremiereEpg(void)
456 memset(epg,0,sizeof(epg));
459 bool cPluginPremiereEpg::Start(void)
461 for(int i=0; i<MAXDVBDEVICES; i++) {
462 cDevice *dev=cDevice::GetDevice(i);
465 dev->AttachFilter(epg[i].filter=new cFilterPremiereEpg);
466 isyslog("Attached premiere EPG filter to device %d",i);
472 void cPluginPremiereEpg::Stop(void)
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;
483 VDRPLUGINCREATOR(cPluginPremiereEpg); // Don't touch this!