nathan@0: /* nathan@0: * PremiereEpg plugin to VDR (C++) nathan@0: * nathan@0: * (C) 2005 Stefan Huelswitt nathan@0: * nathan@0: * This code is base on the commandline tool premiereepg2vdr nathan@0: * (C) 2004-2005 by Axel Katzur software@katzur.de nathan@0: * but has been rewritten from scratch nathan@0: * nathan@0: * This code is free software; you can redistribute it and/or nathan@0: * modify it under the terms of the GNU General Public License nathan@0: * as published by the Free Software Foundation; either version 2 nathan@0: * of the License, or (at your option) any later version. nathan@0: * nathan@0: * This code is distributed in the hope that it will be useful, nathan@0: * but WITHOUT ANY WARRANTY; without even the implied warranty of nathan@0: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the nathan@0: * GNU General Public License for more details. nathan@0: * nathan@0: * You should have received a copy of the GNU General Public License nathan@0: * along with this program; if not, write to the Free Software nathan@0: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. nathan@0: * Or, point your browser to http://www.gnu.org/copyleft/gpl.html nathan@0: */ nathan@0: nathan@0: #include nathan@0: #include nathan@0: #include nathan@0: #include nathan@0: #include nathan@0: #include nathan@0: #include nathan@0: nathan@0: //#define DEBUG nathan@0: //#define DEBUG2 nathan@0: nathan@0: #ifdef DEBUG nathan@0: #define d(x) { (x); } nathan@0: #else nathan@0: #define d(x) ; nathan@0: #endif nathan@0: #ifdef DEBUG2 nathan@0: #define d2(x) { (x); } nathan@0: #else nathan@0: #define d2(x) ; nathan@0: #endif nathan@0: nathan@0: #define PMT_SCAN_TIMEOUT 10 // seconds nathan@0: #define PMT_SCAN_IDLE 300 // seconds nathan@0: nathan@0: static const char *VERSION = "0.0.1"; nathan@0: static const char *DESCRIPTION = "Parse extended Premiere EPG data"; nathan@0: nathan@0: // --- CIT --------------------------------------------------------------------- nathan@0: nathan@0: namespace SI { nathan@0: nathan@0: #define CIT_LEN 17 nathan@0: nathan@0: struct cit { nathan@0: u_char table_id :8; nathan@0: #if BYTE_ORDER == BIG_ENDIAN nathan@0: u_char section_syntax_indicator :1; nathan@0: u_char :3; nathan@0: u_char section_length_hi :4; nathan@0: #else nathan@0: u_char section_length_hi :4; nathan@0: u_char :3; nathan@0: u_char section_syntax_indicator :1; nathan@0: #endif nathan@0: u_char section_length_lo :8; nathan@0: u_char service_id_hi :8; nathan@0: u_char service_id_lo :8; nathan@0: #if BYTE_ORDER == BIG_ENDIAN nathan@0: u_char :2; nathan@0: u_char version_number :5; nathan@0: u_char current_next_indicator :1; nathan@0: #else nathan@0: u_char current_next_indicator :1; nathan@0: u_char version_number :5; nathan@0: u_char :2; nathan@0: #endif nathan@0: u_char section_number :8; nathan@0: u_char last_section_number :8; nathan@0: u_char content_id_hi_hi :8; nathan@0: u_char content_id_hi_lo :8; nathan@0: u_char content_id_lo_hi :8; nathan@0: u_char content_id_lo_lo :8; nathan@0: u_char duration_h :8; nathan@0: u_char duration_m :8; nathan@0: u_char duration_s :8; nathan@0: #if BYTE_ORDER == BIG_ENDIAN nathan@0: u_char reserved :4; nathan@0: u_char descriptors_loop_length_hi :4; nathan@0: #else nathan@0: u_char descriptors_loop_length_hi :4; nathan@0: u_char reserved :4; nathan@0: #endif nathan@0: u_char descriptors_loop_length_lo :8; nathan@0: }; nathan@0: nathan@0: class CIT : public NumberedSection { nathan@0: public: nathan@0: CIT(const unsigned char *data, bool doCopy=true) : NumberedSection(data, doCopy) {} nathan@0: CIT() {} nathan@0: int getContentId(void) const; nathan@0: time_t getDuration(void) const; nathan@0: DescriptorLoop eventDescriptors; nathan@0: protected: nathan@0: virtual void Parse(void); nathan@0: private: nathan@0: const cit *s; nathan@0: }; nathan@0: nathan@0: int CIT::getContentId(void) const { nathan@0: return (HILO(s->content_id_hi)<<16) | (HILO(s->content_id_lo)); nathan@0: } nathan@0: nathan@0: time_t CIT::getDuration(void) const { nathan@0: return DVBTime::getDuration(s->duration_h,s->duration_m,s->duration_s); nathan@0: } nathan@0: nathan@0: void CIT::Parse(void) { nathan@0: unsigned int offset=0; nathan@0: data.setPointerAndOffset(s, offset); nathan@0: eventDescriptors.setData(data+offset,HILO(s->descriptors_loop_length)); nathan@0: } nathan@0: nathan@0: } // end of namespace nathan@0: nathan@0: // --- cDescrF2 ---------------------------------------------------------------- nathan@0: nathan@0: class cDescrF2 { nathan@0: private: nathan@0: SI::Descriptor *d; nathan@0: SI::CharArray data; nathan@0: int idx, loop, nloop, index; nathan@0: public: nathan@0: cDescrF2(SI::Descriptor *D); nathan@0: int TransportStreamId(void) { return data.TwoBytes(2); } nathan@0: int OrgNetworkId(void) { return data.TwoBytes(4); } nathan@0: int ServiceId(void) { return data.TwoBytes(6); } nathan@0: void Start(void); nathan@0: bool Next(void); nathan@0: time_t StartTime(void); nathan@0: int Index(void) { return index; } nathan@0: }; nathan@0: nathan@0: cDescrF2::cDescrF2(SI::Descriptor *D) nathan@0: { nathan@0: d=D; nathan@0: data=d->getData(); nathan@0: Start(); nathan@0: } nathan@0: nathan@0: void cDescrF2::Start(void) nathan@0: { nathan@0: idx=8; loop=0; nloop=-3; index=-1; nathan@0: } nathan@0: nathan@0: bool cDescrF2::Next(void) nathan@0: { nathan@0: loop+=3; nathan@0: if(loop>=nloop) { nathan@0: idx+=nloop+3; nathan@0: if(idx>=d->getLength()) return false; nathan@0: loop=0; nloop=data[idx+2]; nathan@0: } nathan@0: index++; nathan@0: return true; nathan@0: } nathan@0: nathan@0: time_t cDescrF2::StartTime(void) nathan@0: { nathan@0: int off=idx+3+loop; nathan@0: return SI::DVBTime::getTime(data[idx+0],data[idx+1],data[off+0],data[off+1],data[off+2]); nathan@0: } nathan@0: nathan@0: // --- cFilterPremiereEpg ------------------------------------------------------ nathan@0: nathan@0: class cFilterPremiereEpg : public cFilter { nathan@0: private: nathan@0: int pmtpid, pmtidx, pmtnext; nathan@0: // nathan@0: void NextPmt(void); nathan@0: protected: nathan@0: virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length); nathan@0: public: nathan@0: cFilterPremiereEpg(void); nathan@0: virtual void SetStatus(bool On); nathan@0: void Trigger(void); nathan@0: }; nathan@0: nathan@0: cFilterPremiereEpg::cFilterPremiereEpg(void) nathan@0: { nathan@0: Trigger(); nathan@0: Set(0x00,0x00); nathan@0: } nathan@0: nathan@0: void cFilterPremiereEpg::Trigger(void) nathan@0: { nathan@0: d(printf("trigger\n")) nathan@0: pmtpid=0; pmtidx=0; pmtnext=0; nathan@0: } nathan@0: nathan@0: void cFilterPremiereEpg::SetStatus(bool On) nathan@0: { nathan@0: d(printf("setstatus %d\n",On)) nathan@0: cFilter::SetStatus(On); nathan@0: Trigger(); nathan@0: } nathan@0: nathan@0: void cFilterPremiereEpg::NextPmt(void) nathan@0: { nathan@0: Del(pmtpid,0x02); nathan@0: pmtpid=0; nathan@0: pmtidx++; nathan@0: d(printf("PMT next\n")) nathan@0: } nathan@0: nathan@0: void cFilterPremiereEpg::Process(u_short Pid, u_char Tid, const u_char *Data, int Length) nathan@0: { nathan@0: if(Pid==0 && Tid==SI::TableIdPAT) { nathan@0: int now=time(0); nathan@0: if(!pmtnext || now>pmtnext) { nathan@0: if(pmtpid) NextPmt(); nathan@0: if(!pmtpid) { nathan@0: SI::PAT pat(Data,false); nathan@0: if(pat.CheckCRCAndParse()) { nathan@0: SI::PAT::Association assoc; nathan@0: int idx=0; nathan@0: for(SI::Loop::Iterator it; pat.associationLoop.getNext(assoc,it);) { nathan@0: if(!assoc.isNITPid()) { nathan@0: if(idx++==pmtidx) { nathan@0: pmtpid=assoc.getPid(); nathan@0: Add(pmtpid,0x02); nathan@0: pmtnext=now+PMT_SCAN_TIMEOUT; nathan@0: d(printf("PMT pid now 0x%04x (idx=%d)\n",pmtpid,pmtidx)) nathan@0: break; nathan@0: } nathan@0: } nathan@0: } nathan@0: if(!pmtpid) { nathan@0: pmtidx=0; nathan@0: pmtnext=now+PMT_SCAN_IDLE; nathan@0: d(printf("PMT scan idle\n")) nathan@0: } nathan@0: } nathan@0: } nathan@0: } nathan@0: } nathan@0: else if(pmtpid>0 && Pid==pmtpid && Tid==SI::TableIdPMT && Source() && Transponder()) { nathan@0: SI::PMT pmt(Data,false); nathan@0: if(pmt.CheckCRCAndParse()) { nathan@0: SI::PMT::Stream stream; nathan@0: for(SI::Loop::Iterator it; pmt.streamLoop.getNext(stream,it); ) { nathan@0: if(stream.getStreamType()==0x05) { nathan@0: SI::CharArray data=stream.getData(); nathan@0: if((data[1]&0xE0)==0xE0 && (data[3]&0xF0)==0xF0) { nathan@0: bool prvData=false, usrData=false; nathan@0: SI::Descriptor *d; nathan@0: for(SI::Loop::Iterator it; (d=stream.streamDescriptors.getNext(it)); ) { nathan@0: switch(d->getDescriptorTag()) { nathan@0: case SI::PrivateDataSpecifierDescriptorTag: nathan@0: d(printf("prv: %d %08x\n",d->getLength(),d->getData().FourBytes(2))) nathan@0: if(d->getLength()==6 && d->getData().FourBytes(2)==0x000000be) nathan@0: prvData=true; nathan@0: break; nathan@0: case 0x90: nathan@0: d(printf("usr: %d %08x\n",d->getLength(),d->getData().FourBytes(2))) nathan@0: if(d->getLength()==6 && d->getData().FourBytes(2)==0x0000ffff) nathan@0: usrData=true; nathan@0: break; nathan@0: default: nathan@0: break; nathan@0: } nathan@0: delete d; nathan@0: } nathan@0: if(prvData && usrData) { nathan@0: int pid=stream.getPid(); nathan@0: d(printf("found citpid 0x%04x",pid)) nathan@0: if(!Matches(pid,0xA0)) { nathan@0: Add(pid,0xA0); nathan@0: d(printf(" (added)")) nathan@0: } nathan@0: d(printf("\n")) nathan@0: } nathan@0: } nathan@0: } nathan@0: } nathan@0: NextPmt(); pmtnext=0; nathan@0: } nathan@0: } nathan@0: else if(Tid==0xA0 && Source()) { nathan@0: SI::CIT cit(Data,false); nathan@0: if(cit.CheckCRCAndParse()) { nathan@0: cSchedulesLock SchedulesLock(true,10); nathan@0: cSchedules *Schedules=(cSchedules *)cSchedules::Schedules(SchedulesLock); nathan@0: if(Schedules) { nathan@0: int nCount=0; nathan@0: time_t firstTime=0; nathan@0: SI::Descriptor *d; nathan@0: int LanguagePreferenceShort=-1; nathan@0: int LanguagePreferenceExt=-1; nathan@0: bool UseExtendedEventDescriptor=false; nathan@0: SI::ExtendedEventDescriptors *ExtendedEventDescriptors=0; nathan@0: SI::ShortEventDescriptor *ShortEventDescriptor=0; nathan@0: for(SI::Loop::Iterator it; (d=cit.eventDescriptors.getNext(it)); ) { nathan@0: switch(d->getDescriptorTag()) { nathan@0: case 0xF2: nathan@0: if(nCount>=0) { nathan@0: nCount++; nathan@0: cDescrF2 f2(d); nathan@0: if(f2.Next()) { nathan@0: if(nCount==1) firstTime=f2.StartTime(); nathan@0: else { nathan@0: time_t time=f2.StartTime(); nathan@0: if(firstTimetime+5*60) nathan@0: nCount=-1; nathan@0: } nathan@0: } nathan@0: } nathan@0: break; nathan@0: case SI::ExtendedEventDescriptorTag: nathan@0: { nathan@0: SI::ExtendedEventDescriptor *eed=(SI::ExtendedEventDescriptor *)d; nathan@0: if(I18nIsPreferredLanguage(Setup.EPGLanguages,I18nLanguageIndex(eed->languageCode), LanguagePreferenceExt) || !ExtendedEventDescriptors) { nathan@0: delete ExtendedEventDescriptors; nathan@0: ExtendedEventDescriptors=new SI::ExtendedEventDescriptors; nathan@0: UseExtendedEventDescriptor=true; nathan@0: } nathan@0: if(UseExtendedEventDescriptor) { nathan@0: ExtendedEventDescriptors->Add(eed); nathan@0: d=NULL; // so that it is not deleted nathan@0: } nathan@0: if(eed->getDescriptorNumber()==eed->getLastDescriptorNumber()) nathan@0: UseExtendedEventDescriptor=false; nathan@0: } nathan@0: break; nathan@0: case SI::ShortEventDescriptorTag: nathan@0: { nathan@0: SI::ShortEventDescriptor *sed=(SI::ShortEventDescriptor *)d; nathan@0: if(I18nIsPreferredLanguage(Setup.EPGLanguages,I18nLanguageIndex(sed->languageCode), LanguagePreferenceShort) || !ShortEventDescriptor) { nathan@0: delete ShortEventDescriptor; nathan@0: ShortEventDescriptor=sed; nathan@0: d=NULL; // so that it is not deleted nathan@0: } nathan@0: } nathan@0: break; nathan@0: default: nathan@0: break; nathan@0: } nathan@0: delete d; nathan@0: } nathan@0: nathan@0: bool Modified=false; nathan@0: int optCount=0; nathan@0: for(SI::Loop::Iterator it; (d=cit.eventDescriptors.getNext(it)); ) { nathan@0: if(d->getDescriptorTag()==0xF2) { nathan@0: optCount++; nathan@0: nathan@0: cDescrF2 f2(d); nathan@0: tChannelID channelID(Source(),f2.OrgNetworkId(),f2.TransportStreamId(),f2.ServiceId()); nathan@0: cChannel *channel=Channels.GetByChannelID(channelID,true); nathan@0: if(!channel) continue; nathan@0: nathan@0: cSchedule *pSchedule=(cSchedule *)Schedules->GetSchedule(channelID); nathan@0: if(!pSchedule) { nathan@0: pSchedule=new cSchedule(channelID); nathan@0: Schedules->Add(pSchedule); nathan@0: } nathan@0: nathan@0: for(f2.Start(); f2.Next();) { nathan@0: u_int16_t EventId=(cit.getContentId()<<4) | f2.Index(); nathan@0: time_t StartTime=f2.StartTime(); nathan@0: nathan@0: bool isOpt=false; nathan@0: if(f2.Index()==0 && nCount>1) isOpt=true; nathan@0: nathan@0: 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)))) nathan@0: if(StartTime+cit.getDuration()+Setup.EPGLinger*60GetEvent(EventId,StartTime); nathan@0: if(!pEvent) { nathan@0: d2(printf("(new)\n")) nathan@0: pEvent=pSchedule->AddEvent(new cEvent(channelID,EventId)); nathan@0: if(!pEvent) continue; nathan@0: } nathan@0: else { nathan@0: d2(printf("(upd)\n")) nathan@0: pEvent->SetSeen(); nathan@0: if(pEvent->TableID()==0x00) continue; nathan@0: if(Tid==pEvent->TableID() && pEvent->Version()==cit.getVersionNumber()) continue; nathan@0: } nathan@0: pEvent->SetEventID(EventId); nathan@0: pEvent->SetTableID(Tid); nathan@0: pEvent->SetVersion(cit.getVersionNumber()); nathan@0: pEvent->SetStartTime(StartTime); nathan@0: pEvent->SetDuration(cit.getDuration()); nathan@0: nathan@0: if(ShortEventDescriptor) { nathan@0: char buffer[256+32]; nathan@0: ShortEventDescriptor->name.getText(buffer,sizeof(buffer)-32); nathan@0: if(isOpt) { nathan@0: char o[32]; nathan@0: snprintf(o,sizeof(o)," (Option %d)",optCount); nathan@0: strcat(buffer,o); nathan@0: } nathan@0: pEvent->SetTitle(buffer); nathan@0: pEvent->SetShortText(ShortEventDescriptor->text.getText(buffer,sizeof(buffer))); nathan@0: } nathan@0: if(ExtendedEventDescriptors) { nathan@0: char buffer[ExtendedEventDescriptors->getMaximumTextLength(": ")+1]; nathan@0: pEvent->SetDescription(ExtendedEventDescriptors->getText(buffer,sizeof(buffer),": ")); nathan@0: } nathan@0: nathan@0: pEvent->SetComponents(NULL); nathan@0: pEvent->FixEpgBugs(); nathan@0: Modified=true; nathan@0: } nathan@0: if(Modified) { nathan@0: pSchedule->Sort(); nathan@0: Schedules->SetModified(pSchedule); nathan@0: } nathan@0: } nathan@0: delete d; nathan@0: } nathan@0: delete ExtendedEventDescriptors; nathan@0: delete ShortEventDescriptor; nathan@0: } nathan@0: } nathan@0: } nathan@0: } nathan@0: nathan@0: // --- cPluginPremiereEpg ------------------------------------------------------ nathan@0: nathan@0: class cPluginPremiereEpg : public cPlugin { nathan@0: private: nathan@0: struct { nathan@0: cFilterPremiereEpg *filter; nathan@0: cDevice *device; nathan@0: } epg[MAXDVBDEVICES]; nathan@0: public: nathan@0: cPluginPremiereEpg(void); nathan@0: virtual const char *Version(void) { return VERSION; } nathan@0: virtual const char *Description(void) { return DESCRIPTION; } nathan@0: virtual bool Start(void); nathan@0: virtual void Stop(void); nathan@0: }; nathan@0: nathan@0: cPluginPremiereEpg::cPluginPremiereEpg(void) nathan@0: { nathan@0: memset(epg,0,sizeof(epg)); nathan@0: } nathan@0: nathan@0: bool cPluginPremiereEpg::Start(void) nathan@0: { nathan@0: for(int i=0; iAttachFilter(epg[i].filter=new cFilterPremiereEpg); nathan@0: isyslog("Attached premiere EPG filter to device %d",i); nathan@0: } nathan@0: } nathan@0: return true; nathan@0: } nathan@0: nathan@0: void cPluginPremiereEpg::Stop(void) nathan@0: { nathan@0: for(int i=0; iDetach(epg[i].filter); nathan@0: delete epg[i].filter; nathan@0: epg[i].device=0; nathan@0: epg[i].filter=0; nathan@0: } nathan@0: } nathan@0: nathan@0: VDRPLUGINCREATOR(cPluginPremiereEpg); // Don't touch this!