premiereepg.c
author nathan
Sat, 29 Dec 2007 11:18:58 +0100
branchtrunk
changeset 4 ac6bf154890e
parent 2 3562cacb3b0b
child 6 b0218bef406f
permissions -rw-r--r--
release 0.0.3
     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 <vdr/i18n.h>
    32 #include <libsi/section.h>
    33 #include <libsi/descriptor.h>
    34 
    35 //#define DEBUG
    36 //#define DEBUG2
    37 
    38 #ifdef DEBUG
    39 #define d(x) { (x); }
    40 #else
    41 #define d(x) ; 
    42 #endif
    43 #ifdef DEBUG2
    44 #define d2(x) { (x); }
    45 #else
    46 #define d2(x) ; 
    47 #endif
    48 
    49 #define PMT_SCAN_TIMEOUT  10  // seconds
    50 #define PMT_SCAN_IDLE     300 // seconds
    51 
    52 static const char *VERSION        = "0.0.3";
    53 static const char *DESCRIPTION    = "Parses extended Premiere EPG data";
    54 
    55 // --- cSetupPremiereEpg -------------------------------------------------------
    56 
    57 const char *optPats[] = {
    58   "%s",
    59   "%s (Option %d)",
    60   "%s (O%d)",
    61   "#%2$d %1$s",
    62   "[%2$d] %1$s"
    63   };
    64 #define NUM_PATS (sizeof(optPats)/sizeof(char *))
    65 
    66 class cSetupPremiereEpg {
    67 public:
    68   int OptPat;
    69 public:
    70   cSetupPremiereEpg(void);
    71   };
    72 
    73 cSetupPremiereEpg SetupPE;
    74 
    75 cSetupPremiereEpg::cSetupPremiereEpg(void)
    76 {
    77   OptPat=1;
    78 }
    79 
    80 // --- i18n --------------------------------------------------------------------
    81 
    82 const tI18nPhrase Phrases[] = {
    83 /*
    84 */
    85   { "PremiereEPG",
    86     "PremiereEPG",
    87     "", // TODO
    88     "", // TODO
    89     "", // TODO
    90     "", // TODO
    91     "", // TODO
    92     "", // TODO
    93     "", // TODO
    94     "", // TODO
    95     "", // TODO
    96     "", // TODO
    97     "", // TODO
    98     "", // TODO
    99     "", // TODO
   100     "", // TODO
   101   },
   102   { "Parses extended Premiere EPG data",
   103     "Liest erweiterte Premiere EPG Daten ein",
   104     "", // TODO
   105     "", // TODO
   106     "", // TODO
   107     "", // TODO
   108     "", // TODO
   109     "", // TODO
   110     "", // TODO
   111     "", // TODO
   112     "", // TODO
   113     "", // TODO
   114     "", // TODO
   115     "", // TODO
   116     "", // TODO
   117     "", // TODO
   118   },
   119   { "Tag option events",
   120     "Options Events markieren",
   121     "", // TODO
   122     "", // TODO
   123     "", // TODO
   124     "", // TODO
   125     "", // TODO
   126     "", // TODO
   127     "", // TODO
   128     "", // TODO
   129     "", // TODO
   130     "", // TODO
   131     "", // TODO
   132     "", // TODO
   133     "", // TODO
   134     "", // TODO
   135   },
   136 
   137   { NULL }
   138   };
   139 
   140 // --- cMenuSetupPremiereEpg ------------------------------------------------------------
   141 
   142 class cMenuSetupPremiereEpg : public cMenuSetupPage {
   143 private:
   144   cSetupPremiereEpg data;
   145   const char *optDisp[NUM_PATS];
   146   char buff[NUM_PATS][32];
   147 protected:
   148   virtual void Store(void);
   149 public:
   150   cMenuSetupPremiereEpg(void);
   151   };
   152 
   153 cMenuSetupPremiereEpg::cMenuSetupPremiereEpg(void)
   154 {
   155   data=SetupPE;
   156   SetSection(tr("PremiereEPG"));
   157   optDisp[0]=tr("off");
   158   for(unsigned int i=1; i<NUM_PATS; i++) {
   159     snprintf(buff[i],sizeof(buff[i]),optPats[i],"Event",1);
   160     optDisp[i]=buff[i];
   161     }
   162   Add(new cMenuEditStraItem(tr("Tag option events"),&data.OptPat,NUM_PATS,optDisp));
   163 }
   164 
   165 void cMenuSetupPremiereEpg::Store(void)
   166 {
   167   SetupPE=data;
   168   SetupStore("OptionPattern",SetupPE.OptPat);
   169 }
   170 
   171 // --- CIT ---------------------------------------------------------------------
   172 
   173 namespace SI {
   174 
   175 #define CIT_LEN 17
   176 
   177 struct cit {
   178    u_char table_id                               :8;
   179 #if BYTE_ORDER == BIG_ENDIAN
   180    u_char section_syntax_indicator               :1;
   181    u_char                                        :3;
   182    u_char section_length_hi                      :4;
   183 #else
   184    u_char section_length_hi                      :4;
   185    u_char                                        :3;
   186    u_char section_syntax_indicator               :1;
   187 #endif
   188    u_char section_length_lo                      :8;
   189    u_char service_id_hi                          :8;
   190    u_char service_id_lo                          :8;
   191 #if BYTE_ORDER == BIG_ENDIAN
   192    u_char                                        :2;
   193    u_char version_number                         :5;
   194    u_char current_next_indicator                 :1;
   195 #else
   196    u_char current_next_indicator                 :1;
   197    u_char version_number                         :5;
   198    u_char                                        :2;
   199 #endif
   200    u_char section_number                         :8;
   201    u_char last_section_number                    :8;
   202    u_char content_id_hi_hi                       :8;
   203    u_char content_id_hi_lo                       :8;
   204    u_char content_id_lo_hi                       :8;
   205    u_char content_id_lo_lo                       :8;
   206    u_char duration_h                             :8;
   207    u_char duration_m                             :8;
   208    u_char duration_s                             :8;
   209 #if BYTE_ORDER == BIG_ENDIAN
   210    u_char                                        :4;
   211    u_char descriptors_loop_length_hi             :4;
   212 #else
   213    u_char descriptors_loop_length_hi             :4;
   214    u_char                                        :4;
   215 #endif
   216    u_char descriptors_loop_length_lo             :8;
   217 };
   218 
   219 class CIT : public NumberedSection {
   220 public:
   221    CIT(const unsigned char *data, bool doCopy=true) : NumberedSection(data, doCopy) {}
   222    CIT() {}
   223    int getContentId(void) const;
   224    time_t getDuration(void) const;
   225    DescriptorLoop eventDescriptors;
   226 protected:
   227    virtual void Parse(void);
   228 private:
   229    const cit *s;
   230 };
   231 
   232 int CIT::getContentId(void) const {
   233    return (HILO(s->content_id_hi)<<16) | (HILO(s->content_id_lo));
   234 }
   235 
   236 time_t CIT::getDuration(void) const {
   237    return DVBTime::getDuration(s->duration_h,s->duration_m,s->duration_s);
   238 }
   239 
   240 void CIT::Parse(void) {
   241    unsigned int offset=0;
   242    data.setPointerAndOffset<const cit>(s, offset);
   243    eventDescriptors.setData(data+offset,HILO(s->descriptors_loop_length));
   244 }
   245 
   246 } // end of namespace
   247 
   248 // --- cDescrF2 ----------------------------------------------------------------
   249 
   250 class cDescrF2 {
   251 private:
   252   SI::Descriptor *d;
   253   SI::CharArray data;
   254   int idx, loop, nloop, index;
   255 public:
   256   cDescrF2(SI::Descriptor *D);
   257   int TransportStreamId(void) { return data.TwoBytes(2); }
   258   int OrgNetworkId(void)      { return data.TwoBytes(4); }
   259   int ServiceId(void)         { return data.TwoBytes(6); }
   260   void Start(void);
   261   bool Next(void);
   262   time_t StartTime(void);
   263   int Index(void) { return index; }
   264   };
   265 
   266 cDescrF2::cDescrF2(SI::Descriptor *D)
   267 {
   268   d=D;
   269   data=d->getData();
   270   Start();
   271 }
   272 
   273 void cDescrF2::Start(void)
   274 {
   275   idx=8; loop=0; nloop=-3; index=-1;
   276 }
   277 
   278 bool cDescrF2::Next(void)
   279 {
   280   loop+=3;
   281   if(loop>=nloop) {
   282     idx+=nloop+3;
   283     if(idx>=d->getLength()) return false;
   284     loop=0; nloop=data[idx+2];
   285     }
   286  index++;
   287  return true;
   288 }
   289 
   290 time_t cDescrF2::StartTime(void)
   291 {
   292   int off=idx+3+loop;
   293   return SI::DVBTime::getTime(data[idx+0],data[idx+1],data[off+0],data[off+1],data[off+2]);
   294 }
   295 
   296 // --- cFilterPremiereEpg ------------------------------------------------------
   297 
   298 class cFilterPremiereEpg : public cFilter {
   299 private:
   300   int pmtpid, pmtidx, pmtnext;
   301   //
   302   void NextPmt(void);
   303 protected:
   304   virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length);
   305 public:
   306   cFilterPremiereEpg(void);
   307   virtual void SetStatus(bool On);
   308   void Trigger(void);
   309   };
   310 
   311 cFilterPremiereEpg::cFilterPremiereEpg(void)
   312 {
   313   Trigger();
   314   Set(0x00,0x00);
   315 }
   316 
   317 void cFilterPremiereEpg::Trigger(void)
   318 {
   319   d(printf("trigger\n"))
   320   pmtpid=0; pmtidx=0; pmtnext=0;
   321 }
   322 
   323 void cFilterPremiereEpg::SetStatus(bool On)
   324 {
   325   d(printf("setstatus %d\n",On))
   326   cFilter::SetStatus(On);
   327   Trigger();
   328 }
   329 
   330 void cFilterPremiereEpg::NextPmt(void)
   331 {
   332   Del(pmtpid,0x02);
   333   pmtpid=0;
   334   pmtidx++;
   335   d(printf("PMT next\n"))
   336 }
   337 
   338 void cFilterPremiereEpg::Process(u_short Pid, u_char Tid, const u_char *Data, int Length)
   339 {
   340   if(Pid==0 && Tid==SI::TableIdPAT) {
   341     int now=time(0);
   342     if(!pmtnext || now>pmtnext) {
   343       if(pmtpid) NextPmt();
   344       if(!pmtpid) {
   345         SI::PAT pat(Data,false);
   346         if(pat.CheckCRCAndParse()) {
   347           SI::PAT::Association assoc;
   348           int idx=0;
   349           for(SI::Loop::Iterator it; pat.associationLoop.getNext(assoc,it);) {
   350             if(!assoc.isNITPid()) {
   351               if(idx++==pmtidx) {
   352                 pmtpid=assoc.getPid();
   353                 Add(pmtpid,0x02);
   354                 pmtnext=now+PMT_SCAN_TIMEOUT;
   355                 d(printf("PMT pid now 0x%04x (idx=%d)\n",pmtpid,pmtidx))
   356                 break;
   357                 }
   358               }
   359             }
   360           if(!pmtpid) {
   361             pmtidx=0;
   362             pmtnext=now+PMT_SCAN_IDLE;
   363             d(printf("PMT scan idle\n"))
   364             }
   365           }
   366         }
   367       }
   368     }
   369   else if(pmtpid>0 && Pid==pmtpid && Tid==SI::TableIdPMT && Source() && Transponder()) {
   370     SI::PMT pmt(Data,false);
   371     if(pmt.CheckCRCAndParse()) {
   372       SI::PMT::Stream stream;
   373       for(SI::Loop::Iterator it; pmt.streamLoop.getNext(stream,it); ) {
   374         if(stream.getStreamType()==0x05) {
   375           SI::CharArray data=stream.getData();
   376           if((data[1]&0xE0)==0xE0 && (data[3]&0xF0)==0xF0) {
   377             bool prvData=false, usrData=false;
   378             SI::Descriptor *d;
   379             for(SI::Loop::Iterator it; (d=stream.streamDescriptors.getNext(it)); ) {
   380               switch(d->getDescriptorTag()) {
   381                 case SI::PrivateDataSpecifierDescriptorTag:
   382                   d(printf("prv: %d %08x\n",d->getLength(),d->getData().FourBytes(2)))
   383                   if(d->getLength()==6 && d->getData().FourBytes(2)==0x000000be)
   384                     prvData=true;
   385                   break;
   386                 case 0x90:
   387                   d(printf("usr: %d %08x\n",d->getLength(),d->getData().FourBytes(2)))
   388                   if(d->getLength()==6 && d->getData().FourBytes(2)==0x0000ffff)
   389                     usrData=true;
   390                   break;
   391                 default:
   392                   break;
   393                 }
   394               delete d;
   395               }
   396             if(prvData && usrData) {
   397               int pid=stream.getPid();
   398               d(printf("found citpid 0x%04x",pid))
   399               if(!Matches(pid,0xA0)) {
   400                 Add(pid,0xA0);
   401                 d(printf(" (added)"))
   402                 }
   403               d(printf("\n"))
   404               }
   405             }
   406           }
   407         }
   408       NextPmt(); pmtnext=0;
   409       }
   410     }
   411   else if(Tid==0xA0 && Source()) {
   412     SI::CIT cit(Data,false);
   413     if(cit.CheckCRCAndParse()) {
   414       cSchedulesLock SchedulesLock(true,10);
   415       cSchedules *Schedules=(cSchedules *)cSchedules::Schedules(SchedulesLock);
   416       if(Schedules) {
   417         int nCount=0;
   418         time_t firstTime=0;
   419         SI::Descriptor *d;
   420         int LanguagePreferenceShort=-1;
   421         int LanguagePreferenceExt=-1;
   422         bool UseExtendedEventDescriptor=false;
   423         SI::ExtendedEventDescriptors *ExtendedEventDescriptors=0;
   424         SI::ShortEventDescriptor *ShortEventDescriptor=0;
   425         for(SI::Loop::Iterator it; (d=cit.eventDescriptors.getNext(it)); ) {
   426           switch(d->getDescriptorTag()) {
   427             case 0xF2:
   428               if(nCount>=0) {
   429                 nCount++;
   430                 cDescrF2 f2(d);
   431                 if(f2.Next()) {
   432                   if(nCount==1) firstTime=f2.StartTime();
   433                   else {
   434                     time_t time=f2.StartTime();
   435                     if(firstTime<time-5*50 || firstTime>time+5*60)
   436                       nCount=-1;
   437                     }
   438                   }
   439                 }
   440               break;
   441             case SI::ExtendedEventDescriptorTag:
   442               {
   443               SI::ExtendedEventDescriptor *eed=(SI::ExtendedEventDescriptor *)d;
   444 #if VDRVERSNUM < 10332
   445               if(I18nIsPreferredLanguage(Setup.EPGLanguages,I18nLanguageIndex(eed->languageCode), LanguagePreferenceExt) || !ExtendedEventDescriptors) {
   446 #else
   447               if(I18nIsPreferredLanguage(Setup.EPGLanguages,eed->languageCode, LanguagePreferenceExt) || !ExtendedEventDescriptors) {
   448 #endif
   449                  delete ExtendedEventDescriptors;
   450                  ExtendedEventDescriptors=new SI::ExtendedEventDescriptors;
   451                  UseExtendedEventDescriptor=true;
   452                  }
   453               if(UseExtendedEventDescriptor) {
   454                  ExtendedEventDescriptors->Add(eed);
   455                  d=NULL; // so that it is not deleted
   456                  }
   457               if(eed->getDescriptorNumber()==eed->getLastDescriptorNumber())
   458                  UseExtendedEventDescriptor=false;
   459               }
   460               break;
   461             case SI::ShortEventDescriptorTag:
   462               {
   463               SI::ShortEventDescriptor *sed=(SI::ShortEventDescriptor *)d;
   464 #if VDRVERSNUM < 10332
   465               if(I18nIsPreferredLanguage(Setup.EPGLanguages,I18nLanguageIndex(sed->languageCode), LanguagePreferenceShort) || !ShortEventDescriptor) {
   466 #else
   467               if(I18nIsPreferredLanguage(Setup.EPGLanguages,sed->languageCode, LanguagePreferenceShort) || !ShortEventDescriptor) {
   468 #endif
   469                  delete ShortEventDescriptor;
   470                  ShortEventDescriptor=sed;
   471                  d=NULL; // so that it is not deleted
   472                  }
   473               }
   474               break;
   475             default:
   476               break;
   477             }
   478           delete d;
   479           }
   480 
   481         bool Modified=false;
   482         int optCount=0;
   483         for(SI::Loop::Iterator it; (d=cit.eventDescriptors.getNext(it)); ) {
   484           if(d->getDescriptorTag()==0xF2) {
   485             optCount++;
   486 
   487             cDescrF2 f2(d);
   488             tChannelID channelID(Source(),f2.OrgNetworkId(),f2.TransportStreamId(),f2.ServiceId());
   489             cChannel *channel=Channels.GetByChannelID(channelID,true);
   490             if(!channel) continue;
   491 
   492             cSchedule *pSchedule=(cSchedule *)Schedules->GetSchedule(channelID);
   493             if(!pSchedule) {
   494                pSchedule=new cSchedule(channelID);
   495                Schedules->Add(pSchedule);
   496                }
   497 
   498             for(f2.Start(); f2.Next();) {
   499               u_int16_t EventId=(cit.getContentId()<<4) | f2.Index();
   500               time_t StartTime=f2.StartTime();
   501 
   502               bool isOpt=false;
   503               if(f2.Index()==0 && nCount>1) isOpt=true;
   504 
   505               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))))
   506               if(StartTime+cit.getDuration()+Setup.EPGLinger*60<time(0)) {
   507                 d2(printf("(old)\n"))
   508                 continue;
   509                 }
   510 
   511               bool newEvent=false;
   512               cEvent *pEvent=(cEvent *)pSchedule->GetEvent(EventId,StartTime);
   513               if(!pEvent) {
   514                  d2(printf("(new)\n"))
   515 #if VDRVERSNUM >= 10325
   516                  pEvent=new cEvent(EventId);
   517 #else
   518                  pEvent=new cEvent(channelID,EventId);
   519 #endif
   520                  if(!pEvent) continue;
   521                  newEvent=true;
   522                  }
   523               else {
   524                  d2(printf("(upd)\n"))
   525                  pEvent->SetSeen();
   526                  if(pEvent->TableID()==0x00) continue;
   527                  if(Tid==pEvent->TableID() && pEvent->Version()==cit.getVersionNumber()) continue;
   528                  }
   529               pEvent->SetEventID(EventId);
   530               pEvent->SetTableID(Tid);
   531               pEvent->SetVersion(cit.getVersionNumber());
   532               pEvent->SetStartTime(StartTime);
   533               pEvent->SetDuration(cit.getDuration());
   534 
   535               if(ShortEventDescriptor) {
   536                 char buffer[256];
   537                 ShortEventDescriptor->name.getText(buffer,sizeof(buffer));
   538                 if(isOpt) {
   539                   char buffer2[sizeof(buffer)+32];
   540                   snprintf(buffer2,sizeof(buffer2),optPats[SetupPE.OptPat],buffer,optCount);
   541                   pEvent->SetTitle(buffer2);
   542                   }
   543                 else
   544                   pEvent->SetTitle(buffer);
   545                 pEvent->SetShortText(ShortEventDescriptor->text.getText(buffer,sizeof(buffer)));
   546                 }
   547               if(ExtendedEventDescriptors) {
   548                 char buffer[ExtendedEventDescriptors->getMaximumTextLength(": ")+1];
   549                 pEvent->SetDescription(ExtendedEventDescriptors->getText(buffer,sizeof(buffer),": "));
   550                 }
   551 
   552               if(newEvent) pSchedule->AddEvent(pEvent);
   553               pEvent->SetComponents(NULL);
   554               pEvent->FixEpgBugs();
   555               Modified=true;
   556               }
   557             if(Modified) {
   558               pSchedule->Sort();
   559               Schedules->SetModified(pSchedule);
   560               }
   561             }
   562           delete d;
   563           }
   564         delete ExtendedEventDescriptors;
   565         delete ShortEventDescriptor;
   566         }
   567       }
   568     }
   569 }
   570 
   571 // --- cPluginPremiereEpg ------------------------------------------------------
   572 
   573 class cPluginPremiereEpg : public cPlugin {
   574 private:
   575   struct {
   576     cFilterPremiereEpg *filter;
   577     cDevice *device;
   578     } epg[MAXDVBDEVICES];
   579 public:
   580   cPluginPremiereEpg(void);
   581   virtual const char *Version(void) { return VERSION; }
   582   virtual const char *Description(void) { return tr(DESCRIPTION); }
   583   virtual bool Start(void);
   584   virtual void Stop(void);
   585   virtual cMenuSetupPage *SetupMenu(void);
   586   virtual bool SetupParse(const char *Name, const char *Value);
   587   };
   588 
   589 cPluginPremiereEpg::cPluginPremiereEpg(void)
   590 {
   591   memset(epg,0,sizeof(epg));
   592 }
   593 
   594 bool cPluginPremiereEpg::Start(void)
   595 {
   596   RegisterI18n(Phrases);
   597   for(int i=0; i<MAXDVBDEVICES; i++) {
   598     cDevice *dev=cDevice::GetDevice(i);
   599     if(dev) {
   600       epg[i].device=dev;
   601       dev->AttachFilter(epg[i].filter=new cFilterPremiereEpg);
   602       isyslog("Attached premiere EPG filter to device %d",i);
   603       }
   604     }
   605   return true;
   606 }
   607 
   608 void cPluginPremiereEpg::Stop(void)
   609 {
   610   for(int i=0; i<MAXDVBDEVICES; i++) {
   611     cDevice *dev=epg[i].device;
   612     if(dev) dev->Detach(epg[i].filter);
   613     delete epg[i].filter;
   614     epg[i].device=0;
   615     epg[i].filter=0;
   616     }
   617 }
   618 
   619 cMenuSetupPage *cPluginPremiereEpg::SetupMenu(void)
   620 {
   621   return new cMenuSetupPremiereEpg;
   622 }
   623 
   624 bool cPluginPremiereEpg::SetupParse(const char *Name, const char *Value)
   625 {
   626   if      (!strcasecmp(Name, "OptionPattern")) SetupPE.OptPat = atoi(Value);
   627   else return false;
   628   return true;
   629 }
   630 
   631 VDRPLUGINCREATOR(cPluginPremiereEpg); // Don't touch this!