summaryrefslogtreecommitdiff
path: root/command/markad-standalone.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'command/markad-standalone.cpp')
-rw-r--r--command/markad-standalone.cpp1632
1 files changed, 1632 insertions, 0 deletions
diff --git a/command/markad-standalone.cpp b/command/markad-standalone.cpp
new file mode 100644
index 0000000..9d4cb82
--- /dev/null
+++ b/command/markad-standalone.cpp
@@ -0,0 +1,1632 @@
+/*
+ * markad-standalone.cpp: A program for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include "markad-standalone.h"
+
+bool SYSLOG=false;
+cMarkAdStandalone *cmasta=NULL;
+int SysLogLevel=2;
+
+void syslog_with_tid(int priority, const char *format, ...)
+{
+ va_list ap;
+ if (SYSLOG)
+ {
+ char fmt[255];
+ snprintf(fmt, sizeof(fmt), "[%d] %s", getpid(), format);
+ va_start(ap, format);
+ vsyslog(priority, fmt, ap);
+ va_end(ap);
+ }
+ else
+ {
+ char fmt[255];
+ snprintf(fmt, sizeof(fmt), "markad: [%d] %s", getpid(), format);
+ va_start(ap, format);
+ vprintf(fmt,ap);
+ va_end(ap);
+ printf("\n");
+ fflush(stdout);
+ }
+}
+
+void cMarkAdStandalone::AddStartMark()
+{
+ char *buf;
+ if (asprintf(&buf,"start of recording (0)")!=-1)
+ {
+ marks.Add(MT_COMMON,0,buf);
+ isyslog(buf);
+ free(buf);
+ }
+}
+
+bool cMarkAdStandalone::CheckFirstMark()
+{
+ if (marksAligned) return true;
+
+ // Check the second mark
+ clMark *second=marks.GetNext(0);
+ if (!second) return false;
+
+ if ((second->type==MT_LOGOSTART) || (second->type==MT_BORDERSTART) ||
+ (second->type==MT_CHANNELSTART))
+ {
+ clMark *first=marks.Get(0);
+ if (first) marks.Del(first);
+ marksAligned=true;
+ }
+
+ if ((second->type==MT_LOGOSTOP) || (second->type==MT_BORDERSTOP) ||
+ (second->type==MT_CHANNELSTOP))
+ {
+ marksAligned=true;
+ }
+
+ // If we have an aspectchange, check the next aspectchange mark
+ // and the difference between
+ if ((second->type==MT_ASPECTCHANGE) && (macontext.Video.Info.FramesPerSecond>0))
+ {
+ clMark *next=marks.GetNext(second->position,MT_ASPECTCHANGE);
+ if (next)
+ {
+ int MAXPOSDIFF=(int) (macontext.Video.Info.FramesPerSecond*60*13);
+ if ((next->position-second->position)>MAXPOSDIFF)
+ {
+ clMark *first=marks.Get(0);
+ if (first) marks.Del(first);
+ marksAligned=true;
+ }
+ }
+ }
+ return marksAligned;
+}
+
+void cMarkAdStandalone::AddMark(MarkAdMark *Mark)
+{
+ if (!Mark) return;
+ if (!Mark->Type) return;
+
+ if (Mark->Type==MT_LOGOSTART)
+ {
+ // check if last mark is an aspectratio change
+ clMark *prev=marks.GetLast();
+ if ((prev) && (prev->type==MT_ASPECTCHANGE))
+ {
+ if ((Mark->Position-prev->position)<20)
+ {
+ if (Mark->Comment) isyslog(Mark->Comment);
+ isyslog("aspectratio change in short distance, moving mark (%i->%i)",Mark->Position,prev->position);
+ marks.Add(MT_MOVED,prev->position,Mark->Comment);
+ return;
+ }
+ }
+ }
+
+ if (((Mark->Type & 0xF0)==MT_LOGOCHANGE) && (macontext.Video.Info.FramesPerSecond>0))
+ {
+ // check if the distance to the last mark is large enough!
+ clMark *prev=marks.GetLast();
+ if ((prev) && ((prev->type & 0xF0)==MT_LOGOCHANGE))
+ {
+ int MINMARKDIFF=(int) (macontext.Video.Info.FramesPerSecond*60*2);
+ if ((Mark->Position-prev->position)<MINMARKDIFF)
+ {
+ if (Mark->Comment) isyslog(Mark->Comment);
+ isyslog("logo distance too short, deleting (%i,%i)",prev->position,Mark->Position);
+ marks.Del(prev);
+ return;
+ }
+ }
+ }
+
+ if (((Mark->Type & 0xF0)==MT_BORDERCHANGE) && (Mark->Position>25000) &&
+ (!macontext.Video.Options.IgnoreLogoDetection))
+ {
+ isyslog("border change detected. logo detection disabled");
+ macontext.Video.Options.IgnoreLogoDetection=true;
+ marks.Del(MT_LOGOSTART);
+ marks.Del(MT_LOGOSTOP);
+ }
+
+ if ((((Mark->Type & 0xF0)==MT_CHANNELCHANGE) || (Mark->Type==MT_ASPECTCHANGE)) &&
+ (Mark->Position>25000) && (bDecodeVideo))
+ {
+ bool TurnOff=true;
+ if (Mark->Type==MT_ASPECTCHANGE)
+ {
+ if (marks.Count(MT_ASPECTCHANGE)<3)
+ {
+ TurnOff=false;
+ }
+ }
+
+ if (Mark->Type==MT_CHANNELCHANGE)
+ {
+ if ((marks.Count(MT_CHANNELSTART)+marks.Count(MT_CHANNELSTOP))<3)
+ {
+ TurnOff=false;
+ }
+ }
+
+ if (TurnOff)
+ {
+ isyslog("%s change detected. logo/border detection disabled",
+ Mark->Type==MT_ASPECTCHANGE ? "aspectratio" : "audio channel");
+
+ bDecodeVideo=false;
+ macontext.Video.Data.Valid=false;
+ marks.Del(MT_LOGOSTART);
+ marks.Del(MT_LOGOSTOP);
+ marks.Del(MT_BORDERSTART);
+ marks.Del(MT_BORDERSTOP);
+ }
+ }
+ CheckFirstMark();
+ clMark *old=marks.Get(Mark->Position);
+ if (old)
+ {
+ // Aspect- / Channelchange wins over Logo/Border
+ if (((old->type & 0xF0)!=MT_ASPECTCHANGE) && ((old->type & 0xF0)!=MT_CHANNELCHANGE))
+ {
+ if (Mark->Comment) isyslog(Mark->Comment);
+ marks.Add(Mark->Type,Mark->Position,Mark->Comment);
+ }
+ }
+ else
+ {
+ if (Mark->Comment) isyslog(Mark->Comment);
+ marks.Add(Mark->Type,Mark->Position,Mark->Comment);
+ }
+}
+
+void cMarkAdStandalone::RateMarks()
+{
+ if (macontext.Info.Length)
+ {
+ if ((marks.Count()>3) && (macontext.Info.Length>30))
+ {
+ int logomarks=marks.Count(MT_LOGOSTART) + marks.Count(MT_LOGOSTOP);
+ int audiomarks=marks.Count(MT_CHANNELSTART) + marks.Count(MT_CHANNELSTOP);
+
+ // If we have logomarks or audiomarks get rid of the aspect changes,
+ // cause if we have a recording with (>=3) aspect changes the
+ // logomarks were already deleted in AddMark
+ if ((logomarks) || (audiomarks))
+ {
+ marks.Del(MT_ASPECTCHANGE);
+ }
+ }
+ }
+
+ // Check the first mark again
+ CheckFirstMark();
+
+ // TODO: more checks
+}
+
+void cMarkAdStandalone::SaveFrame(int frame)
+{
+ if (!macontext.Video.Info.Width) return;
+ if (!macontext.Video.Data.Valid) return;
+
+ FILE *pFile;
+ char szFilename[256];
+
+ // Open file
+ sprintf(szFilename, "/tmp/frame%06d.pgm", frame);
+ pFile=fopen(szFilename, "wb");
+ if (pFile==NULL)
+ return;
+
+ // Write header
+ fprintf(pFile, "P5\n%d %d\n255\n", macontext.Video.Data.PlaneLinesize[0],
+ macontext.Video.Info.Height);
+
+ // Write pixel data
+ fwrite(macontext.Video.Data.Plane[0],1,
+ macontext.Video.Data.PlaneLinesize[0]*macontext.Video.Info.Height,pFile);
+ // Close file
+ fclose(pFile);
+}
+
+void cMarkAdStandalone::CheckIndex(const char *Directory)
+{
+ // Here we check if the index is more
+ // advanced than our framecounter.
+ // If not we wait. If we wait too much,
+ // we discard this check...
+
+#define WAITTIME 10
+
+ if (!indexFile) return;
+ if (sleepcnt>=2) return; // we already slept too much
+
+ bool notenough=true;
+ do
+ {
+ struct stat statbuf;
+ if (stat(indexFile,&statbuf)==-1) return;
+
+ int maxframes=statbuf.st_size/8;
+ if (maxframes<(framecnt+200))
+ {
+ if ((difftime(time(NULL),statbuf.st_mtime))>=10) return; // "old" file
+ marks.Save(Directory,macontext.Video.Info.FramesPerSecond,isTS);
+ sleep(WAITTIME); // now we sleep and hopefully the index will grow
+ waittime+=WAITTIME;
+ if (errno==EINTR) return;
+ sleepcnt++;
+ if (sleepcnt>=2)
+ {
+ esyslog("no new data after %i seconds, skipping wait!",
+ sleepcnt*WAITTIME);
+ notenough=false; // something went wrong?
+ }
+ }
+ else
+ {
+ sleepcnt=0;
+ notenough=false;
+ }
+ }
+ while (notenough);
+}
+
+bool cMarkAdStandalone::ProcessFile(const char *Directory, int Number)
+{
+ if (!Directory) return false;
+ if (!Number) return false;
+
+ CheckIndex(Directory);
+ if (abort) return false;
+
+ const int datalen=385024;
+ uchar data[datalen];
+
+ char *fbuf;
+ if (isTS)
+ {
+ if (asprintf(&fbuf,"%s/%05i.ts",Directory,Number)==-1) return false;
+ }
+ else
+ {
+ if (asprintf(&fbuf,"%s/%03i.vdr",Directory,Number)==-1) return false;
+ }
+
+ int f=open(fbuf,O_RDONLY);
+ free(fbuf);
+ if (f==-1) return false;
+
+ int dataread;
+ dsyslog("processing file %05i",Number);
+
+ while ((dataread=read(f,data,datalen))>0)
+ {
+ if (abort) break;
+ MarkAdMark *mark=NULL;
+
+ if ((video_demux) && (video) && (streaminfo))
+ {
+ uchar *pkt;
+ int pktlen;
+
+ uchar *tspkt = data;
+ int tslen = dataread;
+
+ while (tslen>0)
+ {
+ int len=video_demux->Process(macontext.Info.VPid,tspkt,tslen,&pkt,&pktlen);
+ if (len<0)
+ {
+ break;
+ }
+ else
+ {
+ if (pkt)
+ {
+ if (streaminfo->FindVideoInfos(&macontext,pkt,pktlen))
+ {
+ if (!framecnt)
+ {
+ isyslog("%s %i%c",(macontext.Video.Info.Height>576) ? "HDTV" : "SDTV",
+ macontext.Video.Info.Height,
+ macontext.Video.Info.Interlaced ? 'i' : 'p');
+ AddStartMark();
+ }
+ //printf("%05i( %c )\n",framecnt,frametypes[macontext.Video.Info.Pict_Type]);
+ framecnt++;
+ if (macontext.Video.Info.Pict_Type==MA_I_TYPE)
+ {
+ lastiframe=iframe;
+ iframe=framecnt-1;
+ }
+ }
+
+ bool dRes=true;
+ if ((decoder) && (bDecodeVideo)) dRes=decoder->DecodeVideo(&macontext,pkt,pktlen);
+ if (dRes)
+ {
+ if ((framecnt-iframe)<=3)
+ {
+ mark=video->Process(lastiframe);
+ AddMark(mark);
+ //SaveFrame(lastiframe); // TODO: JUST FOR DEBUGGING!
+ }
+ }
+ }
+ tspkt+=len;
+ tslen-=len;
+ }
+ }
+ }
+
+ if ((ac3_demux) && (streaminfo) && (audio))
+ {
+ uchar *pkt;
+ int pktlen;
+
+ uchar *tspkt = data;
+ int tslen = dataread;
+
+ while (tslen>0)
+ {
+ int len=ac3_demux->Process(macontext.Info.DPid,tspkt,tslen,&pkt,&pktlen);
+ if (len<0)
+ {
+ break;
+ }
+ else
+ {
+ if (pkt)
+ {
+ if (streaminfo->FindAC3AudioInfos(&macontext,pkt,pktlen))
+ {
+ if ((!isTS) && (!noticeVDR_AC3))
+ {
+ dsyslog("found AC3");
+ if (mp2_demux)
+ {
+ delete mp2_demux;
+ mp2_demux=NULL;
+ }
+ noticeVDR_AC3=true;
+ }
+ mark=audio->Process(lastiframe);
+ AddMark(mark);
+ }
+ }
+ tspkt+=len;
+ tslen-=len;
+ }
+ }
+ }
+
+ if ((mp2_demux) && (decoder) && (audio) && (bDecodeAudio))
+ {
+ uchar *pkt;
+ int pktlen;
+
+ uchar *tspkt = data;
+ int tslen = dataread;
+
+ while (tslen>0)
+ {
+ int len=mp2_demux->Process(macontext.Info.APid,tspkt,tslen,&pkt,&pktlen);
+ if (len<0)
+ {
+ break;
+ }
+ else
+ {
+ if (pkt)
+ {
+ if (decoder->DecodeMP2(&macontext,pkt,pktlen))
+ {
+ if ((!isTS) && (!noticeVDR_MP2))
+ {
+ dsyslog("found MP2");
+ noticeVDR_MP2=true;
+ }
+ mark=audio->Process(lastiframe);
+ AddMark(mark);
+ }
+ }
+ tspkt+=len;
+ tslen-=len;
+ }
+ }
+ }
+
+ CheckIndex(Directory);
+ if (abort)
+ {
+ if (f!=-1) close(f);
+ return false;
+ }
+ }
+ close(f);
+ return true;
+}
+
+void cMarkAdStandalone::Process(const char *Directory)
+{
+ if (abort) return;
+
+ struct timeval tv1,tv2;
+ struct timezone tz;
+
+ gettimeofday(&tv1,&tz);
+
+ if (bBackupMarks) marks.Backup(Directory,isTS);
+
+ for (int i=1; i<=MaxFiles; i++)
+ {
+ if (abort) break;
+ if (!ProcessFile(Directory,i))
+ {
+ break;
+ }
+ }
+ if (abort)
+ {
+ isyslog("aborted");
+ }
+ else
+ {
+ if (lastiframe)
+ {
+ MarkAdMark tempmark;
+ tempmark.Type=MT_COMMON;
+ tempmark.Position=lastiframe;
+ char *buf;
+
+ if (asprintf(&buf,"stop of recording (%i)",lastiframe)!=-1)
+ {
+ tempmark.Comment=buf;
+ AddMark(&tempmark);
+ free(buf);
+ }
+ }
+
+ gettimeofday(&tv2,&tz);
+ time_t sec;
+ suseconds_t usec;
+ sec=tv2.tv_sec-tv1.tv_sec;
+ usec=tv2.tv_usec-tv1.tv_usec;
+ if (usec<0)
+ {
+ usec+=1000000;
+ sec--;
+ }
+ RateMarks();
+ if (marks.Save(Directory,macontext.Video.Info.FramesPerSecond,isTS))
+ {
+ bool bIndexError=false;
+ if (marks.CheckIndex(Directory,isTS,&bIndexError))
+ {
+ if (bIndexError)
+ {
+ if ((macontext.Info.VPid.Type==MARKAD_PIDTYPE_VIDEO_H264) && (!isTS))
+ {
+ esyslog("index doesn't match marks, sorry you're lost");
+ }
+ else
+ {
+ esyslog("index doesn't match marks%s",
+ isTS ? ", please recreate it" : ", please run genindex");
+ }
+ }
+ }
+ }
+
+ double etime,ftime=0,ptime=0;
+ etime=sec+((double) usec/1000000)-waittime;
+ if (etime>0) ftime=framecnt/etime;
+ if (macontext.Video.Info.FramesPerSecond>0)
+ ptime=ftime/macontext.Video.Info.FramesPerSecond;
+ isyslog("processed time %.2fs, %i frames, %.1f fps, %.1f pps",
+ etime,framecnt,ftime,ptime);
+
+ }
+}
+
+bool cMarkAdStandalone::LoadInfo(const char *Directory)
+{
+ char *buf;
+ if (isTS)
+ {
+ if (asprintf(&buf,"%s/info",Directory)==-1) return false;
+ }
+ else
+ {
+ if (asprintf(&buf,"%s/info.vdr",Directory)==-1) return false;
+ }
+
+ FILE *f;
+ f=fopen(buf,"r");
+ if (!f)
+ {
+ free(buf);
+ return false;
+ }
+
+ char *line=NULL;
+ size_t length;
+ while (getline(&line,&length,f)!=-1)
+ {
+ if (line[0]=='C')
+ {
+ int ntok=0;
+ char *str=line,*tok;
+ while ((tok=strtok(str," ")))
+ {
+ if (ntok==1) macontext.Info.ChannelID=strdup(tok);
+ ntok++;
+ str=NULL;
+ }
+ if (macontext.Info.ChannelID)
+ {
+ for (int i=0; i<(int) strlen(macontext.Info.ChannelID); i++)
+ {
+ if (macontext.Info.ChannelID[i]=='.') macontext.Info.ChannelID[i]='_';
+ }
+ }
+ }
+ if (line[0]=='E')
+ {
+ int result=sscanf(line,"%*c %*i %*i %i %*i %*x",&macontext.Info.Length);
+ if (result==1)
+ {
+ macontext.Info.Length/=60;
+ }
+ else
+ {
+ macontext.Info.Length=0;
+ }
+ if ((bIgnoreAudioInfo) && (bIgnoreVideoInfo)) break;
+ }
+ if (line[0]=='X')
+ {
+ int stream=0,type=0;
+ char descr[256]="";
+ int result=sscanf(line,"%*c %i %i %250c",&stream,&type,(char *) &descr);
+ if ((result!=0) && (result!=EOF))
+ {
+ if ((stream==1) && (!bIgnoreVideoInfo))
+ {
+ if ((type!=1) && (type!=5))
+ {
+ // we dont have 4:3, so ignore AspectRatio-Changes
+ macontext.Video.Options.IgnoreAspectRatio=true;
+ isyslog("broadcasts aspectratio is not 4:3, disabling aspect ratio");
+ }
+ }
+
+ if ((stream==2) && (!bIgnoreAudioInfo))
+ {
+ if (type==5)
+ {
+ // if we have DolbyDigital 2.0 disable AC3
+ if (strchr(descr,'2'))
+ {
+ macontext.Info.DPid.Num=0;
+ isyslog("broadcast with DolbyDigital2.0, disabling AC3 decoding");
+ }
+ // if we have DolbyDigital 5.1 disable video decoding
+ if (strchr(descr,'5'))
+ {
+ bDecodeVideo=false;
+ isyslog("broadcast with DolbyDigital5.1, disabling video decoding");
+ }
+
+ }
+ }
+ }
+ }
+ }
+ if (line) free(line);
+
+ fclose(f);
+ free(buf);
+ if (!macontext.Info.ChannelID)
+ {
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+}
+
+bool cMarkAdStandalone::CheckTS(const char *Directory)
+{
+ MaxFiles=0;
+ isTS=false;
+ if (!Directory) return false;
+ char *buf;
+ if (asprintf(&buf,"%s/00001.ts",Directory)==-1) return false;
+ struct stat statbuf;
+ if (stat(buf,&statbuf)==-1)
+ {
+ if (errno!=ENOENT)
+ {
+ free(buf);
+ return false;
+ }
+ free(buf);
+ if (asprintf(&buf,"%s/001.vdr",Directory)==-1) return false;
+ if (stat(buf,&statbuf)==-1)
+ {
+ free(buf);
+ return false;
+ }
+ free(buf);
+ // .VDR detected
+ isTS=false;
+ MaxFiles=999;
+ return true;
+ }
+ free(buf);
+ // .TS detected
+ isTS=true;
+ MaxFiles=65535;
+ return true;
+}
+
+bool cMarkAdStandalone::CheckVDRHD(const char *Directory)
+{
+ char *buf;
+ if (asprintf(&buf,"%s/001.vdr",Directory)==-1) return false;
+
+ int fd=open(buf,O_RDONLY);
+ free(buf);
+ if (fd==-1) return false;
+
+ uchar pes_buf[32];
+ if (read(fd,pes_buf,sizeof(pes_buf))!=sizeof(pes_buf))
+ {
+ close(fd);
+ return false;
+ }
+ close(fd);
+
+ if ((pes_buf[0]==0) && (pes_buf[1]==0) && (pes_buf[2]==1) && ((pes_buf[3] & 0xF0)==0xE0))
+ {
+ int payloadstart=9+pes_buf[8];
+ if (payloadstart>23) return false;
+ uchar *start=&pes_buf[payloadstart];
+ if ((start[0]==0) && (start[1]==0) && (start[2]==1) && (start[5]==0) && (start[6]==0)
+ && (start[7]==0) && (start[8]==1))
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool cMarkAdStandalone::CheckPATPMT(const char *Directory)
+{
+ char *buf;
+ if (asprintf(&buf,"%s/00001.ts",Directory)==-1) return false;
+
+ int fd=open(buf,O_RDONLY);
+ free(buf);
+ if (fd==-1) return false;
+
+ uchar patpmt_buf[564];
+ uchar *patpmt;
+
+ if (read(fd,patpmt_buf,sizeof(patpmt_buf))!=sizeof(patpmt_buf))
+ {
+ close(fd);
+ return false;
+ }
+ close(fd);
+ patpmt=patpmt_buf;
+
+ if ((patpmt[0]==0x47) && ((patpmt[1] & 0x5F)==0x40) && (patpmt[2]==0x11) &&
+ ((patpmt[3] & 0x10)==0x10)) patpmt+=188; // skip SDT
+
+ // some checks
+ if ((patpmt[0]!=0x47) || (patpmt[188]!=0x47)) return false; // no TS-Sync
+ if (((patpmt[1] & 0x5F)!=0x40) && (patpmt[2]!=0)) return false; // no PAT
+ if ((patpmt[3] & 0x10)!=0x10) return false; // PAT not without AFC
+ if ((patpmt[191] & 0x10)!=0x10) return false; // PMT not without AFC
+ struct PAT *pat = (struct PAT *) &patpmt[5];
+
+ // more checks
+ if (pat->reserved1!=3) return false; // is always 11
+ if (pat->reserved3!=7) return false; // is always 111
+
+ int pid=pat->pid_L+(pat->pid_H<<8);
+ int pmtpid=((patpmt[189] & 0x1f)<<8)+patpmt[190];
+ if (pid!=pmtpid) return false; // pid in PAT differs from pid in PMT
+
+ struct PMT *pmt = (struct PMT *) &patpmt[193];
+
+ // still more checks
+ if (pmt->reserved1!=3) return false; // is always 11
+ if (pmt->reserved2!=3) return false; // is always 11
+ if (pmt->reserved3!=7) return false; // is always 111
+ if (pmt->reserved4!=15) return false; // is always 1111
+
+ if ((pmt->program_number_H!=pat->program_number_H) ||
+ (pmt->program_number_L!=pat->program_number_L)) return false;
+
+ int desc_len=(pmt->program_info_length_H<<8)+pmt->program_info_length_L;
+ if (desc_len>166) return false; // beyond patpmt buffer
+
+ int section_end = 196+(pmt->section_length_H<<8)+pmt->section_length_L;
+ section_end-=4; // we don't care about the CRC32
+ if (section_end>376) return false; //beyond patpmt buffer
+
+ int i=205+desc_len;
+
+ while (i<section_end)
+ {
+ struct ES_DESCRIPTOR *es=NULL;
+ struct STREAMINFO *si = (struct STREAMINFO *) &patpmt[i];
+ int esinfo_len=(si->ES_info_length_H<<8)+si->ES_info_length_L;
+ if (esinfo_len)
+ {
+ es = (struct ES_DESCRIPTOR *) &patpmt[i+sizeof(struct STREAMINFO)];
+ }
+
+ // oh no -> more checks!
+ if (si->reserved1!=7) return false;
+ if (si->reserved2!=15) return false;
+
+ int pid=(si->PID_H<<8)+si->PID_L;
+
+ switch (si->stream_type)
+ {
+ case 0x1:
+ case 0x2:
+ macontext.Info.VPid.Type=MARKAD_PIDTYPE_VIDEO_H262;
+ // just use the first pid
+ if (!macontext.Info.VPid.Num) macontext.Info.VPid.Num=pid;
+ break;
+
+ case 0x3:
+ case 0x4:
+ // just use the first pid
+ if (!macontext.Info.APid.Num) macontext.Info.APid.Num=pid;
+ break;
+
+ case 0x6:
+ if (es)
+ {
+ if (es->Descriptor_Tag==0x6A) macontext.Info.DPid.Num=pid;
+ }
+ break;
+
+ case 0x1b:
+ macontext.Info.VPid.Type=MARKAD_PIDTYPE_VIDEO_H264;
+ // just use the first pid
+ if (!macontext.Info.VPid.Num) macontext.Info.VPid.Num=pid;
+ break;
+ }
+
+ i+=(sizeof(struct STREAMINFO)+esinfo_len);
+ }
+
+ return true;
+}
+
+bool cMarkAdStandalone::CreatePidfile(const char *Directory)
+{
+ char *buf;
+ if (asprintf(&buf,"%s/markad.pid",Directory)==-1) return false;
+
+ // check for other running markad process
+ FILE *oldpid=fopen(buf,"r");
+ if (oldpid)
+ {
+ // found old pidfile, check if it's still running
+ int pid;
+ if (fscanf(oldpid,"%i\n",&pid)==1)
+ {
+ char procname[256]="";
+ snprintf(procname,sizeof(procname),"/proc/%i",pid);
+ struct stat statbuf;
+ if (stat(procname,&statbuf)!=-1)
+ {
+ // found another, running markad
+ isyslog("another instance is running on this recording");
+ abort=duplicate=true;
+ }
+ }
+ fclose(oldpid);
+ }
+ if (duplicate) return false;
+
+ FILE *pidfile=fopen(buf,"w+");
+
+ if (getuid()==0 || geteuid()!=0)
+ {
+ // if we are root, set fileowner to owner of 001.vdr/00001.ts file
+ char *spath=NULL;
+ if (asprintf(&spath,"%s/%s",Directory,isTS ? "00001.ts" : "001.vdr")!=-1)
+ {
+ struct stat statbuf;
+ if (!stat(spath,&statbuf))
+ {
+ chown(buf,statbuf.st_uid, statbuf.st_gid);
+ }
+ free(spath);
+ }
+ }
+
+ free(buf);
+ if (!pidfile) return false;
+ fprintf(pidfile,"%i\n",(int) getpid());
+ fflush(pidfile);
+ fclose(pidfile);
+ return true;
+}
+
+void cMarkAdStandalone::RemovePidfile(const char *Directory)
+{
+ char *buf;
+ if (asprintf(&buf,"%s/markad.pid",Directory)!=-1)
+ {
+ unlink(buf);
+ free(buf);
+ }
+}
+
+const char cMarkAdStandalone::frametypes[8]={'?','I','P','B','D','S','s','b'};
+
+cMarkAdStandalone::cMarkAdStandalone(const char *Directory, bool BackupMarks, int LogoExtraction,
+ int LogoWidth, int LogoHeight, bool DecodeVideo,
+ bool DecodeAudio, bool IgnoreVideoInfo, bool IgnoreAudioInfo,
+ const char *LogoDir, const char *MarkFileName, bool ASD,
+ bool noPid)
+{
+ directory=Directory;
+ abort=false;
+
+ noticeVDR_MP2=false;
+ noticeVDR_AC3=false;
+
+ sleepcnt=0;
+ waittime=0;
+ duplicate=false;
+ marksAligned=false;
+
+ memset(&macontext,0,sizeof(macontext));
+ macontext.LogoDir=(char *) LogoDir;
+ macontext.Options.LogoExtraction=LogoExtraction;
+ macontext.Options.LogoWidth=LogoWidth;
+ macontext.Options.LogoHeight=LogoHeight;
+ macontext.Audio.Options.AudioSilenceDetection=ASD;
+
+ bDecodeVideo=DecodeVideo;
+ bDecodeAudio=DecodeAudio;
+ bIgnoreAudioInfo=IgnoreAudioInfo;
+ bIgnoreVideoInfo=IgnoreVideoInfo;
+
+ bBackupMarks=BackupMarks;
+
+ macontext.Info.DPid.Type=MARKAD_PIDTYPE_AUDIO_AC3;
+ macontext.Info.APid.Type=MARKAD_PIDTYPE_AUDIO_MP2;
+
+ isyslog("starting v%s",VERSION);
+ isyslog("on %s",Directory);
+
+ if (!bDecodeAudio)
+ {
+ isyslog("audio decoding disabled by user");
+ }
+ if (!bDecodeVideo)
+ {
+ isyslog("video decoding disabled by user");
+ }
+ if (bIgnoreAudioInfo)
+ {
+ isyslog("audio info usage disabled by user");
+ }
+ if (bIgnoreVideoInfo)
+ {
+ isyslog("video info usage disabled by user");
+ }
+
+ if (LogoExtraction!=-1)
+ {
+ // just to be sure extraction works
+ bDecodeVideo=true;
+ bIgnoreAudioInfo=true;
+ bIgnoreVideoInfo=true;
+ }
+
+ if (!CheckTS(Directory))
+ {
+ video_demux=NULL;
+ ac3_demux=NULL;
+ mp2_demux=NULL;
+ decoder=NULL;
+ video=NULL;
+ audio=NULL;
+ return;
+ }
+
+ if (!noPid)
+ {
+ CreatePidfile(Directory);
+ if (abort)
+ {
+ video_demux=NULL;
+ ac3_demux=NULL;
+ mp2_demux=NULL;
+ decoder=NULL;
+ video=NULL;
+ audio=NULL;
+ return;
+ }
+ }
+
+ if (isTS)
+ {
+ if (!CheckPATPMT(Directory))
+ {
+ esyslog("no PAT/PMT found -> nothing to process");
+ abort=true;
+ }
+ if (!macontext.Audio.Options.AudioSilenceDetection)
+ {
+ macontext.Info.APid.Num=0;
+ }
+ if (asprintf(&indexFile,"%s/index",Directory)==-1) indexFile=NULL;
+ }
+ else
+ {
+ if (macontext.Audio.Options.AudioSilenceDetection)
+ {
+ macontext.Info.APid.Num=-1;
+ }
+ macontext.Info.DPid.Num=-1;
+ macontext.Info.VPid.Num=-1;
+
+ if (CheckVDRHD(Directory))
+ {
+ macontext.Info.VPid.Type=MARKAD_PIDTYPE_VIDEO_H264;
+ }
+ else
+ {
+ macontext.Info.VPid.Type=MARKAD_PIDTYPE_VIDEO_H262;
+ }
+ if (asprintf(&indexFile,"%s/index.vdr",Directory)==-1) indexFile=NULL;
+ }
+
+ if (!LoadInfo(Directory))
+ {
+ if (bDecodeVideo)
+ {
+ esyslog("failed loading info - logo %s disabled",
+ (LogoExtraction!=-1) ? "extraction" : "detection");
+ }
+ }
+
+ if (MarkFileName[0]) marks.SetFileName(MarkFileName);
+
+ if (macontext.Info.VPid.Num)
+ {
+ if (isTS)
+ {
+ dsyslog("using %s-video (0x%04x)",
+ macontext.Info.VPid.Type==MARKAD_PIDTYPE_VIDEO_H264 ? "H264": "H262",
+ macontext.Info.VPid.Num);
+ }
+ else
+ {
+ dsyslog("using %s-video",
+ macontext.Info.VPid.Type==MARKAD_PIDTYPE_VIDEO_H264 ? "H264": "H262");
+ }
+ video_demux = new cMarkAdDemux();
+ }
+ else
+ {
+ video_demux=NULL;
+ }
+
+ if (macontext.Info.APid.Num)
+ {
+ if (macontext.Info.APid.Num!=-1)
+ dsyslog("using MP2 (0x%04x)",macontext.Info.APid.Num);
+ mp2_demux = new cMarkAdDemux();
+ }
+ else
+ {
+ mp2_demux=NULL;
+ }
+
+ if (macontext.Info.DPid.Num)
+ {
+ if (macontext.Info.DPid.Num!=-1)
+ dsyslog("using AC3 (0x%04x)",macontext.Info.DPid.Num);
+ ac3_demux = new cMarkAdDemux();
+ }
+ else
+ {
+ ac3_demux=NULL;
+ }
+
+ if (!abort)
+ {
+ decoder = new cMarkAdDecoder(macontext.Info.VPid.Type==MARKAD_PIDTYPE_VIDEO_H264,
+ macontext.Info.APid.Num!=0,macontext.Info.DPid.Num!=0);
+ video = new cMarkAdVideo(&macontext);
+ audio = new cMarkAdAudio(&macontext);
+ streaminfo = new cMarkAdStreamInfo;
+ if (macontext.Info.ChannelID)
+ dsyslog("channel %s",macontext.Info.ChannelID);
+ }
+ else
+ {
+ decoder=NULL;
+ video=NULL;
+ audio=NULL;
+ streaminfo=NULL;
+ }
+
+ framecnt=0;
+ lastiframe=0;
+ iframe=0;
+}
+
+cMarkAdStandalone::~cMarkAdStandalone()
+{
+ if (macontext.Info.ChannelID) free(macontext.Info.ChannelID);
+ if (indexFile) free(indexFile);
+
+ if (video_demux) delete video_demux;
+ if (ac3_demux) delete ac3_demux;
+ if (mp2_demux) delete mp2_demux;
+ if (decoder) delete decoder;
+ if (video) delete video;
+ if (audio) delete audio;
+ if (streaminfo) delete streaminfo;
+
+ if ((directory) && (!duplicate)) RemovePidfile(directory);
+}
+
+bool isnumber(const char *s)
+{
+ while (*s)
+ {
+ if (!isdigit(*s))
+ return false;
+ s++;
+ }
+ return true;
+}
+
+int usage()
+{
+ // nothing done, give the user some help
+ printf("Usage: markad [options] cmd <record>\n"
+ "options:\n"
+ "-b --background\n"
+ " markad runs as a background-process\n"
+ " this will be automatically set if called with \"after\"\n"
+ "-d --disable=<option>\n"
+ " <option> 1 = disable video 2 = disable audio\n"
+ " 3 = disable video and audio\n"
+ "-i --ignoreinfo=<info>\n"
+ " ignores hints from info(.vdr) file\n"
+ " <info> 1 = ignore audio info 2 = ignore video info\n"
+ " 3 = ignore video and audio info\n"
+ "-l --logocachedir\n"
+ " directory where logos stored, default /var/lib/markad\n"
+ "-p, --priority level=<priority>\n"
+ " priority-level of markad when running in background\n"
+ " <-20...19> default 19\n"
+ "-v, --verbose\n"
+ " increments loglevel by one, can be given multiple times\n"
+ "-B --backupmarks\n"
+ " make a backup of existing marks\n"
+ "-L --extractlogo=<direction>[,width[,height]]\n"
+ " extracts logo to /tmp as pgm files (must be renamed)\n"
+ " <direction> 0 = top left, 1 = top right\n"
+ " 2 = bottom left, 3 = bottom right\n"
+ " [width] range from 50 to %3i, default %3i (SD)\n"
+ " default %3i (HD)\n"
+ " [height] range from 20 to %3i, default %3i\n"
+ "-O, --OSD\n"
+ " markad sends an OSD-Message for start and end\n"
+ "-V --version\n"
+ " print version-info and exit\n"
+ " --asd\n"
+ " enable audio silence detecion\n"
+ " --markfile=<markfilename>\n"
+ " set a different markfile-name\n"
+ " --online[=1|2] (default is 1)\n"
+ " start markad immediately when called with \"before\" as cmd\n"
+ " if online is 1, markad starts online for live-recordings\n"
+ " only, online=2 starts markad online for every recording\n"
+ " live-recordings are identified by having a '@' in the\n"
+ " filename so the entry 'Mark instant recording' in the menu\n"
+ " 'Setup - Recording' of the vdr should be set to 'yes'\n"
+ "\ncmd: one of\n"
+ "- dummy-parameter if called directly\n"
+ "after markad starts to analyze the recording\n"
+ "before markad exits immediately if called with \"before\"\n"
+ "edited markad exits immediately if called with \"edited\"\n"
+ "nice runs markad with nice(19)\n"
+ "\n<record> is the name of the directory where the recording\n"
+ " is stored\n\n",
+ LOGO_MAXWIDTH,LOGO_DEFWIDTH,LOGO_DEFHDWIDTH,
+ LOGO_MAXHEIGHT,LOGO_DEFHEIGHT
+ );
+ return -1;
+}
+
+void signal_handler(int sig)
+{
+ if (sig==SIGUSR1)
+ {
+ // TODO: what we are supposed to do?
+ }
+ else
+ {
+ if (cmasta)
+ {
+ cmasta->SetAbort();
+ }
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ int c;
+ bool bAfter=false,bBefore=false,bEdited=false;
+ bool bFork=false,bNice=false,bImmediateCall=false;
+ bool bASD=false;
+ int niceLevel = 19;
+ char *recDir=NULL;
+ char *tok,*str;
+ int ntok;
+ int logoExtraction=-1;
+ int logoWidth=-1;
+ int logoHeight=-1;
+ bool bBackupMarks=false,bNoPid=false;
+ char markFileName[1024]="";
+ char logoDirectory[1024]="";
+ bool bDecodeVideo=true;
+ bool bDecodeAudio=true;
+ bool bIgnoreAudioInfo=false;
+ bool bIgnoreVideoInfo=false;
+ int online=1;
+
+ strcpy(logoDirectory,"/var/lib/markad");
+
+ while (1)
+ {
+ int option_index = 0;
+ static struct option long_options[] =
+ {
+ {"ac3",0,0,'a'
+ },
+ {"background", 0, 0, 'b'},
+ {"comments", 0, 0, 'c'},
+ {"disable", 1, 0, 'd'},
+ {"ignoreinfo", 1, 0, 'i' },
+ {"jumplogo",0,0,'j'},
+ {"logocachedir", 1, 0, 'l'},
+ {"nelonen",0,0,'n'},
+ {"overlap",0,0,'o' },
+ {"priority",1,0,'p'},
+ {"statisticfile",1,0,'s'},
+ {"verbose", 0, 0, 'v'},
+
+ {"asd",0,0,6},
+ {"loglevel",1,0,2},
+ {"markfile",1,0,1},
+ {"nopid",0,0,5},
+ {"online",2,0,4},
+ {"pass3only",0,0,7},
+ {"testmode",0,0,3},
+
+ {"backupmarks", 0, 0, 'B'},
+ {"scenechangedetection", 0, 0, 'C'},
+ {"extractlogo", 1, 0, 'L'},
+ {"OSD",0,0,'O' },
+ {"savelogo", 0, 0, 'S'},
+ {"version", 0, 0, 'V'},
+
+ {0, 0, 0, 0}
+ };
+
+ c = getopt_long (argc, argv, "abcd:i:jl:nop:s:vBCL:O:SV",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c)
+ {
+
+ case 'a':
+ // --ac3
+ break;
+
+ case 'b':
+ // --background
+ bFork = SYSLOG = true;
+ break;
+
+ case 'c':
+ // --comments
+ break;
+
+ case 'd':
+ // --disable
+ switch (atoi(optarg))
+ {
+ case 1:
+ bDecodeVideo=false;
+ break;
+ case 2:
+ bDecodeAudio=false;
+ break;
+ case 3:
+ bDecodeVideo=false;
+ bDecodeAudio=false;
+ break;
+ default:
+ fprintf(stderr, "markad: invalid disable option: %s\n", optarg);
+ return 2;
+ break;
+ }
+ break;
+
+ case 'i':
+ // --ignoreinfo
+ switch (atoi(optarg))
+ {
+ case 1:
+ bIgnoreAudioInfo=true;
+ break;
+ case 2:
+ bIgnoreVideoInfo=true;
+ break;
+ case 3:
+ bIgnoreVideoInfo=true;
+ bIgnoreAudioInfo=true;
+ break;
+ default:
+ fprintf(stderr, "markad: invalid ignoreinfo option: %s\n", optarg);
+ return 2;
+ break;
+ }
+ break;
+
+ case 'j':
+ // --jumplogo
+ break;
+
+ case 'l':
+ strncpy(logoDirectory,optarg,1024);
+ logoDirectory[1023]=0;
+ break;
+
+ case 'n':
+ // --nelonen
+ break;
+
+ case 'o':
+ // --overlap
+ break;
+
+ case 'p':
+ // --priority
+ if (isnumber(optarg) || *optarg=='-')
+ niceLevel = atoi(optarg);
+ else
+ {
+ fprintf(stderr, "markad: invalid priority level: %s\n", optarg);
+ return 2;
+ }
+ bNice = true;
+ break;
+
+ case 's':
+ // --statisticfile
+ break;
+
+ case 'v':
+ // --verbose
+ SysLogLevel++;
+ if (SysLogLevel>10) SysLogLevel=10;
+ break;
+
+ case 'B':
+ // --backupmarks
+ bBackupMarks=true;
+ break;
+
+ case 'C':
+ // --scenechangedetection
+ break;
+
+ case 'L':
+ // --extractlogo
+ str=optarg;
+ ntok=0;
+ while (tok=strtok(str,","))
+ {
+ switch (ntok)
+ {
+ case 0:
+ logoExtraction=atoi(tok);
+ if ((logoExtraction<0) || (logoExtraction>3))
+ {
+ fprintf(stderr, "markad: invalid extractlogo value: %s\n", tok);
+ return 2;
+ }
+ break;
+
+ case 1:
+ logoWidth=atoi(tok);
+ if ((logoWidth<50) || (logoWidth>LOGO_MAXWIDTH))
+ {
+ fprintf(stderr, "markad: invalid width value: %s\n", tok);
+ return 2;
+ }
+ break;
+
+ case 2:
+ logoHeight=atoi(tok);
+ if ((logoHeight<20) || (logoHeight>LOGO_MAXHEIGHT))
+ {
+ fprintf(stderr, "markad: invalid height value: %s\n", tok);
+ return 2;
+ }
+ break;
+
+ default:
+ break;
+ }
+ str=NULL;
+ ntok++;
+ }
+ break;
+
+ case 'O':
+ // --OSD
+ break;
+
+ case 'S':
+ // --savelogo
+ break;
+
+ case 'V':
+ printf("markad %s - marks advertisements in VDR recordings\n",VERSION);
+ return 0;
+
+ case '?':
+ printf("unknow option ?\n");
+ break;
+
+ case 0:
+ printf ("option %s", long_options[option_index].name);
+ if (optarg)
+ printf (" with arg %s", optarg);
+ printf ("\n");
+ break;
+
+ case 1: // --markfile
+ strncpy(markFileName,optarg,1024);
+ markFileName[1023]=0;
+ break;
+
+ case 2: // --loglevel
+ SysLogLevel=atoi(optarg);
+ if (SysLogLevel>10) SysLogLevel=10;
+ if (SysLogLevel<0) SysLogLevel=2;
+ break;
+
+ case 3: // --testmode
+ break;
+
+ case 4: // --online
+ online=atoi(optarg);
+ if ((online!=1) && (online!=2))
+ {
+ fprintf(stderr, "markad: invalid online value: %s\n", optarg);
+ return 2;
+ }
+ break;
+
+ case 5: // --nopid
+ bNoPid=true;
+ break;
+
+ case 6: // --asd
+ bASD=true;
+ break;
+
+ case 7: // --pass3only
+ break;
+
+ default:
+ printf ("? getopt returned character code 0%o ? (option_index %d)\n", c,option_index);
+ }
+ }
+
+ if (optind < argc)
+ {
+ while (optind < argc)
+ {
+ if (strcmp(argv[optind], "after" ) == 0 )
+ {
+ bAfter = bFork = bNice = SYSLOG = true;
+ }
+ else if (strcmp(argv[optind], "before" ) == 0 )
+ {
+ bBefore = bFork = bNice = SYSLOG = true;
+ }
+ else if (strcmp(argv[optind], "edited" ) == 0 )
+ {
+ bEdited = true;
+ }
+ else if (strcmp(argv[optind], "nice" ) == 0 )
+ {
+ bNice = true;
+ }
+ else if (strcmp(argv[optind], "-" ) == 0 )
+ {
+ bImmediateCall = true;
+ }
+ else
+ {
+ if ( strstr(argv[optind],".rec") != NULL )
+ recDir = argv[optind];
+ }
+ optind++;
+ }
+ }
+
+ // do nothing if called from vdr before/after the video is cutted
+ if ((bAfter) && (online)) return 0;
+ if (bBefore)
+ {
+ if (!online) return 0;
+ if ((online==1) && (!strchr(recDir,'@'))) return 0;
+ }
+ if (bEdited) return 0;
+
+ // we can run, if one of bImmediateCall, bAfter, bBefore or bNice is true
+ // and recDir is given
+ if ( (bImmediateCall || bBefore || bAfter || bNice) && recDir )
+ {
+ // if bFork is given go in background
+ if ( bFork )
+ {
+ (void)umask((mode_t)0011);
+ //close_files();
+ pid_t pid = fork();
+ if (pid < 0)
+ {
+ char *err=strerror(errno);
+ fprintf(stderr, "%s\n",err);
+ esyslog("fork ERROR: %s",err);
+ return 2;
+ }
+ if (pid != 0)
+ {
+ isyslog("forked to pid %d",pid);
+ return 0; // initial program immediately returns
+ }
+ }
+ if ( bFork )
+ {
+ isyslog("(forked) pid %d", getpid());
+ if (chdir("/")==-1)
+ {
+ perror("chdir");
+ exit(EXIT_FAILURE);
+ }
+ if (setsid() == (pid_t)(-1))
+ {
+ perror("setsid");
+ exit(EXIT_FAILURE);
+ }
+ if (signal(SIGHUP, SIG_IGN) == SIG_ERR)
+ {
+ perror("signal(SIGHUP, SIG_IGN)");
+ errno = 0;
+ }
+ int f;
+
+ f = open("/dev/null", O_RDONLY);
+ if (f == -1)
+ {
+ perror("/dev/null");
+ errno = 0;
+ }
+ else
+ {
+ if (dup2(f, fileno(stdin)) == -1)
+ {
+ perror("dup2");
+ errno = 0;
+ }
+ (void)close(f);
+ }
+
+ f = open("/dev/null", O_WRONLY);
+ if (f == -1)
+ {
+ perror("/dev/null");
+ errno = 0;
+ }
+ else
+ {
+ if (dup2(f, fileno(stdout)) == -1)
+ {
+ perror("dup2");
+ errno = 0;
+ }
+ if (dup2(f, fileno(stderr)) == -1)
+ {
+ perror("dup2");
+ errno = 0;
+ }
+ (void)close(f);
+ }
+ }
+
+ int MaxPossibleFileDescriptors = getdtablesize();
+ for (int i = STDERR_FILENO + 1; i < MaxPossibleFileDescriptors; i++)
+ close(i); //close all dup'ed filedescriptors
+
+ // should we renice ?
+ if ( bNice )
+ {
+ if (setpriority(PRIO_PROCESS,0,niceLevel)==-1)
+ {
+ esyslog("failed to set nice to %d",niceLevel);
+ }
+ }
+
+ if (bBefore) sleep(10);
+
+ // now do the work...
+ struct stat statbuf;
+ if (stat(recDir,&statbuf)==-1)
+ {
+ fprintf(stderr,"%s not found\n",recDir);
+ return -1;
+ }
+
+ if (!S_ISDIR(statbuf.st_mode))
+ {
+ fprintf(stderr,"%s is not a directory\n",recDir);
+ return -1;
+ }
+
+ cmasta = new cMarkAdStandalone(recDir,bBackupMarks, logoExtraction, logoWidth, logoHeight,
+ bDecodeVideo,bDecodeAudio,bIgnoreVideoInfo,bIgnoreAudioInfo,
+ logoDirectory,markFileName,bASD,bNoPid);
+ if (!cmasta) return -1;
+
+ // ignore some signals
+ signal(SIGHUP, SIG_IGN);
+
+ // catch some signals
+ signal(SIGINT, signal_handler);
+ signal(SIGTERM, signal_handler);
+ signal(SIGUSR1, signal_handler);
+
+ cmasta->Process(recDir);
+ delete cmasta;
+ return 0;
+ }
+
+ return usage();
+}