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>
32 #include <libsi/section.h>
33 #include <libsi/descriptor.h>
44 #define d2(x) { (x); }
49 #define PMT_SCAN_TIMEOUT 10 // seconds
50 #define PMT_SCAN_IDLE 300 // seconds
52 static const char *VERSION = "0.0.5";
53 static const char *DESCRIPTION = "Parses extended Premiere EPG data";
55 // --- cSetupPremiereEpg -------------------------------------------------------
57 const char *optPats[] = {
64 #define NUM_PATS (sizeof(optPats)/sizeof(char *))
66 class cSetupPremiereEpg {
72 cSetupPremiereEpg(void);
75 cSetupPremiereEpg SetupPE;
77 cSetupPremiereEpg::cSetupPremiereEpg(void)
84 // --- i18n --------------------------------------------------------------------
86 const tI18nPhrase Phrases[] = {
106 { "Parses extended Premiere EPG data",
107 "Liest erweiterte Premiere EPG Daten ein",
123 { "Tag option events",
124 "Options Events markieren",
140 { "Show order information",
141 "Bestellhinweise anzeigen",
157 { "Show rating information",
158 "Altersfreigaben anzeigen",
298 // --- cMenuSetupPremiereEpg ------------------------------------------------------------
300 class cMenuSetupPremiereEpg : public cMenuSetupPage {
302 cSetupPremiereEpg data;
303 const char *optDisp[NUM_PATS];
304 char buff[NUM_PATS][32];
306 virtual void Store(void);
308 cMenuSetupPremiereEpg(void);
311 cMenuSetupPremiereEpg::cMenuSetupPremiereEpg(void)
314 SetSection(tr("PremiereEPG"));
315 optDisp[0]=tr("off");
316 for(unsigned int i=1; i<NUM_PATS; i++) {
317 snprintf(buff[i],sizeof(buff[i]),optPats[i],"Event",1);
320 Add(new cMenuEditStraItem(tr("Tag option events"),&data.OptPat,NUM_PATS,optDisp));
321 Add(new cMenuEditBoolItem(tr("Show order information"),&data.OrderInfo));
322 Add(new cMenuEditBoolItem(tr("Show rating information"),&data.RatingInfo));
325 void cMenuSetupPremiereEpg::Store(void)
328 SetupStore("OptionPattern",SetupPE.OptPat);
329 SetupStore("OrderInfo",SetupPE.OrderInfo);
330 SetupStore("RatingInfo",SetupPE.RatingInfo);
333 // --- CIT ---------------------------------------------------------------------
341 #if BYTE_ORDER == BIG_ENDIAN
342 u_char section_syntax_indicator :1;
344 u_char section_length_hi :4;
346 u_char section_length_hi :4;
348 u_char section_syntax_indicator :1;
350 u_char section_length_lo :8;
351 u_char service_id_hi :8;
352 u_char service_id_lo :8;
353 #if BYTE_ORDER == BIG_ENDIAN
355 u_char version_number :5;
356 u_char current_next_indicator :1;
358 u_char current_next_indicator :1;
359 u_char version_number :5;
362 u_char section_number :8;
363 u_char last_section_number :8;
364 u_char content_id_hi_hi :8;
365 u_char content_id_hi_lo :8;
366 u_char content_id_lo_hi :8;
367 u_char content_id_lo_lo :8;
368 u_char duration_h :8;
369 u_char duration_m :8;
370 u_char duration_s :8;
371 #if BYTE_ORDER == BIG_ENDIAN
373 u_char descriptors_loop_length_hi :4;
375 u_char descriptors_loop_length_hi :4;
378 u_char descriptors_loop_length_lo :8;
381 class CIT : public NumberedSection {
383 CIT(const unsigned char *data, bool doCopy=true) : NumberedSection(data, doCopy) {}
385 int getContentId(void) const;
386 time_t getDuration(void) const;
387 DescriptorLoop eventDescriptors;
389 virtual void Parse(void);
394 int CIT::getContentId(void) const {
395 return (HILO(s->content_id_hi)<<16) | (HILO(s->content_id_lo));
398 time_t CIT::getDuration(void) const {
399 return DVBTime::getDuration(s->duration_h,s->duration_m,s->duration_s);
402 void CIT::Parse(void) {
403 #if VDRVERSNUM >= 10343
406 unsigned int offset=0;
408 data.setPointerAndOffset<const cit>(s, offset);
409 eventDescriptors.setData(data+offset,HILO(s->descriptors_loop_length));
412 } // end of namespace
414 // --- cDescrF2 ----------------------------------------------------------------
420 int idx, loop, nloop, index;
422 cDescrF2(SI::Descriptor *D);
423 int TransportStreamId(void) { return data.TwoBytes(2); }
424 int OrgNetworkId(void) { return data.TwoBytes(4); }
425 int ServiceId(void) { return data.TwoBytes(6); }
428 time_t StartTime(void);
429 int Index(void) { return index; }
432 cDescrF2::cDescrF2(SI::Descriptor *D)
439 void cDescrF2::Start(void)
441 idx=8; loop=0; nloop=-3; index=-1;
444 bool cDescrF2::Next(void)
449 if(idx>=d->getLength()) return false;
450 loop=0; nloop=data[idx+2];
456 time_t cDescrF2::StartTime(void)
459 return SI::DVBTime::getTime(data[idx+0],data[idx+1],data[off+0],data[off+1],data[off+2]);
462 // --- cFilterPremiereEpg ------------------------------------------------------
464 class cFilterPremiereEpg : public cFilter {
466 int pmtpid, pmtidx, pmtnext;
470 virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length);
472 cFilterPremiereEpg(void);
473 virtual void SetStatus(bool On);
477 cFilterPremiereEpg::cFilterPremiereEpg(void)
483 void cFilterPremiereEpg::Trigger(void)
485 d(printf("trigger\n"))
486 pmtpid=0; pmtidx=0; pmtnext=0;
489 void cFilterPremiereEpg::SetStatus(bool On)
491 d(printf("setstatus %d\n",On))
492 cFilter::SetStatus(On);
496 void cFilterPremiereEpg::NextPmt(void)
501 d(printf("PMT next\n"))
504 void cFilterPremiereEpg::Process(u_short Pid, u_char Tid, const u_char *Data, int Length)
506 if(Pid==0 && Tid==SI::TableIdPAT) {
508 if(!pmtnext || now>pmtnext) {
509 if(pmtpid) NextPmt();
511 SI::PAT pat(Data,false);
512 if(pat.CheckCRCAndParse()) {
513 SI::PAT::Association assoc;
515 for(SI::Loop::Iterator it; pat.associationLoop.getNext(assoc,it);) {
516 if(!assoc.isNITPid()) {
518 pmtpid=assoc.getPid();
520 pmtnext=now+PMT_SCAN_TIMEOUT;
521 d(printf("PMT pid now 0x%04x (idx=%d)\n",pmtpid,pmtidx))
528 pmtnext=now+PMT_SCAN_IDLE;
529 d(printf("PMT scan idle\n"))
535 else if(pmtpid>0 && Pid==pmtpid && Tid==SI::TableIdPMT && Source() && Transponder()) {
536 SI::PMT pmt(Data,false);
537 if(pmt.CheckCRCAndParse()) {
538 SI::PMT::Stream stream;
539 for(SI::Loop::Iterator it; pmt.streamLoop.getNext(stream,it); ) {
540 if(stream.getStreamType()==0x05) {
541 SI::CharArray data=stream.getData();
542 if((data[1]&0xE0)==0xE0 && (data[3]&0xF0)==0xF0) {
543 bool prvData=false, usrData=false;
545 for(SI::Loop::Iterator it; (d=stream.streamDescriptors.getNext(it)); ) {
546 switch(d->getDescriptorTag()) {
547 case SI::PrivateDataSpecifierDescriptorTag:
548 d(printf("prv: %d %08x\n",d->getLength(),d->getData().FourBytes(2)))
549 if(d->getLength()==6 && d->getData().FourBytes(2)==0x000000be)
553 d(printf("usr: %d %08x\n",d->getLength(),d->getData().FourBytes(2)))
554 if(d->getLength()==6 && d->getData().FourBytes(2)==0x0000ffff)
562 if(prvData && usrData) {
563 int pid=stream.getPid();
564 d(printf("found citpid 0x%04x",pid))
565 if(!Matches(pid,0xA0)) {
567 d(printf(" (added)"))
574 NextPmt(); pmtnext=0;
577 else if(Tid==0xA0 && Source()) {
578 SI::CIT cit(Data,false);
579 if(cit.CheckCRCAndParse()) {
580 cSchedulesLock SchedulesLock(true,10);
581 cSchedules *Schedules=(cSchedules *)cSchedules::Schedules(SchedulesLock);
586 int LanguagePreferenceShort=-1;
587 int LanguagePreferenceExt=-1;
588 bool UseExtendedEventDescriptor=false;
589 SI::ExtendedEventDescriptors *ExtendedEventDescriptors=0;
590 SI::ShortEventDescriptor *ShortEventDescriptor=0;
591 char *order=0, *rating=0;
592 for(SI::Loop::Iterator it; (d=cit.eventDescriptors.getNext(it)); ) {
593 switch(d->getDescriptorTag()) {
594 case 0xF0: // order information
595 if(SetupPE.OrderInfo) {
596 static const char *text[] = {
605 const unsigned char *data=d->getData().getData()+2;
606 for(int i=0; i<5; i++) {
608 if(l>0) p+=snprintf(&buff[p],sizeof(buff)-p,"\n%s: %.*s",tr(text[i]),l,&data[1]);
611 if(p>0) order=strdup(buff);
614 case 0xF1: // parental rating
615 if(SetupPE.RatingInfo) {
618 const unsigned char *data=d->getData().getData()+2;
619 p+=snprintf(&buff[p],sizeof(buff)-p,"\n%s: %d %s",tr("Rating"),data[0]+3,tr("years"));
622 if(l>0) p+=snprintf(&buff[p],sizeof(buff)-p," (%.*s)",l,&data[1]);
623 if(p>0) rating=strdup(buff);
626 case 0xF2: // transmisions
631 if(nCount==1) firstTime=f2.StartTime();
633 time_t time=f2.StartTime();
634 if(firstTime<time-5*50 || firstTime>time+5*60)
640 case SI::ExtendedEventDescriptorTag:
642 SI::ExtendedEventDescriptor *eed=(SI::ExtendedEventDescriptor *)d;
643 #if VDRVERSNUM < 10332
644 if(I18nIsPreferredLanguage(Setup.EPGLanguages,I18nLanguageIndex(eed->languageCode), LanguagePreferenceExt) || !ExtendedEventDescriptors) {
646 if(I18nIsPreferredLanguage(Setup.EPGLanguages,eed->languageCode, LanguagePreferenceExt) || !ExtendedEventDescriptors) {
648 delete ExtendedEventDescriptors;
649 ExtendedEventDescriptors=new SI::ExtendedEventDescriptors;
650 UseExtendedEventDescriptor=true;
652 if(UseExtendedEventDescriptor) {
653 ExtendedEventDescriptors->Add(eed);
654 d=NULL; // so that it is not deleted
656 if(eed->getDescriptorNumber()==eed->getLastDescriptorNumber())
657 UseExtendedEventDescriptor=false;
660 case SI::ShortEventDescriptorTag:
662 SI::ShortEventDescriptor *sed=(SI::ShortEventDescriptor *)d;
663 #if VDRVERSNUM < 10332
664 if(I18nIsPreferredLanguage(Setup.EPGLanguages,I18nLanguageIndex(sed->languageCode), LanguagePreferenceShort) || !ShortEventDescriptor) {
666 if(I18nIsPreferredLanguage(Setup.EPGLanguages,sed->languageCode, LanguagePreferenceShort) || !ShortEventDescriptor) {
668 delete ShortEventDescriptor;
669 ShortEventDescriptor=sed;
670 d=NULL; // so that it is not deleted
682 for(SI::Loop::Iterator it; (d=cit.eventDescriptors.getNext(it)); ) {
683 if(d->getDescriptorTag()==0xF2) {
687 tChannelID channelID(Source(),f2.OrgNetworkId(),f2.TransportStreamId(),f2.ServiceId());
688 cChannel *channel=Channels.GetByChannelID(channelID,true);
689 if(!channel) continue;
691 cSchedule *pSchedule=(cSchedule *)Schedules->GetSchedule(channelID);
693 pSchedule=new cSchedule(channelID);
694 Schedules->Add(pSchedule);
697 for(f2.Start(); f2.Next();) {
698 u_int16_t EventId=(cit.getContentId()<<4) | f2.Index();
699 time_t StartTime=f2.StartTime();
702 if(f2.Index()==0 && nCount>1) isOpt=true;
704 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))))
705 if(StartTime+cit.getDuration()+Setup.EPGLinger*60<time(0)) {
706 d2(printf("(old)\n"))
711 cEvent *pEvent=(cEvent *)pSchedule->GetEvent(EventId,StartTime);
713 d2(printf("(new)\n"))
714 #if VDRVERSNUM >= 10325
715 pEvent=new cEvent(EventId);
717 pEvent=new cEvent(channelID,EventId);
719 if(!pEvent) continue;
723 d2(printf("(upd)\n"))
725 if(pEvent->TableID()==0x00) continue;
726 if(Tid==pEvent->TableID() && pEvent->Version()==cit.getVersionNumber()) continue;
728 pEvent->SetEventID(EventId);
729 pEvent->SetTableID(Tid);
730 pEvent->SetVersion(cit.getVersionNumber());
731 pEvent->SetStartTime(StartTime);
732 pEvent->SetDuration(cit.getDuration());
734 if(ShortEventDescriptor) {
736 ShortEventDescriptor->name.getText(buffer,sizeof(buffer));
738 char buffer2[sizeof(buffer)+32];
739 snprintf(buffer2,sizeof(buffer2),optPats[SetupPE.OptPat],buffer,optCount);
740 pEvent->SetTitle(buffer2);
743 pEvent->SetTitle(buffer);
744 pEvent->SetShortText(ShortEventDescriptor->text.getText(buffer,sizeof(buffer)));
746 if(ExtendedEventDescriptors) {
747 char buffer[ExtendedEventDescriptors->getMaximumTextLength(": ")+1];
748 pEvent->SetDescription(ExtendedEventDescriptors->getText(buffer,sizeof(buffer),": "));
750 if(order || rating) {
751 int len=(pEvent->Description() ? strlen(pEvent->Description()) : 0) +
752 (order ? strlen(order) : 0) +
753 (rating ? strlen(rating) : 0);
756 if(pEvent->Description()) strcat(buffer,pEvent->Description());
757 if(rating) strcat(buffer,rating);
758 if(order) strcat(buffer,order);
759 pEvent->SetDescription(buffer);
762 if(newEvent) pSchedule->AddEvent(pEvent);
763 #if VDRVERSNUM >= 10318
764 pEvent->SetComponents(NULL);
766 pEvent->FixEpgBugs();
771 Schedules->SetModified(pSchedule);
776 delete ExtendedEventDescriptors;
777 delete ShortEventDescriptor;
785 // --- cPluginPremiereEpg ------------------------------------------------------
787 class cPluginPremiereEpg : public cPlugin {
790 cFilterPremiereEpg *filter;
792 } epg[MAXDVBDEVICES];
794 cPluginPremiereEpg(void);
795 virtual const char *Version(void) { return VERSION; }
796 virtual const char *Description(void) { return tr(DESCRIPTION); }
797 virtual bool Start(void);
798 virtual void Stop(void);
799 virtual cMenuSetupPage *SetupMenu(void);
800 virtual bool SetupParse(const char *Name, const char *Value);
803 cPluginPremiereEpg::cPluginPremiereEpg(void)
805 memset(epg,0,sizeof(epg));
808 bool cPluginPremiereEpg::Start(void)
810 RegisterI18n(Phrases);
811 for(int i=0; i<MAXDVBDEVICES; i++) {
812 cDevice *dev=cDevice::GetDevice(i);
815 dev->AttachFilter(epg[i].filter=new cFilterPremiereEpg);
816 isyslog("Attached premiere EPG filter to device %d",i);
822 void cPluginPremiereEpg::Stop(void)
824 for(int i=0; i<MAXDVBDEVICES; i++) {
825 cDevice *dev=epg[i].device;
826 if(dev) dev->Detach(epg[i].filter);
827 delete epg[i].filter;
833 cMenuSetupPage *cPluginPremiereEpg::SetupMenu(void)
835 return new cMenuSetupPremiereEpg;
838 bool cPluginPremiereEpg::SetupParse(const char *Name, const char *Value)
840 if (!strcasecmp(Name, "OptionPattern")) SetupPE.OptPat = atoi(Value);
841 else if (!strcasecmp(Name, "OrderInfo")) SetupPE.OrderInfo = atoi(Value);
842 else if (!strcasecmp(Name, "RatingInfo")) SetupPE.RatingInfo = atoi(Value);
847 VDRPLUGINCREATOR(cPluginPremiereEpg); // Don't touch this!