From 46c144d3f294b74996cc9276528a9862e4d70efe Mon Sep 17 00:00:00 2001 From: hschang Date: Wed, 14 Jun 2017 18:37:56 +0900 Subject: [PATCH] Support scrambled playback. --- RecordTimer.py | 19 +- configure.ac | 2 + data/setup.xml | 2 + lib/base/httpstream.cpp | 6 + lib/base/httpstream.h | 1 + lib/base/itssource.h | 1 + lib/base/rawfile.cpp | 5 + lib/base/rawfile.h | 1 + lib/dvb/dvb.cpp | 15 +- lib/dvb/dvb.h | 1 + lib/dvb/metaparser.cpp | 8 +- lib/dvb/metaparser.h | 2 +- lib/dvb/pmt.cpp | 76 +++-- lib/dvb/pmt.h | 27 +- lib/dvb/pvrparse.cpp | 6 + lib/dvb_ci/dvbci.cpp | 56 +++- lib/dvb_ci/dvbci.h | 3 +- lib/python/Components/RecordingConfig.py | 4 +- lib/python/Components/SystemInfo.py | 1 + lib/python/Plugins/SystemPlugins/Makefile.am | 2 +- .../SystemPlugins/PvrDescrambleConvert/Makefile.am | 9 + .../PvrDescrambleConvert/PvrDescrambleConvert.py | 370 +++++++++++++++++++++ .../SystemPlugins/PvrDescrambleConvert/__init__.py | 1 + .../PvrDescrambleConvert/meta/Makefile.am | 4 + .../meta/plugin_pvrdescrambleconvert.xml | 16 + .../SystemPlugins/PvrDescrambleConvert/plugin.py | 63 ++++ lib/service/iservice.h | 7 +- lib/service/servicedvb.cpp | 50 +-- lib/service/servicedvbrecord.cpp | 202 ++++++++++- lib/service/servicedvbrecord.h | 14 +- lib/service/servicehdmi.cpp | 2 +- lib/service/servicehdmi.h | 3 +- lib/service/servicem2ts.cpp | 6 + 33 files changed, 892 insertions(+), 93 deletions(-) create mode 100644 lib/python/Plugins/SystemPlugins/PvrDescrambleConvert/Makefile.am create mode 100644 lib/python/Plugins/SystemPlugins/PvrDescrambleConvert/PvrDescrambleConvert.py create mode 100644 lib/python/Plugins/SystemPlugins/PvrDescrambleConvert/__init__.py create mode 100644 lib/python/Plugins/SystemPlugins/PvrDescrambleConvert/meta/Makefile.am create mode 100644 lib/python/Plugins/SystemPlugins/PvrDescrambleConvert/meta/plugin_pvrdescrambleconvert.xml create mode 100644 lib/python/Plugins/SystemPlugins/PvrDescrambleConvert/plugin.py diff --git a/RecordTimer.py b/RecordTimer.py index d3ccd75..32fb923 100755 --- a/RecordTimer.py +++ b/RecordTimer.py @@ -90,7 +90,7 @@ class RecordTimerEntry(timer.TimerEntry, object): Notifications.AddNotification(Screens.Standby.TryQuitMainloop, 1, onSessionOpenCallback=RecordTimerEntry.stopTryQuitMainloop, default_yes = default_yes) ################################################################# - def __init__(self, serviceref, begin, end, name, description, eit, disabled = False, justplay = False, afterEvent = AFTEREVENT.AUTO, checkOldTimers = False, dirname = None, tags = None): + def __init__(self, serviceref, begin, end, name, description, eit, disabled = False, justplay = False, afterEvent = AFTEREVENT.AUTO, checkOldTimers = False, dirname = None, tags = None, descramble = True, record_ecm = False, filename = None): timer.TimerEntry.__init__(self, int(begin), int(end)) if checkOldTimers == True: @@ -122,14 +122,24 @@ class RecordTimerEntry(timer.TimerEntry, object): self.autoincreasetime = 3600 * 24 # 1 day self.tags = tags or [] + self.descramble = descramble + self.record_ecm = record_ecm + self.log_entries = [] self.resetState() - + + self.Filename = filename + self.pvrConvert = False + def log(self, code, msg): self.log_entries.append((int(time()), code, msg)) print "[TIMER]", msg def calculateFilename(self): + if self.Filename: + self.log(0, "Filename calculated as: '%s'" % self.Filename) + return + service_name = self.service_ref.getServiceName() begin_date = strftime("%Y%m%d %H%M", localtime(self.begin)) begin_shortdate = strftime("%Y%m%d", localtime(self.begin)) @@ -196,7 +206,7 @@ class RecordTimerEntry(timer.TimerEntry, object): if event_id is None: event_id = -1 - prep_res=self.record_service.prepare(self.Filename + ".ts", self.begin, self.end, event_id, self.name.replace("\n", ""), self.description.replace("\n", ""), ' '.join(self.tags)) + prep_res=self.record_service.prepare(self.Filename + ".ts", self.begin, self.end, event_id, self.name.replace("\n", ""), self.description.replace("\n", ""), ' '.join(self.tags), bool(self.descramble), bool(self.record_ecm)) if prep_res: if prep_res == -255: self.log(4, "failed to write meta information") @@ -376,6 +386,9 @@ class RecordTimerEntry(timer.TimerEntry, object): # that in our state, with also keeping the possibility to re-try. # TODO: this has to be done. elif event == iRecordableService.evStart: + if self.pvrConvert: + return + text = _("A record has been started:\n%s") % self.name if self.dirnameHadToFallback: text = '\n'.join((text, _("Please note that the previously selected media could not be accessed and therefore the default directory is being used instead."))) diff --git a/configure.ac b/configure.ac index 00a4bd2..36488af 100644 --- a/configure.ac +++ b/configure.ac @@ -310,6 +310,8 @@ lib/python/Plugins/SystemPlugins/FastChannelChange/Makefile lib/python/Plugins/SystemPlugins/FastChannelChange/meta/Makefile lib/python/Plugins/SystemPlugins/Ultimo4kMiscControl/Makefile lib/python/Plugins/SystemPlugins/Ultimo4kMiscControl/meta/Makefile +lib/python/Plugins/SystemPlugins/PvrDescrambleConvert/Makefile +lib/python/Plugins/SystemPlugins/PvrDescrambleConvert/meta/Makefile lib/python/Tools/Makefile lib/service/Makefile lib/components/Makefile diff --git a/data/setup.xml b/data/setup.xml index 5ce6b62..9613440 100755 --- a/data/setup.xml +++ b/data/setup.xml @@ -61,6 +61,8 @@ config.usage.frontend_priority config.recording.ascii_filenames config.recording.filename_composition + config.recording.always_ecm + config.recording.never_decrypt config.subtitles.subtitle_fontcolor diff --git a/lib/base/httpstream.cpp b/lib/base/httpstream.cpp index 8f55425..ee4ffe4 100644 --- a/lib/base/httpstream.cpp +++ b/lib/base/httpstream.cpp @@ -352,3 +352,9 @@ off_t eHttpStream::length() { return (off_t)-1; } + +off_t eHttpStream::offset() +{ + return 0; +} + diff --git a/lib/base/httpstream.h b/lib/base/httpstream.h index 228d2b0..84ea5e9 100644 --- a/lib/base/httpstream.h +++ b/lib/base/httpstream.h @@ -32,6 +32,7 @@ class eHttpStream: public iTsSource, public eSocketBase, public Object, public e off_t lseek(off_t offset, int whence); ssize_t read(off_t offset, void *buf, size_t count); off_t length(); + off_t offset(); int valid(); bool isStream() { return true; } public: diff --git a/lib/base/itssource.h b/lib/base/itssource.h index adaeb4c..735574c 100644 --- a/lib/base/itssource.h +++ b/lib/base/itssource.h @@ -14,6 +14,7 @@ public: virtual off_t length()=0; virtual int valid()=0; + virtual off_t offset() = 0; virtual bool isStream() { return false; } }; diff --git a/lib/base/rawfile.cpp b/lib/base/rawfile.cpp index 3a09e07..e32b262 100644 --- a/lib/base/rawfile.cpp +++ b/lib/base/rawfile.cpp @@ -251,3 +251,8 @@ off_t eRawFile::length() { return m_totallength; } + +off_t eRawFile::offset() +{ + return m_last_offset; +} diff --git a/lib/base/rawfile.h b/lib/base/rawfile.h index 7b736a3..02bf2e3 100644 --- a/lib/base/rawfile.h +++ b/lib/base/rawfile.h @@ -19,6 +19,7 @@ public: off_t lseek(off_t offset, int whence); ssize_t read(off_t offset, void *buf, size_t count); off_t length(); + off_t offset(); int valid(); private: int m_fd; /* for uncached */ diff --git a/lib/dvb/dvb.cpp b/lib/dvb/dvb.cpp index 756c685..bc79306 100644 --- a/lib/dvb/dvb.cpp +++ b/lib/dvb/dvb.cpp @@ -2035,7 +2035,8 @@ RESULT eDVBChannel::playSource(ePtr &source, const char *streaminfo_f return -ENOENT; } - m_tstools.setSource(source, streaminfo_file); + m_source = source; + m_tstools.setSource(m_source, streaminfo_file); /* DON'T EVEN THINK ABOUT FIXING THIS. FIX THE ATI SOURCES FIRST, THEN DO A REAL FIX HERE! */ @@ -2072,12 +2073,12 @@ RESULT eDVBChannel::playSource(ePtr &source, const char *streaminfo_f m_pvr_thread = new eDVBChannelFilePush(); m_pvr_thread->enablePVRCommit(1); /* If the source specifies a length, it's a file. If not, it's a stream */ - m_pvr_thread->setStreamMode(source->isStream()); + m_pvr_thread->setStreamMode(m_source->isStream()); m_pvr_thread->setScatterGather(this); m_event(this, evtPreStart); - m_pvr_thread->start(source, m_pvr_fd_dst); + m_pvr_thread->start(m_source, m_pvr_fd_dst); CONNECT(m_pvr_thread->m_event, eDVBChannel::pvrEvent); m_state = state_ok; @@ -2099,8 +2100,9 @@ void eDVBChannel::stopSource() ::close(m_pvr_fd_dst); m_pvr_fd_dst = -1; } - ePtr d; - m_tstools.setSource(d); + + m_source = NULL; + m_tstools.setSource(m_source); } void eDVBChannel::stopFile() @@ -2141,8 +2143,7 @@ RESULT eDVBChannel::getCurrentPosition(iDVBDemux *decoding_demux, pts_t &pos, in } else now = pos; /* fixup supplied */ - off_t off = 0; /* TODO: fixme */ - r = m_tstools.fixupPTS(off, now); + r = m_tstools.fixupPTS(m_source ? m_source->offset() : 0, now); if (r) { eDebug("fixup PTS failed"); diff --git a/lib/dvb/dvb.h b/lib/dvb/dvb.h index c0acc4f..ff9ea6f 100644 --- a/lib/dvb/dvb.h +++ b/lib/dvb/dvb.h @@ -287,6 +287,7 @@ private: Signal1 m_stateChanged; Signal2 m_event; int m_state; + ePtr m_source; /* for channel list */ ePtr m_mgr; diff --git a/lib/dvb/metaparser.cpp b/lib/dvb/metaparser.cpp index 5eff4d9..19007fc 100644 --- a/lib/dvb/metaparser.cpp +++ b/lib/dvb/metaparser.cpp @@ -8,6 +8,7 @@ eDVBMetaParser::eDVBMetaParser() m_data_ok = 0; m_length = 0; m_filesize = 0; + m_scrambled = 0; } int eDVBMetaParser::parseFile(const std::string &basename) @@ -94,6 +95,9 @@ int eDVBMetaParser::parseMeta(const std::string &tsname) case 7: m_service_data = line; break; + case 8: + m_scrambled = atoi(line); + break; default: break; } @@ -149,8 +153,8 @@ int eDVBMetaParser::parseRecordings(const std::string &filename) m_time_create = 0; m_length = 0; m_filesize = fileSize(filename); - m_data_ok = 1; + m_scrambled = 0; fclose(f); updateMeta(filename.c_str()); return 0; @@ -172,7 +176,7 @@ int eDVBMetaParser::updateMeta(const std::string &tsname) FILE *f = fopen(filename.c_str(), "w"); if (!f) return -ENOENT; - fprintf(f, "%s\n%s\n%s\n%d\n%s\n%d\n%lld\n%s\n", ref.toString().c_str(), m_name.c_str(), m_description.c_str(), m_time_create, m_tags.c_str(), m_length, m_filesize, m_service_data.c_str() ); + fprintf(f, "%s\n%s\n%s\n%d\n%s\n%d\n%lld\n%s\n%d\n", ref.toString().c_str(), m_name.c_str(), m_description.c_str(), m_time_create, m_tags.c_str(), m_length, m_filesize, m_service_data.c_str(), m_scrambled); fclose(f); return 0; } diff --git a/lib/dvb/metaparser.h b/lib/dvb/metaparser.h index 3368294..a171290 100644 --- a/lib/dvb/metaparser.h +++ b/lib/dvb/metaparser.h @@ -15,7 +15,7 @@ public: long long fileSize(const std::string &basename); eServiceReferenceDVB m_ref; - int m_data_ok, m_time_create, m_length; + int m_data_ok, m_time_create, m_length, m_scrambled; std::string m_name, m_description, m_tags, m_service_data; long long m_filesize; }; diff --git a/lib/dvb/pmt.cpp b/lib/dvb/pmt.cpp index e6c1d2c..d5a7800 100644 --- a/lib/dvb/pmt.cpp +++ b/lib/dvb/pmt.cpp @@ -32,7 +32,8 @@ eDVBServicePMTHandler::eDVBServicePMTHandler() m_use_decode_demux = 0; m_pmt_pid = -1; m_dsmcc_pid = -1; - m_isstreamclient = false; + m_service_type = livetv; + m_descramble = true; m_ca_disabled = false; m_pmt_ready = false; eDVBResourceManager::getInstance(m_resourceManager); @@ -114,6 +115,19 @@ void eDVBServicePMTHandler::channelEvent(iDVBChannel *channel, int event) } } +void eDVBServicePMTHandler::registerCAService() +{ + int demuxes[2] = {0,0}; + uint8_t demuxid; + m_demux->getCADemuxID(demuxid); + demuxes[0]=demuxid; + if (m_decode_demux_num != 0xFF) + demuxes[1]=m_decode_demux_num; + else + demuxes[1]=demuxes[0]; + eDVBCAService::register_service(m_reference, demuxes, m_ca_servicePtr); +} + void eDVBServicePMTHandler::PMTready(int error) { if (error) @@ -123,26 +137,24 @@ void eDVBServicePMTHandler::PMTready(int error) m_pmt_ready = true; m_have_cached_program = false; serviceEvent(eventNewProgramInfo); + mDemuxId = m_decode_demux_num; - if (!m_pvr_channel) // don't send campmt to camd.socket for playbacked services + if (!m_pvr_channel) { eEPGCache::getInstance()->PMTready(this); - if(!m_ca_servicePtr && !m_isstreamclient) + } + + if (m_descramble) + { + if(!m_ca_servicePtr) { - int demuxes[2] = {0,0}; - uint8_t tmp; - m_demux->getCADemuxID(tmp); - demuxes[0]=tmp; - if (m_decode_demux_num != 0xFF) - demuxes[1]=m_decode_demux_num; - else - demuxes[1]=demuxes[0]; - eDVBCAService::register_service(m_reference, demuxes, m_ca_servicePtr); - if (!m_ca_disabled) - eDVBCIInterfaces::getInstance()->recheckPMTHandlers(); + registerCAService(); } if (!m_ca_disabled) + { + eDVBCIInterfaces::getInstance()->recheckPMTHandlers(); eDVBCIInterfaces::getInstance()->gotPMT(this); + } } if (m_ca_servicePtr) { @@ -152,6 +164,9 @@ void eDVBServicePMTHandler::PMTready(int error) else eDebug("eDVBServicePMTHandler cannot call buildCAPMT"); } + + if (m_service_type == pvrDescramble) + serviceEvent(eventStartPvrDescramble); } } @@ -1123,19 +1138,20 @@ void eDVBServicePMTHandler::SDTScanEvent(int event) } } -int eDVBServicePMTHandler::tune(eServiceReferenceDVB &ref, int use_decode_demux, eCueSheet *cue, bool simulate, eDVBService *service) +int eDVBServicePMTHandler::tune(eServiceReferenceDVB &ref, int use_decode_demux, eCueSheet *cue, bool simulate, eDVBService *service, serviceType type, bool descramble) { ePtr s; - return tuneExt(ref, use_decode_demux, s, NULL, cue, simulate, service); + return tuneExt(ref, use_decode_demux, s, NULL, cue, simulate, service, type, descramble); } -int eDVBServicePMTHandler::tuneExt(eServiceReferenceDVB &ref, int use_decode_demux, ePtr &source, const char *streaminfo_file, eCueSheet *cue, bool simulate, eDVBService *service, bool isstreamclient) +int eDVBServicePMTHandler::tuneExt(eServiceReferenceDVB &ref, int use_decode_demux, ePtr &source, const char *streaminfo_file, eCueSheet *cue, bool simulate, eDVBService *service, serviceType type, bool descramble) { RESULT res=0; m_reference = ref; m_use_decode_demux = use_decode_demux; m_no_pat_entry_delay->stop(); - m_isstreamclient = isstreamclient; + m_service_type = type; + m_descramble = descramble; /* use given service as backup. This is used for timeshift where we want to clone the live stream using the cache, but in fact have a PVR channel */ m_service = service; @@ -1153,7 +1169,7 @@ int eDVBServicePMTHandler::tuneExt(eServiceReferenceDVB &ref, int use_decode_dem if (!m_resourceManager->getChannelList(db)) db->getService((eServiceReferenceDVB&)m_reference, m_service); - if (!res && !simulate && !m_ca_disabled) + if (!res && !simulate && m_descramble && !m_ca_disabled) eDVBCIInterfaces::getInstance()->addPMTHandler(this); } else if (!simulate) // no simulation of playback services { @@ -1180,10 +1196,16 @@ int eDVBServicePMTHandler::tuneExt(eServiceReferenceDVB &ref, int use_decode_dem eDebug("alloc PVR"); /* allocate PVR */ eDVBChannelID chid; - if (m_isstreamclient) ref.getChannelID(chid); + if (m_service_type == streamclient) ref.getChannelID(chid); res = m_resourceManager->allocatePVRChannel(chid, m_pvr_channel); if (res) + { eDebug("allocatePVRChannel failed!\n"); + } + else if (descramble) + { + eDVBCIInterfaces::getInstance()->addPMTHandler(this); + } m_channel = m_pvr_channel; } @@ -1221,15 +1243,7 @@ int eDVBServicePMTHandler::tuneExt(eServiceReferenceDVB &ref, int use_decode_dem m_pvr_channel->setCueSheet(cue); if (m_pvr_channel->getDemux(m_pvr_demux_tmp, (!m_use_decode_demux) ? 0 : iDVBChannel::capDecode)) - { - if (m_isstreamclient) - { - eDebug("Allocating %s-decoding a demux for http channel failed.", m_use_decode_demux ? "" : "non-"); - return -2; - } - else - eDebug("Allocating %s-decoding a demux for PVR channel failed.", m_use_decode_demux ? "" : "non-"); - } + eDebug("[eDVBServicePMTHandler] Allocating %s-decoding a demux for PVR channel failed.", m_use_decode_demux ? "" : "non-"); else if (source) m_pvr_channel->playSource(source, streaminfo_file); else @@ -1301,6 +1315,10 @@ void eDVBServicePMTHandler::removeCaHandler() eDVBCIInterfaces::getInstance()->removePMTHandler(this); } +bool eDVBServicePMTHandler::isCiConnected() +{ + eDVBCIInterfaces::getInstance()->isCiConnected(this); +} CAServiceMap eDVBCAService::exist; ChannelMap eDVBCAService::exist_channels; diff --git a/lib/dvb/pmt.h b/lib/dvb/pmt.h index eb4f593..f26e453 100644 --- a/lib/dvb/pmt.h +++ b/lib/dvb/pmt.h @@ -140,6 +140,7 @@ class eDVBServicePMTHandler: public Object eAUTable > m_AIT; eAUTable > m_OC; + void registerCAService(); void PMTready(int error); void PATready(int error); @@ -190,6 +191,7 @@ public: eventMisconfiguration, // a channel was not found in any list, or no frontend was found which could provide this channel eventNoDiskSpace, // no disk space available + eventStartPvrDescramble, // start PVR Descramble Convert }; #ifndef SWIG Signal1 serviceEvent; @@ -282,19 +284,38 @@ public: void getDemuxID(int &id) { id = mDemuxId; } void setCaDisable(bool disable) { m_ca_disabled = disable; } + enum serviceType + { + livetv = 0, + recording = 1, + scrambled_recording = 2, + playback = 3, + timeshift_recording = 4, + scrambled_timeshift_recording = 5, + timeshift_playback = 6, + streamserver = 7, + scrambled_streamserver = 8, + streamclient = 9, + offline = 10, + pvrDescramble = 11, + }; + /* deprecated interface */ - int tune(eServiceReferenceDVB &ref, int use_decode_demux, eCueSheet *sg=0, bool simulate=false, eDVBService *service = 0); + int tune(eServiceReferenceDVB &ref, int use_decode_demux, eCueSheet *sg=0, bool simulate=false, eDVBService *service = 0, serviceType type = livetv, bool descramble = true); /* new interface */ - int tuneExt(eServiceReferenceDVB &ref, int use_decode_demux, ePtr &, const char *streaminfo_file, eCueSheet *sg=0, bool simulate=false, eDVBService *service = 0, bool isstreamclient=false); + int tuneExt(eServiceReferenceDVB &ref, int use_decode_demux, ePtr &, const char *streaminfo_file, eCueSheet *sg=0, bool simulate=false, eDVBService *service = 0, serviceType type = livetv, bool descramble=true); void free(); void addCaHandler(); void removeCaHandler(); + bool isCiConnected(); + bool isPmtReady() { return m_pmt_ready; } private: bool m_have_cached_program; program m_cached_program; - bool m_isstreamclient; + bool m_descramble; + serviceType m_service_type; #endif }; diff --git a/lib/dvb/pvrparse.cpp b/lib/dvb/pvrparse.cpp index 173a774..5b7e790 100755 --- a/lib/dvb/pvrparse.cpp +++ b/lib/dvb/pvrparse.cpp @@ -535,6 +535,12 @@ int eMPEGStreamParserTS::processPacket(const unsigned char *pkt, off_t offset) if (!(pkt[3] & 0x10)) /* no payload? */ return 0; + if (pkt[3] & 0xc0) + { + /* scrambled stream, we cannot parse pts */ + return 0; + } + if (pkt[3] & 0x20) // adaptation field present? pkt += pkt[4] + 4 + 1; /* skip adaptation field and header */ else diff --git a/lib/dvb_ci/dvbci.cpp b/lib/dvb_ci/dvbci.cpp index 60a64dd..e7618d0 100644 --- a/lib/dvb_ci/dvbci.cpp +++ b/lib/dvb_ci/dvbci.cpp @@ -226,7 +226,11 @@ void eDVBCIInterfaces::ciRemoved(eDVBCISlot *slot) slot->linked_next->setSource(slot->current_source); else // last CI in chain { - setInputSource(slot->current_tuner, slot->current_source); + if(slot->current_tuner == 99) + slot->setSource(PVR_NONE); + else + setInputSource(slot->current_tuner, slot->current_source); + slot->removeVtunerPid(); } slot->linked_next = 0; @@ -244,6 +248,7 @@ static bool canDescrambleMultipleServices(int slotid) snprintf(configStr, 255, "config.ci.%d.canDescrambleMultipleServices", slotid); std::string str; ePythonConfigQuery::getConfigValue(configStr, str); + eDebugCI("canDescrambleMultipleServices %s", str.c_str()); if ( str == "auto" ) { std::string appname = eDVBCI_UI::getInstance()->getAppName(slotid); @@ -494,6 +499,8 @@ void eDVBCIInterfaces::recheckPMTHandlers() eDVBFrontend *fe = (eDVBFrontend*) &(*frontend); tunernum = fe->getSlotID(); } + else // no frontend, PVR + tunernum = 99; } ASSERT(tunernum != -1); data_source tuner_source = TUNER_A; @@ -502,12 +509,15 @@ void eDVBCIInterfaces::recheckPMTHandlers() case 0 ... 22: tuner_source = (data_source)tunernum; break; + case 99: tuner_source = PVR; + break; default: eDebug("try to get source for tuner %d!!\n", tunernum); break; } ci_it->current_tuner = tunernum; - setInputSource(tunernum, ci_source); + if (tunernum != 99) + setInputSource(tunernum, ci_source); ci_it->setSource(tuner_source); } else @@ -597,7 +607,11 @@ void eDVBCIInterfaces::removePMTHandler(eDVBServicePMTHandler *pmthandler) slot->linked_next->setSource(slot->current_source); else { - setInputSource(slot->current_tuner, slot->current_source); + if(slot->current_tuner == 99) + slot->setSource(PVR_NONE); + else + setInputSource(slot->current_tuner, slot->current_source); + slot->removeVtunerPid(); } @@ -640,6 +654,19 @@ void eDVBCIInterfaces::gotPMT(eDVBServicePMTHandler *pmthandler) tmp = tmp->linked_next; } } + +} + +bool eDVBCIInterfaces::isCiConnected(eDVBServicePMTHandler *pmthandler) +{ + bool ret = false; + PMTHandlerList::iterator it=std::find(m_pmt_handlers.begin(), m_pmt_handlers.end(), pmthandler); + if (it != m_pmt_handlers.end() && it->cislot) + { + ret = true; + } + + return ret; } int eDVBCIInterfaces::getMMIState(int slotid) @@ -652,12 +679,11 @@ int eDVBCIInterfaces::getMMIState(int slotid) return slot->getMMIState(); } -static const char *tuner_source[] = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "CI0", "CI1", "CI2", "CI3"}; +static const char *g_tuner_source[] = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "CI0", "CI1", "CI2", "CI3"}; int eDVBCIInterfaces::setInputSource(int tuner_no, data_source source) { -// eDebug("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); -// eDebug("eDVBCIInterfaces::setInputSource(%d %d)", tuner_no, (int)source); + eDebugCI("[eDVBCIInterfaces] setInputSource(%d, %d)", tuner_no, (int)source); if (getNumOfSlots() > 1) // FIXME .. we force DM8000 when more than one CI Slot is avail { char buf[64]; @@ -669,13 +695,10 @@ int eDVBCIInterfaces::setInputSource(int tuner_no, data_source source) return 0; } - if (tuner_no > 3) - eDebug("setInputSource(%d, %d) failed... dm8000 just have four inputs", tuner_no, (int)source); - switch(source) { - case TUNER_A ... CI_D: - fprintf(input, tuner_source[(int)source]); + case TUNER_A ... CI_D: + fprintf(input, g_tuner_source[(int)source]); break; default: eDebug("setInputSource for input %d failed!!!\n", (int)source); @@ -1268,6 +1291,7 @@ void eDVBCISlot::removeService(uint16_t program_number) int eDVBCISlot::setSource(data_source source) { current_source = source; + eDebugCI("[eDVBCISlot] setSource %d", (int)source); if (eDVBCIInterfaces::getInstance()->getNumOfSlots() > 1) // FIXME .. we force DM8000 when more than one CI Slot is avail { char buf[64]; @@ -1276,7 +1300,13 @@ int eDVBCISlot::setSource(data_source source) switch(source) { case TUNER_A ... CI_D: - fprintf(ci, tuner_source[(int)source]); + fprintf(ci, g_tuner_source[(int)source]); + break; + case PVR: + fprintf(ci, "PVR"); + break; + case PVR_NONE: + fprintf(ci, "PVR_NONE"); break; default: eDebug("CI Slot %d: setSource %d failed!!!\n", getSlotID(), (int)source); @@ -1299,7 +1329,7 @@ int eDVBCISlot::setSource(data_source source) fprintf(ci, "%s", source==TUNER_A ? "A" : "B"); // configure CI data source (TunerA, TunerB) fclose(ci); } - eDebug("CI Slot %d setSource(%d)", getSlotID(), (int)source); + eDebugCI("[eDVBCISlot] CI Slot %d setSource(%d)", getSlotID(), (int)source); return 0; } diff --git a/lib/dvb_ci/dvbci.h b/lib/dvb_ci/dvbci.h index 4a49d7c..4262623 100644 --- a/lib/dvb_ci/dvbci.h +++ b/lib/dvb_ci/dvbci.h @@ -36,7 +36,7 @@ struct queueData enum data_source { - TUNER_A=0, TUNER_B, TUNER_C, TUNER_D, TUNER_E, TUNER_F, TUNER_G, TUNER_H, TUNER_I, TUNER_J, TUNER_K, TUNER_L, TUNER_M, TUNER_N, TUNER_O, TUNER_P, TUNER_Q, TUNER_R, TUNER_S, TUNER_T, TUNER_U, TUNER_V, CI_A, CI_B, CI_C, CI_D + TUNER_A=0, TUNER_B, TUNER_C, TUNER_D, TUNER_E, TUNER_F, TUNER_G, TUNER_H, TUNER_I, TUNER_J, TUNER_K, TUNER_L, TUNER_M, TUNER_N, TUNER_O, TUNER_P, TUNER_Q, TUNER_R, TUNER_S, TUNER_T, TUNER_U, TUNER_V, CI_A, CI_B, CI_C, CI_D, PVR, PVR_NONE }; typedef std::pair providerPair; @@ -141,6 +141,7 @@ public: void removePMTHandler(eDVBServicePMTHandler *pmthandler); void recheckPMTHandlers(); void gotPMT(eDVBServicePMTHandler *pmthandler); + bool isCiConnected(eDVBServicePMTHandler *pmthandler); void ciRemoved(eDVBCISlot *slot); int getSlotState(int slot); diff --git a/lib/python/Components/RecordingConfig.py b/lib/python/Components/RecordingConfig.py index 40dfb2c..6979de6 100755 --- a/lib/python/Components/RecordingConfig.py +++ b/lib/python/Components/RecordingConfig.py @@ -11,4 +11,6 @@ def InitRecordingConfig(): config.recording.filename_composition = ConfigSelection(default = "standard", choices = [ ("standard", _("standard")), ("short", _("Short filenames")), - ("long", _("Long filenames")) ] ) \ No newline at end of file + ("long", _("Long filenames")) ] ) + config.recording.always_ecm = ConfigYesNo(default = False) + config.recording.never_decrypt = ConfigYesNo(default = False) \ No newline at end of file diff --git a/lib/python/Components/SystemInfo.py b/lib/python/Components/SystemInfo.py index 67f2e75..6fcab1d 100644 --- a/lib/python/Components/SystemInfo.py +++ b/lib/python/Components/SystemInfo.py @@ -31,4 +31,5 @@ SystemInfo["FrontpanelDisplayGrayscale"] = fileExists("/dev/dbox/oled0") SystemInfo["DeepstandbySupport"] = HardwareInfo().get_device_name() != "dm800" SystemInfo["HdmiInSupport"] = HardwareInfo().get_vu_device_name() == "ultimo4k" SystemInfo["WOWLSupport"] = HardwareInfo().get_vu_device_name() == "ultimo4k" +SystemInfo["ScrambledPlayback"] = HardwareInfo().get_vu_device_name() in ("solo4k", "ultimo4k") diff --git a/lib/python/Plugins/SystemPlugins/Makefile.am b/lib/python/Plugins/SystemPlugins/Makefile.am index a5c65be..4cf41a6 100755 --- a/lib/python/Plugins/SystemPlugins/Makefile.am +++ b/lib/python/Plugins/SystemPlugins/Makefile.am @@ -8,7 +8,7 @@ SUBDIRS = SoftwareManager FrontprocessorUpgrade PositionerSetup Satfinder \ Blindscan RemoteControlCode UI3DSetup UIPositionSetup HDMICEC LEDBrightnessSetup \ FirmwareUpgrade CrashReport 3GModemManager WirelessAccessPoint ZappingModeSelection \ DeviceManager TransCodingSetup WOLSetup NetDrive AudioEffect AnimationSetup \ - BoxModeConfig Solo4kMiscControl FastChannelChange Ultimo4kMiscControl + BoxModeConfig Solo4kMiscControl FastChannelChange Ultimo4kMiscControl PvrDescrambleConvert install_PYTHON = \ __init__.py diff --git a/lib/python/Plugins/SystemPlugins/PvrDescrambleConvert/Makefile.am b/lib/python/Plugins/SystemPlugins/PvrDescrambleConvert/Makefile.am new file mode 100644 index 0000000..be3f0fb --- /dev/null +++ b/lib/python/Plugins/SystemPlugins/PvrDescrambleConvert/Makefile.am @@ -0,0 +1,9 @@ +installdir = $(pkglibdir)/python/Plugins/SystemPlugins/PvrDescrambleConvert + +SUBDIRS = meta + +install_PYTHON = \ + __init__.py \ + plugin.py \ + PvrDescrambleConvert.py + diff --git a/lib/python/Plugins/SystemPlugins/PvrDescrambleConvert/PvrDescrambleConvert.py b/lib/python/Plugins/SystemPlugins/PvrDescrambleConvert/PvrDescrambleConvert.py new file mode 100644 index 0000000..56e0220 --- /dev/null +++ b/lib/python/Plugins/SystemPlugins/PvrDescrambleConvert/PvrDescrambleConvert.py @@ -0,0 +1,370 @@ +from Components.config import config, ConfigSubsection, ConfigSelection +from Components.UsageConfig import preferredInstantRecordPath, defaultMoviePath + +from enigma import eTimer, eServiceReference, eServiceCenter, iServiceInformation, iRecordableService +from RecordTimer import RecordTimerEntry, RecordTimer +from Tools.Directories import fileExists +from ServiceReference import ServiceReference + +from Tools import Notifications +from Screens.MessageBox import MessageBox + +import os +import glob +import time + +config.plugins.pvrdesconvertsetup = ConfigSubsection() +config.plugins.pvrdesconvertsetup.activate = ConfigSelection(default = "disable", choices = [ ("enable", _("Enable")), ("disable", _("Disable"))] ) + +# lib/dvb/pmt.h +SERVICETYPE_PVR_DESCRAMBLE = 11 + +def fileExist(fileName, flag = os.W_OK): + return os.access(fileName, flag) + +def sync(): + if hasattr(os, 'sync'): + os.sync() + else: + import ctypes + libc = ctypes.CDLL("libc.so.6") + libc.sync() + +# iStaticServiceInformation +class StubInfo: + def getName(self, sref): + return os.path.split(sref.getPath())[1] + def getLength(self, sref): + return -1 + def getEvent(self, sref, *args): + return None + def isPlayable(self): + return True + def getInfo(self, sref, w): + if w == iServiceInformation.sTimeCreate: + return os.stat(sref.getPath()).st_ctime + if w == iServiceInformation.sFileSize: + return os.stat(sref.getPath()).st_size + if w == iServiceInformation.sDescription: + return sref.getPath() + return 0 + def getInfoString(self, sref, w): + return '' +stubInfo = StubInfo() + +class PVRDescrambleConvertInfos: + def __init__(self): + self.navigation = None + + def getNavigation(self): + if not self.navigation: + import NavigationInstance + if NavigationInstance: + self.navigation = NavigationInstance.instance + + return self.navigation + + def getRecordings(self): + recordings = [] + nav = self.getNavigation() + if nav: + recordings = nav.getRecordings() + print "getRecordings : ", recordings + + return recordings + + def getInstandby(self): + from Screens.Standby import inStandby + return inStandby + + def getCurrentMoviePath(self): + if not fileExists(config.movielist.last_videodir.value): + config.movielist.last_videodir.value = defaultMoviePath() + config.movielist.last_videodir.save() + + curMovieRef = eServiceReference("2:0:1:0:0:0:0:0:0:0:" + config.movielist.last_videodir.value) + return curMovieRef + +class PVRDescrambleConvert(PVRDescrambleConvertInfos): + def __init__(self): + PVRDescrambleConvertInfos.__init__(self) + config.misc.standbyCounter.addNotifier(self.enterStandby, initial_call = False) + + self.convertTimer = eTimer() + self.convertTimer.callback.append(self.startConvert) + + self.stopConvertTimer = eTimer() + self.stopConvertTimer.callback.append(self.stopConvert) + + self.converting = None + self.convertFilename = None + self.currentPvr = None + + self.pvrLists = [] + + self.oldService = None + + def enterStandby(self, configElement): + if config.plugins.pvrdesconvertsetup.activate.value == "enable": + instandby = self.getInstandby() + instandby.onClose.append(self.leaveStandby) + + self.loadScrambledPvrList() + + # register record callback + self.appendRecordEventCB() + + self.startConvertTimer() + + def leaveStandby(self): + self.removeRecordEventCB() + self.convertTimer.stop() + self.stopConvert() + + def startConvertTimer(self): + self.convertTimer.start(3000, True) + + def startStopConvertTimer(self): + self.stopConvertTimer.start(500, True) + + def appendRecordEventCB(self): + nav = self.getNavigation() + if nav: + if self.gotRecordEvent not in nav.record_event: + nav.record_event.append(self.gotRecordEvent) + + def removeRecordEventCB(self): + nav = self.getNavigation() + if nav: + if self.gotRecordEvent in nav.record_event: + nav.record_event.remove(self.gotRecordEvent) + + def gotRecordEvent(self, service, event): + if service.getServiceType() == SERVICETYPE_PVR_DESCRAMBLE: + if event == iRecordableService.evEnd: + if self.getInstandby(): + self.startConvertTimer() + elif event == iRecordableService.evPvrEof: + self.stopConvert(convertFinished = True) + elif event == iRecordableService.evRecordFailed: + self.startStopConvertTimer() + else: + if event in (iRecordableService.evPvrTuneStart, iRecordableService.evTuneStart): + if self.currentPvr: + self.pvrLists.insert(0, self.currentPvr) + self.currentPvr = None + self.startStopConvertTimer() + elif event == iRecordableService.evEnd: + if self.getInstandby(): + self.startConvertTimer() + + def loadScrambledPvrList(self): + self.pvrLists = [] + + serviceHandler = eServiceCenter.getInstance() + curMovieRef = self.getCurrentMoviePath() + movieRefList = serviceHandler.list(curMovieRef) + + if movieRefList is None: + print "Load pvr list failed!" + return + + while 1: + sref = movieRefList.getNext() + if not sref.valid(): + break + + if config.ParentalControl.servicepinactive.value and config.ParentalControl.storeservicepin.value != "never": + from Components.ParentalControl import parentalControl + if not parentalControl.sessionPinCached and parentalControl.isProtected(sref): + continue + + if sref.flags & eServiceReference.mustDescent: + continue + + if not sref.getPath(): + return + + info = serviceHandler.info(sref) + + real_sref = "1:0:0:0:0:0:0:0:0:0:" + if info is not None: + real_sref = info.getInfoString(sref, iServiceInformation.sServiceref) + + if info is None: + info = stubInfo + + begin = info.getInfo(sref, iServiceInformation.sTimeCreate) + + # convert separe-separated list of tags into a set + name = info.getName(sref) + scrambled = info.getInfo(sref, iServiceInformation.sIsScrambled) + length = info.getLength(sref) + + if scrambled == 1: + #print "====" * 30 + #print "[loadScrambledPvrList] sref.toString() : ", sref.toString() + #print "[loadScrambledPvrList] sref.getPath() : ", sref.getPath() + #print "[loadScrambledPvrList] name : ", name + #print "[loadScrambledPvrList] begin : ", begin + #print "[loadScrambledPvrList] length : ", length + #print "[loadScrambledPvrList] scrambled : ", scrambled + #print "" + rec = (begin, sref, name, length, real_sref) + if rec not in self.pvrLists: + self.pvrLists.append( rec ) + + self.pvrLists.sort() + + def checkBeforeStartConvert(self): + return self.pvrLists and (not bool(self.getRecordings())) and (not self.converting) and self.getInstandby() + + def startConvert(self): + if not self.checkBeforeStartConvert(): + return + + self.currentPvr = self.pvrLists.pop(0) + if self.currentPvr is None: + return + + (_begin, sref, name, length, real_ref) = self.currentPvr + + m_path = sref.getPath() + sref = eServiceReference(real_ref + m_path) + + begin = int(time.time()) + end = begin + 3600 # dummy + #end = begin + int(length) + 2 + description = "" + eventid = None + + if isinstance(sref, eServiceReference): + sref = ServiceReference(sref) + + if m_path.endswith('.ts'): + m_path = m_path[:-3] + + filename = m_path + "_pvrdesc" + + recording = RecordTimerEntry(sref, begin, end, name, description, eventid, dirname = preferredInstantRecordPath(), filename=filename) + recording.dontSave = True + recording.autoincrease = True + recording.setAutoincreaseEnd() + recording.pvrConvert = True # do not handle evStart event + + nav = self.getNavigation() + simulTimerList = nav.RecordTimer.record(recording) + if simulTimerList is None: # no conflict + recordings = self.getRecordings() + if len(recordings) == 1: + self.converting = recording + self.convertFilename = (sref.getPath(), filename + ".ts") + else: + print "[PVRDescrambleConvert] error, wrong recordings info." + else: + self.currentPvr = None + self.startConvertTimer() + + if len(simulTimerList) > 1: # with other recording + print "[PVRDescrambleConvert] conflicts !" + else: + print "[PVRDescrambleConvert] Couldn't record due to invalid service %s" % sref + recording.autoincrease = False + + print "[PVRDescrambleConvert] startConvert, self.converting : ", self.converting + + def removeStr(self, fileName, s): + if fileName.find(s) == -1: + return fileName + + sp = fileName.split(s) + + return sp[0] + sp[1] + + def renameDelPvr(self, pvrName, subName): + targetName = pvrName + subName + outName = self.removeStr(pvrName, ".ts") + "_del" + ".ts" + subName + + if fileExist(targetName): + #print "RENAME %s -> %s" % (targetName, outName) + os.rename(targetName, outName) + return outName + + return None + + def renameConvertPvr(self, pvrName, subName): + targetName = pvrName + subName + outName = self.removeStr(pvrName, "_pvrdesc") + subName + + if fileExist(targetName): + #print "RENAME %s -> %s" % (targetName, outName) + os.rename(targetName, outName) + return outName + + return None + + def renamePvr(self, pvr_ori, pvr_convert): + pvr_ori_del = self.renameDelPvr(pvr_ori, "") + if not pvr_ori_del: + return None + + self.renameDelPvr(pvr_ori, ".meta") + self.renameDelPvr(pvr_ori, ".ap") + self.renameDelPvr(pvr_ori, ".sc") + self.renameDelPvr(pvr_ori, ".cuts") + + pvr_convert_fixed = self.renameConvertPvr(pvr_convert, "") + if not pvr_convert_fixed: + return None + + self.renameConvertPvr(pvr_convert, ".meta") + self.renameConvertPvr(pvr_convert, ".ap") + self.renameConvertPvr(pvr_convert, ".sc") + self.renameConvertPvr(pvr_convert, ".cuts") + + if os.path.exists(pvr_convert[:-3] + '.eit'): + os.remove(pvr_convert[:-3] + '.eit') + + return pvr_ori_del + + def stopConvert(self, convertFinished = False): + name = "Unknown" + if self.currentPvr: + (_begin, sref, name, length, real_ref) = self.currentPvr + self.currentPvr = None + + if self.converting: + nav = self.getNavigation() + nav.RecordTimer.removeEntry(self.converting) + convertFilename = self.convertFilename + self.converting = None + self.convertFilename = None + + if convertFilename: + (pvr_ori, pvr_convert) = convertFilename + if convertFinished: + # check size + if fileExist(pvr_convert, os.F_OK) and os.stat(pvr_convert).st_size: + pvr_ori_del = self.renamePvr(pvr_ori, pvr_convert) + if pvr_ori_del: + self.deletePvr(pvr_ori_del) + self.addNotification(_("A PVR descramble converting is finished.\n%s") % name) + else: + self.deletePvr(pvr_convert) + else: + self.deletePvr(pvr_convert) + + sync() + + def deletePvr(self, filename): + serviceHandler = eServiceCenter.getInstance() + ref = eServiceReference(1, 0, filename) + offline = serviceHandler.offlineOperations(ref) + if offline.deleteFromDisk(0): + print "[PVRDescrambleConvert] delete failed : ", filename + + def addNotification(self, text): + Notifications.AddNotification(MessageBox, text, type=MessageBox.TYPE_INFO, timeout=5) + +pvr_descramble_convert = PVRDescrambleConvert() + diff --git a/lib/python/Plugins/SystemPlugins/PvrDescrambleConvert/__init__.py b/lib/python/Plugins/SystemPlugins/PvrDescrambleConvert/__init__.py new file mode 100644 index 0000000..e9be9ab --- /dev/null +++ b/lib/python/Plugins/SystemPlugins/PvrDescrambleConvert/__init__.py @@ -0,0 +1 @@ +#nothing diff --git a/lib/python/Plugins/SystemPlugins/PvrDescrambleConvert/meta/Makefile.am b/lib/python/Plugins/SystemPlugins/PvrDescrambleConvert/meta/Makefile.am new file mode 100644 index 0000000..7f9af15 --- /dev/null +++ b/lib/python/Plugins/SystemPlugins/PvrDescrambleConvert/meta/Makefile.am @@ -0,0 +1,4 @@ +installdir = $(datadir)/meta + +dist_install_DATA = plugin_pvrdescrambleconvert.xml + diff --git a/lib/python/Plugins/SystemPlugins/PvrDescrambleConvert/meta/plugin_pvrdescrambleconvert.xml b/lib/python/Plugins/SystemPlugins/PvrDescrambleConvert/meta/plugin_pvrdescrambleconvert.xml new file mode 100644 index 0000000..3b4f426 --- /dev/null +++ b/lib/python/Plugins/SystemPlugins/PvrDescrambleConvert/meta/plugin_pvrdescrambleconvert.xml @@ -0,0 +1,16 @@ + + + + + + hschang(hschang@dev3) + PVR Descramble Convert + enigma2-plugin-systemplugins-pvrdescrambleconvert + PVR descramble in standby. + PVR descramble in standby. + + + + + + diff --git a/lib/python/Plugins/SystemPlugins/PvrDescrambleConvert/plugin.py b/lib/python/Plugins/SystemPlugins/PvrDescrambleConvert/plugin.py new file mode 100644 index 0000000..8548b0a --- /dev/null +++ b/lib/python/Plugins/SystemPlugins/PvrDescrambleConvert/plugin.py @@ -0,0 +1,63 @@ +from Plugins.Plugin import PluginDescriptor +from Screens.Screen import Screen +from Components.ConfigList import ConfigListScreen +from Components.ActionMap import ActionMap +from Components.Sources.StaticText import StaticText +from Components.config import config, getConfigListEntry +from PvrDescrambleConvert import pvr_descramble_convert + +class PvrDescrambleConvertSetup(Screen, ConfigListScreen): + skin = """ + + + + + + + + + """ + + def __init__(self,session): + Screen.__init__(self,session) + self.title = _("Pvr Descramble Convert Setup") + self.session = session + self["shortcuts"] = ActionMap(["ShortcutActions", "SetupActions" ], + { + "ok": self.keySave, + "cancel": self.keyCancel, + "red": self.keyCancel, + "green": self.keySave, + }, -2) + self.list = [] + ConfigListScreen.__init__(self, self.list,session = self.session) + self["key_red"] = StaticText(_("Cancel")) + self["key_green"] = StaticText(_("Save")) + + self["description"] = StaticText("") + self.createConfig() + self.createSetup() + + def createConfig(self): + self.enableEntry = getConfigListEntry(_("Enable PVR Descramble in standby"), config.plugins.pvrdesconvertsetup.activate) + + def createSetup(self): + self.list = [] + self.list.append( self.enableEntry ) + self["config"].list = self.list + self["config"].l.setList(self.list) + +def main(session, **kwargs): + session.open(PvrDescrambleConvertSetup) + +def Plugins(**kwargs): + list = [] + list.append( + PluginDescriptor(name=_("PVR Descramble Convert Setup"), + description=_("PVR descramble in standby"), + where = [PluginDescriptor.WHERE_PLUGINMENU], + needsRestart = False, + fnc = main)) + + return list + diff --git a/lib/service/iservice.h b/lib/service/iservice.h index f0be087..33ff66b 100644 --- a/lib/service/iservice.h +++ b/lib/service/iservice.h @@ -361,6 +361,7 @@ public: sHBBTVUrl, sLiveStreamDemuxId, + sIsScrambled, sUser = 0x100 }; @@ -900,6 +901,8 @@ public: evRecordWriteError, evNewEventInfo, evTuneStart, + evPvrTuneStart, + evPvrEof, }; enum { NoError=0, @@ -910,6 +913,7 @@ public: errTuneFailed=-255, errMisconfiguration = -256, errNoResources = -257, + errNoCiConnected = -258 }; }; @@ -925,13 +929,14 @@ public: virtual RESULT connectEvent(const Slot2 &event, ePtr &connection)=0; #endif virtual SWIG_VOID(RESULT) getError(int &SWIG_OUTPUT)=0; - virtual RESULT prepare(const char *filename, time_t begTime=-1, time_t endTime=-1, int eit_event_id=-1, const char *name=0, const char *descr=0, const char *tags=0)=0; + virtual RESULT prepare(const char *filename, time_t begTime=-1, time_t endTime=-1, int eit_event_id=-1, const char *name=0, const char *descr=0, const char *tags=0, bool descramble = true, bool recordecm = false)=0; virtual RESULT prepareStreaming()=0; virtual RESULT start(bool simulate=false)=0; virtual RESULT stop()=0; virtual SWIG_VOID(RESULT) frontendInfo(ePtr &SWIG_OUTPUT)=0; virtual SWIG_VOID(RESULT) stream(ePtr &SWIG_OUTPUT)=0; virtual SWIG_VOID(RESULT) subServices(ePtr &SWIG_OUTPUT)=0; + virtual SWIG_VOID(RESULT) getServiceType(int &SWIG_OUTPUT)=0; }; SWIG_TEMPLATE_TYPEDEF(ePtr, iRecordableServicePtr); diff --git a/lib/service/servicedvb.cpp b/lib/service/servicedvb.cpp index acc998a..1494cc5 100755 --- a/lib/service/servicedvb.cpp +++ b/lib/service/servicedvb.cpp @@ -376,6 +376,8 @@ int eStaticServiceDVBPVRInformation::getInfo(const eServiceReference &ref, int w return m_parser.m_time_create; else return iServiceInformation::resNA; + case iServiceInformation::sIsScrambled: + return m_parser.m_scrambled; default: return iServiceInformation::resNA; } @@ -832,21 +834,9 @@ RESULT eServiceFactoryDVB::play(const eServiceReference &ref, ePtr &ptr) { - if (ref.path.empty()) - { - ptr = new eDVBServiceRecord((eServiceReferenceDVB&)ref); - return 0; - } else - { - bool isstream = ref.path.substr(0, 7) == "http://"; - if(isstream) - { - ptr = new eDVBServiceRecord((eServiceReferenceDVB&)ref, isstream); - return 0; - } - ptr = 0; - return -1; - } + bool isstream = ref.path.substr(0, 7) == "http://"; + ptr = new eDVBServiceRecord((eServiceReferenceDVB&)ref, isstream); + return 0; } RESULT eServiceFactoryDVB::list(const eServiceReference &ref, ePtr &ptr) @@ -1134,7 +1124,7 @@ void eDVBServicePlay::serviceEventTimeshift(int event) if (m_skipmode < 0) m_cue->seekTo(0, -1000); ePtr source = createTsSource(r); - m_service_handler_timeshift.tuneExt(r, 1, source, r.path.c_str(), m_cue, 0, m_dvb_service); /* use the decoder demux for everything */ + m_service_handler_timeshift.tuneExt(r, 1, source, r.path.c_str(), m_cue, 0, m_dvb_service, false); /* use the decoder demux for everything */ m_event((iPlayableService*)this, evUser+1); } @@ -1164,7 +1154,9 @@ void eDVBServicePlay::serviceEventTimeshift(int event) resetTimeshift(1); ePtr source = createTsSource(r); - m_service_handler_timeshift.tuneExt(r, 1, source, m_timeshift_file_next.c_str(), m_cue, 0, m_dvb_service); /* use the decoder demux for everything */ + int use_decode_demux = 1; + eDVBServicePMTHandler::serviceType type = eDVBServicePMTHandler::timeshift_playback; + m_service_handler_timeshift.tuneExt(r, use_decode_demux, source, m_timeshift_file_next.c_str(), m_cue, 0, m_dvb_service, type, false); /* use the decoder demux for everything */ m_event((iPlayableService*)this, evUser+1); } @@ -1180,26 +1172,44 @@ void eDVBServicePlay::serviceEventTimeshift(int event) RESULT eDVBServicePlay::start() { eServiceReferenceDVB service = (eServiceReferenceDVB&)m_reference; + bool scrambled = true; + eDVBServicePMTHandler::serviceType type = eDVBServicePMTHandler::livetv; + + int use_decode_demux = 0; /* in pvr mode, we only want to use one demux. in tv mode, we're using two (one for decoding, one for data source), as we must be prepared to start recording from the data demux. */ if (m_is_pvr) { + use_decode_demux = 1; eDVBMetaParser meta; if (!meta.parseFile(m_reference.path)) { service = meta.m_ref; service.path = m_reference.path; + scrambled = meta.m_scrambled; + } + else + { + /* when there is no meta file we need to handle ts/m2ts as descrambled */ + scrambled = false; } m_cue = new eCueSheet(); + type = eDVBServicePMTHandler::playback; } else m_event(this, evStart); + if (m_is_stream) + { + scrambled = true; + type = eDVBServicePMTHandler::streamclient; + } + m_first_program_info = 1; ePtr source = createTsSource(service); - m_service_handler.tuneExt(service, m_is_pvr, source, service.path.c_str(), m_cue, false, m_dvb_service, m_is_stream); + m_service_handler.tuneExt(service, use_decode_demux, source, service.path.c_str(), m_cue, false, m_dvb_service, type, scrambled); if (m_is_pvr) { @@ -2516,7 +2526,9 @@ void eDVBServicePlay::switchToTimeshift() m_cue->seekTo(0, -1000); ePtr source = createTsSource(r); - m_service_handler_timeshift.tuneExt(r, 1, source, m_timeshift_file.c_str(), m_cue, 0, m_dvb_service); /* use the decoder demux for everything */ + int use_decode_demux = 1; + eDVBServicePMTHandler::serviceType type = eDVBServicePMTHandler::timeshift_playback; + m_service_handler_timeshift.tuneExt(r, use_decode_demux, source, m_timeshift_file.c_str(), m_cue, 0, m_dvb_service, type, false); /* use the decoder demux for everything */ eDebug("eDVBServicePlay::switchToTimeshift, in pause mode now."); pause(); diff --git a/lib/service/servicedvbrecord.cpp b/lib/service/servicedvbrecord.cpp index 4af0320..6ce66ae 100644 --- a/lib/service/servicedvbrecord.cpp +++ b/lib/service/servicedvbrecord.cpp @@ -3,6 +3,8 @@ #include #include #include +#include + #include /* for cutlist */ @@ -22,12 +24,16 @@ eDVBServiceRecord::eDVBServiceRecord(const eServiceReferenceDVB &ref, bool isstr CONNECT(m_event_handler.m_eit_changed, eDVBServiceRecord::gotNewEvent); m_state = stateIdle; m_want_record = 0; + m_record_ecm = false; + m_descramble = true; + m_pvr_descramble = false; m_tuned = 0; m_target_fd = -1; m_error = 0; m_streaming = 0; m_simulate = false; m_last_event_id = -1; + m_serviceType = eDVBServicePMTHandler::recording; } void eDVBServiceRecord::serviceEvent(int event) @@ -71,7 +77,12 @@ void eDVBServiceRecord::serviceEvent(int event) if (m_state == stateIdle) doPrepare(); else if (m_want_record) /* doRecord can be called from Prepared and Recording state */ - doRecord(); + { + if (m_pvr_descramble) + updateDecoder(); + else + doRecord(); + } m_event((iRecordableService*)this, evNewProgramInfo); break; } @@ -83,14 +94,34 @@ void eDVBServiceRecord::serviceEvent(int event) m_error = errNoResources; m_event((iRecordableService*)this, evTuneFailed); break; + case eDVBServicePMTHandler::eventEOF: + m_event((iRecordableService*)this, evPvrEof); + break; + case eDVBServicePMTHandler::eventStartPvrDescramble: + if (m_want_record) + { + doRecord(); + } + break; } } -RESULT eDVBServiceRecord::prepare(const char *filename, time_t begTime, time_t endTime, int eit_event_id, const char *name, const char *descr, const char *tags) +RESULT eDVBServiceRecord::prepare(const char *filename, time_t begTime, time_t endTime, int eit_event_id, const char *name, const char *descr, const char *tags, bool descramble, bool recordecm) { + bool config_recording_always_ecm = ePythonConfigQuery::getConfigBoolValue("config.recording.always_ecm", false); + bool config_recording_never_decrypt = ePythonConfigQuery::getConfigBoolValue("config.recording.never_decrypt", false); m_filename = filename; m_streaming = 0; - + m_descramble = config_recording_never_decrypt ? false : descramble; + m_record_ecm = config_recording_always_ecm ? true : recordecm; + + // force descramble for _pvrdesc.ts + if (strstr(filename, "_pvrdesc.ts")) + { + m_pvr_descramble = true; + m_descramble = true; + } + if (m_state == stateIdle) { int ret = doPrepare(); @@ -136,7 +167,8 @@ RESULT eDVBServiceRecord::prepare(const char *filename, time_t begTime, time_t e meta.m_description = descr; if (tags) meta.m_tags = tags; - ret = meta.updateMeta(filename) ? -255 : 0; + meta.m_scrambled = !m_descramble; + ret = meta.updateMeta(m_filename) ? -255 : 0; if (!ret) { const eit_event_struct *event = 0; @@ -162,7 +194,7 @@ RESULT eDVBServiceRecord::prepare(const char *filename, time_t begTime, time_t e if ( event ) { eDebug("found event.. store to disc"); - std::string fname = filename; + std::string fname = m_filename; fname.erase(fname.length()-2, 2); fname+="eit"; int fd = open(fname.c_str(), O_CREAT|O_WRONLY, 0777); @@ -234,32 +266,62 @@ int eDVBServiceRecord::doPrepare() /* allocate a ts recorder if we don't already have one. */ if (m_state == stateIdle) { - bool isstreamclient = false; + int use_decode_demux = 0; + + if (m_streaming) + { + m_serviceType = m_record_ecm ? eDVBServicePMTHandler::scrambled_streamserver : eDVBServicePMTHandler::streamserver; + } + else + { + m_serviceType = m_record_ecm ? eDVBServicePMTHandler::scrambled_recording : eDVBServicePMTHandler::recording; + } + m_pids_active.clear(); m_state = statePrepared; ePtr source; - if (!m_ref.path.empty()) + if (!m_simulate && !m_ref.path.empty()) { if (m_is_stream_client) { - isstreamclient = true; + m_descramble = true; // assume stream is scrambled + m_record_ecm = false; + m_serviceType = eDVBServicePMTHandler::streamclient; eHttpStream *f = new eHttpStream(); f->open(m_ref.path.c_str()); source = ePtr(f); } else { + use_decode_demux = 1; + /* re-record a recording */ + eDVBMetaParser meta; + if (!meta.parseFile(m_ref.path)) + { + m_descramble = meta.m_scrambled; + } + + if(m_pvr_descramble) + { + m_serviceType = eDVBServicePMTHandler::pvrDescramble; + } + else + { + m_serviceType = eDVBServicePMTHandler::offline; + } + eRawFile *f = new eRawFile(); f->open(m_ref.path.c_str()); source = ePtr(f); } + m_event((iRecordableService*)this, evPvrTuneStart); } else { m_event((iRecordableService*)this, evTuneStart); } - return m_service_handler.tuneExt(m_ref, 0, source, m_ref.path.c_str(), 0, m_simulate, 0, isstreamclient); + return m_service_handler.tuneExt(m_ref, use_decode_demux, source, m_ref.path.c_str(), 0, m_simulate, 0, m_serviceType, m_descramble); } return 0; } @@ -279,6 +341,22 @@ int eDVBServiceRecord::doRecord() if (!m_record && m_tuned && !m_streaming && !m_simulate) { + if (m_pvr_descramble) + { + if (m_service_handler.isPmtReady()) + { + if (!m_service_handler.isCiConnected()) + { + m_event((iRecordableService*)this, evRecordFailed); + return errNoCiConnected; + } + } + else + { + return 0; + } + } + eDebug("Recording to %s...", m_filename.c_str()); ::remove(m_filename.c_str()); int fd = ::open(m_filename.c_str(), O_WRONLY|O_CREAT|O_LARGEFILE, 0644); @@ -404,6 +482,41 @@ int eDVBServiceRecord::doRecord() if (program.textPid != -1) pids_to_record.insert(program.textPid); // Videotext + if (m_record_ecm) + { + for (std::list::const_iterator i(program.caids.begin()); + i != program.caids.end(); ++i) + { + if (i->capid >= 0) pids_to_record.insert(i->capid); + } + pids_to_record.insert(EventInformationSection::PID); + pids_to_record.insert(TimeAndDateSection::PID); + } + + { + int isCrypted = (int)program.isCrypted(); + int scrambled = !m_descramble; + if (!m_descramble) + { + scrambled = isCrypted; + } + else if (!isCrypted) + { + scrambled = 0; + } + else // m_descramble && isCrypted + { + if (!m_service_handler.isCiConnected()) + scrambled = 1; + else + scrambled = 0; + } + eDVBMetaParser dvbParser; + dvbParser.parseFile(m_filename); + dvbParser.m_scrambled = scrambled; + dvbParser.updateMeta(m_filename); + } + /* find out which pids are NEW and which pids are obsolete.. */ std::set new_pids, obsolete_pids; @@ -446,6 +559,77 @@ int eDVBServiceRecord::doRecord() return 0; } +void eDVBServiceRecord::updateDecoder() +{ + int vpid = -1, vpidtype = -1, apid = -1, apidtype = -1, pcrpid = -1; + + eDVBServicePMTHandler &h = m_service_handler; + + eDVBServicePMTHandler::program program; + if (m_service_handler.getProgramInfo(program)) + eDebug("getting program info failed."); + else + { + eDebugNoNewLine("have %zd video stream(s)", program.videoStreams.size()); + if (!program.videoStreams.empty()) + { + eDebugNoNewLine(" ("); + for (std::vector::const_iterator + i(program.videoStreams.begin()); + i != program.videoStreams.end(); ++i) + { + if (vpid == -1) + { + vpid = i->pid; + vpidtype = i->type; + } + if (i != program.videoStreams.begin()) + eDebugNoNewLine(", "); + eDebugNoNewLine("%04x", i->pid); + } + eDebugNoNewLine(")"); + } + + eDebugNoNewLine(", and %zd audio stream(s)", program.audioStreams.size()); + if (!program.audioStreams.empty()) + { + eDebugNoNewLine(" ("); + for (std::vector::const_iterator + i(program.audioStreams.begin()); + i != program.audioStreams.end(); ++i) + { + if (i != program.audioStreams.begin()) + eDebugNoNewLine(", "); + eDebugNoNewLine("%04x", i->pid); + } + eDebugNoNewLine(")"); + } + + apid = program.audioStreams[program.defaultAudioStream].pid; + apidtype = program.audioStreams[program.defaultAudioStream].type; + + eDebugNoNewLine(", and the pcr pid is %04x", program.pcrPid); + pcrpid = program.pcrPid; + } + + if (!m_decoder) + { + h.getDecodeDemux(m_decode_demux); + if (m_decode_demux) + { + m_decode_demux->getMPEGDecoder(m_decoder, 0); + } + } + + if (m_decoder) + { + m_decoder->setVideoPID(vpid, vpidtype); + m_decoder->setAudioPID(apid, apidtype); + m_decoder->setSyncPCR(-1); + m_decoder->play(); + } +} + RESULT eDVBServiceRecord::frontendInfo(ePtr &ptr) { ptr = this; diff --git a/lib/service/servicedvbrecord.h b/lib/service/servicedvbrecord.h index 7b53803..7662380 100644 --- a/lib/service/servicedvbrecord.h +++ b/lib/service/servicedvbrecord.h @@ -18,8 +18,10 @@ class eDVBServiceRecord: public eDVBServiceBase, { DECLARE_REF(eDVBServiceRecord); public: + eDVBServicePMTHandler::serviceType m_serviceType; + RESULT connectEvent(const Slot2 &event, ePtr &connection); - RESULT prepare(const char *filename, time_t begTime, time_t endTime, int eit_event_id, const char *name, const char *descr, const char *tags); + RESULT prepare(const char *filename, time_t begTime, time_t endTime, int eit_event_id, const char *name, const char *descr, const char *tags, bool descramble, bool recordecm); RESULT prepareStreaming(); RESULT start(bool simulate=false); RESULT stop(); @@ -27,6 +29,7 @@ public: RESULT getError(int &error) { error = m_error; return 0; } RESULT frontendInfo(ePtr &ptr); RESULT subServices(ePtr &ptr); + RESULT getServiceType(int &serviceType) { serviceType = m_serviceType; return 0; }; // iStreamableService PyObject *getStreamingData(); @@ -34,10 +37,18 @@ public: // iSubserviceList int getNumberOfSubservices(); RESULT getSubservice(eServiceReference &subservice, unsigned int n); + +protected: + ePtr m_decode_demux; + ePtr m_decoder; + private: enum { stateIdle, statePrepared, stateRecording }; bool m_simulate; int m_state, m_want_record; + bool m_record_ecm; + bool m_descramble; + bool m_pvr_descramble; bool m_is_stream_client; friend class eServiceFactoryDVB; eDVBServiceRecord(const eServiceReferenceDVB &ref, bool isstreamclient = false); @@ -60,6 +71,7 @@ private: int doPrepare(); int doRecord(); + void updateDecoder(); /* events */ void serviceEvent(int event); diff --git a/lib/service/servicehdmi.cpp b/lib/service/servicehdmi.cpp index 2767982..d7c321e 100644 --- a/lib/service/servicehdmi.cpp +++ b/lib/service/servicehdmi.cpp @@ -205,7 +205,7 @@ eServiceHDMIRecord::eServiceHDMIRecord(const eServiceReference &ref, ePtr &nav_instance); RESULT connectEvent(const Slot2 &event, ePtr &connection); - RESULT prepare(const char *filename, time_t begTime, time_t endTime, int eit_event_id, const char *name, const char *descr, const char *tags); + RESULT prepare(const char *filename, time_t begTime, time_t endTime, int eit_event_id, const char *name, const char *descr, const char *tags, bool descramble, bool recordecm); RESULT prepareStreaming(); RESULT start(bool simulate=false); RESULT stop(); @@ -98,6 +98,7 @@ public: RESULT frontendInfo(ePtr &ptr); RESULT stream(ePtr &ptr); RESULT subServices(ePtr &ptr); + RESULT getServiceType(int &serviceType) { serviceType = -1; return -1; }; private: enum { stateIdle, statePrepared, stateRecording }; diff --git a/lib/service/servicem2ts.cpp b/lib/service/servicem2ts.cpp index e79907d..4c2fc80 100644 --- a/lib/service/servicem2ts.cpp +++ b/lib/service/servicem2ts.cpp @@ -17,6 +17,7 @@ public: off_t lseek(off_t offset, int whence); ssize_t read(off_t offset, void *buf, size_t count); off_t length(); + off_t offset(); int valid(); private: int m_sync_offset; @@ -309,6 +310,11 @@ off_t eM2TSFile::length() return m_length; } +off_t eM2TSFile::offset() +{ + return m_current_offset; +} + eServiceFactoryM2TS::eServiceFactoryM2TS() { ePtr sc; -- 2.7.4