premiereepg.c
author nathan
Mon, 28 Jan 2008 17:39:51 +0100
branchtrunk
changeset 23 3c10fdd8ccce
parent 20 bc64e11172f5
child 25 f0ca0c236cfc
permissions -rw-r--r--
use po2i18n for non-locale vdr
     1 /*
     2  * PremiereEpg plugin to VDR (C++)
     3  *
     4  * (C) 2005-2008 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 <vdr/config.h>
    33 #include <libsi/section.h>
    34 #include <libsi/descriptor.h>
    35 #include "i18n.h"
    36 #include "version.h"
    37 
    38 #if APIVERSNUM < 10401
    39 #error You need at least VDR API version 1.4.1 for this plugin
    40 #endif
    41 
    42 //#define DEBUG
    43 //#define DEBUG2
    44 
    45 #ifdef DEBUG
    46 #define d(x) { (x); }
    47 #else
    48 #define d(x) ; 
    49 #endif
    50 #ifdef DEBUG2
    51 #define d2(x) { (x); }
    52 #else
    53 #define d2(x) ; 
    54 #endif
    55 
    56 #define PMT_SCAN_TIMEOUT  10  // seconds
    57 #define PMT_SCAN_IDLE     300 // seconds
    58 
    59 // --- cSetupPremiereEpg -------------------------------------------------------
    60 
    61 const char *optPats[] = {
    62   "%s",
    63   "%s (Option %d)",
    64   "%s (O%d)",
    65   "#%2$d %1$s",
    66   "[%2$d] %1$s"
    67   };
    68 #define NUM_PATS (sizeof(optPats)/sizeof(char *))
    69 
    70 class cSetupPremiereEpg {
    71 public:
    72   int OptPat;
    73   int OrderInfo;
    74   int RatingInfo;
    75   int FixEpg;
    76 public:
    77   cSetupPremiereEpg(void);
    78   };
    79 
    80 cSetupPremiereEpg SetupPE;
    81 
    82 cSetupPremiereEpg::cSetupPremiereEpg(void)
    83 {
    84   OptPat=1;
    85   OrderInfo=1;
    86   RatingInfo=1;
    87   FixEpg=0;
    88 }
    89 
    90 // --- cMenuSetupPremiereEpg ------------------------------------------------------------
    91 
    92 class cMenuSetupPremiereEpg : public cMenuSetupPage {
    93 private:
    94   cSetupPremiereEpg data;
    95   const char *optDisp[NUM_PATS];
    96   char buff[NUM_PATS][32];
    97 protected:
    98   virtual void Store(void);
    99 public:
   100   cMenuSetupPremiereEpg(void);
   101   };
   102 
   103 cMenuSetupPremiereEpg::cMenuSetupPremiereEpg(void)
   104 {
   105   data=SetupPE;
   106   SetSection(tr("PremiereEPG"));
   107   optDisp[0]=tr("off");
   108   for(unsigned int i=1; i<NUM_PATS; i++) {
   109     snprintf(buff[i],sizeof(buff[i]),optPats[i],"Event",1);
   110     optDisp[i]=buff[i];
   111     }
   112   Add(new cMenuEditStraItem(tr("Tag option events"),&data.OptPat,NUM_PATS,optDisp));
   113   Add(new cMenuEditBoolItem(tr("Show order information"),&data.OrderInfo));
   114   Add(new cMenuEditBoolItem(tr("Show rating information"),&data.RatingInfo));
   115   Add(new cMenuEditBoolItem(tr("Fix EPG data"),&data.FixEpg));
   116 }
   117 
   118 void cMenuSetupPremiereEpg::Store(void)
   119 {
   120   SetupPE=data;
   121   SetupStore("OptionPattern",SetupPE.OptPat);
   122   SetupStore("OrderInfo",SetupPE.OrderInfo);
   123   SetupStore("RatingInfo",SetupPE.RatingInfo);
   124   SetupStore("FixEpg",SetupPE.FixEpg);
   125 }
   126 
   127 // --- CRC16 -------------------------------------------------------------------
   128 
   129 #define POLY 0xA001 // CRC16
   130 
   131 unsigned int crc16(unsigned int crc, unsigned char const *p, int len)
   132 {
   133   while(len--) {
   134     crc^=*p++;
   135     for(int i=0; i<8; i++)
   136       crc=(crc&1) ? (crc>>1)^POLY : (crc>>1);
   137     }
   138   return crc&0xFFFF;
   139 }
   140 
   141 // --- cFilterPremiereEpg ------------------------------------------------------
   142 
   143 #define STARTTIME_BIAS (20*60)
   144 
   145 class cFilterPremiereEpg : public cFilter {
   146 private:
   147   int pmtpid, pmtsid, pmtidx, pmtnext;
   148   //
   149   void NextPmt(void);
   150 protected:
   151   virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length);
   152 public:
   153   cFilterPremiereEpg(void);
   154   virtual void SetStatus(bool On);
   155   void Trigger(void);
   156   };
   157 
   158 cFilterPremiereEpg::cFilterPremiereEpg(void)
   159 {
   160   Trigger();
   161   Set(0x00,0x00);
   162 }
   163 
   164 void cFilterPremiereEpg::Trigger(void)
   165 {
   166   d(printf("trigger\n"))
   167   pmtpid=0; pmtidx=0; pmtnext=0;
   168 }
   169 
   170 void cFilterPremiereEpg::SetStatus(bool On)
   171 {
   172   d(printf("setstatus %d\n",On))
   173   cFilter::SetStatus(On);
   174   Trigger();
   175 }
   176 
   177 void cFilterPremiereEpg::NextPmt(void)
   178 {
   179   Del(pmtpid,0x02);
   180   pmtpid=0;
   181   pmtidx++;
   182   d(printf("PMT next\n"))
   183 }
   184 
   185 void cFilterPremiereEpg::Process(u_short Pid, u_char Tid, const u_char *Data, int Length)
   186 {
   187   int now=time(0);
   188   if(Pid==0 && Tid==SI::TableIdPAT) {
   189     if(!pmtnext || now>pmtnext) {
   190       if(pmtpid) NextPmt();
   191       if(!pmtpid) {
   192         SI::PAT pat(Data,false);
   193         if(pat.CheckCRCAndParse()) {
   194           SI::PAT::Association assoc;
   195           int idx=0;
   196           for(SI::Loop::Iterator it; pat.associationLoop.getNext(assoc,it);) {
   197             if(!assoc.isNITPid()) {
   198               if(idx++==pmtidx) {
   199                 pmtpid=assoc.getPid();
   200                 pmtsid=assoc.getServiceId();
   201                 Add(pmtpid,0x02);
   202                 pmtnext=now+PMT_SCAN_TIMEOUT;
   203                 d(printf("PMT pid now 0x%04x (idx=%d)\n",pmtpid,pmtidx))
   204                 break;
   205                 }
   206               }
   207             }
   208           if(!pmtpid) {
   209             pmtidx=0;
   210             pmtnext=now+PMT_SCAN_IDLE;
   211             d(printf("PMT scan idle\n"))
   212             }
   213           }
   214         }
   215       }
   216     }
   217   else if(pmtpid>0 && Pid==pmtpid && Tid==SI::TableIdPMT && Source() && Transponder()) {
   218     SI::PMT pmt(Data,false);
   219     if(pmt.CheckCRCAndParse() && pmt.getServiceId()==pmtsid) {
   220       SI::PMT::Stream stream;
   221       for(SI::Loop::Iterator it; pmt.streamLoop.getNext(stream,it); ) {
   222         if(stream.getStreamType()==0x05) {
   223           SI::CharArray data=stream.getData();
   224           if((data[1]&0xE0)==0xE0 && (data[3]&0xF0)==0xF0) {
   225             bool prvData=false, usrData=false;
   226             SI::Descriptor *d;
   227             for(SI::Loop::Iterator it; (d=stream.streamDescriptors.getNext(it)); ) {
   228               switch(d->getDescriptorTag()) {
   229                 case SI::PrivateDataSpecifierDescriptorTag:
   230                   d(printf("prv: %d %08x\n",d->getLength(),d->getData().FourBytes(2)))
   231                   if(d->getLength()==6 && d->getData().FourBytes(2)==0x000000be)
   232                     prvData=true;
   233                   break;
   234                 case 0x90:
   235                   d(printf("usr: %d %08x\n",d->getLength(),d->getData().FourBytes(2)))
   236                   if(d->getLength()==6 && d->getData().FourBytes(2)==0x0000ffff)
   237                     usrData=true;
   238                   break;
   239                 default:
   240                   break;
   241                 }
   242               delete d;
   243               }
   244             if(prvData && usrData) {
   245               int pid=stream.getPid();
   246               d(printf("found citpid 0x%04x",pid))
   247               if(!Matches(pid,0xA0)) {
   248                 Add(pid,0xA0);
   249                 d(printf(" (added)"))
   250                 }
   251               d(printf("\n"))
   252               }
   253             }
   254           }
   255         }
   256       NextPmt(); pmtnext=0;
   257       }
   258     }
   259   else if(Tid==0xA0 && Source()) {
   260     SI::PremiereCIT cit(Data,false);
   261     if(cit.CheckCRCAndParse()) {
   262       cSchedulesLock SchedulesLock(true,10);
   263       cSchedules *Schedules=(cSchedules *)cSchedules::Schedules(SchedulesLock);
   264       if(Schedules) {
   265         int nCount=0;
   266         SI::ExtendedEventDescriptors *ExtendedEventDescriptors=0;
   267         SI::ShortEventDescriptor *ShortEventDescriptor=0;
   268         char *order=0, *rating=0;
   269         {
   270         time_t firstTime=0;
   271         SI::Descriptor *d;
   272         bool UseExtendedEventDescriptor=false;
   273         int LanguagePreferenceShort=-1;
   274         int LanguagePreferenceExt=-1;
   275         for(SI::Loop::Iterator it; (d=cit.eventDescriptors.getNext(it)); ) {
   276           switch(d->getDescriptorTag()) {
   277             case 0xF0: // order information
   278               if(SetupPE.OrderInfo) {
   279                 static const char *text[] = {
   280                   trNOOP("Ordernumber"),
   281                   trNOOP("Price"),
   282                   trNOOP("Ordering"),
   283                   trNOOP("SMS"),
   284                   trNOOP("WWW")
   285                   };
   286                 char buff[512];
   287                 int p=0;
   288                 const unsigned char *data=d->getData().getData()+2;
   289                 for(int i=0; i<5; i++) {
   290                   int l=data[0]; 
   291                   if(l>0) p+=snprintf(&buff[p],sizeof(buff)-p,"\n%s: %.*s",tr(text[i]),l,&data[1]);
   292                   data+=l+1;
   293                   }
   294                 if(p>0) order=strdup(buff);
   295                 }
   296               break;
   297             case 0xF1: // parental rating
   298               if(SetupPE.RatingInfo) {
   299                 char buff[512];
   300                 int p=0;
   301                 const unsigned char *data=d->getData().getData()+2;
   302                 p+=snprintf(&buff[p],sizeof(buff)-p,"\n%s: %d %s",tr("Rating"),data[0]+3,tr("years"));
   303                 data+=7;
   304                 int l=data[0]; 
   305                 if(l>0) p+=snprintf(&buff[p],sizeof(buff)-p," (%.*s)",l,&data[1]);
   306                 if(p>0) rating=strdup(buff);
   307                 }
   308               break;
   309             case SI::PremiereContentTransmissionDescriptorTag:
   310               if(nCount>=0) {
   311                 SI::PremiereContentTransmissionDescriptor *pct=(SI::PremiereContentTransmissionDescriptor *)d;
   312                 nCount++;
   313                 SI::PremiereContentTransmissionDescriptor::StartDayEntry sd;
   314                 SI::Loop::Iterator it;
   315                 if(pct->startDayLoop.getNext(sd,it)) {
   316                   SI::PremiereContentTransmissionDescriptor::StartDayEntry::StartTimeEntry st;
   317                   SI::Loop::Iterator it2;
   318                   if(sd.startTimeLoop.getNext(st,it2)) {
   319                     time_t StartTime=st.getStartTime(sd.getMJD());
   320                     if(nCount==1) firstTime=StartTime;
   321                     else if(firstTime<StartTime-5*50 || firstTime>StartTime+5*60)
   322                       nCount=-1;
   323                     }
   324                   }
   325                 }
   326               break;
   327             case SI::ExtendedEventDescriptorTag:
   328               {
   329               SI::ExtendedEventDescriptor *eed=(SI::ExtendedEventDescriptor *)d;
   330               if(I18nIsPreferredLanguage(Setup.EPGLanguages,eed->languageCode, LanguagePreferenceExt) || !ExtendedEventDescriptors) {
   331                  delete ExtendedEventDescriptors;
   332                  ExtendedEventDescriptors=new SI::ExtendedEventDescriptors;
   333                  UseExtendedEventDescriptor=true;
   334                  }
   335               if(UseExtendedEventDescriptor) {
   336                  ExtendedEventDescriptors->Add(eed);
   337                  d=NULL; // so that it is not deleted
   338                  }
   339               if(eed->getDescriptorNumber()==eed->getLastDescriptorNumber())
   340                  UseExtendedEventDescriptor=false;
   341               }
   342               break;
   343             case SI::ShortEventDescriptorTag:
   344               {
   345               SI::ShortEventDescriptor *sed=(SI::ShortEventDescriptor *)d;
   346               if(I18nIsPreferredLanguage(Setup.EPGLanguages,sed->languageCode, LanguagePreferenceShort) || !ShortEventDescriptor) {
   347                  delete ShortEventDescriptor;
   348                  ShortEventDescriptor=sed;
   349                  d=NULL; // so that it is not deleted
   350                  }
   351               }
   352               break;
   353             default:
   354               break;
   355             }
   356           delete d;
   357           }
   358         }
   359 
   360         {
   361         bool Modified=false;
   362         int optCount=0;
   363         unsigned int crc[3];
   364         crc[0]=cit.getContentId();
   365         SI::PremiereContentTransmissionDescriptor *pct;
   366         for(SI::Loop::Iterator it; (pct=(SI::PremiereContentTransmissionDescriptor *)cit.eventDescriptors.getNext(it,SI::PremiereContentTransmissionDescriptorTag)); ) {
   367           int nid=pct->getOriginalNetworkId();
   368           int tid=pct->getTransportStreamId();
   369           int sid=pct->getServiceId();
   370           if(SetupPE.FixEpg) {
   371             if(nid==133) {
   372 	      if     (tid==0x03 && sid==0xf0) { tid=0x02; sid=0xe0; }
   373 	      else if(tid==0x03 && sid==0xf1) { tid=0x02; sid=0xe1; }
   374 	      else if(tid==0x03 && sid==0xf5) { tid=0x03; sid=0xdc; }
   375 	      else if(tid==0x04 && sid==0xd2) { tid=0x11; sid=0xe2; }
   376 	      else if(tid==0x11 && sid==0xd3) { tid=0x11; sid=0xe3; }
   377 	      else if(tid==0x01 && sid==0xd4) { tid=0x04; sid=0xe4; }
   378               }
   379             }
   380           tChannelID channelID(Source(),nid,tid,sid);
   381           cChannel *channel=Channels.GetByChannelID(channelID,true);
   382           if(!channel) continue;
   383 
   384           cSchedule *pSchedule=(cSchedule *)Schedules->GetSchedule(channelID);
   385           if(!pSchedule) {
   386              pSchedule=new cSchedule(channelID);
   387              Schedules->Add(pSchedule);
   388              }
   389 
   390           optCount++;
   391           SI::PremiereContentTransmissionDescriptor::StartDayEntry sd;
   392           int index=0;
   393           for(SI::Loop::Iterator it; pct->startDayLoop.getNext(sd,it); ) {
   394             int mjd=sd.getMJD();
   395             SI::PremiereContentTransmissionDescriptor::StartDayEntry::StartTimeEntry st;
   396             for(SI::Loop::Iterator it2; sd.startTimeLoop.getNext(st,it2); ) {
   397               time_t StartTime=st.getStartTime(mjd);
   398               time_t EndTime=StartTime+cit.getDuration();
   399               int runningStatus=(StartTime<now && now<EndTime) ? SI::RunningStatusRunning : ((StartTime-30<now && now<StartTime) ? SI::RunningStatusStartsInAFewSeconds : SI::RunningStatusNotRunning);
   400               bool isOpt=false;
   401               if(index++==0 && nCount>1) isOpt=true;
   402               crc[1]=isOpt ? optCount : 0;
   403               crc[2]=StartTime / STARTTIME_BIAS;
   404               tEventID EventId=((('P'<<8)|'W')<<16) | crc16(0,(unsigned char *)crc,sizeof(crc));
   405 
   406               d2(printf("%s R%d %04x/%.4x %d %d/%d %s +%d ",*channelID.ToString(),runningStatus,EventId&0xFFFF,cit.getContentId(),index,isOpt,optCount,stripspace(ctime(&StartTime)),(int)cit.getDuration()/60))
   407               if(EndTime+Setup.EPGLinger*60<now) {
   408                 d2(printf("(old)\n"))
   409                 continue;
   410                 }
   411 
   412               bool newEvent=false;
   413               cEvent *pEvent=(cEvent *)pSchedule->GetEvent(EventId,-1);
   414               if(!pEvent) {
   415                 d2(printf("(new)\n"))
   416                 pEvent=new cEvent(EventId);
   417                 if(!pEvent) continue;
   418                 newEvent=true;
   419                 }
   420               else {
   421                 d2(printf("(upd)\n"))
   422                 pEvent->SetSeen();
   423                 if(pEvent->TableID()==0x00 || pEvent->Version()==cit.getVersionNumber()) {
   424                   if(pEvent->RunningStatus()!=runningStatus)
   425                     pSchedule->SetRunningStatus(pEvent,runningStatus,channel);
   426                   continue;
   427                   }
   428                 }
   429               pEvent->SetEventID(EventId);
   430               pEvent->SetTableID(Tid);
   431               pEvent->SetVersion(cit.getVersionNumber());
   432               pEvent->SetStartTime(StartTime);
   433               pEvent->SetDuration(cit.getDuration());
   434 
   435               if(ShortEventDescriptor) {
   436                 char buffer[256];
   437                 ShortEventDescriptor->name.getText(buffer,sizeof(buffer));
   438                 if(isOpt) {
   439                   char buffer2[sizeof(buffer)+32];
   440                   snprintf(buffer2,sizeof(buffer2),optPats[SetupPE.OptPat],buffer,optCount);
   441                   pEvent->SetTitle(buffer2);
   442                   }
   443                 else
   444                   pEvent->SetTitle(buffer);
   445                 d2(printf("title: %s\n",pEvent->Title()))
   446                 pEvent->SetShortText(ShortEventDescriptor->text.getText(buffer,sizeof(buffer)));
   447                 }
   448               if(ExtendedEventDescriptors) {
   449                 char buffer[ExtendedEventDescriptors->getMaximumTextLength(": ")+1];
   450                 pEvent->SetDescription(ExtendedEventDescriptors->getText(buffer,sizeof(buffer),": "));
   451                 }
   452               if(order || rating) {
   453                 int len=(pEvent->Description() ? strlen(pEvent->Description()) : 0) +
   454                         (order                 ? strlen(order) : 0) +
   455                         (rating                ? strlen(rating) : 0);
   456                 char buffer[len+32];
   457                 buffer[0]=0;
   458                 if(pEvent->Description()) strcat(buffer,pEvent->Description());
   459                 if(rating)                strcat(buffer,rating);
   460                 if(order)                 strcat(buffer,order);
   461                 pEvent->SetDescription(buffer);
   462                 }
   463 
   464               if(newEvent) pSchedule->AddEvent(pEvent);
   465               pEvent->SetComponents(NULL);
   466               pEvent->FixEpgBugs();
   467               if(pEvent->RunningStatus()!=runningStatus)
   468                 pSchedule->SetRunningStatus(pEvent,runningStatus,channel);
   469               pSchedule->DropOutdated(StartTime,EndTime,Tid,cit.getVersionNumber());
   470               Modified=true;
   471               }
   472             }
   473           if(Modified) {
   474             pSchedule->Sort();
   475             Schedules->SetModified(pSchedule);
   476             }
   477           delete pct;
   478           }
   479         }
   480         delete ExtendedEventDescriptors;
   481         delete ShortEventDescriptor;
   482         free(order);
   483         free(rating);
   484         }
   485       }
   486     }
   487 }
   488 
   489 // --- cPluginPremiereEpg ------------------------------------------------------
   490 
   491 static const char *DESCRIPTION    = trNOOP("Parses extended Premiere EPG data");
   492 
   493 class cPluginPremiereEpg : public cPlugin {
   494 private:
   495   struct {
   496     cFilterPremiereEpg *filter;
   497     cDevice *device;
   498     } epg[MAXDVBDEVICES];
   499 public:
   500   cPluginPremiereEpg(void);
   501   virtual const char *Version(void) { return PluginVersion; }
   502   virtual const char *Description(void) { return tr(DESCRIPTION); }
   503   virtual bool Start(void);
   504   virtual void Stop(void);
   505   virtual cMenuSetupPage *SetupMenu(void);
   506   virtual bool SetupParse(const char *Name, const char *Value);
   507   };
   508 
   509 cPluginPremiereEpg::cPluginPremiereEpg(void)
   510 {
   511   memset(epg,0,sizeof(epg));
   512 }
   513 
   514 bool cPluginPremiereEpg::Start(void)
   515 {
   516 #if APIVERSNUM < 10507
   517   RegisterI18n(Phrases);
   518 #endif
   519   for(int i=0; i<MAXDVBDEVICES; i++) {
   520     cDevice *dev=cDevice::GetDevice(i);
   521     if(dev) {
   522       epg[i].device=dev;
   523       dev->AttachFilter(epg[i].filter=new cFilterPremiereEpg);
   524       isyslog("Attached premiere EPG filter to device %d",i);
   525       }
   526     }
   527   return true;
   528 }
   529 
   530 void cPluginPremiereEpg::Stop(void)
   531 {
   532   for(int i=0; i<MAXDVBDEVICES; i++) {
   533     cDevice *dev=epg[i].device;
   534     if(dev) dev->Detach(epg[i].filter);
   535     delete epg[i].filter;
   536     epg[i].device=0;
   537     epg[i].filter=0;
   538     }
   539 }
   540 
   541 cMenuSetupPage *cPluginPremiereEpg::SetupMenu(void)
   542 {
   543   return new cMenuSetupPremiereEpg;
   544 }
   545 
   546 bool cPluginPremiereEpg::SetupParse(const char *Name, const char *Value)
   547 {
   548   if      (!strcasecmp(Name, "OptionPattern")) SetupPE.OptPat     = atoi(Value);
   549   else if (!strcasecmp(Name, "OrderInfo"))     SetupPE.OrderInfo  = atoi(Value);
   550   else if (!strcasecmp(Name, "RatingInfo"))    SetupPE.RatingInfo = atoi(Value);
   551   else if (!strcasecmp(Name, "FixEpg"))        SetupPE.FixEpg     = atoi(Value);
   552   else return false;
   553   return true;
   554 }
   555 
   556 VDRPLUGINCREATOR(cPluginPremiereEpg); // Don't touch this!