1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/premiereepg.c Sat Dec 29 11:17:32 2007 +0100
1.3 @@ -0,0 +1,483 @@
1.4 +/*
1.5 + * PremiereEpg plugin to VDR (C++)
1.6 + *
1.7 + * (C) 2005 Stefan Huelswitt <s.huelswitt@gmx.de>
1.8 + *
1.9 + * This code is base on the commandline tool premiereepg2vdr
1.10 + * (C) 2004-2005 by Axel Katzur software@katzur.de
1.11 + * but has been rewritten from scratch
1.12 + *
1.13 + * This code is free software; you can redistribute it and/or
1.14 + * modify it under the terms of the GNU General Public License
1.15 + * as published by the Free Software Foundation; either version 2
1.16 + * of the License, or (at your option) any later version.
1.17 + *
1.18 + * This code is distributed in the hope that it will be useful,
1.19 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
1.20 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1.21 + * GNU General Public License for more details.
1.22 + *
1.23 + * You should have received a copy of the GNU General Public License
1.24 + * along with this program; if not, write to the Free Software
1.25 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
1.26 + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
1.27 + */
1.28 +
1.29 +#include <vdr/plugin.h>
1.30 +#include <vdr/filter.h>
1.31 +#include <vdr/epg.h>
1.32 +#include <vdr/channels.h>
1.33 +#include <vdr/dvbdevice.h>
1.34 +#include <libsi/section.h>
1.35 +#include <libsi/descriptor.h>
1.36 +
1.37 +//#define DEBUG
1.38 +//#define DEBUG2
1.39 +
1.40 +#ifdef DEBUG
1.41 +#define d(x) { (x); }
1.42 +#else
1.43 +#define d(x) ;
1.44 +#endif
1.45 +#ifdef DEBUG2
1.46 +#define d2(x) { (x); }
1.47 +#else
1.48 +#define d2(x) ;
1.49 +#endif
1.50 +
1.51 +#define PMT_SCAN_TIMEOUT 10 // seconds
1.52 +#define PMT_SCAN_IDLE 300 // seconds
1.53 +
1.54 +static const char *VERSION = "0.0.1";
1.55 +static const char *DESCRIPTION = "Parse extended Premiere EPG data";
1.56 +
1.57 +// --- CIT ---------------------------------------------------------------------
1.58 +
1.59 +namespace SI {
1.60 +
1.61 +#define CIT_LEN 17
1.62 +
1.63 +struct cit {
1.64 + u_char table_id :8;
1.65 +#if BYTE_ORDER == BIG_ENDIAN
1.66 + u_char section_syntax_indicator :1;
1.67 + u_char :3;
1.68 + u_char section_length_hi :4;
1.69 +#else
1.70 + u_char section_length_hi :4;
1.71 + u_char :3;
1.72 + u_char section_syntax_indicator :1;
1.73 +#endif
1.74 + u_char section_length_lo :8;
1.75 + u_char service_id_hi :8;
1.76 + u_char service_id_lo :8;
1.77 +#if BYTE_ORDER == BIG_ENDIAN
1.78 + u_char :2;
1.79 + u_char version_number :5;
1.80 + u_char current_next_indicator :1;
1.81 +#else
1.82 + u_char current_next_indicator :1;
1.83 + u_char version_number :5;
1.84 + u_char :2;
1.85 +#endif
1.86 + u_char section_number :8;
1.87 + u_char last_section_number :8;
1.88 + u_char content_id_hi_hi :8;
1.89 + u_char content_id_hi_lo :8;
1.90 + u_char content_id_lo_hi :8;
1.91 + u_char content_id_lo_lo :8;
1.92 + u_char duration_h :8;
1.93 + u_char duration_m :8;
1.94 + u_char duration_s :8;
1.95 +#if BYTE_ORDER == BIG_ENDIAN
1.96 + u_char reserved :4;
1.97 + u_char descriptors_loop_length_hi :4;
1.98 +#else
1.99 + u_char descriptors_loop_length_hi :4;
1.100 + u_char reserved :4;
1.101 +#endif
1.102 + u_char descriptors_loop_length_lo :8;
1.103 +};
1.104 +
1.105 +class CIT : public NumberedSection {
1.106 +public:
1.107 + CIT(const unsigned char *data, bool doCopy=true) : NumberedSection(data, doCopy) {}
1.108 + CIT() {}
1.109 + int getContentId(void) const;
1.110 + time_t getDuration(void) const;
1.111 + DescriptorLoop eventDescriptors;
1.112 +protected:
1.113 + virtual void Parse(void);
1.114 +private:
1.115 + const cit *s;
1.116 +};
1.117 +
1.118 +int CIT::getContentId(void) const {
1.119 + return (HILO(s->content_id_hi)<<16) | (HILO(s->content_id_lo));
1.120 +}
1.121 +
1.122 +time_t CIT::getDuration(void) const {
1.123 + return DVBTime::getDuration(s->duration_h,s->duration_m,s->duration_s);
1.124 +}
1.125 +
1.126 +void CIT::Parse(void) {
1.127 + unsigned int offset=0;
1.128 + data.setPointerAndOffset<const cit>(s, offset);
1.129 + eventDescriptors.setData(data+offset,HILO(s->descriptors_loop_length));
1.130 +}
1.131 +
1.132 +} // end of namespace
1.133 +
1.134 +// --- cDescrF2 ----------------------------------------------------------------
1.135 +
1.136 +class cDescrF2 {
1.137 +private:
1.138 + SI::Descriptor *d;
1.139 + SI::CharArray data;
1.140 + int idx, loop, nloop, index;
1.141 +public:
1.142 + cDescrF2(SI::Descriptor *D);
1.143 + int TransportStreamId(void) { return data.TwoBytes(2); }
1.144 + int OrgNetworkId(void) { return data.TwoBytes(4); }
1.145 + int ServiceId(void) { return data.TwoBytes(6); }
1.146 + void Start(void);
1.147 + bool Next(void);
1.148 + time_t StartTime(void);
1.149 + int Index(void) { return index; }
1.150 + };
1.151 +
1.152 +cDescrF2::cDescrF2(SI::Descriptor *D)
1.153 +{
1.154 + d=D;
1.155 + data=d->getData();
1.156 + Start();
1.157 +}
1.158 +
1.159 +void cDescrF2::Start(void)
1.160 +{
1.161 + idx=8; loop=0; nloop=-3; index=-1;
1.162 +}
1.163 +
1.164 +bool cDescrF2::Next(void)
1.165 +{
1.166 + loop+=3;
1.167 + if(loop>=nloop) {
1.168 + idx+=nloop+3;
1.169 + if(idx>=d->getLength()) return false;
1.170 + loop=0; nloop=data[idx+2];
1.171 + }
1.172 + index++;
1.173 + return true;
1.174 +}
1.175 +
1.176 +time_t cDescrF2::StartTime(void)
1.177 +{
1.178 + int off=idx+3+loop;
1.179 + return SI::DVBTime::getTime(data[idx+0],data[idx+1],data[off+0],data[off+1],data[off+2]);
1.180 +}
1.181 +
1.182 +// --- cFilterPremiereEpg ------------------------------------------------------
1.183 +
1.184 +class cFilterPremiereEpg : public cFilter {
1.185 +private:
1.186 + int pmtpid, pmtidx, pmtnext;
1.187 + //
1.188 + void NextPmt(void);
1.189 +protected:
1.190 + virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length);
1.191 +public:
1.192 + cFilterPremiereEpg(void);
1.193 + virtual void SetStatus(bool On);
1.194 + void Trigger(void);
1.195 + };
1.196 +
1.197 +cFilterPremiereEpg::cFilterPremiereEpg(void)
1.198 +{
1.199 + Trigger();
1.200 + Set(0x00,0x00);
1.201 +}
1.202 +
1.203 +void cFilterPremiereEpg::Trigger(void)
1.204 +{
1.205 + d(printf("trigger\n"))
1.206 + pmtpid=0; pmtidx=0; pmtnext=0;
1.207 +}
1.208 +
1.209 +void cFilterPremiereEpg::SetStatus(bool On)
1.210 +{
1.211 + d(printf("setstatus %d\n",On))
1.212 + cFilter::SetStatus(On);
1.213 + Trigger();
1.214 +}
1.215 +
1.216 +void cFilterPremiereEpg::NextPmt(void)
1.217 +{
1.218 + Del(pmtpid,0x02);
1.219 + pmtpid=0;
1.220 + pmtidx++;
1.221 + d(printf("PMT next\n"))
1.222 +}
1.223 +
1.224 +void cFilterPremiereEpg::Process(u_short Pid, u_char Tid, const u_char *Data, int Length)
1.225 +{
1.226 + if(Pid==0 && Tid==SI::TableIdPAT) {
1.227 + int now=time(0);
1.228 + if(!pmtnext || now>pmtnext) {
1.229 + if(pmtpid) NextPmt();
1.230 + if(!pmtpid) {
1.231 + SI::PAT pat(Data,false);
1.232 + if(pat.CheckCRCAndParse()) {
1.233 + SI::PAT::Association assoc;
1.234 + int idx=0;
1.235 + for(SI::Loop::Iterator it; pat.associationLoop.getNext(assoc,it);) {
1.236 + if(!assoc.isNITPid()) {
1.237 + if(idx++==pmtidx) {
1.238 + pmtpid=assoc.getPid();
1.239 + Add(pmtpid,0x02);
1.240 + pmtnext=now+PMT_SCAN_TIMEOUT;
1.241 + d(printf("PMT pid now 0x%04x (idx=%d)\n",pmtpid,pmtidx))
1.242 + break;
1.243 + }
1.244 + }
1.245 + }
1.246 + if(!pmtpid) {
1.247 + pmtidx=0;
1.248 + pmtnext=now+PMT_SCAN_IDLE;
1.249 + d(printf("PMT scan idle\n"))
1.250 + }
1.251 + }
1.252 + }
1.253 + }
1.254 + }
1.255 + else if(pmtpid>0 && Pid==pmtpid && Tid==SI::TableIdPMT && Source() && Transponder()) {
1.256 + SI::PMT pmt(Data,false);
1.257 + if(pmt.CheckCRCAndParse()) {
1.258 + SI::PMT::Stream stream;
1.259 + for(SI::Loop::Iterator it; pmt.streamLoop.getNext(stream,it); ) {
1.260 + if(stream.getStreamType()==0x05) {
1.261 + SI::CharArray data=stream.getData();
1.262 + if((data[1]&0xE0)==0xE0 && (data[3]&0xF0)==0xF0) {
1.263 + bool prvData=false, usrData=false;
1.264 + SI::Descriptor *d;
1.265 + for(SI::Loop::Iterator it; (d=stream.streamDescriptors.getNext(it)); ) {
1.266 + switch(d->getDescriptorTag()) {
1.267 + case SI::PrivateDataSpecifierDescriptorTag:
1.268 + d(printf("prv: %d %08x\n",d->getLength(),d->getData().FourBytes(2)))
1.269 + if(d->getLength()==6 && d->getData().FourBytes(2)==0x000000be)
1.270 + prvData=true;
1.271 + break;
1.272 + case 0x90:
1.273 + d(printf("usr: %d %08x\n",d->getLength(),d->getData().FourBytes(2)))
1.274 + if(d->getLength()==6 && d->getData().FourBytes(2)==0x0000ffff)
1.275 + usrData=true;
1.276 + break;
1.277 + default:
1.278 + break;
1.279 + }
1.280 + delete d;
1.281 + }
1.282 + if(prvData && usrData) {
1.283 + int pid=stream.getPid();
1.284 + d(printf("found citpid 0x%04x",pid))
1.285 + if(!Matches(pid,0xA0)) {
1.286 + Add(pid,0xA0);
1.287 + d(printf(" (added)"))
1.288 + }
1.289 + d(printf("\n"))
1.290 + }
1.291 + }
1.292 + }
1.293 + }
1.294 + NextPmt(); pmtnext=0;
1.295 + }
1.296 + }
1.297 + else if(Tid==0xA0 && Source()) {
1.298 + SI::CIT cit(Data,false);
1.299 + if(cit.CheckCRCAndParse()) {
1.300 + cSchedulesLock SchedulesLock(true,10);
1.301 + cSchedules *Schedules=(cSchedules *)cSchedules::Schedules(SchedulesLock);
1.302 + if(Schedules) {
1.303 + int nCount=0;
1.304 + time_t firstTime=0;
1.305 + SI::Descriptor *d;
1.306 + int LanguagePreferenceShort=-1;
1.307 + int LanguagePreferenceExt=-1;
1.308 + bool UseExtendedEventDescriptor=false;
1.309 + SI::ExtendedEventDescriptors *ExtendedEventDescriptors=0;
1.310 + SI::ShortEventDescriptor *ShortEventDescriptor=0;
1.311 + for(SI::Loop::Iterator it; (d=cit.eventDescriptors.getNext(it)); ) {
1.312 + switch(d->getDescriptorTag()) {
1.313 + case 0xF2:
1.314 + if(nCount>=0) {
1.315 + nCount++;
1.316 + cDescrF2 f2(d);
1.317 + if(f2.Next()) {
1.318 + if(nCount==1) firstTime=f2.StartTime();
1.319 + else {
1.320 + time_t time=f2.StartTime();
1.321 + if(firstTime<time-5*50 || firstTime>time+5*60)
1.322 + nCount=-1;
1.323 + }
1.324 + }
1.325 + }
1.326 + break;
1.327 + case SI::ExtendedEventDescriptorTag:
1.328 + {
1.329 + SI::ExtendedEventDescriptor *eed=(SI::ExtendedEventDescriptor *)d;
1.330 + if(I18nIsPreferredLanguage(Setup.EPGLanguages,I18nLanguageIndex(eed->languageCode), LanguagePreferenceExt) || !ExtendedEventDescriptors) {
1.331 + delete ExtendedEventDescriptors;
1.332 + ExtendedEventDescriptors=new SI::ExtendedEventDescriptors;
1.333 + UseExtendedEventDescriptor=true;
1.334 + }
1.335 + if(UseExtendedEventDescriptor) {
1.336 + ExtendedEventDescriptors->Add(eed);
1.337 + d=NULL; // so that it is not deleted
1.338 + }
1.339 + if(eed->getDescriptorNumber()==eed->getLastDescriptorNumber())
1.340 + UseExtendedEventDescriptor=false;
1.341 + }
1.342 + break;
1.343 + case SI::ShortEventDescriptorTag:
1.344 + {
1.345 + SI::ShortEventDescriptor *sed=(SI::ShortEventDescriptor *)d;
1.346 + if(I18nIsPreferredLanguage(Setup.EPGLanguages,I18nLanguageIndex(sed->languageCode), LanguagePreferenceShort) || !ShortEventDescriptor) {
1.347 + delete ShortEventDescriptor;
1.348 + ShortEventDescriptor=sed;
1.349 + d=NULL; // so that it is not deleted
1.350 + }
1.351 + }
1.352 + break;
1.353 + default:
1.354 + break;
1.355 + }
1.356 + delete d;
1.357 + }
1.358 +
1.359 + bool Modified=false;
1.360 + int optCount=0;
1.361 + for(SI::Loop::Iterator it; (d=cit.eventDescriptors.getNext(it)); ) {
1.362 + if(d->getDescriptorTag()==0xF2) {
1.363 + optCount++;
1.364 +
1.365 + cDescrF2 f2(d);
1.366 + tChannelID channelID(Source(),f2.OrgNetworkId(),f2.TransportStreamId(),f2.ServiceId());
1.367 + cChannel *channel=Channels.GetByChannelID(channelID,true);
1.368 + if(!channel) continue;
1.369 +
1.370 + cSchedule *pSchedule=(cSchedule *)Schedules->GetSchedule(channelID);
1.371 + if(!pSchedule) {
1.372 + pSchedule=new cSchedule(channelID);
1.373 + Schedules->Add(pSchedule);
1.374 + }
1.375 +
1.376 + for(f2.Start(); f2.Next();) {
1.377 + u_int16_t EventId=(cit.getContentId()<<4) | f2.Index();
1.378 + time_t StartTime=f2.StartTime();
1.379 +
1.380 + bool isOpt=false;
1.381 + if(f2.Index()==0 && nCount>1) isOpt=true;
1.382 +
1.383 + 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))))
1.384 + if(StartTime+cit.getDuration()+Setup.EPGLinger*60<time(0)) {
1.385 + d2(printf("(old)\n"))
1.386 + continue;
1.387 + }
1.388 +
1.389 + cEvent *pEvent=(cEvent *)pSchedule->GetEvent(EventId,StartTime);
1.390 + if(!pEvent) {
1.391 + d2(printf("(new)\n"))
1.392 + pEvent=pSchedule->AddEvent(new cEvent(channelID,EventId));
1.393 + if(!pEvent) continue;
1.394 + }
1.395 + else {
1.396 + d2(printf("(upd)\n"))
1.397 + pEvent->SetSeen();
1.398 + if(pEvent->TableID()==0x00) continue;
1.399 + if(Tid==pEvent->TableID() && pEvent->Version()==cit.getVersionNumber()) continue;
1.400 + }
1.401 + pEvent->SetEventID(EventId);
1.402 + pEvent->SetTableID(Tid);
1.403 + pEvent->SetVersion(cit.getVersionNumber());
1.404 + pEvent->SetStartTime(StartTime);
1.405 + pEvent->SetDuration(cit.getDuration());
1.406 +
1.407 + if(ShortEventDescriptor) {
1.408 + char buffer[256+32];
1.409 + ShortEventDescriptor->name.getText(buffer,sizeof(buffer)-32);
1.410 + if(isOpt) {
1.411 + char o[32];
1.412 + snprintf(o,sizeof(o)," (Option %d)",optCount);
1.413 + strcat(buffer,o);
1.414 + }
1.415 + pEvent->SetTitle(buffer);
1.416 + pEvent->SetShortText(ShortEventDescriptor->text.getText(buffer,sizeof(buffer)));
1.417 + }
1.418 + if(ExtendedEventDescriptors) {
1.419 + char buffer[ExtendedEventDescriptors->getMaximumTextLength(": ")+1];
1.420 + pEvent->SetDescription(ExtendedEventDescriptors->getText(buffer,sizeof(buffer),": "));
1.421 + }
1.422 +
1.423 + pEvent->SetComponents(NULL);
1.424 + pEvent->FixEpgBugs();
1.425 + Modified=true;
1.426 + }
1.427 + if(Modified) {
1.428 + pSchedule->Sort();
1.429 + Schedules->SetModified(pSchedule);
1.430 + }
1.431 + }
1.432 + delete d;
1.433 + }
1.434 + delete ExtendedEventDescriptors;
1.435 + delete ShortEventDescriptor;
1.436 + }
1.437 + }
1.438 + }
1.439 +}
1.440 +
1.441 +// --- cPluginPremiereEpg ------------------------------------------------------
1.442 +
1.443 +class cPluginPremiereEpg : public cPlugin {
1.444 +private:
1.445 + struct {
1.446 + cFilterPremiereEpg *filter;
1.447 + cDevice *device;
1.448 + } epg[MAXDVBDEVICES];
1.449 +public:
1.450 + cPluginPremiereEpg(void);
1.451 + virtual const char *Version(void) { return VERSION; }
1.452 + virtual const char *Description(void) { return DESCRIPTION; }
1.453 + virtual bool Start(void);
1.454 + virtual void Stop(void);
1.455 + };
1.456 +
1.457 +cPluginPremiereEpg::cPluginPremiereEpg(void)
1.458 +{
1.459 + memset(epg,0,sizeof(epg));
1.460 +}
1.461 +
1.462 +bool cPluginPremiereEpg::Start(void)
1.463 +{
1.464 + for(int i=0; i<MAXDVBDEVICES; i++) {
1.465 + cDevice *dev=cDevice::GetDevice(i);
1.466 + if(dev) {
1.467 + epg[i].device=dev;
1.468 + dev->AttachFilter(epg[i].filter=new cFilterPremiereEpg);
1.469 + isyslog("Attached premiere EPG filter to device %d",i);
1.470 + }
1.471 + }
1.472 + return true;
1.473 +}
1.474 +
1.475 +void cPluginPremiereEpg::Stop(void)
1.476 +{
1.477 + for(int i=0; i<MAXDVBDEVICES; i++) {
1.478 + cDevice *dev=epg[i].device;
1.479 + if(dev) dev->Detach(epg[i].filter);
1.480 + delete epg[i].filter;
1.481 + epg[i].device=0;
1.482 + epg[i].filter=0;
1.483 + }
1.484 +}
1.485 +
1.486 +VDRPLUGINCREATOR(cPluginPremiereEpg); // Don't touch this!