keyids.py keymapparser.py mytest.py skin.py timer.py tools.py GlobalActions.py \
e2reactor.py
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = enigma2.pc
+
metadir = $(datadir)/meta
meta_DATA = index-enigma2.xml
CLEANFILES = index-enigma2.xml
checkit = True
for timer in root.findall("timer"):
newTimer = createTimer(timer)
- if (self.record(newTimer, True, True) is not None) and (checkit == True):
+ if (self.record(newTimer, True, dosave=False) is not None) and (checkit == True):
from Tools.Notifications import AddPopup
from Screens.MessageBox import MessageBox
AddPopup(_("Timer overlap in timers.xml detected!\nPlease recheck it!"), type = MessageBox.TYPE_ERROR, timeout = 0, id = "TimerLoadFailed")
-AC_INIT([enigma2],[2.8.0],[enigma2-devel@lists.elitedvb.net])
+AC_INIT([enigma2],[3.0.0],[enigma2-devel@lists.elitedvb.net])
AM_INIT_AUTOMAKE([dist-bzip2 no-define tar-pax -Wno-portability])
AC_CONFIG_HEADERS([enigma2_config.h])
AC_CHECK_HEADERS([dbox/fp.h dbox/lcd-ks0713.h])
-AC_PATH_PROG(MSGFMT,[msgfmt])
-AC_PATH_PROG(MSGINIT,[msginit])
-AC_PATH_PROG(MSGMERGE,[msgmerge])
-AC_PATH_PROG(MSGUNIQ,[msguniq])
-AC_PATH_PROG(XGETTEXT,[xgettext])
-if test -z "$MSGFMT" -o -z "$MSGINIT" -o -z "$MSGMERGE" -o -z "$MSGUNIQ" -o -z "$XGETTEXT"; then
- AC_MSG_ERROR([Could not find required gettext tools])
-fi
+AC_PATH_PROG(MSGFMT, msgfmt, AC_MSG_ERROR(Could not find msgfmt))
AM_PATH_PYTHON
AX_PYTHON_DEVEL
fi
AC_ARG_WITH(po,
- AS_HELP_STRING([--without-po],[disable updating of po files]),
- [with_po="$withval"],[with_po="yes"])
+ AS_HELP_STRING([--with-po],[enable updating of po files]),
+ [with_po="$withval"],[with_po="no"])
+if test "$with_po" = "yes"; then
+ AC_PATH_PROG(MSGINIT, msginit)
+ AC_PATH_PROG(MSGMERGE, msgmerge)
+ AC_PATH_PROG(MSGUNIQ, msguniq)
+ AC_PATH_PROG(XGETTEXT, xgettext)
+ if test -z "$MSGINIT" -o -z "$MSGMERGE" -o -z "$MSGUNIQ" -o -z "$XGETTEXT"; then
+ AC_MSG_ERROR([Could not find required gettext tools])
+ fi
+fi
AM_CONDITIONAL(UPDATE_PO, test "$with_po" = "yes")
+ENIGMA2_CFLAGS="-fno-rtti -fno-exceptions"
+AC_SUBST(ENIGMA2_CFLAGS)
+
CPPFLAGS="$CPPFLAGS $PYTHON_CPPFLAGS"
-CXXFLAGS="$CXXFLAGS $BASE_CFLAGS $PTHREAD_CFLAGS $DEBUG_CFLAGS -fno-rtti -fno-exceptions -Wall"
+CFLAGS="$CFLAGS $DEBUG_CFLAGS -Wall"
+CXXFLAGS="$CXXFLAGS $DEBUG_CFLAGS -Wall $BASE_CFLAGS $ENIGMA2_CFLAGS $PTHREAD_CFLAGS"
AC_CONFIG_FILES([
Makefile
<item level="2" text="Fast Forward speeds">config.seek.speeds_forward</item>
<item level="2" text="Rewind speeds">config.seek.speeds_backward</item>
<item level="2" text="Slow Motion speeds">config.seek.speeds_slowmotion</item>
-<!-- TRANSLATORS: Note that "Enter" in the two strings below should *not*
- be interpreted as "Give speed as input". The intended meaning is
- instead "Initial speed when starting winding", i.e. the speed at
- which "winding mode" is entered when first pressing "rewind" or
- "fast forward". -->
- <item level="2" text="Enter Fast Forward at speed">config.seek.enter_forward</item>
- <item level="2" text="Enter Rewind at speed">config.seek.enter_backward</item>
-<!-- TRANSLATORS: The effect of "Non-smooth winding" is that rather
- than using ordinary "continuous" or "smooth" winding, a fast
- sequence of stills is shown when winding at high speeds. This
- makes it much easier too follow when almost each frame comes from
- a new scene. The effect is achieved by repeating each shown frame
- a couple of times. The settings control both at which speed this
- winding mode sets in, and how many times each frame should be
- repeated. This was previously called "Discontinuous playback"
- which was incomprehensible. "Non-smooth winding" may be a better
- term, but note that there is nothing irregular about it. Synonyms
- better suited for translation to other languages may be "stepwise
- winding/playback", or "winding/playback using stills". -->
- <item level="2" text="Use non-smooth winding at speeds above">config.seek.stepwise_minspeed</item>
- <item level="2" text="Frame repeat count during non-smooth winding">config.seek.stepwise_repeat</item>
+<!-- TRANSLATORS: The following is the speed you get on the first press on fast-forward.
+ It was previously called "enter fast forward at speed" which was easily misunderstood. -->
+ <item level="2" text="Initial Fast Forward speed">config.seek.enter_forward</item>
+ <item level="2" text="Initial Rewind speed">config.seek.enter_backward</item>
<item level="2" text="Behavior of 'pause' when paused">config.seek.on_pause</item>
<item level="2" text="Behavior of 0 key in PiP-mode">config.usage.pip_zero_button</item>
<item level="2" text="Alternative services tuner priority">config.usage.alternatives_priority</item>
Name: enigma2
Description: Enigma2
Version: @VERSION@
-Libs:
-Cflags: -I${includedir}/enigma2
+Cflags: -I${includedir}/enigma2 @ENIGMA2_CFLAGS@
+Requires.private: sigc++-1.2
case aLPCM:
bypass = 6;
break;
+ case aDTSHD:
+ bypass = 0x10;
+ break;
}
eDebugNoNewLine("AUDIO_SET_BYPASS(%d) - ", bypass);
ePtr<eDVBDemux> m_demux;
int m_fd, m_fd_demux, m_dev, m_is_freezed;
public:
- enum { aMPEG, aAC3, aDTS, aAAC, aAACHE, aLPCM };
+ enum { aMPEG, aAC3, aDTS, aAAC, aAACHE, aLPCM, aDTSHD };
eDVBAudio(eDVBDemux *demux, int dev);
enum { aMonoLeft, aStereo, aMonoRight };
void setChannel(int channel);
int first;
ePtr<iDVBDemux> m_demux;
eMainloop *ml;
+
+ /* needed to detect broken table version handling (seen on some m2ts files) */
+ struct timespec m_prev_table_update;
+ int m_table_cnt;
public:
eAUTable()
int begin(eMainloop *m, const eDVBTableSpec &spec, ePtr<iDVBDemux> demux)
{
+ m_table_cnt = 0;
ml = m;
m_demux = demux;
first= 1;
if (current && (!current->getSpec(spec)))
{
+ /* detect broken table version handling (seen on some m2ts files) */
+ if (m_table_cnt)
+ {
+ if (abs(timeout_usec(m_prev_table_update)) > 500000)
+ m_table_cnt = -1;
+ else if (m_table_cnt > 1) // two pmt update within one second
+ {
+ eDebug("Seen two consecutive table version changes within 500ms. "
+ "This seems broken, so auto update for pid %04x, table %02x is now disabled!!",
+ spec.pid, spec.tid);
+ m_table_cnt = 0;
+ return;
+ }
+ }
+
+ ++m_table_cnt;
+ clock_gettime(CLOCK_MONOTONIC, &m_prev_table_update);
+
next = new Table();
CONNECT(next->tableReady, eAUTable::slotTableReady);
spec.flags &= ~(eDVBTableSpec::tfAnyVersion|eDVBTableSpec::tfThisVersion|eDVBTableSpec::tfHaveTimeout);
/** Set Displayed Video PID and type */
virtual RESULT setVideoPID(int vpid, int type)=0;
- enum { af_MPEG, af_AC3, af_DTS, af_AAC };
+ enum { af_MPEG, af_AC3, af_DTS, af_AAC, af_DTSHD };
/** Set Displayed Audio PID and type */
virtual RESULT setAudioPID(int apid, int type)=0;
#include <dvbsi++/registration_descriptor.h>
eDVBServicePMTHandler::eDVBServicePMTHandler()
- :m_ca_servicePtr(0), m_dvb_scan(0), m_decode_demux_num(0xFF)
+ :m_ca_servicePtr(0), m_dvb_scan(0), m_decode_demux_num(0xFF), m_no_pat_entry_delay(eTimer::create())
{
m_use_decode_demux = 0;
m_pmt_pid = -1;
eDVBResourceManager::getInstance(m_resourceManager);
CONNECT(m_PMT.tableReady, eDVBServicePMTHandler::PMTready);
CONNECT(m_PAT.tableReady, eDVBServicePMTHandler::PATready);
+ CONNECT(m_no_pat_entry_delay->timeout, eDVBServicePMTHandler::sendEventNoPatEntry);
}
eDVBServicePMTHandler::~eDVBServicePMTHandler()
}
}
+void eDVBServicePMTHandler::sendEventNoPatEntry()
+{
+ serviceEvent(eventNoPATEntry);
+}
+
void eDVBServicePMTHandler::PATready(int)
{
+ eDebug("PATready");
ePtr<eTable<ProgramAssociationSection> > ptr;
if (!m_PAT.getCurrent(ptr))
{
+ int service_id_single = -1;
+ int pmtpid_single = -1;
int pmtpid = -1;
+ int cnt=0;
std::vector<ProgramAssociationSection*>::const_iterator i;
for (i = ptr->getSections().begin(); pmtpid == -1 && i != ptr->getSections().end(); ++i)
{
const ProgramAssociationSection &pat = **i;
ProgramAssociationConstIterator program;
for (program = pat.getPrograms()->begin(); pmtpid == -1 && program != pat.getPrograms()->end(); ++program)
+ {
+ ++cnt;
if (eServiceID((*program)->getProgramNumber()) == m_reference.getServiceID())
pmtpid = (*program)->getProgramMapPid();
+ if (++cnt == 1 && pmtpid_single == -1 && pmtpid == -1)
+ {
+ pmtpid_single = (*program)->getProgramMapPid();
+ service_id_single = (*program)->getProgramNumber();
+ }
+ else
+ pmtpid_single = service_id_single = -1;
+ }
}
- if (pmtpid == -1)
- serviceEvent(eventNoPATEntry);
- else
+ if (pmtpid_single != -1) // only one PAT entry .. and not valid pmtpid found
+ {
+ eDebug("use single pat entry!");
+ m_reference.setServiceID(eServiceID(service_id_single));
+ pmtpid = pmtpid_single;
+ }
+ if (pmtpid == -1) {
+ eDebug("no PAT entry found.. start delay");
+ m_no_pat_entry_delay->start(1000, true);
+ }
+ else {
+ eDebug("use pmtpid %04x for service_id %04x", pmtpid, m_reference.getServiceID().get());
+ m_no_pat_entry_delay->stop();
m_PMT.begin(eApp, eDVBPMTSpec(pmtpid, m_reference.getServiceID().get()), m_demux);
+ }
} else
serviceEvent(eventNoPAT);
}
for (i = ptr->getSections().begin(); i != ptr->getSections().end(); ++i)
{
const ProgramMapSection &pmt = **i;
+ int is_hdmv = 0;
+
program.pcrPid = pmt.getPcrPid();
+ for (DescriptorConstIterator desc = pmt.getDescriptors()->begin();
+ desc != pmt.getDescriptors()->end(); ++desc)
+ {
+ if ((*desc)->getTag() == CA_DESCRIPTOR)
+ {
+ CaDescriptor *descr = (CaDescriptor*)(*desc);
+ program::capid_pair pair;
+ pair.caid = descr->getCaSystemId();
+ pair.capid = descr->getCaPid();
+ program.caids.push_back(pair);
+ }
+ else if ((*desc)->getTag() == REGISTRATION_DESCRIPTOR)
+ {
+ RegistrationDescriptor *d = (RegistrationDescriptor*)(*desc);
+ if (d->getFormatIdentifier() == 0x48444d56) // HDMV
+ is_hdmv = 1;
+ }
+ }
+
ElementaryStreamInfoConstIterator es;
for (es = pmt.getEsInfo()->begin(); es != pmt.getEsInfo()->end(); ++es)
{
audio.type = audioStream::atAACHE;
forced_audio = 1;
}
- case 0x80: // user private ... but blueray LPCM
- if (!isvideo && !isaudio)
+ case 0x80: // user private ... but bluray LPCM
+ case 0xA0: // bluray secondary LPCM
+ if (!isvideo && !isaudio && is_hdmv)
{
isaudio = 1;
audio.type = audioStream::atLPCM;
}
- case 0x81: // user private ... but blueray AC3
- if (!isvideo && !isaudio)
+ case 0x81: // user private ... but bluray AC3
+ case 0xA1: // bluray secondary AC3
+ if (!isvideo && !isaudio && is_hdmv)
{
isaudio = 1;
audio.type = audioStream::atAC3;
}
- case 0x82: // Blueray DTS (dvb user private...)
- case 0xA2: // Blueray secondary DTS
- if (!isvideo && !isaudio)
+ case 0x82: // bluray DTS (dvb user private...)
+ case 0xA2: // bluray secondary DTS
+ if (!isvideo && !isaudio && is_hdmv)
{
isaudio = 1;
audio.type = audioStream::atDTS;
}
+ case 0x86: // bluray DTS-HD (dvb user private...)
+ case 0xA6: // bluray secondary DTS-HD
+ if (!isvideo && !isaudio && is_hdmv)
+ {
+ isaudio = 1;
+ audio.type = audioStream::atDTSHD;
+ }
case 0x06: // PES Private
case 0xEA: // TS_PSI_ST_SMPTE_VC1
{
default:
break;
}
- if (isteletext && (isaudio || isvideo))
+ if (isteletext && (isaudio || isvideo))
{
- eDebug("ambiguous streamtype for PID %04x detected.. forced as teletext!", (*es)->getPid());
+ eDebug("ambiguous streamtype for PID %04x detected.. forced as teletext!", (*es)->getPid());
continue; // continue with next PID
}
else if (issubtitle && (isaudio || isvideo))
else
continue;
}
- for (DescriptorConstIterator desc = pmt.getDescriptors()->begin();
- desc != pmt.getDescriptors()->end(); ++desc)
- {
- if ((*desc)->getTag() == CA_DESCRIPTOR)
- {
- CaDescriptor *descr = (CaDescriptor*)(*desc);
- program::capid_pair pair;
- pair.caid = descr->getCaSystemId();
- pair.capid = descr->getCaPid();
- program.caids.push_back(pair);
- }
- }
}
ret = 0;
{
RESULT res=0;
m_reference = ref;
-
m_use_decode_demux = use_decode_demux;
+ m_no_pat_entry_delay->stop();
/* 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;
{
if (!ref.getServiceID().get() /* incorrect sid in meta file or recordings.epl*/ )
{
- eWarning("no .meta file found, trying to find PMT pid");
eDVBTSTools tstools;
+ bool b = source || !tstools.openFile(ref.path.c_str(), 1);
+ eWarning("no .meta file found, trying to find PMT pid");
if (source)
- tstools.setSource(source, streaminfo_file ? streaminfo_file : ref.path.c_str());
- else if (tstools.openFile(ref.path.c_str()))
- eWarning("failed to open file");
- else
+ tstools.setSource(source, NULL);
+ if (b)
{
int service_id, pmt_pid;
if (!tstools.findPMT(pmt_pid, service_id))
m_pmt_pid = pmt_pid;
}
}
+ else
+ eWarning("no valid source to find PMT pid!");
}
eDebug("alloc PVR");
/* allocate PVR */
int m_use_decode_demux;
uint8_t m_decode_demux_num;
+ ePtr<eTimer> m_no_pat_entry_delay;
public:
eDVBServicePMTHandler();
~eDVBServicePMTHandler();
{
int pid,
rdsPid; // hack for some radio services which transmit radiotext on different pid (i.e. harmony fm, HIT RADIO FFH, ...)
- enum { atMPEG, atAC3, atDTS, atAAC, atAACHE, atLPCM };
+ enum { atMPEG, atAC3, atDTS, atAAC, atAACHE, atLPCM, atDTSHD };
int type; // mpeg2, ac3, dts, ...
int component_tag;
{
uint16_t caid;
int capid;
- bool operator< (const struct capid_pair &t) { return t.caid < caid; }
+ bool operator< (const struct capid_pair &t) const { return t.caid < caid; }
};
std::vector<videoStream> videoStreams;
std::vector<audioStream> audioStreams;
int getPMT(ePtr<eTable<ProgramMapSection> > &ptr) { return m_PMT.getCurrent(ptr); }
int getChannel(eUsePtr<iDVBChannel> &channel);
void resetCachedProgram() { m_have_cached_program = false; }
+ void sendEventNoPatEntry();
/* deprecated interface */
int tune(eServiceReferenceDVB &ref, int use_decode_demux, eCueSheet *sg=0, bool simulate=false, eDVBService *service = 0);
pts_t current = i->second - currentDelta;
pts_t diff = current - lastpts_t;
- if (llabs(diff) > (90000*5)) // 5sec diff
+ if (llabs(diff) > (90000*10)) // 10sec diff
{
// eDebug("%llx < %llx, have discont. new timestamp is %llx (diff is %llx)!", current, lastpts_t, i->second, diff);
currentDelta = i->second - lastpts_t; /* FIXME: should be the extrapolated new timestamp, based on the current rate */
break;
case 0x71: // AC3 / DTS
break;
+ case 0x72: // DTS - HD
+ break;
default:
eDebug("skip unknwn stream_id_extension %02x\n", payload[9+offs]);
continue;
else if (direction == +1)
direction = 0;
}
- /* let's find the next frame after the given offset */
off_t start = offset;
+#if 0
+ /* backtrack to find the previous sequence start, in case of MPEG2 */
+ if ((data & 0xFF) == 0x00) {
+ do {
+ --start;
+ if (m_streaminfo.getStructureEntry(start, data, 0))
+ {
+ eDebug("get previous failed");
+ return -1;
+ }
+ } while (((data & 0xFF) != 9) && ((data & 0xFF) != 0x00) && ((data & 0xFF) != 0xB3)); /* sequence start or previous frame */
+ if ((data & 0xFF) != 0xB3)
+ start = offset; /* Failed to find corresponding sequence start, so never mind */
+ }
+
+#endif
+
+ /* let's find the next frame after the given offset */
do {
if (m_streaminfo.getStructureEntry(offset, data, 1))
{
// eDebug("%08llx@%llx (next)", data, offset);
} while (((data & 0xFF) != 9) && ((data & 0xFF) != 0x00)); /* next frame */
+#if 0
/* align to TS pkt start */
-// start = start - (start % 188);
-// offset = offset - (offset % 188);
+ start = start - (start % 188);
+ offset = offset - (offset % 188);
+#endif
len = offset - start;
_offset = start;
if ((icolor & 0xFF000000) == 0xFF000000)
{
int index = icolor & 0xFFFFFF;
- eDebug("[eListboxPythonMultiContent] template color index: %d", index);
+ if (PyTuple_GetItem(data, index) == Py_None)
+ return ePyObject();
return PyTuple_GetItem(data, index);
}
// painter.fill(eRect(0, 10, s.width(), s.height()-20));
pts_t in = 0, out = 0;
+ int xm, xm_last = -1;
std::multiset<cueEntry>::iterator i(m_cue_entries.begin());
continue;
} else if (i->what == 1) /* out */
out = i++->where;
- else if (i->what == 2) /* mark */
+ else /* mark or last */
{
- int xm = scale(i->where);
- painter.setForegroundColor(gRGB(0xFF8080));
- painter.fill(eRect(xm - 2, 0, 4, s.height()));
+ xm = scale(i->where);
+ if (i->what == 2) {
+ painter.setForegroundColor(gRGB(0xFF8080));
+ if (xm - 2 < xm_last) /* Make sure last is not overdrawn */
+ painter.fill(eRect(xm_last, 0, 2 + xm - xm_last, s.height()));
+ else
+ painter.fill(eRect(xm - 2, 0, 4, s.height()));
+ } else if (i->what == 3) {
+ painter.setForegroundColor(gRGB(0x80FF80));
+ painter.fill(eRect(xm - 1, 0, 3, s.height()));
+ xm_last = xm + 2;
+ }
i++;
continue;
- } else /* other marker, like last position */
- {
- ++i;
- continue;
}
}
struct ePangoSubtitlePage
{
- pts_t show_pts;
+ pts_t m_show_pts;
int m_timeout; /* in milliseconds */
std::vector<ePangoSubtitlePageElement> m_elements;
void clear() { m_elements.clear(); }
};
+struct eVobSubtitlePage
+{
+ pts_t m_show_pts;
+ int m_timeout; /* in milliseconds */
+ ePtr<gPixmap> m_pixmap;
+};
+
class eDVBTeletextSubtitlePage;
-class eDVBPangoSubtitlePage;
-class ePangoSubtitlePage;
+class eDVBSubtitlePage;
class eSubtitleWidget: public eWidget, public Object
{
"mpeg": "movie",
"mkv": "movie",
"mp4": "movie",
- "mov": "movie"
+ "mov": "movie",
+ "m2ts": "movie",
}
def FileEntryComponent(name, absolute = None, isDir = False):
return (res >> 8)
def createPartition(self):
- cmd = 'printf "0,\n;\n;\n;\ny\n" | sfdisk -f ' + self.disk_path
+ cmd = 'printf "8,\n;0,0\n;0,0\n;0,0\ny\n" | sfdisk -f -uS ' + self.disk_path
res = system(cmd)
return (res >> 8)
from time import localtime, mktime, gmtime
from ServiceReference import ServiceReference
from enigma import iServiceInformation, eServiceCenter, eServiceReference
+from timer import TimerEntry
class TimerSanityCheck:
def __init__(self, timerlist, newtimer=None):
self.rep_eventlist.append((begin, idx))
begin += 86400
rflags >>= 1
- else:
+ elif timer.state < TimerEntry.StateEnded:
self.nrep_eventlist.extend([(timer.begin,self.bflag,idx),(timer.end,self.eflag,idx)])
idx += 1
config.seek.selfdefined_79 = ConfigNumber(default=300)
config.seek.speeds_forward = ConfigSet(default=[2, 4, 8, 16, 32, 64, 128], choices=[2, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128])
- config.seek.speeds_backward = ConfigSet(default=[8, 16, 32, 64, 128], choices=[1, 2, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128])
+ config.seek.speeds_backward = ConfigSet(default=[2, 4, 8, 16, 32, 64, 128], choices=[1, 2, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128])
config.seek.speeds_slowmotion = ConfigSet(default=[2, 4, 8], choices=[2, 4, 6, 8, 12, 16, 25])
config.seek.enter_forward = ConfigSelection(default = "2", choices = ["2", "4", "6", "8", "12", "16", "24", "32", "48", "64", "96", "128"])
config.seek.enter_backward = ConfigSelection(default = "1", choices = ["1", "2", "4", "6", "8", "12", "16", "24", "32", "48", "64", "96", "128"])
- config.seek.stepwise_minspeed = ConfigSelection(default = "16", choices = ["Never", "2", "4", "6", "8", "12", "16", "24", "32", "48", "64", "96", "128"])
- config.seek.stepwise_repeat = ConfigSelection(default = "3", choices = ["2", "3", "4", "5", "6"])
config.seek.on_pause = ConfigSelection(default = "play", choices = [
("play", _("Play")),
self.saved_config_speeds_backward = config.seek.speeds_backward.value
self.saved_config_enter_forward = config.seek.enter_forward.value
self.saved_config_enter_backward = config.seek.enter_backward.value
- self.saved_config_seek_stepwise_minspeed = config.seek.stepwise_minspeed.value
- self.saved_config_seek_stepwise_repeat = config.seek.stepwise_repeat.value
self.saved_config_seek_on_pause = config.seek.on_pause.value
self.saved_config_seek_speeds_slowmotion = config.seek.speeds_slowmotion.value
config.seek.speeds_slowmotion.value = [ ]
config.seek.enter_forward.value = "2"
config.seek.enter_backward.value = "2"
- config.seek.stepwise_minspeed.value = "Never"
- config.seek.stepwise_repeat.value = "3"
config.seek.on_pause.value = "play"
def restore_infobar_seek_config(self):
config.seek.speeds_slowmotion.value = self.saved_config_seek_speeds_slowmotion
config.seek.enter_forward.value = self.saved_config_enter_forward
config.seek.enter_backward.value = self.saved_config_enter_backward
- config.seek.stepwise_minspeed.value = self.saved_config_seek_stepwise_minspeed
- config.seek.stepwise_repeat.value = self.saved_config_seek_stepwise_repeat
config.seek.on_pause.value = self.saved_config_seek_on_pause
def __init__(self, session, dvd_device = None, dvd_filelist = [ ], args = None):
HelpableScreen.__init__(self)
self.save_infobar_seek_config()
self.change_infobar_seek_config()
- InfoBarSeek.__init__(self, useSeekBackHack=False)
+ InfoBarSeek.__init__(self)
InfoBarPVRState.__init__(self)
self.dvdScreen = self.session.instantiateDialog(DVDOverlay)
-include Python.h \
-include $(top_builddir)/enigma2_config.h
+AM_CXXFLAGS = @LIBDDVD_CFLAGS@
+
plugindir = $(pkglibdir)/python/Plugins/Extensions/DVDPlayer
plugin_LTLIBRARIES = servicedvd.la
servicedvd.h
servicedvd_la_LDFLAGS = -avoid-version -module
+servicedvd_la_LIBADD = @LIBDDVD_LIBS@
# 'None' is magic to start at the list of mountpoints
defaultDir = config.mediaplayer.defaultDir.getValue()
- self.filelist = FileList(defaultDir, matchingPattern = "(?i)^.*\.(mp2|mp3|ogg|ts|wav|wave|m3u|pls|e2pls|mpg|vob|avi|divx|m4v|mkv|mp4|m4a|dat|flac|mov)", useServiceRef = True, additionalExtensions = "4098:m3u 4098:e2pls 4098:pls")
+ self.filelist = FileList(defaultDir, matchingPattern = "(?i)^.*\.(mp2|mp3|ogg|ts|wav|wave|m3u|pls|e2pls|mpg|vob|avi|divx|m4v|mkv|mp4|m4a|dat|flac|mov|m2ts)", useServiceRef = True, additionalExtensions = "4098:m3u 4098:e2pls 4098:pls")
self["filelist"] = self.filelist
self.playlist = MyPlayList()
"cancel": self.cancel,
"up": self.keyUp,
"down": self.keyDown,
- }, -3)
+ }, -2)
self.settings = ConfigSubsection()
choicelist = [(PAGE_AUDIO,_("audio tracks")), (PAGE_SUBTITLES,_("Subtitles"))]
streams = []
conflist = []
selectedidx = 0
-
- service = self.session.nav.getCurrentService()
- self.audioTracks = audio = service and service.audioTracks()
- n = audio and audio.getNumberOfTracks() or 0
-
+
if self.settings.menupage.getValue() == PAGE_AUDIO:
self.setTitle(_("Select audio track"))
+ service = self.session.nav.getCurrentService()
+ self.audioTracks = audio = service and service.audioTracks()
+ n = audio and audio.getNumberOfTracks() or 0
if SystemInfo["CanDownmixAC3"]:
self.settings.downmix = ConfigOnOff(default=config.av.downmix_ac3.value)
self.settings.downmix.addNotifier(self.changeAC3Downmix, initial_call = False)
number = "%x%02x" % (x[3],x[2])
elif x[0] == 2:
- types = ("UTF-8 text","SSA / AAS",".SRT file")
+ types = (_("<unknown>"), "UTF-8 text", "SSA", "AAS", ".SRT file", "VOB", "PGS (unsupported)")
description = types[x[2]]
streams.append((x, "", number, description, language, selected))
conflist.append(getConfigListEntry(Plugins[0][0], ConfigNothing()))
self.plugincallfunc = Plugins[0][1]
if len(Plugins) > 1:
- print "these plugins are installed but not displayed in the dialog box:", Plugins[1:]
+ print "plugin(s) installed but not displayed in the dialog box:", Plugins[1:]
self["config"].list = conflist
self["config"].l.setList(conflist)
SEEK_STATE_PAUSE = (1, 0, 0, "||")
SEEK_STATE_EOF = (1, 0, 0, "END")
- def __init__(self, actionmap = "InfobarSeekActions", useSeekBackHack=True):
+ def __init__(self, actionmap = "InfobarSeekActions"):
self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
{
iPlayableService.evSeekableStatusChanged: self.__seekableStatusChanged,
self.__seekableStatusChanged()
def makeStateForward(self, n):
-# minspeed = config.seek.stepwise_minspeed.value
-# repeat = int(config.seek.stepwise_repeat.value)
-# if minspeed != "Never" and n >= int(minspeed) and repeat > 1:
-# return (0, n * repeat, repeat, ">> %dx" % n)
-# else:
- return (0, n, 0, ">> %dx" % n)
+ return (0, n, 0, ">> %dx" % n)
def makeStateBackward(self, n):
-# minspeed = config.seek.stepwise_minspeed.value
-# repeat = int(config.seek.stepwise_repeat.value)
-# if minspeed != "Never" and n >= int(minspeed) and repeat > 1:
-# return (0, -n * repeat, repeat, "<< %dx" % n)
-# else:
- return (0, -n, 0, "<< %dx" % n)
+ return (0, -n, 0, "<< %dx" % n)
def makeStateSlowMotion(self, n):
return (0, 0, n, "/%d" % n)
return True
def jumpPreviousMark(self):
- # we add 2 seconds, so if the play position is <2s after
+ # we add 5 seconds, so if the play position is <5s after
# the mark, the mark before will be used
self.jumpPreviousNextMark(lambda x: -x-5*90000, start=True)
def jumpNextMark(self):
- if not self.jumpPreviousNextMark(lambda x: x):
+ if not self.jumpPreviousNextMark(lambda x: x-90000):
self.doSeek(-1)
def getNearestCutPoint(self, pts, cmp=abs, start=False):
# can be optimized
- beforecut = False
+ beforecut = True
nearest = None
+ bestdiff = -1
+ instate = True
if start:
- beforecut = True
bestdiff = cmp(0 - pts)
if bestdiff >= 0:
nearest = [0, False]
beforecut = False
if cp[1] == self.CUT_TYPE_IN: # Start is here, disregard previous marks
diff = cmp(cp[0] - pts)
- if diff >= 0:
+ if start and diff >= 0:
nearest = cp
bestdiff = diff
else:
nearest = None
- if cp[1] in (self.CUT_TYPE_MARK, self.CUT_TYPE_LAST):
+ bestdiff = -1
+ if cp[1] == self.CUT_TYPE_IN:
+ instate = True
+ elif cp[1] == self.CUT_TYPE_OUT:
+ instate = False
+ elif cp[1] in (self.CUT_TYPE_MARK, self.CUT_TYPE_LAST):
diff = cmp(cp[0] - pts)
- if diff >= 0 and (nearest is None or bestdiff > diff):
+ if instate and diff >= 0 and (nearest is None or bestdiff > diff):
nearest = cp
bestdiff = diff
return nearest
servicedvb.cpp \
servicedvbrecord.cpp \
servicefs.cpp \
- servicemp3.cpp
+ servicemp3.cpp \
+ servicem2ts.cpp
serviceincludedir = $(pkgincludedir)/lib/service
serviceinclude_HEADERS = \
servicedvb.h \
servicedvbrecord.h \
servicefs.h \
- servicemp3.h
+ servicemp3.h \
+ servicem2ts.h
if HAVE_LIBXINE
libenigma_service_a_SOURCES += \
info.m_description = "AAC-HE";
else if (program.audioStreams[i].type == eDVBServicePMTHandler::audioStream::atDTS)
info.m_description = "DTS";
+ else if (program.audioStreams[i].type == eDVBServicePMTHandler::audioStream::atDTSHD)
+ info.m_description = "DTS-HD";
else
info.m_description = "???";
--- /dev/null
+#include <lib/base/init_num.h>
+#include <lib/base/init.h>
+#include <lib/dvb/metaparser.h>
+#include <lib/service/servicem2ts.h>
+
+DEFINE_REF(eServiceFactoryM2TS)
+
+class eM2TSFile: public iTsSource
+{
+ DECLARE_REF(eM2TSFile);
+ eSingleLock m_lock;
+public:
+ eM2TSFile(const char *filename, bool cached=false);
+ ~eM2TSFile();
+
+ // iTsSource
+ off_t lseek(off_t offset, int whence);
+ ssize_t read(off_t offset, void *buf, size_t count);
+ off_t length();
+ int valid();
+private:
+ int m_sync_offset;
+ int m_fd; /* for uncached */
+ FILE *m_file; /* for cached */
+ off_t m_current_offset, m_length;
+ bool m_cached;
+ off_t lseek_internal(off_t offset, int whence);
+};
+
+class eStaticServiceM2TSInformation: public iStaticServiceInformation
+{
+ DECLARE_REF(eStaticServiceM2TSInformation);
+ eServiceReference m_ref;
+ eDVBMetaParser m_parser;
+public:
+ eStaticServiceM2TSInformation(const eServiceReference &ref);
+ RESULT getName(const eServiceReference &ref, std::string &name);
+ int getLength(const eServiceReference &ref);
+ RESULT getEvent(const eServiceReference &ref, ePtr<eServiceEvent> &SWIG_OUTPUT, time_t start_time);
+ int isPlayable(const eServiceReference &ref, const eServiceReference &ignore) { return 1; }
+ int getInfo(const eServiceReference &ref, int w);
+ std::string getInfoString(const eServiceReference &ref,int w);
+ PyObject *getInfoObject(const eServiceReference &r, int what);
+};
+
+DEFINE_REF(eStaticServiceM2TSInformation);
+
+eStaticServiceM2TSInformation::eStaticServiceM2TSInformation(const eServiceReference &ref)
+{
+ m_ref = ref;
+ m_parser.parseFile(ref.path);
+}
+
+RESULT eStaticServiceM2TSInformation::getName(const eServiceReference &ref, std::string &name)
+{
+ ASSERT(ref == m_ref);
+ if (m_parser.m_name.size())
+ name = m_parser.m_name;
+ else
+ {
+ name = ref.path;
+ size_t n = name.rfind('/');
+ if (n != std::string::npos)
+ name = name.substr(n + 1);
+ }
+ return 0;
+}
+
+int eStaticServiceM2TSInformation::getLength(const eServiceReference &ref)
+{
+ ASSERT(ref == m_ref);
+
+ eDVBTSTools tstools;
+
+ struct stat s;
+ stat(ref.path.c_str(), &s);
+
+ eM2TSFile *file = new eM2TSFile(ref.path.c_str());
+ ePtr<iTsSource> source = file;
+
+ if (!source->valid())
+ return 0;
+
+ tstools.setSource(source);
+
+ /* check if cached data is still valid */
+ if (m_parser.m_data_ok && (s.st_size == m_parser.m_filesize) && (m_parser.m_length))
+ return m_parser.m_length / 90000;
+
+ /* open again, this time with stream info */
+ tstools.setSource(source, ref.path.c_str());
+
+ /* otherwise, re-calc length and update meta file */
+ pts_t len;
+ if (tstools.calcLen(len))
+ return 0;
+
+ m_parser.m_length = len;
+ m_parser.m_filesize = s.st_size;
+ m_parser.updateMeta(ref.path);
+ return m_parser.m_length / 90000;
+}
+
+int eStaticServiceM2TSInformation::getInfo(const eServiceReference &ref, int w)
+{
+ switch (w)
+ {
+ case iServiceInformation::sDescription:
+ return iServiceInformation::resIsString;
+ case iServiceInformation::sServiceref:
+ return iServiceInformation::resIsString;
+ case iServiceInformation::sFileSize:
+ return m_parser.m_filesize;
+ case iServiceInformation::sTimeCreate:
+ if (m_parser.m_time_create)
+ return m_parser.m_time_create;
+ else
+ return iServiceInformation::resNA;
+ default:
+ return iServiceInformation::resNA;
+ }
+}
+
+std::string eStaticServiceM2TSInformation::getInfoString(const eServiceReference &ref,int w)
+{
+ switch (w)
+ {
+ case iServiceInformation::sDescription:
+ return m_parser.m_description;
+ case iServiceInformation::sServiceref:
+ return m_parser.m_ref.toString();
+ case iServiceInformation::sTags:
+ return m_parser.m_tags;
+ default:
+ return "";
+ }
+}
+
+PyObject *eStaticServiceM2TSInformation::getInfoObject(const eServiceReference &r, int what)
+{
+ switch (what)
+ {
+ case iServiceInformation::sFileSize:
+ return PyLong_FromLongLong(m_parser.m_filesize);
+ default:
+ Py_RETURN_NONE;
+ }
+}
+
+RESULT eStaticServiceM2TSInformation::getEvent(const eServiceReference &ref, ePtr<eServiceEvent> &evt, time_t start_time)
+{
+ if (!ref.path.empty())
+ {
+ ePtr<eServiceEvent> event = new eServiceEvent;
+ std::string filename = ref.path;
+ filename.erase(filename.length()-4, 2);
+ filename+="eit";
+ if (!event->parseFrom(filename, (m_parser.m_ref.getTransportStreamID().get()<<16)|m_parser.m_ref.getOriginalNetworkID().get()))
+ {
+ evt = event;
+ return 0;
+ }
+ }
+ evt = 0;
+ return -1;
+}
+
+DEFINE_REF(eM2TSFile);
+
+eM2TSFile::eM2TSFile(const char *filename, bool cached)
+ :m_lock(false), m_sync_offset(0), m_fd(-1), m_file(NULL), m_current_offset(0), m_length(0), m_cached(cached)
+{
+ if (!m_cached)
+ m_fd = ::open(filename, O_RDONLY | O_LARGEFILE);
+ else
+ m_file = ::fopen64(filename, "rb");
+ if (valid())
+ m_current_offset = m_length = lseek_internal(0, SEEK_END);
+}
+
+eM2TSFile::~eM2TSFile()
+{
+ if (m_cached)
+ {
+ if (m_file)
+ {
+ ::fclose(m_file);
+ m_file = 0;
+ }
+ }
+ else
+ {
+ if (m_fd >= 0)
+ ::close(m_fd);
+ m_fd = -1;
+ }
+}
+
+off_t eM2TSFile::lseek(off_t offset, int whence)
+{
+ eSingleLocker l(m_lock);
+
+ offset = (offset % 188) + (offset * 192) / 188;
+
+ if (offset != m_current_offset)
+ m_current_offset = lseek_internal(offset, whence);
+
+ return m_current_offset;
+}
+
+off_t eM2TSFile::lseek_internal(off_t offset, int whence)
+{
+ off_t ret;
+
+ if (!m_cached)
+ ret = ::lseek(m_fd, offset, whence);
+ else
+ {
+ if (::fseeko(m_file, offset, whence) < 0)
+ perror("fseeko");
+ ret = ::ftello(m_file);
+ }
+ return ret <= 0 ? ret : (ret % 192) + (ret*188) / 192;
+}
+
+ssize_t eM2TSFile::read(off_t offset, void *b, size_t count)
+{
+ eSingleLocker l(m_lock);
+ unsigned char tmp[192*3];
+ unsigned char *buf = (unsigned char*)b;
+
+ size_t rd=0;
+ offset = (offset % 188) + (offset * 192) / 188;
+
+sync:
+ if ((offset+m_sync_offset) != m_current_offset)
+ {
+// eDebug("seekTo %lld", offset+m_sync_offset);
+ m_current_offset = lseek_internal(offset+m_sync_offset, SEEK_SET);
+ if (m_current_offset < 0)
+ return m_current_offset;
+ }
+
+ while (rd < count) {
+ size_t ret;
+ if (!m_cached)
+ ret = ::read(m_fd, tmp, 192);
+ else
+ ret = ::fread(tmp, 1, 192, m_file);
+ if (ret < 0 || ret < 192)
+ return rd ? rd : ret;
+
+ if (tmp[4] != 0x47)
+ {
+ if (rd > 0) {
+ eDebug("short read at pos %lld async!!", m_current_offset);
+ return rd;
+ }
+ else {
+ int x=0;
+ if (!m_cached)
+ ret = ::read(m_fd, tmp+192, 384);
+ else
+ ret = ::fread(tmp+192, 1, 384, m_file);
+
+#if 0
+ eDebugNoNewLine("m2ts out of sync at pos %lld, real %lld:", offset + m_sync_offset, m_current_offset);
+ for (; x < 192; ++x)
+ eDebugNoNewLine(" %02x", tmp[x]);
+ eDebug("");
+ x=0;
+#else
+ eDebug("m2ts out of sync at pos %lld, real %lld", offset + m_sync_offset, m_current_offset);
+#endif
+ for (; x < 192; ++x)
+ {
+ if (tmp[x] == 0x47 && tmp[x+192] == 0x47)
+ {
+ int add_offs = (x - 4);
+ eDebug("sync found at pos %d, sync_offset is now %d, old was %d", x, add_offs + m_sync_offset, m_sync_offset);
+ m_sync_offset += add_offs;
+ goto sync;
+ }
+ }
+ }
+ }
+
+ memcpy(buf+rd, tmp+4, 188);
+
+ rd += 188;
+ m_current_offset += 188;
+ }
+
+ m_sync_offset %= 188;
+
+ return rd;
+}
+
+int eM2TSFile::valid()
+{
+ if (!m_cached)
+ return m_fd != -1;
+ else
+ return !!m_file;
+}
+
+off_t eM2TSFile::length()
+{
+ return m_length;
+}
+
+eServiceFactoryM2TS::eServiceFactoryM2TS()
+{
+ ePtr<eServiceCenter> sc;
+ eServiceCenter::getPrivInstance(sc);
+ if (sc)
+ {
+ std::list<std::string> extensions;
+ extensions.push_back("m2ts");
+ extensions.push_back("mts");
+ sc->addServiceFactory(eServiceFactoryM2TS::id, this, extensions);
+ }
+}
+
+eServiceFactoryM2TS::~eServiceFactoryM2TS()
+{
+ ePtr<eServiceCenter> sc;
+
+ eServiceCenter::getPrivInstance(sc);
+ if (sc)
+ sc->removeServiceFactory(eServiceFactoryM2TS::id);
+}
+
+RESULT eServiceFactoryM2TS::play(const eServiceReference &ref, ePtr<iPlayableService> &ptr)
+{
+ ptr = new eServiceM2TS(ref);
+ return 0;
+}
+
+RESULT eServiceFactoryM2TS::record(const eServiceReference &ref, ePtr<iRecordableService> &ptr)
+{
+ ptr=0;
+ return -1;
+}
+
+RESULT eServiceFactoryM2TS::list(const eServiceReference &ref, ePtr<iListableService> &ptr)
+{
+ ptr=0;
+ return -1;
+}
+
+RESULT eServiceFactoryM2TS::info(const eServiceReference &ref, ePtr<iStaticServiceInformation> &ptr)
+{
+ ptr=new eStaticServiceM2TSInformation(ref);
+ return 0;
+}
+
+RESULT eServiceFactoryM2TS::offlineOperations(const eServiceReference &ref, ePtr<iServiceOfflineOperations> &ptr)
+{
+ ptr = 0;
+ return -1;
+}
+
+eServiceM2TS::eServiceM2TS(const eServiceReference &ref)
+ :eDVBServicePlay(ref, NULL)
+{
+}
+
+ePtr<iTsSource> eServiceM2TS::createTsSource(eServiceReferenceDVB &ref)
+{
+ ePtr<iTsSource> source = new eM2TSFile(ref.path.c_str());
+ return source;
+}
+
+RESULT eServiceM2TS::isCurrentlySeekable()
+{
+ return 1; // for fast winding we need index files... so only skip forward/backward yet
+}
+
+eAutoInitPtr<eServiceFactoryM2TS> init_eServiceFactoryM2TS(eAutoInitNumbers::service+1, "eServiceFactoryM2TS");
--- /dev/null
+#ifndef __servicem2ts_h
+#define __servicem2ts_h
+
+#include <lib/service/servicedvb.h>
+
+class eServiceFactoryM2TS: public iServiceHandler
+{
+ DECLARE_REF(eServiceFactoryM2TS);
+public:
+ eServiceFactoryM2TS();
+ virtual ~eServiceFactoryM2TS();
+ enum { id = 0x3 };
+
+ // iServiceHandler
+ RESULT play(const eServiceReference &, ePtr<iPlayableService> &ptr);
+ RESULT record(const eServiceReference &, ePtr<iRecordableService> &ptr);
+ RESULT list(const eServiceReference &, ePtr<iListableService> &ptr);
+ RESULT info(const eServiceReference &, ePtr<iStaticServiceInformation> &ptr);
+ RESULT offlineOperations(const eServiceReference &, ePtr<iServiceOfflineOperations> &ptr);
+};
+
+class eServiceM2TS: public eDVBServicePlay
+{
+ friend class eServiceFactoryM2TS;
+protected:
+ eServiceM2TS(const eServiceReference &ref);
+ ePtr<iTsSource> createTsSource(eServiceReferenceDVB &ref);
+
+ // iSeekableService
+ RESULT isCurrentlySeekable();
+};
+
+#endif
#include <lib/gui/esubtitle.h>
#include <lib/service/servicemp3.h>
#include <lib/service/service.h>
+#include <lib/gdi/gpixmap.h>
#include <string>
#include <sys/stat.h>
#define HTTP_TIMEOUT 10
+static GstStaticPadTemplate subsinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS("text/plain; text/x-pango-markup; video/x-dvd-subpicture; subpicture/x-pgs"));
+// int ret = gst_pad_set_caps (ghostpad, caps2);
+// gst_caps_unref(caps2);));
// eServiceFactoryMP3
m_seekTimeout = eTimer::create(eApp);
m_subtitle_sync_timer = eTimer::create(eApp);
m_streamingsrc_timeout = 0;
+ m_subtitle_hide_timer = eTimer::create(eApp);
m_stream_tags = 0;
m_currentAudioStream = -1;
m_currentSubtitleStream = 0;
m_currentTrickRatio = 0;
m_subs_to_pull = 0;
m_buffer_size = 1*1024*1024;
+ m_prev_decoder_time = -1;
+ m_decoder_time_valid_state = 0;
+
CONNECT(m_seekTimeout->timeout, eServiceMP3::seekTimeoutCB);
CONNECT(m_subtitle_sync_timer->timeout, eServiceMP3::pushSubtitles);
+ CONNECT(m_subtitle_hide_timer->timeout, eServiceMP3::hideSubtitles);
CONNECT(m_pump.recv_msg, eServiceMP3::gstPoll);
m_aspect = m_width = m_height = m_framerate = m_progressive = -1;
g_object_set (G_OBJECT (m_gst_playbin), "uri", uri, NULL);
- int flags = 0x47; // ( == GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_AUDIO | GST_PLAY_FLAG_NATIVE_VIDEO | GST_PLAY_FLAG_TEXT )
+ int flags = 0x47; // ( GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_AUDIO | GST_PLAY_FLAG_NATIVE_VIDEO | GST_PLAY_FLAG_TEXT );
g_object_set (G_OBJECT (m_gst_playbin), "flags", flags, NULL);
g_free(uri);
- GstElement *subsink = gst_element_factory_make("appsink", "subtitle_sink");
- if (!subsink)
- eDebug("eServiceMP3::sorry, can't play: missing gst-plugin-appsink");
- else
- {
- m_subs_to_pull_handler_id = g_signal_connect (subsink, "new-buffer", G_CALLBACK (gstCBsubtitleAvail), this);
- g_object_set (G_OBJECT (subsink), "caps", gst_caps_from_string("text/plain; text/x-plain; text/x-pango-markup"), NULL);
- g_object_set (G_OBJECT (m_gst_playbin), "text-sink", subsink, NULL);
- }
-
+ m_gst_subtitlebin = gst_bin_new("subtitle_bin");
+
if ( m_gst_playbin )
{
+ GstElement *appsink = gst_element_factory_make("appsink", "subtitle_sink");
+
+ if (!appsink)
+ eDebug("eServiceMP3::sorry, can't play: missing gst-plugin-appsink");
+
+ GstElement *dvdsubdec = gst_element_factory_make("dvdsubdec", "vobsubtitle_decoder");
+ if ( dvdsubdec )
+ {
+ gst_bin_add_many(GST_BIN(m_gst_subtitlebin), dvdsubdec, appsink, NULL);
+ g_object_set (G_OBJECT (dvdsubdec), "singlebuffer", TRUE, NULL);
+ }
+ else
+ {
+ eDebug("eServiceMP3::missing gst-plugin-dvdsub, no vob subtitle support!");
+ gst_bin_add(GST_BIN(m_gst_subtitlebin), appsink);
+ }
+
+ GstPadTemplate *templ;
+ templ = gst_static_pad_template_get (&subsinktemplate);
+
+ GstPad *ghostpad = gst_ghost_pad_new_no_target_from_template("sink", templ);
+ gst_element_add_pad (m_gst_subtitlebin, ghostpad);
+
+ GstCaps* caps = gst_caps_from_string("text/plain; text/x-pango-markup; video/x-raw-rgb; subpicture/x-pgs");
+ g_object_set (G_OBJECT (appsink), "caps", caps, NULL);
+ gst_caps_unref(caps);
+
+ g_object_set (G_OBJECT (appsink), "async", FALSE, NULL);
+ g_object_set (G_OBJECT (appsink), "sync", TRUE, NULL);
+ g_object_set (G_OBJECT (appsink), "emit-signals", TRUE, NULL);
+ g_object_set (G_OBJECT (appsink), "ts-offset", 0 * GST_SECOND, NULL);
+
+ g_object_set_data (G_OBJECT (ghostpad), "application-instance", this);
+ g_signal_connect (G_OBJECT (ghostpad), "notify::caps", G_CALLBACK (gstGhostpadHasCAPS), this);
+ gst_pad_set_getcaps_function (ghostpad, gstGhostpadGetCAPS);
+ gst_pad_set_acceptcaps_function (ghostpad, gstGhostpadAcceptCAPS);
+ m_ghost_pad_buffer_alloc = GST_PAD_BUFFERALLOCFUNC(ghostpad);
+ m_ghost_pad_chain_function = GST_PAD_CHAINFUNC(ghostpad);
+ m_ghost_pad_subtitle_sink_event = GST_PAD_EVENTFUNC(ghostpad);
+ gst_pad_set_bufferalloc_function (ghostpad, GST_DEBUG_FUNCPTR(gstGhostpadBufferAlloc));
+ gst_pad_set_event_function (ghostpad, GST_DEBUG_FUNCPTR(gstGhostpadSinkEvent));
+ gst_pad_set_chain_function (ghostpad, GST_DEBUG_FUNCPTR(gstGhostpadChainFunction));
+ m_gst_prev_subtitle_caps = gst_caps_new_empty();
+
+ g_object_set (G_OBJECT (m_gst_playbin), "text-sink", m_gst_subtitlebin, NULL);
+ m_subs_to_pull_handler_id = g_signal_connect (appsink, "new-buffer", G_CALLBACK (gstCBsubtitleAvail), this);
+
gst_bus_set_sync_handler(gst_pipeline_get_bus (GST_PIPELINE (m_gst_playbin)), gstBusSyncHandler, this);
char srt_filename[strlen(filename)+1];
strncpy(srt_filename,filename,strlen(filename)-3);
eServiceMP3::~eServiceMP3()
{
// disconnect subtitle callback
- GstElement *sink;
- g_object_get (G_OBJECT (m_gst_playbin), "text-sink", &sink, NULL);
- if (sink)
+ GstElement *appsink = gst_bin_get_by_name(GST_BIN(m_gst_subtitlebin), "subtitle_sink");
+// GstElement *appsink = gst_bin_get_by_name(GST_BIN(m_gst_playbin), "subtitle_sink");
+
+ if (appsink)
{
- g_signal_handler_disconnect (sink, m_subs_to_pull_handler_id);
- gst_object_unref(sink);
+ g_signal_handler_disconnect (appsink, m_subs_to_pull_handler_id);
+ gst_object_unref(appsink);
}
delete m_subtitle_widget;
+ gst_caps_unref(this->m_gst_prev_subtitle_caps);
// disconnect sync handler callback
gst_bus_set_sync_handler(gst_pipeline_get_bus (GST_PIPELINE (m_gst_playbin)), NULL, NULL);
if (m_state == stStopped)
return -1;
+
+ //GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(m_gst_playbin),GST_DEBUG_GRAPH_SHOW_ALL,"e2-playbin");
eDebug("eServiceMP3::stop %s", m_ref.path.c_str());
gst_element_set_state(m_gst_playbin, GST_STATE_NULL);
if (!(ret = seekToImpl(to)))
{
m_subtitle_pages.clear();
+ m_prev_decoder_time = -1;
+ m_decoder_time_valid_state = 0;
m_subs_to_pull = 0;
}
}
/* pos is in nanoseconds. we have 90 000 pts per second. */
pts = pos / 11111;
+// eDebug("gst_element_query_position %lld pts (%lld ms)", pts, pos/1000000);
return 0;
}
return 0;
}
+subtype_t getSubtitleType(GstPad* pad, gchar *g_codec=NULL)
+{
+ subtype_t type = stUnknown;
+ GstCaps* caps = gst_pad_get_negotiated_caps(pad);
+
+ if ( caps )
+ {
+ GstStructure* str = gst_caps_get_structure(caps, 0);
+ const gchar *g_type = gst_structure_get_name(str);
+ eDebug("getSubtitleType::subtitle probe caps type=%s", g_type);
+
+ if ( !strcmp(g_type, "video/x-dvd-subpicture") )
+ type = stVOB;
+ else if ( !strcmp(g_type, "text/x-pango-markup") )
+ type = stSSA;
+ else if ( !strcmp(g_type, "text/plain") )
+ type = stPlainText;
+ else if ( !strcmp(g_type, "subpicture/x-pgs") )
+ type = stPGS;
+ else
+ eDebug("getSubtitleType::unsupported subtitle caps %s (%s)", g_type, g_codec);
+ }
+ else if ( g_codec )
+ {
+ eDebug("getSubtitleType::subtitle probe codec tag=%s", g_codec);
+ if ( !strcmp(g_codec, "VOB") )
+ type = stVOB;
+ else if ( !strcmp(g_codec, "SubStation Alpha") || !strcmp(g_codec, "SSA") )
+ type = stSSA;
+ else if ( !strcmp(g_codec, "ASS") )
+ type = stASS;
+ else if ( !strcmp(g_codec, "UTF-8 plain text") )
+ type = stPlainText;
+ else
+ eDebug("getSubtitleType::unsupported subtitle codec %s", g_codec);
+ }
+ else
+ eDebug("getSubtitleType::unidentifiable subtitle stream!");
+
+ return type;
+}
+
void eServiceMP3::gstBusCall(GstBus *bus, GstMessage *msg)
{
if (!msg)
} break;
case GST_STATE_CHANGE_READY_TO_PAUSED:
{
- GstElement *sink;
- g_object_get (G_OBJECT (m_gst_playbin), "text-sink", &sink, NULL);
- if (sink)
- {
- g_object_set (G_OBJECT (sink), "max-buffers", 2, NULL);
- g_object_set (G_OBJECT (sink), "sync", FALSE, NULL);
- g_object_set (G_OBJECT (sink), "async", FALSE, NULL);
- g_object_set (G_OBJECT (sink), "emit-signals", TRUE, NULL);
- gst_object_unref(sink);
- }
+ GstElement *appsink = gst_bin_get_by_name(GST_BIN(m_gst_subtitlebin), "subtitle_sink");
+// GstElement *appsink = gst_bin_get_by_name(GST_BIN(m_gst_playbin), "subtitle_sink");
+ if (appsink)
+ {
+ g_object_set (G_OBJECT (appsink), "max-buffers", 2, NULL);
+ g_object_set (G_OBJECT (appsink), "sync", FALSE, NULL);
+ g_object_set (G_OBJECT (appsink), "emit-signals", TRUE, NULL);
+ eDebug("eServiceMP3::appsink properties set!");
+ gst_object_unref(appsink);
+ }
setAC3Delay(ac3_delay);
setPCMDelay(pcm_delay);
} break;
continue;
GstStructure* str = gst_caps_get_structure(caps, 0);
const gchar *g_type = gst_structure_get_name(str);
- eDebug("AUDIO STRUCT=%s", g_type);
audio.type = gstCheckAudioPad(str);
g_codec = g_strdup(g_type);
g_lang = g_strdup_printf ("und");
}
for (i = 0; i < n_text; i++)
- {
- gchar *g_lang;
-// gchar *g_type;
-// GstPad* pad = 0;
-// g_signal_emit_by_name (m_gst_playbin, "get-text-pad", i, &pad);
-// GstCaps* caps = gst_pad_get_negotiated_caps(pad);
-// GstStructure* str = gst_caps_get_structure(caps, 0);
-// g_type = gst_structure_get_name(str);
-// g_signal_emit_by_name (m_gst_playbin, "get-text-tags", i, &tags);
+ {
+ gchar *g_codec = NULL, *g_lang = NULL;
+ g_signal_emit_by_name (m_gst_playbin, "get-text-tags", i, &tags);
subtitleStream subs;
- subs.type = stPlainText;
+// int ret;
+
g_lang = g_strdup_printf ("und");
if ( tags && gst_is_tag_list(tags) )
+ {
gst_tag_list_get_string(tags, GST_TAG_LANGUAGE_CODE, &g_lang);
+ gst_tag_list_get_string(tags, GST_TAG_SUBTITLE_CODEC, &g_codec);
+ gst_tag_list_free(tags);
+ }
+
subs.language_code = std::string(g_lang);
- eDebug("eServiceMP3::subtitle stream=%i language=%s"/* type=%s*/, i, g_lang/*, g_type*/);
+ eDebug("eServiceMP3::subtitle stream=%i language=%s codec=%s", i, g_lang, g_codec);
+
+ GstPad* pad = 0;
+ g_signal_emit_by_name (m_gst_playbin, "get-text-pad", i, &pad);
+ if ( subs.type != stSRT )
+ subs.type = getSubtitleType(pad, g_codec);
+
m_subtitleStreams.push_back(subs);
g_free (g_lang);
-// g_free (g_type);
}
m_event((iPlayableService*)this, evUpdatedEventInfo);
break;
GstBusSyncReply eServiceMP3::gstBusSyncHandler(GstBus *bus, GstMessage *message, gpointer user_data)
{
eServiceMP3 *_this = (eServiceMP3*)user_data;
- _this->m_pump.send(1);
+ _this->m_pump.send(Message(1));
/* wake */
return GST_BUS_PASS;
}
return atUnknown;
}
-void eServiceMP3::gstPoll(const int &msg)
+void eServiceMP3::gstPoll(const Message &msg)
{
- /* ok, we have a serious problem here. gstBusSyncHandler sends
- us the wakup signal, but likely before it was posted.
- the usleep, an EVIL HACK (DON'T DO THAT!!!) works around this.
-
- I need to understand the API a bit more to make this work
- proplerly. */
- if (msg == 1)
+ if (msg.type == 1)
{
GstBus *bus = gst_pipeline_get_bus (GST_PIPELINE (m_gst_playbin));
GstMessage *message;
- usleep(1);
- while ((message = gst_bus_pop (bus)))
+ while (message = gst_bus_pop(bus))
{
gstBusCall(bus, message);
gst_message_unref (message);
}
}
- else
+ else if (msg.type == 2)
pullSubtitle();
+ else if (msg.type == 3)
+ gstGhostpadHasCAPS_synced(msg.d.pad);
+ else
+ eDebug("gstPoll unhandled Message %d\n", msg.type);
}
eAutoInitPtr<eServiceFactoryMP3> init_eServiceFactoryMP3(eAutoInitNumbers::service+1, "eServiceFactoryMP3");
void eServiceMP3::gstCBsubtitleAvail(GstElement *appsink, gpointer user_data)
{
- eServiceMP3 *_this = (eServiceMP3*)user_data;
+ eServiceMP3 *_this = (eServiceMP3*)user_data;
eSingleLocker l(_this->m_subs_to_pull_lock);
++_this->m_subs_to_pull;
- _this->m_pump.send(2);
+ _this->m_pump.send(Message(2));
+}
+
+gboolean eServiceMP3::gstGhostpadSinkEvent(GstPad * pad, GstEvent * event)
+{
+// eDebug("eServiceMP3::gstGhostpadSinkEvent %s", gst_structure_get_name (event->structure));
+
+// eServiceMP3 *_this = (eServiceMP3*) (gst_pad_get_parent (pad));
+ eServiceMP3 *_this = (eServiceMP3*) g_object_get_data (G_OBJECT (pad), "application-instance");
+ gboolean ret;
+ GstFormat format;
+
+ if (GST_EVENT_TYPE (event) == GST_EVENT_CUSTOM_DOWNSTREAM_OOB && event->structure && strcmp (gst_structure_get_name (event->structure), "subtitleoverlay-flush-subtitle") == 0)
+ {
+ eDebug ("Custom subtitle flush event");
+// GST_SUBTITLE_OVERLAY_LOCK (self);
+// self->subtitle_flush = TRUE;
+// self->subtitle_error = FALSE;
+// if (self->subtitle_block_pad)
+// gst_pad_set_blocked_async_full (self->subtitle_block_pad, TRUE,
+// _pad_blocked_cb, gst_object_ref (self),
+// (GDestroyNotify) gst_object_unref);
+// if (self->video_block_pad)
+// gst_pad_set_blocked_async_full (self->video_block_pad, TRUE,
+// _pad_blocked_cb, gst_object_ref (self),
+// (GDestroyNotify) gst_object_unref);
+// GST_SUBTITLE_OVERLAY_UNLOCK (self);
+//
+ gst_event_unref (event);
+ event = NULL;
+ ret = TRUE;
+ goto out;
+ } else if (GST_EVENT_TYPE (event) == GST_EVENT_NEWSEGMENT)
+ {
+ gst_event_parse_new_segment_full (event, NULL, NULL, NULL, &format, NULL, NULL, NULL);
+ if (_this->m_gst_subtitle_segment.format != GST_FORMAT_UNDEFINED && _this->m_gst_subtitle_segment.format != format)
+ {
+ eDebug("Subtitle segment format changed: %s -> %s", gst_format_get_name(_this->m_gst_subtitle_segment.format), gst_format_get_name(format));
+ gst_segment_init (&_this->m_gst_subtitle_segment, GST_FORMAT_UNDEFINED);
+ }
+ }
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_FLUSH_STOP:
+ eDebug("Resetting subtitle segment because of flush-stop");
+ gst_segment_init (&_this->m_gst_subtitle_segment, GST_FORMAT_UNDEFINED);
+ /* fall through */
+ case GST_EVENT_FLUSH_START:
+ case GST_EVENT_NEWSEGMENT:
+ case GST_EVENT_EOS:
+// eDebug("GST_EVENT_FLUSH_START GST_EVENT_NEWSEGMENT GST_EVENT_EOS");
+ /* Add our event marker to make sure no events from here go ever outside
+ * the element, they're only interesting for our internal elements */
+// event =
+// GST_EVENT_CAST (gst_mini_object_make_writable (GST_MINI_OBJECT_CAST
+// (event)));
+// if (!event->structure) {
+// event->structure =
+// gst_structure_id_empty_new (_subtitle_overlay_event_marker_id);
+// gst_structure_set_parent_refcount (event->structure,
+// &event->mini_object.refcount);
+// }
+// gst_structure_id_set (event->structure, _subtitle_overlay_event_marker_id,
+// G_TYPE_BOOLEAN, TRUE, NULL);
+ break;
+ default:
+ eDebug("GST_EVENT_TYPE other: %i", GST_EVENT_TYPE (event));
+ break;
+ }
+
+ ret = _this->m_ghost_pad_subtitle_sink_event (pad, gst_event_ref (event));
+// eDebug("original EVENTFUNC returned %i", ret);
+
+ if (GST_EVENT_TYPE (event) == GST_EVENT_NEWSEGMENT) {
+ gboolean update;
+ gdouble rate, applied_rate;
+ gint64 start, stop, position;
+
+ GST_DEBUG_OBJECT (pad, "Newsegment event: %" GST_PTR_FORMAT,
+ event->structure);
+ gst_event_parse_new_segment_full (event, &update, &rate, &applied_rate,
+ &format, &start, &stop, &position);
+
+ GST_DEBUG_OBJECT (pad, "Old subtitle segment: %" GST_SEGMENT_FORMAT,
+ &_this->m_gst_subtitle_segment);
+ if (_this->m_gst_subtitle_segment.format != format) {
+ GST_DEBUG_OBJECT (pad, "Subtitle segment format changed: %s -> %s",
+ gst_format_get_name (_this->m_gst_subtitle_segment.format),
+ gst_format_get_name (format));
+ gst_segment_init (&_this->m_gst_subtitle_segment, format);
+ }
+
+ gst_segment_set_newsegment_full (&_this->m_gst_subtitle_segment, update, rate,
+ applied_rate, format, start, stop, position);
+ GST_DEBUG_OBJECT (pad, "New subtitle segment: %" GST_SEGMENT_FORMAT,
+ &_this->m_gst_subtitle_segment);
+ }
+ gst_event_unref (event);
+//
+out:
+// gst_object_unref (_this);
+ return ret;
+}
+
+GstCaps* eServiceMP3::gstGhostpadGetCAPS(GstPad * pad)
+{
+// eDebug("eServiceMP3::gstGhostpadGetCAPS");
+ return gst_static_pad_template_get_caps(&subsinktemplate);
+}
+
+gboolean eServiceMP3::gstGhostpadAcceptCAPS(GstPad * pad, GstCaps * caps)
+{
+ GstCaps *templ_caps = gst_static_pad_template_get_caps (&subsinktemplate);
+ gboolean ret = gst_caps_can_intersect (templ_caps, caps);
+
+// eDebug("gstGhostpadAcceptCAPS templ=%s, given=%s ret=%i", gst_caps_to_string(templ_caps), gst_caps_to_string(caps), ret);
+ gst_caps_unref (templ_caps);
+
+ return ret;
+}
+
+void eServiceMP3::gstGhostpadLink(gpointer user_data, GstCaps * caps)
+{
+ GstStructure *s;
+ GstPad *sinkpad;
+ eServiceMP3 *_this = (eServiceMP3*)user_data;
+
+ // FIXME: Need to cache events from the ghostpad and pass them forward
+ // now... and keep track of the segment and pass newsegment events
+ // downstream.
+ s = gst_caps_get_structure (caps, 0);
+
+ GstPad *ghostpad = gst_element_get_static_pad(_this->m_gst_subtitlebin, "sink");
+ GstElement *appsink = gst_bin_get_by_name(GST_BIN(_this->m_gst_subtitlebin), "subtitle_sink");
+ GstElement *dvdsubdec = gst_bin_get_by_name(GST_BIN(_this->m_gst_subtitlebin), "vobsubtitle_decoder");
+
+ gst_ghost_pad_set_target(GST_GHOST_PAD(ghostpad), NULL);
+ gst_element_unlink(dvdsubdec, appsink);
+ int ret = -1;
+
+ if ( gst_structure_has_name (s, "video/x-dvd-subpicture") && dvdsubdec )
+ {
+ sinkpad = gst_element_get_static_pad (dvdsubdec, "sink");
+ ret = gst_element_link_pads (dvdsubdec, "src", appsink, "sink");
+// eDebug("gstGhostpadLink:: dvdsubdec+appsink = %i", ret);
+ }
+ else
+ {
+ sinkpad = gst_element_get_static_pad (appsink, "sink");
+// eDebug("gstGhostpadLink:: appsink");
+ }
+
+ gst_ghost_pad_set_target (GST_GHOST_PAD(ghostpad), sinkpad);
+}
+
+GstFlowReturn eServiceMP3::gstGhostpadBufferAlloc(GstPad *pad, guint64 offset, guint size, GstCaps *caps, GstBuffer **buf)
+{
+ eServiceMP3 *_this = (eServiceMP3*) g_object_get_data (G_OBJECT (pad), "application-instance");
+
+// eDebug("eServiceMP3::gstGhostpadBufferAlloc prevcaps=%s newcaps=%s", gst_caps_to_string(_this->m_gst_prev_subtitle_caps), gst_caps_to_string(caps));
+ if (!GST_PAD_CAPS (pad) || !gst_caps_is_equal (_this->m_gst_prev_subtitle_caps, caps))
+ gstGhostpadLink (_this, caps);
+
+ return _this->m_ghost_pad_buffer_alloc (pad, offset, size, caps, buf);
+}
+
+void eServiceMP3::gstGhostpadHasCAPS(GstPad *pad, GParamSpec * unused, gpointer user_data)
+{
+ eServiceMP3 *_this = (eServiceMP3*)user_data;
+
+ gst_object_ref (pad);
+
+ _this->m_pump.send(Message(3, pad));
}
+// after messagepump
+void eServiceMP3::gstGhostpadHasCAPS_synced(GstPad *pad)
+{
+ GstCaps *caps;
+
+ g_object_get (G_OBJECT (pad), "caps", &caps, NULL);
+
+// eDebug("gstGhostpadHasCAPS:: signal::caps = %s", gst_caps_to_string(caps));
+
+ if (caps)
+ {
+ subtitleStream subs;
+
+// eDebug("gstGhostpadHasCAPS_synced %p %d", pad, m_subtitleStreams.size());
+
+ if (!m_subtitleStreams.empty())
+ subs = m_subtitleStreams[m_currentSubtitleStream];
+ else {
+ subs.type = stUnknown;
+ subs.pad = pad;
+ }
+
+ if ( subs.type == stUnknown )
+ {
+ GstTagList *tags;
+// eDebug("gstGhostpadHasCAPS::m_subtitleStreams[%i].type == stUnknown...", m_currentSubtitleStream);
+
+ gchar *g_lang;
+ g_signal_emit_by_name (m_gst_playbin, "get-text-tags", m_currentSubtitleStream, &tags);
+
+ g_lang = g_strdup_printf ("und");
+ if ( tags && gst_is_tag_list(tags) )
+ gst_tag_list_get_string(tags, GST_TAG_LANGUAGE_CODE, &g_lang);
+
+ subs.language_code = std::string(g_lang);
+ GstPad *ghostpad = gst_element_get_static_pad(m_gst_subtitlebin, "sink");
+ subs.type = getSubtitleType(ghostpad);
+
+ if (!m_subtitleStreams.empty())
+ m_subtitleStreams[m_currentSubtitleStream] = subs;
+ else
+ m_subtitleStreams.push_back(subs);
+
+ g_free (g_lang);
+ }
+
+// eDebug("gstGhostpadHasCAPS:: m_gst_prev_subtitle_caps=%s equal=%i",gst_caps_to_string(m_gst_prev_subtitle_caps),gst_caps_is_equal(m_gst_prev_subtitle_caps, caps));
+
+ if (!GST_PAD_CAPS (pad) || !gst_caps_is_equal (m_gst_prev_subtitle_caps, caps))
+ gstGhostpadLink(this, caps);
+
+ m_gst_prev_subtitle_caps = gst_caps_copy(caps);
+
+ gst_caps_unref (caps);
+ }
+
+ gst_object_unref (pad);
+}
+
+GstFlowReturn eServiceMP3::gstGhostpadChainFunction(GstPad * pad, GstBuffer * buffer)
+{
+ GstFlowReturn ret = GST_FLOW_OK;
+
+ eServiceMP3 *_this = (eServiceMP3*)g_object_get_data (G_OBJECT (pad), "application-instance");
+
+// gint64 buf_pos = GST_BUFFER_TIMESTAMP(buffer);
+// gint64 duration_ns = GST_BUFFER_DURATION(buffer);
+ size_t len = GST_BUFFER_SIZE(buffer);
+
+ unsigned char line[len+1];
+ memcpy(line, GST_BUFFER_DATA(buffer), len);
+ line[len] = 0;
+// eDebug("gstGhostpadChainFunction buffer: '%s' caps: %s ", line, gst_caps_to_string(GST_BUFFER_CAPS(buffer)));
+
+ ret = _this->m_ghost_pad_chain_function(pad, buffer);
+// eDebug("original chain func returns %i", ret);
+ return ret;
+}
+
+
+// void eServiceMP3::gstCBsubtitleLink(GObject *obj, GParamSpec *pspec, gpointer user_data)
+// {
+//
+// eServiceMP3 *_this = (eServiceMP3*)user_data;
+// eDebug("gstCBsubtitleCAPS:: m_currentSubtitleStream=%i, m_subtitleStreams.size()=%i", _this->m_currentSubtitleStream, _this->m_subtitleStreams.size());
+//
+// if ( _this->m_currentSubtitleStream >= (int)_this->m_subtitleStreams.size() )
+// {
+// eDebug("return invalid stream count");
+// return;
+// }
+//
+// subtitleStream subs = _this->m_subtitleStreams[_this->m_currentSubtitleStream];
+//
+// if ( subs.type == stUnknown )
+// {
+// GstTagList *tags;
+// eDebug("gstCBsubtitleCAPS::m_subtitleStreams[%i].type == stUnknown...", _this->m_currentSubtitleStream);
+//
+// gchar *g_lang;
+// g_signal_emit_by_name (_this->m_gst_playbin, "get-text-tags", _this->m_currentSubtitleStream, &tags);
+//
+// g_lang = g_strdup_printf ("und");
+// if ( tags && gst_is_tag_list(tags) )
+// gst_tag_list_get_string(tags, GST_TAG_LANGUAGE_CODE, &g_lang);
+// subs.language_code = std::string(g_lang);
+//
+// subs.type = getSubtitleType(GST_PAD(obj));
+//
+// _this->m_subtitleStreams[_this->m_currentSubtitleStream] = subs;
+//
+// g_free (g_lang);
+// }
+//
+// gstCBsubtitleLink(subs.type, _this);
+// }
+
+// void eServiceMP3::gstCBsubtitleLink(subtype_t type, gpointer user_data)
+// {
+// eServiceMP3 *_this = (eServiceMP3*)user_data;
+//
+// if ( type == stVOB )
+// {
+// GstPad *ghostpad = gst_element_get_static_pad(_this->m_gst_subtitlebin, "sink");
+// GstElement *dvdsubdec = gst_bin_get_by_name(GST_BIN(_this->m_gst_subtitlebin), "vobsubtitle_decoder");
+// GstPad *subdecsinkpad = gst_element_get_static_pad (dvdsubdec, "sink");
+// int ret = gst_ghost_pad_set_target((GstGhostPad*)ghostpad, subdecsinkpad);
+// GstElement *appsink = gst_bin_get_by_name(GST_BIN(_this->m_gst_subtitlebin), "subtitle_sink");
+// ret += gst_element_link(dvdsubdec, appsink);
+// eDebug("gstCBsubtitleLink:: dvdsubdec=%p, subdecsinkpad=%p, ghostpad=%p, set target & link=%i", dvdsubdec, subdecsinkpad, ghostpad, ret);
+// }
+// else if ( type < stVOB && type > stUnknown )
+// {
+// GstPad *ghostpad = gst_element_get_static_pad(_this->m_gst_subtitlebin, "sink");
+// GstElement *appsink = gst_bin_get_by_name(GST_BIN(_this->m_gst_subtitlebin), "subtitle_sink");
+// GstPad *appsinkpad = gst_element_get_static_pad (appsink, "sink");
+// GstElement *dvdsubdec = gst_bin_get_by_name(GST_BIN(_this->m_gst_subtitlebin), "vobsubtitle_decoder");
+// gst_element_unlink(dvdsubdec, appsink);
+// int ret = gst_ghost_pad_set_target((GstGhostPad*)ghostpad, appsinkpad);
+// eDebug("gstCBsubtitleLink:: appsink=%p, appsinkpad=%p, ghostpad=%p, set target=%i", appsink, appsinkpad, ghostpad, ret);
+// }
+// else
+// {
+// eDebug("gstCBsubtitleLink:: unsupported subtitles");
+// }
+// }
+/*
+gboolean eServiceMP3::gstCBsubtitleDrop(GstPad *pad, GstBuffer *buffer, gpointer user_data)
+{
+ eDebug("gstCBsubtitleDrop");
+
+ gint64 buf_pos = GST_BUFFER_TIMESTAMP(buffer);
+ gint64 duration_ns = GST_BUFFER_DURATION(buffer);
+ size_t len = GST_BUFFER_SIZE(buffer);
+
+ unsigned char line[len+1];
+ memcpy(line, GST_BUFFER_DATA(buffer), len);
+ line[len] = 0;
+ eDebug("dropping buffer '%s' ", line);
+ return false;
+}*/
+
+
void eServiceMP3::pullSubtitle()
{
- GstElement *sink;
- g_object_get (G_OBJECT (m_gst_playbin), "text-sink", &sink, NULL);
- if (sink)
+ GstElement *appsink = gst_bin_get_by_name(GST_BIN(m_gst_subtitlebin), "subtitle_sink");
+// GstElement *appsink = gst_bin_get_by_name(GST_BIN(m_gst_playbin), "subtitle_sink");
+
+ if (appsink)
{
while (m_subs_to_pull && m_subtitle_pages.size() < 2)
{
{
eSingleLocker l(m_subs_to_pull_lock);
--m_subs_to_pull;
- g_signal_emit_by_name (sink, "pull-buffer", &buffer);
+ g_signal_emit_by_name (appsink, "pull-buffer", &buffer);
}
if (buffer)
{
gint64 buf_pos = GST_BUFFER_TIMESTAMP(buffer);
gint64 duration_ns = GST_BUFFER_DURATION(buffer);
size_t len = GST_BUFFER_SIZE(buffer);
- unsigned char line[len+1];
- memcpy(line, GST_BUFFER_DATA(buffer), len);
- line[len] = 0;
- eDebug("got new subtitle @ buf_pos = %lld ns (in pts=%lld): '%s' ", buf_pos, buf_pos/11111, line);
- ePangoSubtitlePage page;
- gRGB rgbcol(0xD0,0xD0,0xD0);
- page.m_elements.push_back(ePangoSubtitlePageElement(rgbcol, (const char*)line));
- page.show_pts = buf_pos / 11111L;
- page.m_timeout = duration_ns / 1000000;
- m_subtitle_pages.push_back(page);
- pushSubtitles();
+ eDebug("pullSubtitle m_subtitleStreams[m_currentSubtitleStream].type=%i",m_subtitleStreams[m_currentSubtitleStream].type);
+
+ if ( m_subtitleStreams[m_currentSubtitleStream].type )
+ {
+ if ( m_subtitleStreams[m_currentSubtitleStream].type < stVOB )
+ {
+ unsigned char line[len+1];
+ SubtitlePage page;
+ memcpy(line, GST_BUFFER_DATA(buffer), len);
+ line[len] = 0;
+ eDebug("got new text subtitle @ buf_pos = %lld ns (in pts=%lld): '%s' ", buf_pos, buf_pos/11111, line);
+ gRGB rgbcol(0xD0,0xD0,0xD0);
+ page.type = SubtitlePage::Pango;
+ page.pango_page.m_elements.push_back(ePangoSubtitlePageElement(rgbcol, (const char*)line));
+ page.pango_page.m_show_pts = buf_pos / 11111L;
+ page.pango_page.m_timeout = duration_ns / 1000000;
+ m_subtitle_pages.push_back(page);
+ if (m_subtitle_pages.size()==1)
+ pushSubtitles();
+ }
+ else if ( m_subtitleStreams[m_currentSubtitleStream].type == stVOB )
+ {
+ SubtitlePage page;
+ eDebug("got new subpicture @ buf_pos = %lld ns (in pts=%lld), duration=%lld ns, len=%i bytes. ", buf_pos, buf_pos/11111, duration_ns, len);
+ page.type = SubtitlePage::Vob;
+ page.vob_page.m_pixmap = new gPixmap(eSize(720, 576), 32, 1);
+ memcpy(page.vob_page.m_pixmap->surface->data, GST_BUFFER_DATA(buffer), len);
+ page.vob_page.m_show_pts = buf_pos / 11111L;
+ page.vob_page.m_timeout = duration_ns / 1000;
+ m_subtitle_pages.push_back(page);
+ if (m_subtitle_pages.size()==1)
+ pushSubtitles();
+ }
+ else
+ {
+ eDebug("unsupported subpicture... ignoring");
+ }
+ }
gst_buffer_unref(buffer);
}
}
- gst_object_unref(sink);
+ gst_object_unref(appsink);
}
else
eDebug("no subtitle sink!");
void eServiceMP3::pushSubtitles()
{
- ePangoSubtitlePage page;
- pts_t running_pts;
while ( !m_subtitle_pages.empty() )
{
+ SubtitlePage &frontpage = m_subtitle_pages.front();
+ pts_t running_pts;
+ gint64 diff_ms = 0;
+ gint64 show_pts;
+
getPlayPosition(running_pts);
- page = m_subtitle_pages.front();
- gint64 diff_ms = ( page.show_pts - running_pts ) / 90;
- eDebug("eServiceMP3::pushSubtitles show_pts = %lld running_pts = %lld diff = %lld", page.show_pts, running_pts, diff_ms);
- if (diff_ms < -100)
- {
- GstFormat fmt = GST_FORMAT_TIME;
- gint64 now;
- if (gst_element_query_position(m_gst_playbin, &fmt, &now) != -1)
- {
- now /= 11111;
- diff_ms = abs((now - running_pts) / 90);
- eDebug("diff < -100ms check decoder/pipeline diff: decoder: %lld, pipeline: %lld, diff: %lld", running_pts, now, diff_ms);
- if (diff_ms > 100000)
- {
- eDebug("high decoder/pipeline difference.. assume decoder has now started yet.. check again in 1sec");
- m_subtitle_sync_timer->start(1000, true);
- break;
- }
+
+ if (m_decoder_time_valid_state < 4) {
+ ++m_decoder_time_valid_state;
+ if (m_prev_decoder_time == running_pts)
+ m_decoder_time_valid_state = 0;
+ if (m_decoder_time_valid_state < 4) {
+// if (m_decoder_time_valid_state)
+// eDebug("%d: decoder time not valid! prev %lld, now %lld\n", m_decoder_time_valid_state, m_prev_decoder_time/90, running_pts/90);
+// else
+// eDebug("%d: decoder time not valid! now %lld\n", m_decoder_time_valid_state, running_pts/90);
+ m_subtitle_sync_timer->start(25, true);
+ m_prev_decoder_time = running_pts;
+ break;
}
- else
- eDebug("query position for decoder/pipeline check failed!");
- eDebug("subtitle to late... drop");
+ }
+
+ if (frontpage.type == SubtitlePage::Pango)
+ show_pts = frontpage.pango_page.m_show_pts;
+ else
+ show_pts = frontpage.vob_page.m_show_pts;
+
+ diff_ms = ( show_pts - running_pts ) / 90;
+ eDebug("check subtitle: decoder: %lld, show_pts: %lld, diff: %lld ms", running_pts/90, show_pts/90, diff_ms);
+
+ if ( diff_ms < -100 )
+ {
+ eDebug("subtitle too late... drop");
m_subtitle_pages.pop_front();
}
else if ( diff_ms > 20 )
{
-// eDebug("start recheck timer");
- m_subtitle_sync_timer->start(diff_ms > 1000 ? 1000 : diff_ms, true);
+ eDebug("start timer");
+ m_subtitle_sync_timer->start(diff_ms, true);
break;
}
else // immediate show
{
- if (m_subtitle_widget)
- m_subtitle_widget->setPage(page);
+ if ( m_subtitle_widget )
+ {
+ eDebug("show!\n");
+ if ( frontpage.type == SubtitlePage::Pango)
+ m_subtitle_widget->setPage(frontpage.pango_page);
+ else
+ {
+ m_subtitle_widget->setPixmap(frontpage.vob_page.m_pixmap, eRect(0, 0, 720, 576));
+ eDebug("blit vobsub pixmap... hide in %i ms", frontpage.vob_page.m_timeout);
+ m_subtitle_hide_timer->start(frontpage.vob_page.m_timeout, true);
+ }
+ m_subtitle_widget->show();
+ }
m_subtitle_pages.pop_front();
}
}
pullSubtitle();
}
+void eServiceMP3::hideSubtitles()
+{
+// eDebug("eServiceMP3::hideSubtitles()");
+ if ( m_subtitle_widget )
+ m_subtitle_widget->hide();
+}
+
RESULT eServiceMP3::enableSubtitles(eWidget *parent, ePyObject tuple)
{
+// eDebug ("eServiceMP3::enableSubtitles m_currentSubtitleStream=%i this=%p",m_currentSubtitleStream, this);
ePyObject entry;
int tuplesize = PyTuple_Size(tuple);
int pid, type;
gint text_pid = 0;
+ eSingleLocker l(m_subs_to_pull_lock);
+
+// GstPad *pad = 0;
+// g_signal_emit_by_name (m_gst_playbin, "get-text-pad", m_currentSubtitleStream, &pad);
+// gst_element_get_static_pad(m_gst_subtitlebin, "sink");
+// gulong subprobe_handler_id = gst_pad_add_buffer_probe (pad, G_CALLBACK (gstCBsubtitleDrop), NULL);
if (!PyTuple_Check(tuple))
goto error_out;
goto error_out;
type = PyInt_AsLong(entry);
+// eDebug ("eServiceMP3::enableSubtitles new pid=%i",pid);
if (m_currentSubtitleStream != pid)
{
- eSingleLocker l(m_subs_to_pull_lock);
g_object_set (G_OBJECT (m_gst_playbin), "current-text", pid, NULL);
+// eDebug ("eServiceMP3::enableSubtitles g_object_set current-text = %i", pid);
m_currentSubtitleStream = pid;
m_subs_to_pull = 0;
+ m_prev_decoder_time = -1;
m_subtitle_pages.clear();
}
g_object_get (G_OBJECT (m_gst_playbin), "current-text", &text_pid, NULL);
eDebug ("eServiceMP3::switched to subtitle stream %i", text_pid);
+// gst_pad_remove_buffer_probe (pad, subprobe_handler_id);
+
+ m_event((iPlayableService*)this, evUpdatedInfo);
return 0;
PyObject *eServiceMP3::getSubtitleList()
{
- eDebug("eServiceMP3::getSubtitleList");
-
+// eDebug("eServiceMP3::getSubtitleList");
ePyObject l = PyList_New(0);
- int stream_count[sizeof(subtype_t)];
- for ( unsigned int i = 0; i < sizeof(subtype_t); i++ )
- stream_count[i] = 0;
-
+ int stream_idx = 0;
+
for (std::vector<subtitleStream>::iterator IterSubtitleStream(m_subtitleStreams.begin()); IterSubtitleStream != m_subtitleStreams.end(); ++IterSubtitleStream)
{
subtype_t type = IterSubtitleStream->type;
ePyObject tuple = PyTuple_New(5);
+// eDebug("eServiceMP3::getSubtitleList idx=%i type=%i, code=%s", stream_idx, int(type), (IterSubtitleStream->language_code).c_str());
PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(2));
- PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(stream_count[type]));
+ PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(stream_idx));
PyTuple_SET_ITEM(tuple, 2, PyInt_FromLong(int(type)));
PyTuple_SET_ITEM(tuple, 3, PyInt_FromLong(0));
PyTuple_SET_ITEM(tuple, 4, PyString_FromString((IterSubtitleStream->language_code).c_str()));
PyList_Append(l, tuple);
Py_DECREF(tuple);
- stream_count[type]++;
+ stream_idx++;
}
+ eDebug("eServiceMP3::getSubtitleList finished");
return l;
}
typedef struct _GstElement GstElement;
typedef enum { atUnknown, atMPEG, atMP3, atAC3, atDTS, atAAC, atPCM, atOGG, atFLAC } audiotype_t;
-typedef enum { stPlainText, stSSA, stSRT } subtype_t;
+typedef enum { stUnknown, stPlainText, stSSA, stASS, stSRT, stVOB, stPGS } subtype_t;
typedef enum { ctNone, ctMPEGTS, ctMPEGPS, ctMKV, ctAVI, ctMP4, ctVCD, ctCDA } containertype_t;
class eServiceMP3: public iPlayableService, public iPauseableService,
};
int m_state;
GstElement *m_gst_playbin;
+ GstElement *m_gst_subtitlebin;
GstTagList *m_stream_tags;
- eFixedMessagePump<int> m_pump;
+
+ struct Message
+ {
+ Message()
+ :type(-1)
+ {}
+ Message(int type)
+ :type(type)
+ {}
+ Message(int type, GstPad *pad)
+ :type(type)
+ {
+ d.pad=pad;
+ }
+
+ int type;
+ union {
+ GstPad *pad; // for msg type 3
+ } d;
+ };
+
+ eFixedMessagePump<Message> m_pump;
std::string m_error_message;
audiotype_t gstCheckAudioPad(GstStructure* structure);
void gstBusCall(GstBus *bus, GstMessage *msg);
static GstBusSyncReply gstBusSyncHandler(GstBus *bus, GstMessage *message, gpointer user_data);
- static void gstCBsubtitleAvail(GstElement *element, gpointer user_data);
- GstPad* gstCreateSubtitleSink(eServiceMP3* _this, subtype_t type);
- void gstPoll(const int&);
static void gstHTTPSourceSetAgent(GObject *source, GParamSpec *unused, gpointer user_data);
+ static void gstCBsubtitleAvail(GstElement *element, gpointer user_data);
+ static GstCaps* gstGhostpadGetCAPS (GstPad * pad);
+ static gboolean gstGhostpadAcceptCAPS(GstPad * pad, GstCaps * caps);
+ static void gstGhostpadLink(gpointer user_data, GstCaps * caps);
+ static GstFlowReturn gstGhostpadBufferAlloc(GstPad *pad, guint64 offset, guint size, GstCaps *caps, GstBuffer **buf);
+ static void gstGhostpadHasCAPS(GstPad *pad, GParamSpec * unused, gpointer user_data);
+ static gboolean gstGhostpadSinkEvent(GstPad * pad, GstEvent * event);
+ static GstFlowReturn gstGhostpadChainFunction(GstPad * pad, GstBuffer * buffer);
+/* static void gstCBsubtitleCAPS(GObject *obj, GParamSpec *pspec, gpointer user_data);
+ static void gstCBsubtitleLink(subtype_t type, gpointer user_data);
+ static gboolean gstCBsubtitleDrop(GstPad *pad, GstBuffer *buffer, gpointer user_data);*/
+ void gstPoll(const Message&);
+ void gstGhostpadHasCAPS_synced(GstPad *pad);
+
+ GstPadBufferAllocFunction m_ghost_pad_buffer_alloc;
+ GstPadChainFunction m_ghost_pad_chain_function;
+ GstPadEventFunction m_ghost_pad_subtitle_sink_event;
+ GstCaps *m_gst_prev_subtitle_caps;
+ GstSegment m_gst_subtitle_segment;
+ GstPadEventFunction m_gst_sink_event;
- std::list<ePangoSubtitlePage> m_subtitle_pages;
+ struct SubtitlePage
+ {
+ enum { Unknown, Pango, Vob } type;
+ ePangoSubtitlePage pango_page;
+ eVobSubtitlePage vob_page;
+ };
+
+ std::list<SubtitlePage> m_subtitle_pages;
ePtr<eTimer> m_subtitle_sync_timer;
-
+ ePtr<eTimer> m_subtitle_hide_timer;
ePtr<eTimer> m_streamingsrc_timeout;
+ pts_t m_prev_decoder_time;
+ int m_decoder_time_valid_state;
+
void pushSubtitles();
void pullSubtitle();
+ void hideSubtitles();
void sourceTimeout();
int m_subs_to_pull;
sourceStream m_sourceinfo;
GIT = git --git-dir=$(GIT_DIR)
if HAVE_FAKE_GIT_DIR
-ENIGMA2_DATE = `cat $(GIT_DIR)/last_commit_info | grep 'Date:' | cut -d' ' -f4`
-ENIGMA2_COMMIT_DATE = `cat $(GIT_DIR)/last_commit_info | grep 'CommitDate:' | cut -d' ' -f2`
+ENIGMA2_COMMIT_DATE = `grep '^CommitDate:' $(GIT_DIR)/last_commit_info | cut -d' ' -f2`
ENIGMA2_BRANCH = `cat $(GIT_DIR)/branch`
else
-ENIGMA2_DATE =
ENIGMA2_COMMIT_DATE = `$(GIT) log --no-color -n 1 --pretty=format:%cd --date=short`
ENIGMA2_BRANCH = `$(GIT) branch --no-color 2>/dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/'`
ENIGMA2_REV = `$(GIT) describe --abbrev=7 --always --long --tags`
endif
+endif
BUILT_SOURCES = version-intermediate.h
.INTERMEDIATE: version-intermediate.h
.PHONY: version-intermediate.h
version-intermediate.h:
$(AM_V_GEN)touch $@
- if [ -n "$(ENIGMA2_COMMIT_DATE)" ]; then \
- $(AM_V_at)[ -n "$(ENIGMA2_COMMIT_DATE)" ] && echo "#define ENIGMA2_LAST_CHANGE_DATE \"$(ENIGMA2_COMMIT_DATE)\"" >> $@; \
- else \
- $(AM_V_at)[ -n "$(ENIGMA2_DATE)" ] && echo "#define ENIGMA2_LAST_CHANGE_DATE \"$(ENIGMA2_DATE)\"" >> $@; \
- fi;
- $(AM_V_at)[ -n "$(ENIGMA2_BRANCH)" ] && echo "#define ENIGMA2_BRANCH \"$(ENIGMA2_BRANCH)\"" >> $@
+ -$(AM_V_at)[ -n "$(ENIGMA2_COMMIT_DATE)" ] && echo "#define ENIGMA2_COMMIT_DATE \"$(ENIGMA2_COMMIT_DATE)\"" >> $@
+ -$(AM_V_at)[ -n "$(ENIGMA2_BRANCH)" ] && echo "#define ENIGMA2_BRANCH \"$(ENIGMA2_BRANCH)\"" >> $@
-$(AM_V_at)[ -n "$(ENIGMA2_REV)" ] && echo "#define ENIGMA2_REV \"$(ENIGMA2_REV)\"" >> $@
$(AM_V_at)([ -f version.h ] && diff -q version.h $@ >/dev/null) || $(INSTALL_HEADER) $@ version.h
-endif
enigma2$(EXEEXT): $(enigma2_OBJECTS) $(enigma2_DEPENDENCIES) $(enigma2_LDADD_WHOLE)
$(AM_V_CXXLD)$(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ $(enigma2_LDFLAGS) $(enigma2_OBJECTS) \
xml.open("image");
xml.stringFromFile("dreamboxmodel", "/proc/stb/info/model");
xml.stringFromFile("kernelcmdline", "/proc/cmdline");
+ xml.stringFromFile("nimsockets", "/proc/bus/nim_sockets");
if (!getConfigBool("config.plugins.crashlogautosubmit.sendAnonCrashlog", true)) {
xml.cDataFromFile("dreamboxca", "/proc/stb/info/ca");
xml.cDataFromFile("enigma2settings", eEnv::resolve("${sysconfdir}/enigma2/settings"), ".password=");
#include "version_info.h"
#include "version.h"
-#ifndef ENIGMA2_LAST_CHANGE_DATE
-#define ENIGMA2_LAST_CHANGE_DATE __DATE__
+#ifndef ENIGMA2_COMMIT_DATE
+#define ENIGMA2_COMMIT_DATE __DATE__
#endif
-const char *enigma2_date = ENIGMA2_LAST_CHANGE_DATE;
+const char *enigma2_date = ENIGMA2_COMMIT_DATE;
#ifndef ENIGMA2_BRANCH
-#define ENIGMA2_BRANCH "HEAD"
+#define ENIGMA2_BRANCH "(no branch)"
#endif
const char *enigma2_branch = ENIGMA2_BRANCH;