premiereepg.c
author nathan
Sat, 29 Dec 2007 11:18:14 +0100
branchtrunk
changeset 2 3562cacb3b0b
parent 0 a75b9f441157
child 4 ac6bf154890e
permissions -rw-r--r--
release 0.0.2
     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.2";
    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(I18nIsPreferredLanguage(Setup.EPGLanguages,I18nLanguageIndex(eed->languageCode), LanguagePreferenceExt) || !ExtendedEventDescriptors) {
   445                  delete ExtendedEventDescriptors;
   446                  ExtendedEventDescriptors=new SI::ExtendedEventDescriptors;
   447                  UseExtendedEventDescriptor=true;
   448                  }
   449               if(UseExtendedEventDescriptor) {
   450                  ExtendedEventDescriptors->Add(eed);
   451                  d=NULL; // so that it is not deleted
   452                  }
   453               if(eed->getDescriptorNumber()==eed->getLastDescriptorNumber())
   454                  UseExtendedEventDescriptor=false;
   455               }
   456               break;
   457             case SI::ShortEventDescriptorTag:
   458               {
   459               SI::ShortEventDescriptor *sed=(SI::ShortEventDescriptor *)d;
   460               if(I18nIsPreferredLanguage(Setup.EPGLanguages,I18nLanguageIndex(sed->languageCode), LanguagePreferenceShort) || !ShortEventDescriptor) {
   461                  delete ShortEventDescriptor;
   462                  ShortEventDescriptor=sed;
   463                  d=NULL; // so that it is not deleted
   464                  }
   465               }
   466               break;
   467             default:
   468               break;
   469             }
   470           delete d;
   471           }
   472 
   473         bool Modified=false;
   474         int optCount=0;
   475         for(SI::Loop::Iterator it; (d=cit.eventDescriptors.getNext(it)); ) {
   476           if(d->getDescriptorTag()==0xF2) {
   477             optCount++;
   478 
   479             cDescrF2 f2(d);
   480             tChannelID channelID(Source(),f2.OrgNetworkId(),f2.TransportStreamId(),f2.ServiceId());
   481             cChannel *channel=Channels.GetByChannelID(channelID,true);
   482             if(!channel) continue;
   483 
   484             cSchedule *pSchedule=(cSchedule *)Schedules->GetSchedule(channelID);
   485             if(!pSchedule) {
   486                pSchedule=new cSchedule(channelID);
   487                Schedules->Add(pSchedule);
   488                }
   489 
   490             for(f2.Start(); f2.Next();) {
   491               u_int16_t EventId=(cit.getContentId()<<4) | f2.Index();
   492               time_t StartTime=f2.StartTime();
   493 
   494               bool isOpt=false;
   495               if(f2.Index()==0 && nCount>1) isOpt=true;
   496 
   497               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))))
   498               if(StartTime+cit.getDuration()+Setup.EPGLinger*60<time(0)) {
   499                 d2(printf("(old)\n"))
   500                 continue;
   501                 }
   502 
   503               bool newEvent=false;
   504               cEvent *pEvent=(cEvent *)pSchedule->GetEvent(EventId,StartTime);
   505               if(!pEvent) {
   506                  d2(printf("(new)\n"))
   507 #if VDRVERSNUM >= 10325
   508                  pEvent=new cEvent(EventId);
   509 #else
   510                  pEvent=new cEvent(channelID,EventId);
   511 #endif
   512                  if(!pEvent) continue;
   513                  newEvent=true;
   514                  }
   515               else {
   516                  d2(printf("(upd)\n"))
   517                  pEvent->SetSeen();
   518                  if(pEvent->TableID()==0x00) continue;
   519                  if(Tid==pEvent->TableID() && pEvent->Version()==cit.getVersionNumber()) continue;
   520                  }
   521               pEvent->SetEventID(EventId);
   522               pEvent->SetTableID(Tid);
   523               pEvent->SetVersion(cit.getVersionNumber());
   524               pEvent->SetStartTime(StartTime);
   525               pEvent->SetDuration(cit.getDuration());
   526 
   527               if(ShortEventDescriptor) {
   528                 char buffer[256];
   529                 ShortEventDescriptor->name.getText(buffer,sizeof(buffer));
   530                 if(isOpt) {
   531                   char buffer2[sizeof(buffer)+32];
   532                   snprintf(buffer2,sizeof(buffer2),optPats[SetupPE.OptPat],buffer,optCount);
   533                   pEvent->SetTitle(buffer2);
   534                   }
   535                 else
   536                   pEvent->SetTitle(buffer);
   537                 pEvent->SetShortText(ShortEventDescriptor->text.getText(buffer,sizeof(buffer)));
   538                 }
   539               if(ExtendedEventDescriptors) {
   540                 char buffer[ExtendedEventDescriptors->getMaximumTextLength(": ")+1];
   541                 pEvent->SetDescription(ExtendedEventDescriptors->getText(buffer,sizeof(buffer),": "));
   542                 }
   543 
   544               if(newEvent) pSchedule->AddEvent(pEvent);
   545               pEvent->SetComponents(NULL);
   546               pEvent->FixEpgBugs();
   547               Modified=true;
   548               }
   549             if(Modified) {
   550               pSchedule->Sort();
   551               Schedules->SetModified(pSchedule);
   552               }
   553             }
   554           delete d;
   555           }
   556         delete ExtendedEventDescriptors;
   557         delete ShortEventDescriptor;
   558         }
   559       }
   560     }
   561 }
   562 
   563 // --- cPluginPremiereEpg ------------------------------------------------------
   564 
   565 class cPluginPremiereEpg : public cPlugin {
   566 private:
   567   struct {
   568     cFilterPremiereEpg *filter;
   569     cDevice *device;
   570     } epg[MAXDVBDEVICES];
   571 public:
   572   cPluginPremiereEpg(void);
   573   virtual const char *Version(void) { return VERSION; }
   574   virtual const char *Description(void) { return tr(DESCRIPTION); }
   575   virtual bool Start(void);
   576   virtual void Stop(void);
   577   virtual cMenuSetupPage *SetupMenu(void);
   578   virtual bool SetupParse(const char *Name, const char *Value);
   579   };
   580 
   581 cPluginPremiereEpg::cPluginPremiereEpg(void)
   582 {
   583   memset(epg,0,sizeof(epg));
   584 }
   585 
   586 bool cPluginPremiereEpg::Start(void)
   587 {
   588   RegisterI18n(Phrases);
   589   for(int i=0; i<MAXDVBDEVICES; i++) {
   590     cDevice *dev=cDevice::GetDevice(i);
   591     if(dev) {
   592       epg[i].device=dev;
   593       dev->AttachFilter(epg[i].filter=new cFilterPremiereEpg);
   594       isyslog("Attached premiere EPG filter to device %d",i);
   595       }
   596     }
   597   return true;
   598 }
   599 
   600 void cPluginPremiereEpg::Stop(void)
   601 {
   602   for(int i=0; i<MAXDVBDEVICES; i++) {
   603     cDevice *dev=epg[i].device;
   604     if(dev) dev->Detach(epg[i].filter);
   605     delete epg[i].filter;
   606     epg[i].device=0;
   607     epg[i].filter=0;
   608     }
   609 }
   610 
   611 cMenuSetupPage *cPluginPremiereEpg::SetupMenu(void)
   612 {
   613   return new cMenuSetupPremiereEpg;
   614 }
   615 
   616 bool cPluginPremiereEpg::SetupParse(const char *Name, const char *Value)
   617 {
   618   if      (!strcasecmp(Name, "OptionPattern")) SetupPE.OptPat = atoi(Value);
   619   else return false;
   620   return true;
   621 }
   622 
   623 VDRPLUGINCREATOR(cPluginPremiereEpg); // Don't touch this!