2 * PremiereEpg plugin to VDR (C++)
4 * (C) 2005-2007 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>
32 #include <vdr/config.h>
33 #include <libsi/section.h>
34 #include <libsi/descriptor.h>
36 #if APIVERSNUM < 10401
37 #error You need at least VDR API version 1.4.1 for this plugin
39 #if APIVERSNUM < 10507
52 #define d2(x) { (x); }
57 #define PMT_SCAN_TIMEOUT 10 // seconds
58 #define PMT_SCAN_IDLE 300 // seconds
60 static const char *VERSION = "0.0.8";
61 static const char *DESCRIPTION = trNOOP("Parses extended Premiere EPG data");
63 // --- cSetupPremiereEpg -------------------------------------------------------
65 const char *optPats[] = {
72 #define NUM_PATS (sizeof(optPats)/sizeof(char *))
74 class cSetupPremiereEpg {
81 cSetupPremiereEpg(void);
84 cSetupPremiereEpg SetupPE;
86 cSetupPremiereEpg::cSetupPremiereEpg(void)
94 // --- i18n --------------------------------------------------------------------
96 #if APIVERSNUM < 10507
97 const tI18nPhrase Phrases[] = {
115 { "Parses extended Premiere EPG data",
116 "Liest erweiterte Premiere EPG Daten ein",
149 { "Tag option events",
150 "Options Events markieren",
166 { "Show order information",
167 "Bestellhinweise anzeigen",
183 { "Show rating information",
184 "Altersfreigaben anzeigen",
321 "EPG Daten korrigieren",
342 // --- cMenuSetupPremiereEpg ------------------------------------------------------------
344 class cMenuSetupPremiereEpg : public cMenuSetupPage {
346 cSetupPremiereEpg data;
347 const char *optDisp[NUM_PATS];
348 char buff[NUM_PATS][32];
350 virtual void Store(void);
352 cMenuSetupPremiereEpg(void);
355 cMenuSetupPremiereEpg::cMenuSetupPremiereEpg(void)
358 SetSection(tr("PremiereEPG"));
359 optDisp[0]=tr("off");
360 for(unsigned int i=1; i<NUM_PATS; i++) {
361 snprintf(buff[i],sizeof(buff[i]),optPats[i],"Event",1);
364 Add(new cMenuEditStraItem(tr("Tag option events"),&data.OptPat,NUM_PATS,optDisp));
365 Add(new cMenuEditBoolItem(tr("Show order information"),&data.OrderInfo));
366 Add(new cMenuEditBoolItem(tr("Show rating information"),&data.RatingInfo));
367 Add(new cMenuEditBoolItem(tr("Fix EPG data"),&data.FixEpg));
370 void cMenuSetupPremiereEpg::Store(void)
373 SetupStore("OptionPattern",SetupPE.OptPat);
374 SetupStore("OrderInfo",SetupPE.OrderInfo);
375 SetupStore("RatingInfo",SetupPE.RatingInfo);
376 SetupStore("FixEpg",SetupPE.FixEpg);
379 // --- CRC16 -------------------------------------------------------------------
381 #define POLY 0xA001 // CRC16
383 unsigned int crc16(unsigned int crc, unsigned char const *p, int len)
387 for(int i=0; i<8; i++)
388 crc=(crc&1) ? (crc>>1)^POLY : (crc>>1);
393 // --- cFilterPremiereEpg ------------------------------------------------------
395 #define STARTTIME_BIAS (20*60)
397 class cFilterPremiereEpg : public cFilter {
399 int pmtpid, pmtsid, pmtidx, pmtnext;
403 virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length);
405 cFilterPremiereEpg(void);
406 virtual void SetStatus(bool On);
410 cFilterPremiereEpg::cFilterPremiereEpg(void)
416 void cFilterPremiereEpg::Trigger(void)
418 d(printf("trigger\n"))
419 pmtpid=0; pmtidx=0; pmtnext=0;
422 void cFilterPremiereEpg::SetStatus(bool On)
424 d(printf("setstatus %d\n",On))
425 cFilter::SetStatus(On);
429 void cFilterPremiereEpg::NextPmt(void)
434 d(printf("PMT next\n"))
437 void cFilterPremiereEpg::Process(u_short Pid, u_char Tid, const u_char *Data, int Length)
440 if(Pid==0 && Tid==SI::TableIdPAT) {
441 if(!pmtnext || now>pmtnext) {
442 if(pmtpid) NextPmt();
444 SI::PAT pat(Data,false);
445 if(pat.CheckCRCAndParse()) {
446 SI::PAT::Association assoc;
448 for(SI::Loop::Iterator it; pat.associationLoop.getNext(assoc,it);) {
449 if(!assoc.isNITPid()) {
451 pmtpid=assoc.getPid();
452 pmtsid=assoc.getServiceId();
454 pmtnext=now+PMT_SCAN_TIMEOUT;
455 d(printf("PMT pid now 0x%04x (idx=%d)\n",pmtpid,pmtidx))
462 pmtnext=now+PMT_SCAN_IDLE;
463 d(printf("PMT scan idle\n"))
469 else if(pmtpid>0 && Pid==pmtpid && Tid==SI::TableIdPMT && Source() && Transponder()) {
470 SI::PMT pmt(Data,false);
471 if(pmt.CheckCRCAndParse() && pmt.getServiceId()==pmtsid) {
472 SI::PMT::Stream stream;
473 for(SI::Loop::Iterator it; pmt.streamLoop.getNext(stream,it); ) {
474 if(stream.getStreamType()==0x05) {
475 SI::CharArray data=stream.getData();
476 if((data[1]&0xE0)==0xE0 && (data[3]&0xF0)==0xF0) {
477 bool prvData=false, usrData=false;
479 for(SI::Loop::Iterator it; (d=stream.streamDescriptors.getNext(it)); ) {
480 switch(d->getDescriptorTag()) {
481 case SI::PrivateDataSpecifierDescriptorTag:
482 d(printf("prv: %d %08x\n",d->getLength(),d->getData().FourBytes(2)))
483 if(d->getLength()==6 && d->getData().FourBytes(2)==0x000000be)
487 d(printf("usr: %d %08x\n",d->getLength(),d->getData().FourBytes(2)))
488 if(d->getLength()==6 && d->getData().FourBytes(2)==0x0000ffff)
496 if(prvData && usrData) {
497 int pid=stream.getPid();
498 d(printf("found citpid 0x%04x",pid))
499 if(!Matches(pid,0xA0)) {
501 d(printf(" (added)"))
508 NextPmt(); pmtnext=0;
511 else if(Tid==0xA0 && Source()) {
512 SI::PremiereCIT cit(Data,false);
513 if(cit.CheckCRCAndParse()) {
514 cSchedulesLock SchedulesLock(true,10);
515 cSchedules *Schedules=(cSchedules *)cSchedules::Schedules(SchedulesLock);
518 SI::ExtendedEventDescriptors *ExtendedEventDescriptors=0;
519 SI::ShortEventDescriptor *ShortEventDescriptor=0;
520 char *order=0, *rating=0;
524 bool UseExtendedEventDescriptor=false;
525 int LanguagePreferenceShort=-1;
526 int LanguagePreferenceExt=-1;
527 for(SI::Loop::Iterator it; (d=cit.eventDescriptors.getNext(it)); ) {
528 switch(d->getDescriptorTag()) {
529 case 0xF0: // order information
530 if(SetupPE.OrderInfo) {
531 static const char *text[] = {
532 trNOOP("Ordernumber"),
540 const unsigned char *data=d->getData().getData()+2;
541 for(int i=0; i<5; i++) {
543 if(l>0) p+=snprintf(&buff[p],sizeof(buff)-p,"\n%s: %.*s",tr(text[i]),l,&data[1]);
546 if(p>0) order=strdup(buff);
549 case 0xF1: // parental rating
550 if(SetupPE.RatingInfo) {
553 const unsigned char *data=d->getData().getData()+2;
554 p+=snprintf(&buff[p],sizeof(buff)-p,"\n%s: %d %s",tr("Rating"),data[0]+3,tr("years"));
557 if(l>0) p+=snprintf(&buff[p],sizeof(buff)-p," (%.*s)",l,&data[1]);
558 if(p>0) rating=strdup(buff);
561 case SI::PremiereContentTransmissionDescriptorTag:
563 SI::PremiereContentTransmissionDescriptor *pct=(SI::PremiereContentTransmissionDescriptor *)d;
565 SI::PremiereContentTransmissionDescriptor::StartDayEntry sd;
566 SI::Loop::Iterator it;
567 if(pct->startDayLoop.getNext(sd,it)) {
568 SI::PremiereContentTransmissionDescriptor::StartDayEntry::StartTimeEntry st;
569 SI::Loop::Iterator it2;
570 if(sd.startTimeLoop.getNext(st,it2)) {
571 time_t StartTime=st.getStartTime(sd.getMJD());
572 if(nCount==1) firstTime=StartTime;
573 else if(firstTime<StartTime-5*50 || firstTime>StartTime+5*60)
579 case SI::ExtendedEventDescriptorTag:
581 SI::ExtendedEventDescriptor *eed=(SI::ExtendedEventDescriptor *)d;
582 if(I18nIsPreferredLanguage(Setup.EPGLanguages,eed->languageCode, LanguagePreferenceExt) || !ExtendedEventDescriptors) {
583 delete ExtendedEventDescriptors;
584 ExtendedEventDescriptors=new SI::ExtendedEventDescriptors;
585 UseExtendedEventDescriptor=true;
587 if(UseExtendedEventDescriptor) {
588 ExtendedEventDescriptors->Add(eed);
589 d=NULL; // so that it is not deleted
591 if(eed->getDescriptorNumber()==eed->getLastDescriptorNumber())
592 UseExtendedEventDescriptor=false;
595 case SI::ShortEventDescriptorTag:
597 SI::ShortEventDescriptor *sed=(SI::ShortEventDescriptor *)d;
598 if(I18nIsPreferredLanguage(Setup.EPGLanguages,sed->languageCode, LanguagePreferenceShort) || !ShortEventDescriptor) {
599 delete ShortEventDescriptor;
600 ShortEventDescriptor=sed;
601 d=NULL; // so that it is not deleted
616 crc[0]=cit.getContentId();
617 SI::PremiereContentTransmissionDescriptor *pct;
618 for(SI::Loop::Iterator it; (pct=(SI::PremiereContentTransmissionDescriptor *)cit.eventDescriptors.getNext(it,SI::PremiereContentTransmissionDescriptorTag)); ) {
619 int nid=pct->getOriginalNetworkId();
620 int tid=pct->getTransportStreamId();
621 int sid=pct->getServiceId();
624 if (tid==0x03 && sid==0xf0) { tid=0x02; sid=0xe0; }
625 else if(tid==0x03 && sid==0xf1) { tid=0x02; sid=0xe1; }
626 else if(tid==0x03 && sid==0xf5) { tid=0x03; sid=0xdc; }
627 else if(tid==0x04 && sid==0xd2) { tid=0x11; sid=0xe2; }
628 else if(tid==0x11 && sid==0xd3) { tid=0x11; sid=0xe3; }
629 else if(tid==0x01 && sid==0xd4) { tid=0x04; sid=0xe4; }
632 tChannelID channelID(Source(),nid,tid,sid);
633 cChannel *channel=Channels.GetByChannelID(channelID,true);
634 if(!channel) continue;
636 cSchedule *pSchedule=(cSchedule *)Schedules->GetSchedule(channelID);
638 pSchedule=new cSchedule(channelID);
639 Schedules->Add(pSchedule);
643 SI::PremiereContentTransmissionDescriptor::StartDayEntry sd;
645 for(SI::Loop::Iterator it; pct->startDayLoop.getNext(sd,it); ) {
647 SI::PremiereContentTransmissionDescriptor::StartDayEntry::StartTimeEntry st;
648 for(SI::Loop::Iterator it2; sd.startTimeLoop.getNext(st,it2); ) {
649 time_t StartTime=st.getStartTime(mjd);
650 time_t EndTime=StartTime+cit.getDuration();
651 int runningStatus=(StartTime<now && now<EndTime) ? SI::RunningStatusRunning : ((StartTime-30<now && now<StartTime) ? SI::RunningStatusStartsInAFewSeconds : SI::RunningStatusNotRunning);
653 if(index++==0 && nCount>1) isOpt=true;
654 crc[1]=isOpt ? optCount : 0;
655 crc[2]=StartTime / STARTTIME_BIAS;
656 tEventID EventId=((('P'<<8)|'W')<<16) | crc16(0,(unsigned char *)crc,sizeof(crc));
658 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))
659 if(EndTime+Setup.EPGLinger*60<now) {
660 d2(printf("(old)\n"))
665 cEvent *pEvent=(cEvent *)pSchedule->GetEvent(EventId,-1);
667 d2(printf("(new)\n"))
668 pEvent=new cEvent(EventId);
669 if(!pEvent) continue;
673 d2(printf("(upd)\n"))
675 if(pEvent->TableID()==0x00 || pEvent->Version()==cit.getVersionNumber()) {
676 if(pEvent->RunningStatus()!=runningStatus)
677 pSchedule->SetRunningStatus(pEvent,runningStatus,channel);
681 pEvent->SetEventID(EventId);
682 pEvent->SetTableID(Tid);
683 pEvent->SetVersion(cit.getVersionNumber());
684 pEvent->SetStartTime(StartTime);
685 pEvent->SetDuration(cit.getDuration());
687 if(ShortEventDescriptor) {
689 ShortEventDescriptor->name.getText(buffer,sizeof(buffer));
691 char buffer2[sizeof(buffer)+32];
692 snprintf(buffer2,sizeof(buffer2),optPats[SetupPE.OptPat],buffer,optCount);
693 pEvent->SetTitle(buffer2);
696 pEvent->SetTitle(buffer);
697 d2(printf("title: %s\n",pEvent->Title()))
698 pEvent->SetShortText(ShortEventDescriptor->text.getText(buffer,sizeof(buffer)));
700 if(ExtendedEventDescriptors) {
701 char buffer[ExtendedEventDescriptors->getMaximumTextLength(": ")+1];
702 pEvent->SetDescription(ExtendedEventDescriptors->getText(buffer,sizeof(buffer),": "));
704 if(order || rating) {
705 int len=(pEvent->Description() ? strlen(pEvent->Description()) : 0) +
706 (order ? strlen(order) : 0) +
707 (rating ? strlen(rating) : 0);
710 if(pEvent->Description()) strcat(buffer,pEvent->Description());
711 if(rating) strcat(buffer,rating);
712 if(order) strcat(buffer,order);
713 pEvent->SetDescription(buffer);
716 if(newEvent) pSchedule->AddEvent(pEvent);
717 pEvent->SetComponents(NULL);
718 pEvent->FixEpgBugs();
719 if(pEvent->RunningStatus()!=runningStatus)
720 pSchedule->SetRunningStatus(pEvent,runningStatus,channel);
721 pSchedule->DropOutdated(StartTime,EndTime,Tid,cit.getVersionNumber());
727 Schedules->SetModified(pSchedule);
732 delete ExtendedEventDescriptors;
733 delete ShortEventDescriptor;
741 // --- cPluginPremiereEpg ------------------------------------------------------
743 class cPluginPremiereEpg : public cPlugin {
746 cFilterPremiereEpg *filter;
748 } epg[MAXDVBDEVICES];
750 cPluginPremiereEpg(void);
751 virtual const char *Version(void) { return VERSION; }
752 virtual const char *Description(void) { return tr(DESCRIPTION); }
753 virtual bool Start(void);
754 virtual void Stop(void);
755 virtual cMenuSetupPage *SetupMenu(void);
756 virtual bool SetupParse(const char *Name, const char *Value);
759 cPluginPremiereEpg::cPluginPremiereEpg(void)
761 memset(epg,0,sizeof(epg));
764 bool cPluginPremiereEpg::Start(void)
766 #if APIVERSNUM < 10507
767 RegisterI18n(Phrases);
769 for(int i=0; i<MAXDVBDEVICES; i++) {
770 cDevice *dev=cDevice::GetDevice(i);
773 dev->AttachFilter(epg[i].filter=new cFilterPremiereEpg);
774 isyslog("Attached premiere EPG filter to device %d",i);
780 void cPluginPremiereEpg::Stop(void)
782 for(int i=0; i<MAXDVBDEVICES; i++) {
783 cDevice *dev=epg[i].device;
784 if(dev) dev->Detach(epg[i].filter);
785 delete epg[i].filter;
791 cMenuSetupPage *cPluginPremiereEpg::SetupMenu(void)
793 return new cMenuSetupPremiereEpg;
796 bool cPluginPremiereEpg::SetupParse(const char *Name, const char *Value)
798 if (!strcasecmp(Name, "OptionPattern")) SetupPE.OptPat = atoi(Value);
799 else if (!strcasecmp(Name, "OrderInfo")) SetupPE.OrderInfo = atoi(Value);
800 else if (!strcasecmp(Name, "RatingInfo")) SetupPE.RatingInfo = atoi(Value);
801 else if (!strcasecmp(Name, "FixEpg")) SetupPE.FixEpg = atoi(Value);
806 VDRPLUGINCREATOR(cPluginPremiereEpg); // Don't touch this!