diff options
author | Tobias Grimm <tobias@e-tobi.loc> | 2008-12-13 11:01:34 +0100 |
---|---|---|
committer | Tobias Grimm <tobias@e-tobi.loc> | 2008-12-13 11:01:34 +0100 |
commit | e5d1aacca2020e63dbaf320ba8b1de61746ad410 (patch) | |
tree | 0e267f716f34700a439971926fdc79b0172a0f38 | |
parent | 88012c106b8310507e872c72544f423433bac5d2 (diff) | |
download | vdr-plugin-ttxtsubs-0.0.5pre1.tar.gz vdr-plugin-ttxtsubs-0.0.5pre1.tar.bz2 |
- New features:v0.0.5pre1
- More than one language can be chosen, they are used in order of preference
- Handles languages with two ISO 639-2 identifiers (as ger/deu, fre/fra)
- Subtitles can now be turned on or off
- Optional main menu alternative for easy access
- Selectable vertical position of text for 4:3/Anamorphic or Letterbox
- Left, Center or Right horizontal position of text
- Remapping option for those French channels that incorrectly
sends teletext page numbers in decimal instead of hexadecimal.
Very moderate remapping done at the moment, I hope it is enough.
- Subtitles are now recorded with their PTS (timestamps). Not that
the timestamps are completely wrong on some channels, this
may confuse uncareful subtitle extracting software.
- Included patch for VDR 1.3.5 - untested!
- Bugs fixed:
- Fixed a net-to-host-order bug in the si parser that could make it
incorrectly think that there are no subtitles on a channel.
Thanks to Nicolas "tarass"!
- Fixed a bit swapping bug causing incorrect character encoding for
some languages. Thanks to Nicolas "tarass"!
-rw-r--r-- | HISTORY | 23 | ||||
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | README | 2 | ||||
-rw-r--r-- | VDR-1.3.5.patch | 251 | ||||
-rw-r--r-- | siinfo.c | 73 | ||||
-rw-r--r-- | siinfo.h | 4 | ||||
-rw-r--r-- | teletext-chars.h | 4 | ||||
-rw-r--r-- | teletext.c | 2 | ||||
-rw-r--r-- | ttxtsubs.c | 477 | ||||
-rw-r--r-- | ttxtsubsdisplay.c | 91 | ||||
-rw-r--r-- | ttxtsubsdisplayer.c | 48 | ||||
-rw-r--r-- | ttxtsubsdisplayer.h | 5 | ||||
-rw-r--r-- | ttxtsubsfilter.c | 5 | ||||
-rw-r--r-- | ttxtsubsglobals.h | 50 | ||||
-rw-r--r-- | ttxtsubsreceiver.c | 44 | ||||
-rw-r--r-- | ttxtsubsreceiver.h | 8 | ||||
-rw-r--r-- | ttxtsubsrecorder.c | 21 | ||||
-rw-r--r-- | ttxtsubsrecorder.h | 2 |
18 files changed, 909 insertions, 205 deletions
@@ -1,6 +1,29 @@ VDR Plugin 'ttxtsubs' Revision History -------------------------------------- +2004-03-01: Version 0.0.5pre1 + +- New features: + - More than one language can be chosen, they are used in order of preference + - Handles languages with two ISO 639-2 identifiers (as ger/deu, fre/fra) + - Subtitles can now be turned on or off + - Optional main menu alternative for easy access + - Selectable vertical position of text for 4:3/Anamorphic or Letterbox + - Left, Center or Right horizontal position of text + - Remapping option for those French channels that incorrectly + sends teletext page numbers in decimal instead of hexadecimal. + Very moderate remapping done at the moment, I hope it is enough. + - Subtitles are now recorded with their PTS (timestamps). Not that + the timestamps are completely wrong on some channels, this + may confuse uncareful subtitle extracting software. + - Included patch for VDR 1.3.5 - untested! +- Bugs fixed: + - Fixed a net-to-host-order bug in the si parser that could make it + incorrectly think that there are no subtitles on a channel. + Thanks to Nicolas "tarass"! + - Fixed a bit swapping bug causing incorrect character encoding for + some languages. Thanks to Nicolas "tarass"! + 2003-09-26: Version 0.0.4b - Fix patch for VDR 1.2.5 - Minor bug fix for dxr3 display @@ -1,7 +1,7 @@ # # Makefile for a Video Disk Recorder plugin # -# $Id: Makefile,v 1.18 2003/07/16 04:01:44 ragge Exp $ +# $Id: Makefile,v 1.19 2004/03/01 04:41:13 ragge Exp ragge $ # The official name of this plugin. # This name will be used in the '-P...' option of VDR to load the plugin. @@ -83,7 +83,7 @@ dist: clean @-rm -rf $(TMPDIR)/$(ARCHIVE) @echo Distribution package created as $(PACKAGE).tgz -bup: clean patch +bup: clean @-rm -rf $(TMPDIR)/$(ARCHIVE) @mkdir $(TMPDIR)/$(ARCHIVE) @cp -a $(SOURCEFILES) RCS $(TMPDIR)/$(ARCHIVE) @@ -2,7 +2,7 @@ This is a "plugin" for the Video Disk Recorder (VDR). Written by: Ragnar Sundblad <ragge@nada.kth.se> -Project's homepage: - +Project's homepage: http://www.nada.kth.se/~ragge/vdr/ttxtsubs/ Latest version available at: ftp://ftp.nada.kth.se/pub/home/ragge/vdr/ diff --git a/VDR-1.3.5.patch b/VDR-1.3.5.patch new file mode 100644 index 0000000..8a1a32c --- /dev/null +++ b/VDR-1.3.5.patch @@ -0,0 +1,251 @@ +diff -upr ./DIST/Makefile ./Makefile +--- ./DIST/Makefile 2004-01-18 15:16:53.000000000 +0100 ++++ ./Makefile 2004-03-01 05:19:59.000000000 +0100 +@@ -37,7 +37,8 @@ OBJS = audio.o channels.o ci.o config.o + dvbplayer.o dvbspu.o eit.o eitscan.o epg.o filter.o font.o i18n.o interface.o keys.o\ + lirc.o menu.o menuitems.o nit.o osdbase.o osd.o pat.o player.o plugin.o rcu.o\ + receiver.o recorder.o recording.o remote.o remux.o ringbuffer.o sdt.o sections.o sources.o\ +- spu.o status.o svdrp.o thread.o timers.o tools.o transfer.o vdr.o videodir.o ++ spu.o status.o svdrp.o thread.o timers.o tools.o transfer.o vdr.o videodir.o\ ++ vdrttxtsubshooks.o + + FIXFONT_ISO8859_1 = -adobe-courier-bold-r-normal--25-*-100-100-m-*-iso8859-1 + OSDFONT_ISO8859_1 = -adobe-helvetica-medium-r-normal--23-*-100-100-p-*-iso8859-1 +diff -upr ./DIST/dvbplayer.c ./dvbplayer.c +--- ./DIST/dvbplayer.c 2003-10-18 13:31:54.000000000 +0200 ++++ ./dvbplayer.c 2004-03-01 05:18:15.000000000 +0100 +@@ -14,6 +14,7 @@ + #include "ringbuffer.h" + #include "thread.h" + #include "tools.h" ++#include "vdrttxtsubshooks.h" + + // --- cBackTrace ---------------------------------------------------------- + +@@ -323,6 +324,12 @@ void cDvbPlayer::StripAudioPackets(uchar + int l = b[i + 4] * 256 + b[i + 5] + 6; + switch (c) { + case 0xBD: // dolby ++#ifdef VDRTTXTSUBSHOOKS ++ if (b[i + 8] == 0x24 && b[i + 45] >= 0x10 && b[i + 45] < 0x20) { ++ break; // run these through the ring buffer to get somewhat correct ++ // timing for the subtitles ++ } else ++#endif + if (Except) + PlayAudio(&b[i], l); + // continue with deleting the data - otherwise it disturbs DVB replay +@@ -349,6 +356,40 @@ void cDvbPlayer::StripAudioPackets(uchar + } + } + ++#ifdef VDRTTXTSUBSHOOKS ++static void StripTtxtPackets(uchar *b, int Length) ++{ ++ for (int i = 0; i < Length - 6; i++) { ++ if (b[i] == 0x00 && b[i + 1] == 0x00 && b[i + 2] == 0x01) { ++ uchar c = b[i + 3]; ++ int l = b[i + 4] * 256 + b[i + 5] + 6; ++ switch (c) { ++ case 0xBD: // dolby ++ { ++ if (b[i + 8] == 0x24 && b[i + 45] >= 0x10 && b[i + 45] < 0x20) { ++ // EBU Teletext data, ETSI EN 300 472 ++ cVDRTtxtsubsHookListener::Hook()->PlayerTeletextData(&b[i], l); ++ } ++ // continue with deleting the data - otherwise it disturbs DVB replay ++ int n = l; ++ for (int j = i; j < Length && n--; j++) ++ b[j] = 0x00; ++ break; ++ } ++ default: ++ break; ++ } ++ if (l) ++ i += l - 1; // the loop increments, too! ++ } ++ /*XXX ++ else ++ esyslog("ERROR: broken packet header"); ++ XXX*/ ++ } ++} ++#endif ++ + bool cDvbPlayer::NextFile(uchar FileNumber, int FileOffset) + { + if (FileNumber > 0) +@@ -522,6 +563,11 @@ void cDvbPlayer::Action(void) + StripAudioPackets(p, pc, AudioTrack); + } + } ++#ifdef VDRTTXTSUBSHOOKS ++ // pick out the teletext packets here ++ if(p) ++ StripTtxtPackets((uchar *) p, pc); ++#endif + if (p) { + int w = PlayVideo(p, pc); + if (w > 0) { +diff -upr ./DIST/menu.c ./menu.c +--- ./DIST/menu.c 2004-02-29 15:11:16.000000000 +0100 ++++ ./menu.c 2004-03-01 05:18:15.000000000 +0100 +@@ -3104,8 +3104,18 @@ cRecordControl::cRecordControl(cDevice * + isyslog("record %s", fileName); + if (MakeDirs(fileName, true)) { + const cChannel *ch = timer->Channel(); ++#ifdef VDRTTXTSUBSHOOKS ++ cTtxtSubsRecorderBase *subsRecorder = cVDRTtxtsubsHookListener::Hook() ++ ->NewTtxtSubsRecorder(device, ch); ++ recorder = new cRecorder(fileName, ch->Ca(), timer->Priority(), ch->Vpid(), ch->Apid1(), ch->Apid2(), ch->Dpid1(), ch->Dpid2(), subsRecorder); ++#else + recorder = new cRecorder(fileName, ch->Ca(), timer->Priority(), ch->Vpid(), ch->Apid1(), ch->Apid2(), ch->Dpid1(), ch->Dpid2()); ++#endif + if (device->AttachReceiver(recorder)) { ++#ifdef VDRTTXTSUBSHOOKS ++ if(subsRecorder) ++ subsRecorder->DeviceAttach(); ++#endif + Recording.WriteSummary(); + cStatus::MsgRecording(device, Recording.Name()); + if (!Timer && !cReplayControl::LastReplayed()) // an instant recording, maybe from cRecordControls::PauseLiveVideo() +diff -upr ./DIST/menu.h ./menu.h +--- ./DIST/menu.h 2004-02-15 15:11:28.000000000 +0100 ++++ ./menu.h 2004-03-01 05:18:15.000000000 +0100 +@@ -15,6 +15,7 @@ + #include "epg.h" + #include "osd.h" + #include "dvbplayer.h" ++#include "vdrttxtsubshooks.h" + #include "recorder.h" + #include "recording.h" + +diff -upr ./DIST/osd.c ./osd.c +--- ./DIST/osd.c 2003-06-04 18:13:00.000000000 +0200 ++++ ./osd.c 2004-03-01 05:18:15.000000000 +0100 +@@ -12,6 +12,7 @@ + #include "device.h" + #include "i18n.h" + #include "status.h" ++#include "vdrttxtsubshooks.h" + + // --- cOsd ------------------------------------------------------------------ + +@@ -62,6 +63,10 @@ void cOsd::SetColor(eDvbColor colorFg, e + + cOsdBase *cOsd::OpenRaw(int x, int y) + { ++#ifdef VDRTTXTSUBSHOOKS ++ // OSD_HOOK_2 - Information to Checkpatch.sh ++ cVDRTtxtsubsHookListener::Hook()->HideOSD(); ++#endif + #ifdef DEBUG_OSD + return NULL; + #else +@@ -138,6 +143,9 @@ void cOsd::Close(void) + delete osd; + osd = NULL; + #endif ++#ifdef VDRTTXTSUBSHOOKS ++ cVDRTtxtsubsHookListener::Hook()->ShowOSD(); ++#endif + } + + void cOsd::Clear(void) +diff -upr ./DIST/recorder.c ./recorder.c +--- ./DIST/recorder.c 2003-10-18 13:35:02.000000000 +0200 ++++ ./recorder.c 2004-03-01 05:18:15.000000000 +0100 +@@ -10,6 +10,8 @@ + #include <stdarg.h> + #include <stdio.h> + #include <unistd.h> ++#include <stdint.h> ++#include "vdrttxtsubshooks.h" + #include "recorder.h" + + // The size of the array used to buffer video data: +@@ -23,7 +25,11 @@ + #define MINFREEDISKSPACE (512) // MB + #define DISKCHECKINTERVAL 100 // seconds + ++#ifdef VDRTTXTSUBSHOOKS ++cRecorder::cRecorder(const char *FileName, int Ca, int Priority, int VPid, int APid1, int APid2, int DPid1, int DPid2, cTtxtSubsRecorderBase *tsr) ++#else + cRecorder::cRecorder(const char *FileName, int Ca, int Priority, int VPid, int APid1, int APid2, int DPid1, int DPid2) ++#endif + :cReceiver(Ca, Priority, 5, VPid, APid1, APid2, DPid1, DPid2) + ,cThread("recording") + { +@@ -35,6 +41,9 @@ cRecorder::cRecorder(const char *FileNam + fileSize = 0; + active = false; + lastDiskSpaceCheck = time(NULL); ++#ifdef VDRTTXTSUBSHOOKS ++ ttxtSubsRecorder = tsr; ++#endif + + // Make sure the disk is up and running: + +@@ -56,6 +65,10 @@ cRecorder::cRecorder(const char *FileNam + cRecorder::~cRecorder() + { + Detach(); ++#ifdef VDRTTXTSUBSHOOKS ++ if(ttxtSubsRecorder) ++ delete ttxtSubsRecorder; ++#endif + delete index; + delete fileName; + delete remux; +@@ -128,6 +141,19 @@ void cRecorder::Action(void) + break; + } + fileSize += Result; ++#ifdef VDRTTXTSUBSHOOKS ++ // not sure if the pictureType test is needed, but it seems we can get ++ // incomplete pes packets from remux if we are not getting pictures? ++ if (ttxtSubsRecorder && pictureType != NO_PICTURE) { ++ uint8_t *subsp; ++ size_t len; ++ if(ttxtSubsRecorder->GetPacket(&subsp, &len)) { ++ safe_write(recordFile, subsp, len); ++ fileSize += len; ++ // fprintf(stderr, "cRecorder::Action: Wrote ttxtsubs data len %d\n", len); // XXX ++ } ++ } ++#endif + } + else + break; +diff -upr ./DIST/recorder.h ./recorder.h +--- ./DIST/recorder.h 2002-06-08 11:35:03.000000000 +0200 ++++ ./recorder.h 2004-03-01 05:18:15.000000000 +0100 +@@ -15,6 +15,7 @@ + #include "remux.h" + #include "ringbuffer.h" + #include "thread.h" ++#include "vdrttxtsubshooks.h" + + class cRecorder : public cReceiver, cThread { + private: +@@ -29,12 +30,19 @@ private: + time_t lastDiskSpaceCheck; + bool RunningLowOnDiskSpace(void); + bool NextFile(void); ++#ifdef VDRTTXTSUBSHOOKS ++ cTtxtSubsRecorderBase *ttxtSubsRecorder; ++#endif + protected: + virtual void Activate(bool On); + virtual void Receive(uchar *Data, int Length); + virtual void Action(void); + public: ++#ifdef VDRTTXTSUBSHOOKS ++ cRecorder(const char *FileName, int Ca, int Priority, int VPid, int APid1, int APid2, int DPid1, int DPid2, cTtxtSubsRecorderBase *tsr); ++#else + cRecorder(const char *FileName, int Ca, int Priority, int VPid, int APid1, int APid2, int DPid1, int DPid2); ++#endif + // Creates a new recorder that requires conditional access Ca, has + // the given Priority and will record the given PIDs into the file FileName. + virtual ~cRecorder(); @@ -22,6 +22,8 @@ #include "linux/dvb/dmx.h" #include "siinfo.h" +#include "ttxtsubsglobals.h" + #define DESCR_TELETEXT 0x56 #define DESCR_DVBSUBTITLES 0x59 @@ -329,6 +331,23 @@ static void addpageinfo(struct ttxtinfo *info, uint16_t pid, struct ttxt_descr * pa->type = descr->d[descr_index].type_mag >> 3; pa->mag = descr->d[descr_index].type_mag & 0x7; pa->page = descr->d[descr_index].page_no; + + if(globals.frenchSpecial()) { + /* By some silly reason some French channels (Canal+ and CanalSatellite) + announce subtitles with the page number in decimal, for example + page 859 when they really are on page 889. */ + uint8_t newpage = pa->page; + if(pa->page >= 0x50 && pa->page <= 0x59) + newpage += 0x30; + if(pa->page != newpage) { + uint8_t mag = pa->mag; + if(mag == 0) + mag = 8; + fprintf(stderr, "Warning: Remapped page number %01x%02x to %01x%02x!\n", + mag, pa->page, mag, newpage); + pa->page = newpage; + } + } } @@ -343,7 +362,7 @@ static int HasVPID(int vpid, char **pmtsects, int numsects) char *end; psect = (struct PMT_sect *) pmtsects[i]; - end = pmtsects[i] + (psect->syntax_len & 0x3ff) - 7; + end = pmtsects[i] + (ntohs(psect->syntax_len) & 0x3ff) - 7; // skip extra program info sp = ((char *) &(psect->s)) + (ntohs(psect->res_program_info_length) & 0xfff); @@ -374,7 +393,7 @@ static void ExtractTtxtInfo(char **pmtsects, int numsects, struct ttxtinfo *info char *end; psect = (struct PMT_sect *) pmtsects[i]; - end = pmtsects[i] + (psect->syntax_len & 0x3ff) - 7; + end = pmtsects[i] + (ntohs(psect->syntax_len) & 0x3ff) - 7; // skip extra program info sp = ((char *) &(psect->s)) + (ntohs(psect->res_program_info_length) & 0xfff); @@ -572,52 +591,47 @@ void DupTtxtInfo(struct ttxtinfo *in, struct ttxtinfo *out) } -struct ttxtpidinfo *FindSubs(struct ttxtinfo *info, char *lang, int HI, int *pid, int *pageno) +struct ttxtpidinfo *FindSubs(struct ttxtinfo *info, int *pid, int *pageno) { - struct ttxtpidinfo *foundNonHIInfo = NULL; - int foundNonHIPid = 0; - int foundNonHIPage = -1; + struct ttxtpidinfo *foundPI = NULL; + int foundChoise = 1000; + // walk through all available languages and remember most preferred one for(int i = 0; i < info->pidcount; i++) { for(int j = 0; j < info->p[i].pagecount; j++) { - if(info->p[i].i[j].lang[0] == lang[0] && - info->p[i].i[j].lang[1] == lang[1] && - info->p[i].i[j].lang[2] == lang[2]) { - if((!HI && info->p[i].i[j].type == TTXT_SUBTITLE_PAGE) || - (HI && info->p[i].i[j].type == TTXT_SUBTITLE_HEARING_IMPAIRED_PAGE)) { + if((info->p[i].i[j].type != TTXT_SUBTITLE_PAGE) && + (info->p[i].i[j].type != TTXT_SUBTITLE_HEARING_IMPAIRED_PAGE)) + continue; + + int ch = globals.langChoise(info->p[i].i[j].lang, + info->p[i].i[j].type == TTXT_SUBTITLE_HEARING_IMPAIRED_PAGE); + if(ch != -1) { + if(ch < foundChoise) { + foundChoise = ch; *pid = info->p[i].pid; *pageno = info->p[i].i[j].mag * 0x100 + info->p[i].i[j].page; - fprintf(stderr, "ttxtsubs: Found selected subtitles on PID %d, page %03x\n", *pid, - *pageno < 0x100 ? *pageno + 0x800 : *pageno); - return &(info->p[i]); - } else if(HI && info->p[i].i[j].type == TTXT_SUBTITLE_PAGE) { - foundNonHIPid = info->p[i].pid; - foundNonHIPage = info->p[i].i[j].mag * 0x100 + info->p[i].i[j].page; - foundNonHIInfo = &(info->p[i]); + foundPI = &(info->p[i]); } } } } - if(foundNonHIInfo) { - *pid = foundNonHIPid; - *pageno = foundNonHIPage; - fprintf(stderr, "ttxtsubs: Found non HI subtitles on PID %d, page %03x\n", *pid, - *pageno < 0x100 ? *pageno + 0x800 : *pageno); - return foundNonHIInfo; + if(foundPI) { + return foundPI; } if(info->pidcount == 0) fprintf(stderr, "ttxtsubs: No teletext subtitles on channel.\n"); else { - fprintf(stderr, "ttxtsubs: Subtitles for language \"%c%c%c\" not found on channel, available languages:\n", lang[0], lang[1], lang[2]); + fprintf(stderr, "ttxtsubs: Wanted subtitle language(s) not found on channel, available languages:\n"); for(int i = 0; i < info->pidcount; i++) { for(int j = 0; j < info->p[i].pagecount; j++) { int page = info->p[i].i[j].mag * 0x100 + info->p[i].i[j].page; int type = info->p[i].i[j].type; if(page < 0x100) page += 0x800; - fprintf(stderr, " %03x: %c%c%c %s\n", page, info->p[i].i[j].lang[0], info->p[i].i[j].lang[1], info->p[i].i[j].lang[2], + fprintf(stderr, " %03x: %c%c%c %s\n", page, info->p[i].i[j].lang[0], + info->p[i].i[j].lang[1], info->p[i].i[j].lang[2], type == TTXT_INITIAL_PAGE ? "(Initial Page (The teletext start page, not a subtitles page!))" : type == TTXT_SUBTITLE_PAGE ? "(Subtitles)" : type == TTXT_ADDITIONAL_INFO_PAGE ? "(Additional Info Page)" : @@ -633,6 +647,13 @@ struct ttxtpidinfo *FindSubs(struct ttxtinfo *info, char *lang, int HI, int *pid } +void +ClearSICache(void) +{ + gCache.clear(); +} + + #if 0 int XX_GetTtxtSubtitleInfo(uint16_t pid, int card_no, struct ttxtinfo *info) { @@ -35,4 +35,6 @@ int GetTtxtInfo(int card_no, int channel, uint16_t sid, uint16_t vpid, struct tt void FreeTtxtInfoData(struct ttxtinfo *info); void DupTtxtInfo(struct ttxtinfo *in, struct ttxtinfo *out); -struct ttxtpidinfo *FindSubs(struct ttxtinfo *info, char *lang, int HI, int *pid, int *pageno); +struct ttxtpidinfo *FindSubs(struct ttxtinfo *info, int *pid, int *pageno); + +void ClearSICache(void); diff --git a/teletext-chars.h b/teletext-chars.h index 1ebc10b..995a43f 100644 --- a/teletext-chars.h +++ b/teletext-chars.h @@ -53,7 +53,7 @@ uint8_t laG0_nat_opts[13][14] = { {0, '£', '$', '@', '-', '½', '-', '|', '#', '-', '¼', '#', '¾', '÷'}, // 1 - English {0, '#', 'õ', 'S', 'Ä', 'Ö', 'Z', 'Ü', 'Õ', 's', 'ä', 'ö', 'z', 'ü'}, // 2 - Estonian {0, 'é', 'ï', 'à', 'ë', 'ê', 'ù', 'î', '#', 'è', 'â', 'ô', 'û', 'ç'}, // 3 - French -{0, '#', '$', '§', 'Ä', 'Ö', 'Ü', '^', '_', 'º', 'ä', 'ö', 'û', 'ß'}, // 4 - German +{0, '#', '$', '§', 'Ä', 'Ö', 'Ü', '^', '_', 'º', 'ä', 'ö', 'ü', 'ß'}, // 4 - German {0, '£', '$', 'é', 'º', 'ç', '-', '|', '#', 'ù', 'à', 'ò', 'è', 'ì'}, // 5 - Italian {0, '#', '$', 'S', 'e', 'e', 'Z', 'c', 'u', 's', 'a', 'u', 'z', 'i'}, // 6 - Lettish/Lithuanian {0, '#', 'n', 'a', 'Z', 'S', 'L', 'c', 'ó', 'e', 'z', 's', 'l', 'z'}, // 7 - Polish @@ -61,7 +61,7 @@ uint8_t laG0_nat_opts[13][14] = { {0, '#', '¤', 'T', 'Â', 'S', 'A', 'Î', 'i', 't', 'â', 's', 'a', 'î'}, // 9 - Rumanian {0, '#', 'Ë', 'C', 'C', 'Z', 'D', 'S', 'ë', 'c', 'c', 'z', 'd', 's'}, // 10 - Serbian/Croation/Slovenian {0, '#', '¤', 'É', 'Ä', 'Ö', 'Å', 'Ü', '_', 'é', 'ä', 'ö', 'å', 'ü'}, // 11 - Swedish/Finnish/Hungarian -{0, 'T', 'g', 'I', 'S', 'Ö', 'Ç', 'Ü', 'G', 'i', 'S', 'ö', 'ç', 'ü'} // 12 - Turkish +{0, 'T', 'g', 'I', 'S', 'Ö', 'Ç', 'Ü', 'G', 'i', 's', 'ö', 'ç', 'ü'} // 12 - Turkish }; /* @@ -484,7 +484,7 @@ ttxt_packet_in(int data_unit_id, int mag, int pack, uint8_t *data) //if(data[7] & 0x02) // Magazine Serial p.national_charset = - ((data[7] & 0x80) >> 5) + ((data[7] & 0x20) >> 4) + ((data[7] & 0x08) >> 3); + ((data[7] & 0x80) >> 7) + ((data[7] & 0x20) >> 4) + ((data[7] & 0x08) >> 1); valid = 1; @@ -3,7 +3,7 @@ * * See the README file for copyright information and how to reach the author. * - * $Id: ttxtsubs.c,v 1.20 2003/07/19 02:03:15 ragge Exp ragge $ + * $Id: ttxtsubs.c,v 1.22 2004/03/01 04:36:32 ragge Exp $ */ #include <vdr/plugin.h> @@ -12,39 +12,63 @@ #include <vdr/menuitems.h> #include <vdr/config.h> +#include "ttxtsubsglobals.h" #include "ttxtsubsdisplayer.h" #include "ttxtsubsrecorder.h" #include "utils.h" #include "siinfo.h" #include "ttxtsubs.h" -static const char *VERSION = "0.0.4b"; +static const char *VERSION = "0.0.5pre1"; static const char *DESCRIPTION = "Teletext subtitles"; -//static const char *MAINMENUENTRY = "Ttxtsubs"; + +cTtxtsubsConf globals; // ISO 639-2 language codes in VDR order -// XXX should be replaced by something that allows for other languages and for real language names! +// XXX should be replaced with something that allows for other languages and for real language names! +// <http://www.avio-systems.com/dtvcc/iso639-2.txt> // <http://www.loc.gov/standards/iso639-2/englangn_ascii.html> -char *gLanguages[] = -{ "eng", //English - "deu", //Deutsch - "slv", //Slovenian - "ita", //Italian - "dut", //"nld"? Dutch - "por", //Portuguese - "fre", //"fra"? French - "nor", //Norwegian - "fin", //Finnish - "pol", //Polish - "spa", //Spanish - "gre", //Greek - "swe", //"sve? Swedish - "rom", //Romanian - "hun", //Hungarian - "cat", //Catalanian +char *gLanguages[][2] = { + "","", //None + "eng","", //English + "deu","ger", //Deutsch + "slv","", //Slovenian + "ita","", //Italian + "dut","nld", //Dutch + "por","", //Portuguese + "fre","fra", //French + "nor","", //Norwegian + "fin","", //Finnish + "pol","", //Polish + "spa","esl", //Spanish + "gre","ell", //Greek + "swe","sve", //Swedish + "ron","rum", //Romanian + "hun","", //Hungarian + "cat","", //Catalanian // Not in translations! - "dan" //Danish - }; + "dan","" //Danish +}; +const char *gLanguageNames[] = { + "-", + "English", + "Deutsch", + "Slovenski", + "Italiano", + "Nederlands", + "Português", + "Français", + "Norsk", + "suomi", // this is not a typo - it's really lowercase! + "Polski", + "Español", + "Ellinika", + "Svenska", + "Romaneste", + "Magyar", + "Català", + "Dansk" +}; int gNumLanguages = sizeof(gLanguages) / sizeof(gLanguages[0]); class cPluginTtxtsubs : public cPlugin, public cStatus, public cVDRTtxtsubsHookListener { @@ -59,20 +83,11 @@ public: virtual bool ProcessArgs(int argc, char *argv[]); virtual bool Start(void); virtual void Housekeeping(void); - //virtual const char *MainMenuEntry(void) { return MAINMENUENTRY; } + virtual const char *MainMenuEntry(void); virtual cOsdObject *MainMenuAction(void); virtual cMenuSetupPage *SetupMenu(void); virtual bool SetupParse(const char *Name, const char *Value); - // -- Setup stuff - virtual char *Language(void) { return mLanguage; }; - virtual int HearingImpaired(void) { return mHearingImpaired; }; - virtual int Record(void) { return mRecord; }; - - virtual void SetLanguage(char *lang) { strncpy(mLanguage, lang, 4); mLanguage[3] = '\0'; }; - virtual void SetHearingImpaired(int hi) { mHearingImpaired = hi; }; - virtual void SetRecord(int record) { mRecord = record; }; - // -- cStatus protected: virtual void ChannelSwitch(const cDevice *Device, int ChannelNumber); @@ -103,15 +118,14 @@ public: void StopTtxt(void); void ShowTtxt(void); void HideTtxt(void); + void parseLanguages(const char *val); + void parseHIs(const char *val); private: - // Add any member variables or functions you may need here. - cTtxtSubsDisplayer *mRec; + cTtxtSubsDisplayer *mDispl; - // Setup items - char mLanguage[4]; - int mHearingImpaired; - int mRecord; + char mOldLanguage[4]; // language chosen from previous version + int mOldHearingImpaired; // HI setting chosen from previous version // ugly hack for now int mPage; @@ -119,30 +133,31 @@ private: class cMenuSetupTtxtsubs : public cMenuSetupPage { public: - cMenuSetupTtxtsubs(cPluginTtxtsubs *ttxtsubs); + cMenuSetupTtxtsubs(cPluginTtxtsubs *ttxtsubs, int doStore = 0); + ~cMenuSetupTtxtsubs(void); protected: virtual void Store(void); private: cPluginTtxtsubs *mTtxtsubs; - char mLanguage[4]; - int mLanguageNo; - int mHearingImpaired; - int mRecord; + int mLanguageNo[MAXLANGUAGES]; + int mLangHI[MAXLANGUAGES]; + int mSavedFrenchSpecial; + int mDoStore; + cTtxtsubsConf mConf; }; cPluginTtxtsubs::cPluginTtxtsubs(void) : - mRec(NULL), - mHearingImpaired(0), - mRecord(1), - mPage(0x199) + mDispl(NULL), + mOldHearingImpaired(0) { // Initialize any member variables here. // DON'T DO ANYTHING ELSE THAT MAY HAVE SIDE EFFECTS, REQUIRE GLOBAL // VDR OBJECTS TO EXIST OR PRODUCE ANY OUTPUT! - - strncpy(mLanguage, "unk", 4); + + memset(mOldLanguage, 0, 4); + strncpy(globals.mLanguages[0][0], "unk", 4); } cPluginTtxtsubs::~cPluginTtxtsubs() @@ -166,13 +181,37 @@ bool cPluginTtxtsubs::Start(void) { // Start any background activities the plugin shall perform. - if(!memcmp(mLanguage, "unk", 3)) { - int n = Setup.OSDLanguage; - if(n > gNumLanguages) { - strncpy(mLanguage, "eng", 4); + if(!memcmp(globals.mLanguages[0][0], "unk", 3)) { + // no language found in setup + if(strlen(mOldLanguage)) { + // use old setup lang, first try to find it amongst known languages + for(int i = 0; i < gNumLanguages; i++) { + if(!memcmp(mOldLanguage, gLanguages[i][0], 3) || + !memcmp(mOldLanguage, gLanguages[i][1], 3)) { + strncpy(globals.mLanguages[0][0], gLanguages[i][0], 4); + globals.mLanguages[0][0][3] = '\0'; + strncpy(globals.mLanguages[0][1], gLanguages[i][1], 4); + globals.mLanguages[0][1][3] = '\0'; + } + } + if(!memcmp(globals.mLanguages[0][0], "unk", 3)) { + // not found there, just copy it + memcpy(globals.mLanguages[0][0], mOldLanguage, 3); + globals.mLanguages[0][0][3] = '\0'; + } + globals.mHearingImpaireds[0][0] = mOldHearingImpaired; + globals.mHearingImpaireds[0][1] = mOldHearingImpaired; } else { - strncpy(mLanguage, gLanguages[n], 4); - mLanguage[3] = '\0'; + // get lang from OSD lang + int n = Setup.OSDLanguage + 1; + if(n > gNumLanguages) { + strncpy(globals.mLanguages[0][0], "eng", 4); + } else { + strncpy(globals.mLanguages[0][0], gLanguages[n][0], 4); + globals.mLanguages[0][0][3] = '\0'; + strncpy(globals.mLanguages[0][1], gLanguages[n][1], 4); + globals.mLanguages[0][1][3] = '\0'; + } } } @@ -188,9 +227,45 @@ void cPluginTtxtsubs::Housekeeping(void) // Perform any cleanup or other regular tasks. } +const char *cPluginTtxtsubs::MainMenuEntry(void) +{ + switch(globals.mMainMenuEntry) { + case 1: + if(globals.mDoDisplay) + return tr("Hide teletext subtitles"); + else + return tr("Display teletext subtitles"); + case 2: + if(globals.mBottomLB) + return tr("Position Teletext Subtitles for 4:3/Anamorph"); + else + return tr("Position Teletext Subtitles for Letterbox"); + case 3: + return tr("Teletext Subtitles"); + default: + return NULL; + } + + return NULL; +} + cOsdObject *cPluginTtxtsubs::MainMenuAction(void) { // Perform the action when selected from the main VDR menu. + + switch(globals.mMainMenuEntry) { + case 1: + globals.mDoDisplay = globals.mDoDisplay ? 0 : 1; + return NULL; + case 2: + globals.mBottomLB = globals.mBottomLB ? 0 : 1; + return NULL; + case 3: + return new cMenuSetupTtxtsubs(this, 1); + default: + return NULL; + } + return NULL; } @@ -201,9 +276,19 @@ cMenuSetupPage *cPluginTtxtsubs::SetupMenu(void) bool cPluginTtxtsubs::SetupParse(const char *Name, const char *Value) { - if(!strcasecmp(Name, "Language")) { strncpy(mLanguage, Value, 4); mLanguage[3] = '\0'; } - else if(!strcasecmp(Name, "HearingImpaired")) mHearingImpaired = atoi(Value); - else if(!strcasecmp(Name, "Record")) mRecord = atoi(Value); + if(!strcasecmp(Name, "Display")) globals.mDoDisplay = atoi(Value); + else if(!strcasecmp(Name, "Record")) globals.mDoRecord = atoi(Value); + else if(!strcasecmp(Name, "MainMenuEntry")) globals.mMainMenuEntry = atoi(Value); + else if(!strcasecmp(Name, "TextPos")) globals.mTextPos = atoi(Value); + else if(!strcasecmp(Name, "BottomLB")) globals.mBottomLB = atoi(Value); + else if(!strcasecmp(Name, "BottomAdj")) globals.mBottomAdj = atoi(Value); + else if(!strcasecmp(Name, "FrenchSpecial")) globals.mFrenchSpecial = atoi(Value); + else if(!strcasecmp(Name, "Languages")) parseLanguages(Value); + else if(!strcasecmp(Name, "HearingImpaireds")) parseHIs(Value); + // Handle old settings + else if(!strcasecmp(Name, "Language")) { + strncpy(mOldLanguage, Value, 4); mOldLanguage[3] = '\0'; } + else if(!strcasecmp(Name, "HearingImpaired")) mOldHearingImpaired = atoi(Value); else return false; return true; @@ -211,6 +296,9 @@ bool cPluginTtxtsubs::SetupParse(const char *Name, const char *Value) void cPluginTtxtsubs::ChannelSwitch(const cDevice *Device, int ChannelNumber) { + dprint("cPluginTtxtsubs::ChannelSwitch(devicenr: %d, channelnr: %d) - mDispl: %x\n", + Device->DeviceNumber(), ChannelNumber, mDispl); // XXX + if(Device->IsPrimaryDevice()) { if(ChannelNumber) { cChannel *c = Channels.GetByNumber(ChannelNumber); @@ -221,7 +309,7 @@ void cPluginTtxtsubs::ChannelSwitch(const cDevice *Device, int ChannelNumber) if(GetTtxtInfo(Device->ActualDevice()->CardIndex(), ChannelNumber, c->Sid(), c->Vpid(), &info)) { fprintf(stderr, "ttxtsubs: Error: GetTtxtInfo failed!\n"); } else { - if(FindSubs(&info, mLanguage, mHearingImpaired, &pid, &page)) { + if(FindSubs(&info, &pid, &page)) { //fprintf(stderr, "CHANNELSWITCH, pid: %d page: %x\n", pid, page); mPage = page; // XXX remember this for playback (temporary hack)! StartTtxtLive(Device, pid, page); @@ -236,6 +324,8 @@ void cPluginTtxtsubs::ChannelSwitch(const cDevice *Device, int ChannelNumber) void cPluginTtxtsubs::Replaying(const cControl *Control, const char *Name) { + dprint("cPluginTtxtsubs::Replaying\n"); // XXX + StopTtxt(); StartTtxtPlay(mPage); // XXX this page number is just a fallback for old recordings which @@ -244,7 +334,7 @@ void cPluginTtxtsubs::Replaying(const cControl *Control, const char *Name) void cPluginTtxtsubs::PlayerTeletextData(uint8_t *p, int length) { - cTtxtSubsPlayer *r = dynamic_cast<cTtxtSubsPlayer *>(mRec); + cTtxtSubsPlayer *r = dynamic_cast<cTtxtSubsPlayer *>(mDispl); if(!r) { fprintf(stderr, "ttxtsubs: ERROR: not a cTtxtSubsPlayer!\n"); @@ -256,8 +346,8 @@ void cPluginTtxtsubs::PlayerTeletextData(uint8_t *p, int length) cTtxtSubsRecorderBase *cPluginTtxtsubs::NewTtxtSubsRecorder(cDevice *dev, const cChannel *ch) { - if(mRecord) - return new cTtxtSubsRecorder(dev, ch, mLanguage, mHearingImpaired); + if(globals.mDoRecord) + return new cTtxtSubsRecorder(dev, ch); else return NULL; } @@ -267,18 +357,16 @@ cTtxtSubsRecorderBase *cPluginTtxtsubs::NewTtxtSubsRecorder(cDevice *dev, const void cPluginTtxtsubs::StartTtxtLive(const cDevice *Device, int pid, int page) { - //dprint("cPluginTtxtsubs::StartTtxtLive\n"); + dprint("cPluginTtxtsubs::StartTtxtLive(devicenr: %d, pid: %d, page: %03x)\n", + Device->DeviceNumber(), pid, page); // XXX -#if 0 - return; // XXX TEST - No live subs -#endif - - if(!mRec) { + if(!mDispl) { cTtxtSubsLiveReceiver *r; //dprint("teletext subtitles started on pid %d\n", pid); - mRec = r = new cTtxtSubsLiveReceiver(pid, page); + mDispl = r = new cTtxtSubsLiveReceiver(pid, page); if(!cDevice::PrimaryDevice()->ActualDevice()->AttachReceiver(r)) fprintf(stderr, "ttxtsubs: Error: AttachReceiver failed!\n"); // + ShowTtxt(); } else fprintf(stderr, "ttxtsubs: Error: StartTtxtLive called when already started!\n"); } @@ -287,9 +375,10 @@ void cPluginTtxtsubs::StartTtxtPlay(int backup_page) { dprint("cPluginTtxtsubs::StartTtxtPlay\n"); - if(!mRec) { + if(!mDispl) { dprint("ttxtsubs: teletext subtitles replayer started with initial page %03x\n", backup_page); - mRec = new cTtxtSubsPlayer(mLanguage, mHearingImpaired, backup_page); + mDispl = new cTtxtSubsPlayer(backup_page); + ShowTtxt(); } else fprintf(stderr, "ttxtsubs: Error: StartTtxtPlay called when already started!\n"); } @@ -298,68 +387,244 @@ void cPluginTtxtsubs::StopTtxt(void) { dprint("cPluginTtxtsubs::StopTtxt\n"); - if(mRec) { + if(mDispl) { + cTtxtSubsDisplayer *d = mDispl; HideTtxt(); - delete mRec; - mRec = NULL; + mDispl = NULL; + delete d; } } void cPluginTtxtsubs::ShowTtxt(void) { - if(mRec) - mRec->ShowDisplay(); + if(globals.mDoDisplay) + if(mDispl) + mDispl->ShowDisplay(); } void cPluginTtxtsubs::HideTtxt(void) { - if(mRec) - mRec->HideDisplay(); + if(mDispl) + mDispl->HideDisplay(); +} + +void cPluginTtxtsubs::parseLanguages(const char *val) { + size_t i; + const char *p = val; + const char *p2; + size_t len; + + for(i = 0; i < (MAXLANGUAGES*2); i++) { + if(*p == '\0') + break; // end of string + + p2 = strchr(p, ','); // find end of entry + if(p2) { + len = p2 - p; + } else { // no more , found + len = strlen(p); + } + + if(len) { + size_t trlen = len; + if(trlen > 3) + trlen = 3; + memcpy(globals.mLanguages[i/2][i%2], p, trlen); + globals.mLanguages[i/2][i%2][trlen] = '\0'; + } + + p += len + 1; + } +} + +void cPluginTtxtsubs::parseHIs(const char *val) +{ + size_t i; + + for(i = 0; i < (MAXLANGUAGES*2); i++) { + if(val[i] == '\0') + break; + globals.mHearingImpaireds[i/2][i%2] = val[i] != '0'; + } } // ----- cMenuSetupTtxtsubs ----- -cMenuSetupTtxtsubs::cMenuSetupTtxtsubs(cPluginTtxtsubs *ttxtsubs) +//#define TEST +class cMenuSetupTtxtsubsLanguages : public cMenuSetupPage { + public: + cMenuSetupTtxtsubsLanguages(cPluginTtxtsubs *ttxtsubs); + protected: + virtual void Store(void); + private: + cPluginTtxtsubs *mTtxtsubs; +}; + +cMenuSetupTtxtsubsLanguages::cMenuSetupTtxtsubsLanguages(cPluginTtxtsubs *ttxtsubs) + : + mTtxtsubs(ttxtsubs) +{} + +void cMenuSetupTtxtsubsLanguages::Store(void) +{ + fprintf(stderr, "cMenuSetupTtxtsubsLanguages::Store\n"); +} + +cMenuSetupTtxtsubs::cMenuSetupTtxtsubs(cPluginTtxtsubs *ttxtsubs, int doStore) : mTtxtsubs(ttxtsubs), - mLanguageNo(-1), - mHearingImpaired(mTtxtsubs->HearingImpaired()), - mRecord(mTtxtsubs->Record()) + mDoStore(doStore), + mConf(globals) { - memcpy(mLanguage, ttxtsubs->Language(), 3); - mLanguage[3] = '\0'; + //static char *mainMenuAlts[] = {"off", "Display on/off", "4:3/Letterbox", "This menu"}; + // can't get it to store changes in file + static char *mainMenuAlts[] = {"off", "Display on/off", "4:3/Letterbox"}; + static int numMainMenuAlts = sizeof(mainMenuAlts) / sizeof(mainMenuAlts[0]); + static char *textPosAlts[] = {"Left", "Center", "Right"}; + static int numTextPosAlts = sizeof(textPosAlts) / sizeof(textPosAlts[0]); + + mSavedFrenchSpecial = mConf.mFrenchSpecial; + + for(int n = 0; n < MAXLANGUAGES; n++) { + mLanguageNo[n] = -1; + mLangHI[n] = mConf.mHearingImpaireds[n][0]; + + for(int i = 0; i < gNumLanguages; i++) { + if(!strncmp(mConf.mLanguages[n][0], gLanguages[i][0], 4) && + !strncmp(mConf.mLanguages[n][1], gLanguages[i][1], 4)) { + mLanguageNo[n] = i; + break; + } + } + } - for(int i = 0; i < gNumLanguages; i++) { - if(!memcmp(mLanguage, gLanguages[i], 3)) { - mLanguageNo = i; - break; + Add(new cMenuEditBoolItem(tr("Display Subtitles"), &mConf.mDoDisplay, tr("no"), tr("yes"))); + Add(new cMenuEditBoolItem(tr("Record Subtitles"), &mConf.mDoRecord, tr("no"), tr("yes"))); + if(mConf.mMainMenuEntry < 0 || mConf.mMainMenuEntry >= numMainMenuAlts) + mConf.mMainMenuEntry = 0; // menu item segfaults if out of range + Add(new cMenuEditStraItem(tr("Main Menu Alternative"), &mConf.mMainMenuEntry, + numMainMenuAlts, mainMenuAlts)); + if(mConf.mTextPos < 0 || mConf.mTextPos >= numTextPosAlts) + mConf.mTextPos = 0; // menu item segfaults if out of range + Add(new cMenuEditStraItem(tr("Text Horizontal Position"), &mConf.mTextPos, + numTextPosAlts, textPosAlts)); + Add(new cMenuEditBoolItem(tr("Text Vertical Position"), + &mConf.mBottomLB, tr("4:3/Anamorph"), tr("Letterbox"))); + Add(new cMenuEditIntItem(tr("Text Vertical Adjust"), + &mConf.mBottomAdj, -100, 45)); + Add(new cMenuEditBoolItem(tr("Workaround for some French chns"), + &mConf.mFrenchSpecial, tr("no"), tr("yes"))); + + for(int n = 0; n < MAXLANGUAGES; n++) { + char str[100]; + char *allowedc = "abcdefghijklmnopqrstuvwxyz"; + + cOsdItem *item = new cOsdItem("----------------------------"); + item->SetColor(clrCyan); + Add(item); + + sprintf(str, "Language %d", n + 1); + if(mLanguageNo[n] >= 0) { + Add(new cMenuEditStraItem(tr(str), &mLanguageNo[n], gNumLanguages, gLanguageNames)); + } else { + Add(new cMenuEditStrItem(tr(str), mConf.mLanguages[n][0], 4, allowedc)); + Add(new cMenuEditStrItem(tr(str), mConf.mLanguages[n][1], 4, allowedc)); } + + sprintf(str, "Language %d Hearing Impaired", n + 1); + Add(new cMenuEditBoolItem(tr(str), &(mConf.mHearingImpaireds[n][0]), + tr("no"), tr("yes"))); } - if(mLanguageNo >= 0) - Add(new cMenuEditStraItem(tr("Language"), &mLanguageNo, gNumLanguages, gLanguages)); - else - Add(new cMenuEditStrItem(tr("Language"), mLanguage, 4, "abcdefghijklmnopqrstuvwxyz")); - - Add(new cMenuEditBoolItem(tr("Hearing Impaired"), &mHearingImpaired, tr("no"), tr("yes"))); - Add(new cMenuEditBoolItem(tr("Record Subtitles"), &mRecord, tr("no"), tr("yes"))); +#ifdef TEST + //AddSubMenu(new cMenuSetupTtxtsubsLanguages(mTtxtsubs)); +#endif +} + +cMenuSetupTtxtsubs::~cMenuSetupTtxtsubs(void) +{ + if(mSavedFrenchSpecial != mConf.mFrenchSpecial) + ClearSICache(); + + if(mDoStore) { + Store(); + // Setup.Save(); // Can't get it to write to conf file, menu item disabled. + } } void cMenuSetupTtxtsubs::Store(void) { - if(mLanguageNo >= 0) { - SetupStore("Language", gLanguages[mLanguageNo]); - mTtxtsubs->SetLanguage(gLanguages[mLanguageNo]); - } else { - SetupStore("Language", mLanguage); - mTtxtsubs->SetLanguage(mLanguage); + for(int n=0; n < MAXLANGUAGES; n++) { + if(mLanguageNo[n] >= 0) { + strncpy(mConf.mLanguages[n][0], gLanguages[mLanguageNo[n]][0], 4); + mConf.mLanguages[n][0][3] = '\0'; + strncpy(mConf.mLanguages[n][1], gLanguages[mLanguageNo[n]][1], 4); + mConf.mLanguages[n][1][3] = '\0'; + } + + mConf.mHearingImpaireds[n][1] = mConf.mHearingImpaireds[n][0]; } + + SetupStore("Display", mConf.mDoDisplay); + SetupStore("Record", mConf.mDoRecord); + SetupStore("TextPos", mConf.mTextPos); + SetupStore("BottomLB", mConf.mBottomLB); + SetupStore("BottomAdj", mConf.mBottomAdj); + SetupStore("FrenchSpecial", mConf.mFrenchSpecial); + SetupStore("MainMenuEntry", mConf.mMainMenuEntry); + + char lstr[MAXLANGUAGES*2*4 + 1]; + char histr[MAXLANGUAGES*2 + 1]; + lstr[0] = '\0'; + histr[0] = '\0'; + for(int n=0; n < MAXLANGUAGES; n++) { + strncat(lstr, mConf.mLanguages[n][0], 3); + strcat(lstr, ","); + strncat(lstr, mConf.mLanguages[n][1], 3); + if(n != (MAXLANGUAGES - 1)) + strcat(lstr, ","); + + strcat(histr, mConf.mHearingImpaireds[n][0] ? "1" : "0"); + strcat(histr, mConf.mHearingImpaireds[n][1] ? "1" : "0"); + } + SetupStore("Languages", lstr); + SetupStore("HearingImpaireds", histr); + + globals = mConf; +} + + +// returns the index to choise number for the given language, +// lower is more preferred. The result is times two, plus one +// if the HI wanted but not found, or -1 if no match. +// Non HI wanted but found is no match. + +int cTtxtsubsConf::langChoise(const char *lang, const int HI) +{ + size_t i, j; + int result = -1; - SetupStore("HearingImpaired", mHearingImpaired); - mTtxtsubs->SetHearingImpaired(mHearingImpaired); + for(i = 0; i < MAXLANGUAGES; i++) { + for(j = 0; j < 2; j++) { + if(!mLanguages[i][j][0]) + continue; + + if(!memcmp(lang, mLanguages[i][j], 3)) { + if( ( HI && mHearingImpaireds[i][j] ) || + ( !HI && !mHearingImpaireds[i][j] ) ) { + result = i*2; + goto x; + } + if( !HI && mHearingImpaireds[i][j] ) { + result = 1 + i*2; + goto x; + } + } + } + } - SetupStore("Record", mRecord); - mTtxtsubs->SetRecord(mRecord); + x: + return result; } diff --git a/ttxtsubsdisplay.c b/ttxtsubsdisplay.c index a108919..cfb7e88 100644 --- a/ttxtsubsdisplay.c +++ b/ttxtsubsdisplay.c @@ -10,6 +10,7 @@ #include <vdr/osdbase.h> #include <vdr/thread.h> +#include "ttxtsubsglobals.h" #include "ttxtsubsdisplay.h" #include "utils.h" @@ -213,7 +214,7 @@ void cTtxtSubsDisplay::TtxtData(const uint8_t *Data) // if(fi[7] & 0x02) // Magazine Serial page.national_charset = ((fi[7] & 0x80) >> 5) + - ((fi[7] & 0x20) >> 4) + ((fi[7] & 0x08) >> 3); + ((fi[7] & 0x20) >> 4) + ((fi[7] & 0x08) >> 1); mPageState = collecting; gettimeofday(mLastDataTime, NULL); @@ -287,8 +288,23 @@ ttxt2la1(uint8_t *p, char *buf, int natopts) return NULL; } +#if 0 + +#define TEST_CENTER 0 +#if TEST_CENTER +#define SCREENLEFT 0 +#define SCREENWIDTH 720 +#else #define SCREENLEFT 125 +#endif + +#define TEST_169 0 +#if TEST_169 +#define SCREENBOT 480 +#else #define SCREENBOT 540 +#endif + #if 0 #define ROWINCR 45 #define ROWH 36 @@ -299,7 +315,26 @@ ttxt2la1(uint8_t *p, char *buf, int natopts) #define TEXTY 3 #endif #define TEXTX 15 -#define SCREENTOP (SCREENBOT - MAXTTXTROWS * ROWINCR) +//#define SCREENTOP (SCREENBOT - MAXTTXTROWS * ROWINCR) +#define SCREENTOP 100 + +#endif + +enum { + SCREENLEFT = 0, + SCREENRIGHT = 719, + SCREENTOP = 150, + SCREENBOTTOM = 575, + + SIDEMARGIN = 125, + BOTNORM = 540, + BOTLETTERBOX = 482, + + ROWINCR = 43, + ROWH = 34, + TEXTY = 3, + TEXTX = 15 +}; void cTtxtSubsDisplay::ShowOSD(void) { @@ -307,6 +342,7 @@ void cTtxtSubsDisplay::ShowOSD(void) int rowcount = 0; char buf[TTXT_DISPLAYABLE_ROWS][41]; int doneWidthWorkaround = 0; + int bottom = globals.bottomAdj() + (globals.bottomLB() ? BOTLETTERBOX : BOTNORM); cOSDSelfMemoryLock selfmem(&gSelfMem); cMutexLock lock(&mOsdLock); @@ -332,11 +368,8 @@ void cTtxtSubsDisplay::ShowOSD(void) rowcount++; } -#if 1 mOsd = cOsd::OpenRaw(SCREENLEFT, SCREENTOP); -#else - mOsd = cDevice::PrimaryDevice()->NewOsd(SCREENLEFT, SCREENTOP); -#endif + if(mOsd == NULL) { //dprint("Error: cOsd::OpenRaw returned NULL!\n"); return; @@ -347,29 +380,48 @@ void cTtxtSubsDisplay::ShowOSD(void) if(rowcount > MAXTTXTROWS) rowcount = MAXTTXTROWS; - y = SCREENBOT - SCREENTOP - ROWH - (ROWINCR * (rowcount-1)); +#if 0 // XXXX + rowcount = 4; + strcpy(buf[0], "1234567890123456789012345678901234567890"); + strcpy(buf[1], "1234567890123456789012345678901234567890"); + strcpy(buf[2], "1234567890123456789012345678901234567890"); + strcpy(buf[3], "1234567890123456789012345678901234567890"); +#endif + + y = bottom - SCREENTOP - ROWH - (ROWINCR * (rowcount-1)); for(i = 0; i < rowcount; i++) { tWindowHandle wind; - int w = 0; // XXX should be text width - not %4! + int w = 0; + int left = SIDEMARGIN; // XXX Width calculations doesn't work before we have created a window... if(!doneWidthWorkaround) { - wind = mOsd->Create(0, y, 4, ROWH, 2); - mOsd->Fill(0, y, 4, y + ROWH, clrWhite, wind); - mOsd->Fill(0, y, 4, y + ROWH, clrBackground, wind); + //wind = mOsd->Create(0, y, 4, ROWH, 2); + //mOsd->Fill(0, y, 4, y + ROWH, clrWhite, wind); + //mOsd->Fill(0, y, 4, y + ROWH, clrBackground, wind); + wind = mOsd->Create(0, 575, 4, 1, 2); + mOsd->Fill(0, 574, 4, 575, clrWhite, wind); + mOsd->Fill(0, 574, 4, 575, clrTransparent, wind); doneWidthWorkaround = 1; } - - w = mOsd->Width(buf[i]) + 2 * TEXTX; + w = mOsd->Width(buf[i]) + 2 * TEXTX; if(w % 4) w += 4 - (w % 4); - wind = mOsd->Create(0, y, w, ROWH, 2); - //dprint("W: %d\n", w); - mOsd->Fill(0, y, w, y + ROWH, clrWhite, wind); // needed for dxr3s... - mOsd->Fill(0, y, w, y + ROWH, clrBackground, wind); - mOsd->Text(TEXTX, y + TEXTY, buf[i], clrWhite, clrBackground, wind); + switch(globals.textPos()) { + case 1: + left = (SCREENRIGHT - w) / 2; + break; + case 2: + left = SCREENRIGHT - SIDEMARGIN - w; + break; + } + + wind = mOsd->Create(left, y, w, ROWH, 2); + mOsd->Fill(left, y, left + w, y + ROWH, clrWhite, wind); // needed for dxr3s... + mOsd->Fill(left, y, left + w, y + ROWH, clrBackground, wind); + mOsd->Text(left + TEXTX, y + TEXTY, buf[i], clrWhite, clrBackground, wind); y += ROWINCR; } @@ -392,8 +444,11 @@ void cTtxtSubsDisplay::ClearOSD(void) if(mOsd) { //mOsd->Clear(ALL_WINDOWS); +#if 0 + // not needed, windows are removed in mOsd destructor mOsd->Hide(ALL_WINDOWS); mOsd->Flush(); +#endif delete mOsd; mOsd = NULL; } diff --git a/ttxtsubsdisplayer.c b/ttxtsubsdisplayer.c index 5f2f0a6..fe69aa7 100644 --- a/ttxtsubsdisplayer.c +++ b/ttxtsubsdisplayer.c @@ -4,6 +4,7 @@ #include "utils.h" #include "ttxtsubs.h" #include "siinfo.h" +#include "ttxtsubsglobals.h" // ----- class cTtxtSubsDisplayer ----- @@ -17,7 +18,6 @@ cTtxtSubsDisplayer::cTtxtSubsDisplayer(int textpage) { mDisp = new cTtxtSubsDisplay(); mDisp->SetPage(textpage); - ShowDisplay(); mRun = 1; this->Start(); // start thread @@ -108,16 +108,14 @@ void cTtxtSubsLiveReceiver::Receive(uchar *Data, int Length) // ----- class cTtxtSubsPlayer ----- -cTtxtSubsPlayer::cTtxtSubsPlayer(char *lang, int HI, int backup_textpage) +cTtxtSubsPlayer::cTtxtSubsPlayer(int backup_textpage) : cTtxtSubsDisplayer(backup_textpage), - mHearingImpaired(HI), mHasFilteredStream(0), mFoundLangPage(0), + mLangChoise(1000), mLangInfoState(0) { - memcpy(mLanguage, lang, 3); - mLanguage[3] = '\0'; } // Take PES packets and break out the teletext data @@ -169,8 +167,6 @@ void cTtxtSubsPlayer::SearchLanguagePage(uint8_t *p, int len) { char *infoline = "Subtitles Index Page"; int foundlines = 0; - unsigned int foundNonHIPage = 0; - int foundNonHI = 0; // 1 = found non hearing impaired if(len < (3*46)) return; @@ -201,25 +197,26 @@ void cTtxtSubsPlayer::SearchLanguagePage(uint8_t *p, int len) break; case 2: mLangInfoState++; - if(mLangInfoState == 3) - fprintf(stderr, "ttxtsubs: Language \"%c%c%c\" not found in recording, available languages:\n", - mLanguage[0], mLanguage[1], mLanguage[2]); - if(packet < 2) // need a Y2 or more return; + if(mLangInfoState == 3) + fprintf(stderr, "ttxtsubs: Chosen Language not found in recording, available languages:\n"); copy_inv_strip_par(buf, d->data, sizeof(buf)); for(size_t i = 0; i < 40; i += 8) { if(mLangInfoState == 3 && buf[i] >= 'a' && buf[i] <= 'z') - fprintf(stderr, " %c%c%c: %c%c%c %s\n", buf[i+4], buf[i+5], buf[i+6], buf[i], buf[i+1], buf[i+2], - buf[i+3] == ' ' ? "" : buf[i+3] == 'h' ? "(Hearing Impaired)" : "(Unknown type)"); - if(buf[i] == mLanguage[0] && - buf[i+1] == mLanguage[1] && - buf[i+2] == mLanguage[2] && + fprintf(stderr, " %c%c%c: %c%c%c %s\n", buf[i+4], buf[i+5], buf[i+6], + buf[i], buf[i+1], buf[i+2], buf[i+3] == ' ' ? "" : + buf[i+3] == 'h' ? "(Hearing Impaired)" : "(Unknown type)"); + + if(buf[i] >= 'a' && buf[i] <= 'z' && + buf[i+1] >= 'a' && buf[i+1] <= 'z' && + buf[i+2] >= 'a' && buf[i+2] <= 'z' && ((buf[i+3] == ' ') || (buf[i+3] == 'h')) && buf[i+4] >= '1' && buf[i+4] <= '8' && buf[i+5] >= '0' && buf[i+5] <= '9' && buf[i+6] >= '0' && buf[i+6] <= '9' && buf[i+7] == ' ') { + int ch = globals.langChoise((char *)buf+i, buf[i+3] == 'h'); unsigned int page = ((buf[i+4] - '0') << 8) + ((buf[i+5] - '0') << 4) + @@ -228,17 +225,11 @@ void cTtxtSubsPlayer::SearchLanguagePage(uint8_t *p, int len) if(page >= 0x800) page -= 0x800; - if(((!mHearingImpaired) && (buf[3+i] == ' ')) || - (mHearingImpaired && (buf[3+i] == 'h'))) { + if(ch >= 0 && ch < mLangChoise) { + mLangChoise = ch; mDisp->SetPage(page); mFoundLangPage = 1; - fprintf(stderr, "FOUND subtitle page: %03x\n", page); // XXX - return; - } - if(mHearingImpaired && (buf[3+i] == ' ')) { - foundNonHIPage = page; - foundNonHI = 1; - fprintf(stderr, "FOUND non hi subtitle page, remembering: %03x\n", page); // XXX + fprintf(stderr, "Found subtitle page: %03x\n", page); // XXX } } } @@ -246,11 +237,4 @@ void cTtxtSubsPlayer::SearchLanguagePage(uint8_t *p, int len) break; } } - - if(foundNonHI) { - mDisp->SetPage(foundNonHIPage); - mFoundLangPage = 1; - fprintf(stderr, "Didn't find HI page, but found right language: %03x\n", foundNonHIPage); // XXX - return; - } } diff --git a/ttxtsubsdisplayer.h b/ttxtsubsdisplayer.h index 00127ff..cd63595 100644 --- a/ttxtsubsdisplayer.h +++ b/ttxtsubsdisplayer.h @@ -33,15 +33,14 @@ class cTtxtSubsLiveReceiver : public cReceiver, public cTtxtSubsDisplayer { class cTtxtSubsPlayer : public cTtxtSubsDisplayer { public: - cTtxtSubsPlayer(char *lang, int HI, int backup_textpage); + cTtxtSubsPlayer(int backup_textpage); virtual void PES_data(uchar *Data, int Length); private: void SearchLanguagePage(uint8_t *p, int len); - char mLanguage[4]; - int mHearingImpaired; int mHasFilteredStream; int mFoundLangPage; + int mLangChoise; int mLangInfoState; }; diff --git a/ttxtsubsfilter.c b/ttxtsubsfilter.c index 9271215..ae13cc0 100644 --- a/ttxtsubsfilter.c +++ b/ttxtsubsfilter.c @@ -126,6 +126,11 @@ void cTtxtSubsFilter::MakeY0(char *outdata, char *indata, uint16_t newpageno) struct ttxt_data_field *o = (struct ttxt_data_field *) outdata; uint8_t hambuf[2]; + o->data_unit_id = 3; // EBU Teletxt subtitle data + o->data_unit_length = 44; + o->par_loff = 0xc0; // reserved bits + unknown line + o->framing_code = 0xe4; + // new magazine number (Y = 0) ham8_4byte((newpageno >> 8) & 0x7, hambuf); o->mag_addr_ham[0] = invtab[hambuf[0]]; diff --git a/ttxtsubsglobals.h b/ttxtsubsglobals.h new file mode 100644 index 0000000..09bae9d --- /dev/null +++ b/ttxtsubsglobals.h @@ -0,0 +1,50 @@ + +class cPluginTtxtsubs; +class cMenuSetupTtxtsubs; + +#define MAXLANGUAGES 5 + +class cTtxtsubsConf { + friend class cPluginTtxtsubs; + friend class cMenuSetupTtxtsubs; + + public: + cTtxtsubsConf(void) + { + mDoDisplay = 1; + mDoRecord = 1; + mMainMenuEntry = 0; + mTextPos = 0; + mBottomLB = 0; + mBottomAdj = 0; + mFrenchSpecial = 0; + memset(mLanguages, 0, sizeof(mLanguages)); + memset(mHearingImpaireds, 0, sizeof(mHearingImpaireds)); + } + + public: + int doDisplay(void) {return mDoDisplay;} + int doRecord(void) {return mDoRecord;} + int mainMenuEntry(void) {return mMainMenuEntry;} + int textPos(void) {return mTextPos;} + int bottomLB(void) {return mBottomLB;} + int bottomAdj(void) {return mBottomAdj;} + int frenchSpecial(void) {return mFrenchSpecial;} + char (*languages(void))[MAXLANGUAGES][2][4] {return &mLanguages;} + int (*hearingImpaireds(void))[MAXLANGUAGES][2] {return &mHearingImpaireds;} + + int langChoise(const char *lang, const int HI); + + protected: + int mDoDisplay; + int mDoRecord; + int mMainMenuEntry; + int mTextPos; + int mBottomLB; + int mBottomAdj; + int mFrenchSpecial; + char mLanguages[MAXLANGUAGES][2][4]; + int mHearingImpaireds[MAXLANGUAGES][2]; +}; + +extern cTtxtsubsConf globals; diff --git a/ttxtsubsreceiver.c b/ttxtsubsreceiver.c index b8473e6..b254c88 100644 --- a/ttxtsubsreceiver.c +++ b/ttxtsubsreceiver.c @@ -10,6 +10,11 @@ #define MAXINDEXPAGELINES 24 +struct ringBufItem { + encodedPTS pts; + uint8_t data[46]; +}; + // ----- cTtxtSubsReceiver ----- cTtxtSubsReceiver::cTtxtSubsReceiver(int Ca, struct ttxtpidinfo *PI) @@ -17,7 +22,7 @@ cTtxtSubsReceiver::cTtxtSubsReceiver(int Ca, struct ttxtpidinfo *PI) cReceiver(Ca, -1, 1, PI->pid), mGetMutex(), mGetCond(), - mRingBuf(46 * 500, true), + mRingBuf(sizeof(ringBufItem) * 500, true), mPI(*PI), mIndexPageLines(0), mIndexPageCol(0), @@ -26,6 +31,8 @@ cTtxtSubsReceiver::cTtxtSubsReceiver(int Ca, struct ttxtpidinfo *PI) int count = 0; uint16_t *pages = (uint16_t *) malloc(sizeof(uint16_t) * mPI.pagecount); + mPTS.valid = 0; + // find a free page to put the index page on mIndexPageNo = 0x100; int again; @@ -70,13 +77,18 @@ cTtxtSubsReceiver::~cTtxtSubsReceiver() // returns pointer buf if there is new data -uint8_t *cTtxtSubsReceiver::Get(uint8_t *buf) +uint8_t *cTtxtSubsReceiver::Get(uint8_t *buf, encodedPTS *pts) { cFrame *f; f = mRingBuf.Get(); if(f) { - memcpy(buf, f->Data(), 46); + ringBufItem *i = (ringBufItem*) f->Data(); + if(pts) { + *pts = i->pts; + } + + memcpy(buf, i->data, 46); mRingBuf.Drop(f); // fprintf(stderr, "cTtxtSubsReceiver::Get: returned data!\n"); return buf; @@ -117,7 +129,7 @@ void cTtxtSubsReceiver::Activate(bool On) // XXX We should do some filtering here to avoid unneccessary load! void cTtxtSubsReceiver::Receive(uchar *Data, int Length) { - int i; + int i = 0; if(Length != 188) // should never happen return; @@ -125,19 +137,37 @@ void cTtxtSubsReceiver::Receive(uchar *Data, int Length) if(Data[1] & 0x80) // transport_error_indicator return; + if((Data[3] & 0x30) != 0x10) // only accept TS packets with data and no adaption field + return; // (ETSI EN 300 472 $4.1) + + if(Data[1] & 0x40) { // payload_unit_start_indicator + if(((Data[11] >> 6) & 0x02) == 0x02) { // PTS_DTS_flags + mPTS.data[0] = Data[13]; + mPTS.data[1] = Data[14]; + mPTS.data[2] = Data[15]; + mPTS.data[3] = Data[16]; + mPTS.data[4] = Data[17]; + mPTS.valid = 1; + } else { + mPTS.valid = 0; + } + } + + ringBufItem it; + it.pts = mPTS; + // payload_unit_start_indicator for(i = (Data[1] & 0x40) ? 1 : 0; i < 4; i++) { - char buf[46]; if(0xff == Data[4 + i*46]) // stuffing data continue; - if(mFilter.Filter((char *) Data + 4 + i*46, buf)) { + if(mFilter.Filter((char *) Data + 4 + i*46, (char *) it.data)) { // fprintf(stderr, "Forward Packet:\n"); // print_line((char *) Data + 4 + i*46); // print_line(buf); - cFrame *f = new cFrame((uchar *) buf, 46); + cFrame *f = new cFrame((uchar *) &it, sizeof(ringBufItem)); mRingBuf.Put(f); mGetCond.Broadcast(); } diff --git a/ttxtsubsreceiver.h b/ttxtsubsreceiver.h index a88aa65..3e11695 100644 --- a/ttxtsubsreceiver.h +++ b/ttxtsubsreceiver.h @@ -1,6 +1,11 @@ class cRingBufferFrame; +struct encodedPTS { + uint8_t valid; + uint8_t data[5]; +}; + class cTtxtSubsReceiver : public cReceiver { public: @@ -8,7 +13,7 @@ class cTtxtSubsReceiver : public cReceiver virtual ~cTtxtSubsReceiver(); // returns pointer buf if there is new data - uint8_t *Get(uint8_t *buf); + uint8_t *Get(uint8_t *buf, encodedPTS *pts = NULL); // wait for more data void Wait(void); @@ -31,4 +36,5 @@ class cTtxtSubsReceiver : public cReceiver uint8_t mIndexPageLines; uint8_t mIndexPageCol; uint16_t mIndexPageNo; + encodedPTS mPTS; }; diff --git a/ttxtsubsrecorder.c b/ttxtsubsrecorder.c index 7cfb5c0..e31ec67 100644 --- a/ttxtsubsrecorder.c +++ b/ttxtsubsrecorder.c @@ -19,7 +19,7 @@ // ----- cTtxtSubsRecorder ----- -cTtxtSubsRecorder::cTtxtSubsRecorder(cDevice *dev, const cChannel *ch, char *lang, int HI) +cTtxtSubsRecorder::cTtxtSubsRecorder(cDevice *dev, const cChannel *ch) : mDev(dev), mSid(ch->Sid()), @@ -35,7 +35,7 @@ cTtxtSubsRecorder::cTtxtSubsRecorder(cDevice *dev, const cChannel *ch, char *lan if(GetTtxtInfo(dev->CardIndex(), ch->Number(), ch->Sid(), ch->Vpid(), mTtxtinfo)) { fprintf(stderr, "cTtxtSubsRecorder::cTtxtSubsRecorder: GetTtxtSubtitleInfo error!\n"); } else { - pi = FindSubs(mTtxtinfo, lang, HI, &pid, &page); + pi = FindSubs(mTtxtinfo, &pid, &page); if(!pi && mTtxtinfo->pidcount > 0) { pi = &(mTtxtinfo->p[0]); @@ -73,6 +73,7 @@ uint8_t *cTtxtSubsRecorder::GetPacket(uint8_t **outbuf, size_t *lenp) size_t len; uint8_t *b = (uint8_t *) mPacketBuffer; uint8_t line[46]; + encodedPTS pts; *outbuf = NULL; @@ -81,7 +82,7 @@ uint8_t *cTtxtSubsRecorder::GetPacket(uint8_t **outbuf, size_t *lenp) len = 46; // skip PES header area - if(mReceiver->Get(line)) { + if(mReceiver->Get(line, &pts)) { // if first line is a Y0, insert an index page // XXX This isn't really correct, we don't know if there is parallel magazine // transmission and a page in the index pages' mag is in progress... @@ -133,11 +134,23 @@ uint8_t *cTtxtSubsRecorder::GetPacket(uint8_t **outbuf, size_t *lenp) 0x00, 0x00, // len 0x80, // 10 and flags 0x00, // PTS_DTS_flags and other flags - 0x24}; // PES_header_data_length + 0x24, // PES_header_data_length + 0xff, 0xff, 0xff, 0xff, 0xff}; // PTS memcpy((char *) b, (char *) header, sizeof(header)); b[4] = (len-6) >> 8; b[5] = (len-6) & 0xff; +#define TEST_RECORD_PTS 1 +#if TEST_RECORD_PTS + if(pts.valid) { + b[7] |= 0x80; + b[9] = pts.data[0]; + b[10] = pts.data[1]; + b[11] = pts.data[2]; + b[12] = pts.data[3]; + b[13] = pts.data[4]; + } +#endif memset((char *) b + sizeof(header), 0xff, 45 - sizeof(header)); // add stuffing bytes b[45] = 0x1f; // EBU data, our payload type to indicate we have filtered data diff --git a/ttxtsubsrecorder.h b/ttxtsubsrecorder.h index bb9182a..d3ef7ac 100644 --- a/ttxtsubsrecorder.h +++ b/ttxtsubsrecorder.h @@ -6,7 +6,7 @@ struct ttxtinfo; class cTtxtSubsRecorder : public cTtxtSubsRecorderBase { public: - cTtxtSubsRecorder(cDevice *dev, const cChannel *ch, char *lang, int HI); + cTtxtSubsRecorder(cDevice *dev, const cChannel *ch); virtual ~cTtxtSubsRecorder(); // returns a PES packet if there is data to add to the recording |