summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTobias Grimm <tobias@e-tobi.loc>2008-12-13 10:35:43 +0100
committerTobias Grimm <tobias@e-tobi.loc>2008-12-13 10:35:43 +0100
commit76ac85e366bfc27b3b688a4f13031c0735ea2436 (patch)
tree0f4f27bb366be92faee4e8c05fe01cccb32a9794
downloadvdr-plugin-ttxtsubs-0.0.1.tar.gz
vdr-plugin-ttxtsubs-0.0.1.tar.bz2
Initial version 0.0.1v0.0.1
-rw-r--r--COPYING340
-rw-r--r--HISTORY6
-rw-r--r--Makefile107
-rw-r--r--README90
-rw-r--r--VDR.patch250
-rw-r--r--siinfo.c583
-rw-r--r--siinfo.h36
-rw-r--r--teletext-chars.h80
-rw-r--r--teletext-tables.h81
-rw-r--r--teletext.c724
-rw-r--r--teletext.h101
-rw-r--r--ttxtsubs.c357
-rw-r--r--ttxtsubs.h1
-rw-r--r--ttxtsubsdisplay.c331
-rw-r--r--ttxtsubsdisplay.h35
-rw-r--r--ttxtsubsdisplayer.c256
-rw-r--r--ttxtsubsdisplayer.h47
-rw-r--r--ttxtsubsfilter.c158
-rw-r--r--ttxtsubsfilter.h27
-rw-r--r--ttxtsubsreceiver.c253
-rw-r--r--ttxtsubsreceiver.h34
-rw-r--r--ttxtsubsrecorder.c167
-rw-r--r--ttxtsubsrecorder.h23
-rw-r--r--utils.h2
-rw-r--r--vdrttxtsubshooks.c44
-rw-r--r--vdrttxtsubshooks.h36
26 files changed, 4169 insertions, 0 deletions
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..5b6e7c6
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/HISTORY b/HISTORY
new file mode 100644
index 0000000..a38ae75
--- /dev/null
+++ b/HISTORY
@@ -0,0 +1,6 @@
+VDR Plugin 'ttxtsubs' Revision History
+--------------------------------------
+
+2003-03-07: Version 0.0.1
+
+- Initial revision.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..9f9c5fa
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,107 @@
+#
+# Makefile for a Video Disk Recorder plugin
+#
+# $Id: Makefile,v 1.16 2003/03/07 03:01:17 ragge Exp $
+
+# The official name of this plugin.
+# This name will be used in the '-P...' option of VDR to load the plugin.
+# By default the main source file also carries this name.
+#
+PLUGIN = ttxtsubs
+
+### The version number of this plugin (taken from the main source file):
+
+VERSION = $(shell grep 'static const char \*VERSION *=' $(PLUGIN).c | awk '{ print $$6 }' | sed -e 's/[";]//g')
+
+### The C++ compiler and options:
+
+CXX ?= g++
+CXXFLAGS ?= -O2 -Wall -Woverloaded-virtual
+
+### The directory environment:
+
+DVBDIR = ../../../../DVB
+VDRDIR = ../../..
+LIBDIR = ../../lib
+TMPDIR = /tmp
+
+### Allow user defined options to overwrite defaults:
+
+-include $(VDRDIR)/Make.config
+
+### The version number of VDR (taken from VDR's "config.h"):
+
+VDRVERSION = $(shell grep 'define VDRVERSION ' $(VDRDIR)/config.h | awk '{ print $$3 }' | sed -e 's/"//g')
+
+### The name of the distribution archive:
+
+ARCHIVE = $(PLUGIN)-$(VERSION)
+PACKAGE = vdr-$(ARCHIVE)
+BUPPACKAGE = vdr-$(ARCHIVE)-bup-$(shell date +%y%m%d-%H%M%S)
+
+### Includes and Defines (add further entries here):
+
+INCLUDES += -I$(VDRDIR)/include -I$(DVBDIR)/include
+
+DEFINES += -DPLUGIN_NAME_I18N='"$(PLUGIN)"'
+
+### The object files (add further files here):
+
+OBJS = $(PLUGIN).o ttxtsubsdisplayer.o ttxtsubsdisplay.o teletext.o siinfo.o \
+ ttxtsubsfilter.o ttxtsubsrecorder.o ttxtsubsreceiver.o
+
+SOURCEFILES = *.c *.h [A-Z]???*
+
+### Implicit rules:
+
+%.o: %.c
+ $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) $<
+
+# Dependencies:
+
+MAKEDEP = g++ -MM -MG
+DEPFILE = .dependencies
+$(DEPFILE): Makefile
+ @$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.c) > $@
+
+-include $(DEPFILE)
+
+### Targets:
+
+all: libvdr-$(PLUGIN).so
+
+libvdr-$(PLUGIN).so: $(OBJS)
+ $(CXX) $(CXXFLAGS) -shared $(OBJS) -o $@
+ @cp $@ $(LIBDIR)/$@.$(VDRVERSION)
+
+dist: clean
+ @-rm -rf $(TMPDIR)/$(ARCHIVE)
+ @mkdir $(TMPDIR)/$(ARCHIVE)
+ @cp -a $(SOURCEFILES) $(TMPDIR)/$(ARCHIVE)
+ @tar czf $(PACKAGE).tgz -C $(TMPDIR) $(ARCHIVE)
+ @-rm -rf $(TMPDIR)/$(ARCHIVE)
+ @echo Distribution package created as $(PACKAGE).tgz
+
+bup: clean patch
+ @-rm -rf $(TMPDIR)/$(ARCHIVE)
+ @mkdir $(TMPDIR)/$(ARCHIVE)
+ @cp -a $(SOURCEFILES) RCS $(TMPDIR)/$(ARCHIVE)
+ @tar czf $(BUPPACKAGE).tgz -C $(TMPDIR) $(ARCHIVE)
+ @-rm -rf $(TMPDIR)/$(ARCHIVE)
+ @echo Distribution package created as $(BUPPACKAGE).tgz
+ @echo making backup...
+ @sh RCS/backup.sh $(BUPPACKAGE).tgz
+ @echo done.
+
+clean:
+ @-rm -f $(OBJS) $(DEPFILE) *.so *.tgz core* *~
+
+ci: patch
+ ci -u $(SOURCEFILES)
+
+patch:
+ co -l xVDR.patch.temp vdrttxtsubshooks.c vdrttxtsubshooks.h
+ (cd ../../../; diff -upr ./DIST/* .) | grep -v "Only in ." > xVDR.patch.temp
+ cp -p ../../../vdrttxtsubshooks.c .
+ cp -p ../../../vdrttxtsubshooks.h .
+ ci -u xVDR.patch.temp vdrttxtsubshooks.c vdrttxtsubshooks.h
diff --git a/README b/README
new file mode 100644
index 0000000..48c3000
--- /dev/null
+++ b/README
@@ -0,0 +1,90 @@
+This is a "plugin" for the Video Disk Recorder (VDR).
+
+Written by: Ragnar Sundblad <ragge@nada.kth.se>
+
+Project's homepage: -
+
+Latest version available at: ftp://ftp.nada.kth.se/pub/home/ragge/vdr/
+
+See the file COPYING for license information.
+
+
+Description:
+
+This plugin implements displaying, recording and replaying teletext
+based subtitles using the on screen display.
+
+
+Long Description:
+
+The teletext decoder/displayer is currently very basic and limited. It
+can't show colours and it doesn't take into account the editors
+attempts to place the text on different places on the screen.
+
+The DVB drivers from the Metzlers as of late February 2003 and
+Convergence/linuxtv.org as of March 5 2003 have firmware fixes against
+OSD problems. (Thanks a lot guys!) An upgrade to either of those is
+strongly recommended. If the plugin is used with earlier firmware,
+data may get lost when the OSD is updated possibly resulting in bad
+video and audio, especially when recording or playing recordings.
+
+Both the live displayer and the recorder finds subtitling PID and page
+for the selected language, and optionally for the hearing impaired, by
+scanning the DVB Service Information. You don't have to set up channel
+Tpids. Though, if you have set a Sid it will be used to speed up
+Service Information scanning. (Can't say it makes much of a
+difference, though).
+
+The recorder writes a filtered subset of the teletext data with only
+the subtitle pages and an index page at page 100 (or subsequent page
+if that page number is used for subtitling). The filtered stream is
+written as a private_stream_1 with subtype 0x1f (the last teletext
+subtype). An ordinary teletext PES stream recorded for example by
+setting Dpid1 to a teletext PID will typically have a subtype of
+0x10.
+
+The recording player scans the index page to find the selected
+language. (If it can't find an index page it will use the page number
+from the current channel. This may be removed in the future.) The
+index page itself can't be viewed with this plugin.
+
+If you can't select your language, look up its 3 letter acronym in
+<http://www.loc.gov/standards/iso639-2/englangn_ascii.html> and/or
+check the output from ttxtsubs, and enter the language code in your
+VDR setup.conf at ttxtsubs.language. This will hopefully be easier in
+a future release.
+
+Consider this code alpha quality. The code could use some major
+cleanup. The program design was from the beginning just not, as it
+started as a test hack, and it has from then been incompletely
+reworked a few times.
+
+
+Installation:
+
+This plugin is written for VDR 1.1.25. It currently needs a few hooks
+in, and changes to, VDR to work. A patch file is included. The patches
+should be harmless if you don't have the plugin loaded, so even if you
+patch VDR and later decide not to use this plugin you don't
+necessarily have to reinstall VDR. It is probably possible to build
+the plugin for other version of VDR, but don't count on it.
+
+Steps:
+
+As usual, unpack the plugin and make a link:
+ cd ..../vdr-1.1.25/PLUGINS/src
+ tar zxvf ..../vdr-ttxtsubs-0.0.1.tgz
+ ln -s ttxtsubs-0.0.1 ttxtsubs
+
+Go back to your VDR directory and install the patches and the new files:
+ cd ../../
+ cp PLUGINS/src/ttxtsubs-0.0.1/vdrttxtsubshooks.[ch] .
+ patch -b < PLUGINS/src/ttxtsubs-0.0.1/VDR.patch
+
+Rebuild VDR:
+ make clean
+ make include-dir
+ make
+ make plugins
+
+Now you should be able to start VDR with "-P ttxtsubs".
diff --git a/VDR.patch b/VDR.patch
new file mode 100644
index 0000000..3c825c3
--- /dev/null
+++ b/VDR.patch
@@ -0,0 +1,250 @@
+diff -upr ./DIST/vdr-1.1.25/Makefile ./Makefile
+--- ./DIST/vdr-1.1.25/Makefile 2003-01-06 13:28:09.000000000 +0100
++++ ./Makefile 2003-03-07 03:19:33.000000000 +0100
+@@ -36,7 +36,8 @@ OBJS = audio.o channels.o ci.o config.o
+ dvbplayer.o dvbspu.o eit.o eitscan.o font.o i18n.o interface.o keys.o\
+ lirc.o menu.o menuitems.o osdbase.o osd.o player.o plugin.o rcu.o\
+ receiver.o recorder.o recording.o remote.o remux.o ringbuffer.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
+
+ OSDFONT = -adobe-helvetica-medium-r-normal--23-*-100-100-p-*-iso8859-1
+ FIXFONT = -adobe-courier-bold-r-normal--25-*-100-100-m-*-iso8859-1
+diff -upr ./DIST/vdr-1.1.25/dvbplayer.c ./dvbplayer.c
+--- ./DIST/vdr-1.1.25/dvbplayer.c 2003-02-15 11:38:59.000000000 +0100
++++ ./dvbplayer.c 2003-03-07 03:52:55.000000000 +0100
+@@ -13,6 +13,7 @@
+ #include "ringbuffer.h"
+ #include "thread.h"
+ #include "tools.h"
++#include "vdrttxtsubshooks.h"
+
+ // --- cBackTrace ----------------------------------------------------------
+
+@@ -318,6 +319,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
+@@ -347,6 +354,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)
+@@ -514,6 +555,11 @@ void cDvbPlayer::Action(void)
+ p = playFrame->Data();
+ pc = playFrame->Count();
+ }
++#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/vdr-1.1.25/menu.c ./menu.c
+--- ./DIST/vdr-1.1.25/menu.c 2003-02-09 13:55:38.000000000 +0100
++++ ./menu.c 2003-03-07 03:38:14.000000000 +0100
+@@ -2931,8 +2931,18 @@ cRecordControl::cRecordControl(cDevice *
+ fileName = strdup(Recording.FileName());
+ cRecordingUserCommand::InvokeCommand(RUC_BEFORERECORDING, fileName);
+ 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());
+ }
+diff -upr ./DIST/vdr-1.1.25/menu.h ./menu.h
+--- ./DIST/vdr-1.1.25/menu.h 2003-01-12 15:54:05.000000000 +0100
++++ ./menu.h 2003-03-07 03:33:28.000000000 +0100
+@@ -14,6 +14,7 @@
+ #include "device.h"
+ #include "osd.h"
+ #include "dvbplayer.h"
++#include "vdrttxtsubshooks.h"
+ #include "recorder.h"
+ #include "recording.h"
+
+diff -upr ./DIST/vdr-1.1.25/osd.c ./osd.c
+--- ./DIST/vdr-1.1.25/osd.c 2002-12-08 14:17:13.000000000 +0100
++++ ./osd.c 2003-03-07 03:55:32.000000000 +0100
+@@ -12,6 +12,7 @@
+ #include "device.h"
+ #include "i18n.h"
+ #include "status.h"
++#include "vdrttxtsubshooks.h"
+
+ // --- cOsd ------------------------------------------------------------------
+
+@@ -96,6 +97,9 @@ void cOsd::Open(int w, int h)
+ d *= lineHeight;
+ int x = (720 - w + charWidth) / 2; //TODO PAL vs. NTSC???
+ int y = (576 - Setup.OSDheight * lineHeight) / 2 + d;
++#ifdef VDRTTXTSUBSHOOKS
++ cVDRTtxtsubsHookListener::Hook()->HideOSD();
++#endif
+ //XXX
+ osd = OpenRaw(x, y);
+ //XXX TODO this should be transferred to the places where the individual windows are requested (there's too much detailed knowledge here!)
+@@ -136,6 +140,9 @@ void cOsd::Close(void)
+ delete osd;
+ osd = NULL;
+ #endif
++#ifdef VDRTTXTSUBSHOOKS
++ cVDRTtxtsubsHookListener::Hook()->ShowOSD();
++#endif
+ }
+
+ void cOsd::Clear(void)
+diff -upr ./DIST/vdr-1.1.25/recorder.c ./recorder.c
+--- ./DIST/vdr-1.1.25/recorder.c 2003-01-25 17:23:36.000000000 +0100
++++ ./recorder.c 2003-03-07 03:54:10.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:
+@@ -19,7 +21,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)
+ {
+ ringBuffer = NULL;
+@@ -30,6 +36,9 @@ cRecorder::cRecorder(const char *FileNam
+ fileSize = 0;
+ active = false;
+ lastDiskSpaceCheck = time(NULL);
++#ifdef VDRTTXTSUBSHOOKS
++ ttxtSubsRecorder = tsr;
++#endif
+ isyslog("record %s", FileName);
+
+ // Create directories if necessary:
+@@ -57,6 +66,10 @@ cRecorder::cRecorder(const char *FileNam
+ cRecorder::~cRecorder()
+ {
+ Detach();
++#ifdef VDRTTXTSUBSHOOKS
++ if(ttxtSubsRecorder)
++ delete ttxtSubsRecorder;
++#endif
+ delete index;
+ delete fileName;
+ delete remux;
+@@ -130,6 +143,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/vdr-1.1.25/recorder.h ./recorder.h
+--- ./DIST/vdr-1.1.25/recorder.h 2002-06-08 11:35:03.000000000 +0200
++++ ./recorder.h 2003-03-07 03:50:33.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();
diff --git a/siinfo.c b/siinfo.c
new file mode 100644
index 0000000..f878be7
--- /dev/null
+++ b/siinfo.c
@@ -0,0 +1,583 @@
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/poll.h>
+#include <time.h>
+#include <netinet/in.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "linux/dvb/dmx.h"
+#include "siinfo.h"
+
+#define DESCR_TELETEXT 0x56
+#define DESCR_DVBSUBTITLES 0x59
+
+
+#define PACK __attribute__ ((__packed__))
+
+
+// ETSI 300468 6.2.40
+struct ttxt_descr {
+ uint8_t tag PACK;
+ uint8_t length PACK;
+ struct {
+ uint8_t lang[3] PACK;
+ uint8_t type_mag PACK; // 5 bits type, 3 bits mag
+ uint8_t page_no PACK;
+ } d[1] PACK;
+};
+
+struct sect_header {
+ uint8_t table_id PACK;
+ uint16_t syntax_len PACK;
+ uint16_t transport_stream_id PACK;
+ uint8_t ver_cur PACK;
+ uint8_t section_number PACK;
+ uint8_t last_section_number PACK;
+};
+
+// H.222.0 2.4.4.3, Table 2-25
+struct PAT_sect {
+ uint8_t table_id PACK;
+ uint16_t syntax_len PACK;
+ uint16_t transport_stream_id PACK;
+ uint8_t vers_curr PACK;
+ uint8_t sect_no PACK;
+ uint8_t last_sect PACK;
+ struct {
+ uint16_t program_number PACK;
+ uint16_t res_PMTPID PACK;
+ } d[1] PACK;
+};
+
+// H.222.0 2.4.4.8, Table 2-28
+struct PMT_stream {
+ uint8_t stream_type PACK;
+ uint16_t res_PID PACK;
+ uint16_t res_ES_info_len PACK;
+ uint8_t descrs[2] PACK;
+};
+
+struct PMT_sect {
+ uint8_t table_id PACK;
+ uint16_t syntax_len PACK;
+ uint16_t program_number PACK;
+ uint8_t vers_curr PACK;
+ uint8_t sect_no PACK;
+ uint8_t last_sect PACK;
+ uint16_t res_pcr PACK;
+ uint16_t res_program_info_length PACK;
+ struct PMT_stream s PACK;
+};
+
+
+void dump_hex(char *msg, uint8_t *p, int len)
+{
+ int i;
+ printf("%s:", msg);
+ for(i = 0; i < len; i++) {
+ printf(" %02x", p[i]);
+ }
+ printf("\n");
+
+ printf("%s:", msg);
+ for(i = 0; i < len; i++) {
+ printf(" %c", isprint(p[i]) ? p[i] : '.');
+ }
+ printf("\n");
+}
+
+
+static int SetSectFilt(int fd, uint16_t pid, uint8_t tnr, uint8_t mask)
+{
+ int ret;
+
+ struct dmx_sct_filter_params p;
+
+ memset(&p, 0, sizeof(p));
+
+ p.filter.filter[0] = tnr;
+ p.filter.mask[0] = mask;
+ p.pid = pid;
+ p.flags = DMX_IMMEDIATE_START | DMX_CHECK_CRC;
+
+ if ((ret = ioctl(fd, DMX_SET_FILTER, &p)) < 0)
+ perror("DMX SET FILTER:");
+
+ return ret;
+}
+
+
+/*
+ * PID - pid to collect on
+ * table_id - table id to filter out (H.222.0 table 2-26)
+ * sects - pointer to char *[256], points to array of table sections, initially empty
+ * numsects - number of sections in table
+ * returns - 0 if everything ok
+ */
+#define SECTSIZE 1024
+static int CollectSections(int card_no, int pid, int table_id, char **sects, int *numsects)
+{
+ int fd;
+ int ret = -1;
+ int last_section = 0;
+ int done = 0;
+ char *p = NULL;
+ int n;
+ char name[100];
+
+ snprintf(name, sizeof(name), "/dev/dvb/adapter%d/demux0", card_no);
+
+ memset(sects, 0, sizeof(char*) * 256);
+
+ if((fd = open(name, O_RDWR)) < 0){
+ perror("DEMUX DEVICE 1: ");
+ return -1;
+ }
+
+ if(SetSectFilt(fd, pid, table_id, 0xff)) {
+ ret = -1;
+ goto bail;
+ }
+
+ do {
+ struct sect_header *h;
+ int i;
+
+ if(p == NULL)
+ p = (char *) malloc(SECTSIZE);
+
+ n = read(fd, p, SECTSIZE);
+ if(n < 8)
+ continue;
+
+ h = (struct sect_header *) p;
+
+ if(n != ((ntohs(h->syntax_len) & 0xfff) + 3)) {
+ fprintf(stderr, "bad section length: %x / %x!\n", n, ntohs(h->syntax_len) & 0xfff);
+ continue;
+ }
+
+ if(!(h->ver_cur & 0x01)) // if not current
+ continue;
+
+ last_section = h->last_section_number;
+
+ if(!sects[h->section_number]) { // section_number
+ sects[h->section_number] = p;
+ p = NULL;
+ }
+
+ for(i = 0; i <= last_section; i++) {
+ if(!sects[i]) {
+ break;
+ }
+
+ if(i == last_section) {
+ *numsects = last_section + 1;
+ ret = 0;
+ done = 1;
+ }
+ }
+
+ } while (!done);
+
+ bail:
+ close(fd);
+ return ret;
+}
+
+static void FreeSects(char **sects)
+{
+ int i;
+
+ for(i = 0; i < 256; i++) {
+ if(sects[i])
+ free(sects[i]);
+ }
+}
+
+
+static void addpageinfo(struct ttxtinfo *info, uint16_t pid, struct ttxt_descr *descr, int descr_index)
+{
+ struct ttxtpidinfo *pd;
+ struct ttxtpageinfo *pa;
+
+ if(!info->p) {
+ info->p = (struct ttxtpidinfo *) malloc(sizeof(*(info->p)));
+ memset((char *) info->p, 0, sizeof(*(info->p)));
+ info->p[0].pid = pid;
+ info->pidcount++;
+ }
+
+ if(pid != info->p[info->pidcount - 1].pid) {
+ info->pidcount++;
+ info->p = (struct ttxtpidinfo *) realloc(info->p, sizeof(info->p[0]) * info->pidcount);
+ memset((char *) &(info->p[info->pidcount - 1]), 0, sizeof(info->p[0]));
+ info->p[info->pidcount - 1].pid = pid;
+ }
+
+ pd = &(info->p[info->pidcount - 1]);
+
+ pd->pagecount++;
+ if(!pd->i) {
+ pd->i = (struct ttxtpageinfo *) malloc(sizeof(pd->i[0]));
+ } else {
+ pd->i = (struct ttxtpageinfo *) realloc(pd->i, sizeof(pd->i[0]) * pd->pagecount);
+ }
+ memset((char *) &(pd->i[pd->pagecount - 1]), 0, sizeof(pd->i[0]));
+
+ pa = &(pd->i[pd->pagecount - 1]);
+
+ pa->lang[0] = descr->d[descr_index].lang[0];
+ pa->lang[1] = descr->d[descr_index].lang[1];
+ pa->lang[2] = descr->d[descr_index].lang[2];
+ 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;
+}
+
+
+static int HasVPID(int vpid, char **pmtsects, int numsects)
+{
+ int found = 0;
+ int i;
+
+ for(i = 0; i < numsects && !found; i++) {
+ struct PMT_sect *psect;
+ char *sp;
+ char *end;
+
+ psect = (struct PMT_sect *) pmtsects[i];
+ end = pmtsects[i] + (psect->syntax_len & 0x3ff) - 7;
+ // skip extra program info
+ sp = ((char *) &(psect->s)) + (ntohs(psect->res_program_info_length) & 0xfff);
+
+ while(sp < end && !found) {
+ struct PMT_stream *s = (struct PMT_stream *) sp;
+
+ if(s->stream_type == 1 || s->stream_type == 2) { // Video stream
+ if((ntohs(s->res_PID) & 0x1fff) == vpid) {
+ //printf("Found ttxt by vpid: %d/%d\n", s->stream_type, (ntohs(s->res_PID) & 0x1fff));
+ found = 1;
+ break;
+ }
+ }
+ sp += (ntohs(s->res_ES_info_len) & 0xfff) + 5;
+ }
+ }
+
+ return found;
+}
+
+static void ExtractTtxtInfo(char **pmtsects, int numsects, struct ttxtinfo *info, int *foundinfo)
+{
+ int i;
+
+ for(i = 0; i < numsects; i++) {
+ struct PMT_sect *psect;
+ char *sp;
+ char *end;
+
+ psect = (struct PMT_sect *) pmtsects[i];
+ end = pmtsects[i] + (psect->syntax_len & 0x3ff) - 7;
+ // skip extra program info
+ sp = ((char *) &(psect->s)) + (ntohs(psect->res_program_info_length) & 0xfff);
+
+ while(sp < end) {
+ struct PMT_stream *s = (struct PMT_stream *) sp;
+
+ if(s->stream_type == 6) { // PES private data
+ uint8_t *descr;
+
+ for(descr = s->descrs; descr < s->descrs + (ntohs(s->res_ES_info_len) & 0xfff); descr += descr[1] + 2) {
+ if(descr[0] == DESCR_TELETEXT) {
+ struct ttxt_descr *t = (struct ttxt_descr *) descr;
+ int i, count = t->length / sizeof(t->d[0]);
+ for(i = 0; i < count; i++) {
+ addpageinfo(info, ntohs(s->res_PID) & 0x1fff, t, i);
+ //printf("%c%c%c: type %d, page: %01x%02x\n", t->d[i].lang[0], t->d[i].lang[1], t->d[i].lang[2], t->d[i].type_mag >> 3, t->d[i].type_mag & 0x7, t->d[i].page_no);
+ *foundinfo = 1;
+ }
+ }
+ }
+ }
+ sp += (ntohs(s->res_ES_info_len) & 0xfff) + 5;
+ }
+ }
+}
+
+/*
+ * if vpid != 0, we first search if we can find the vpid in this PMT,
+ * and if so we go get the ttxt pid
+ * return <> 0 on error
+ */
+static int FindTtxtInfoInPMT(int card_no, int pid, int vpid, struct ttxtinfo *info, int *foundinfo)
+{
+ int ret = -1;
+ char *pmtsects[256];
+ int numsects;
+
+ ret = CollectSections(card_no, pid, 0x02, pmtsects, &numsects);
+ if(ret)
+ goto bail;
+
+ if(vpid != 0) {
+ if(!HasVPID(vpid, pmtsects, numsects)) {
+ goto bail;
+ }
+ }
+
+ ExtractTtxtInfo(pmtsects, numsects, info, foundinfo);
+
+ bail:
+ FreeSects(pmtsects);
+ return ret;
+}
+
+
+/*
+ * find the ttxt_info in the PMT via the PAT, try first with the SID
+ * and if that fails with the VPID
+ * return <> 0 on error;
+ */
+int GetTtxtInfo(int card_no, uint16_t sid, uint16_t vpid, struct ttxtinfo *info)
+{
+ int ret;
+ char *patsects[256];
+ int numsects;
+ int i;
+ int j;
+ uint16_t pmt_pid = 0;
+ int foundinfo = 0;
+
+ memset((char *) info, 0, sizeof(*info));
+
+ ret = CollectSections(card_no, 0, 0, patsects, &numsects);
+ if(ret)
+ goto bail;
+
+ if(sid != 0) {
+ int found;
+
+ for(i = 0, found = 0; i < numsects && !found; i++) {
+ int numdescrs;
+ struct PAT_sect *s = (struct PAT_sect *) patsects[i];
+
+ numdescrs = ((ntohs(s->syntax_len) & 0x3FF) - 7) / 4;
+
+ for(j = 0; j < numdescrs && !found; j++) {
+ uint16_t pno = ntohs(s->d[j].program_number);
+
+ if(pno == 0)
+ continue; // network pid
+
+ if(pno == sid) {
+ pmt_pid = ntohs(s->d[j].res_PMTPID) & 0x1fff;
+ found = 1;
+ }
+ }
+ }
+ }
+
+ if(pmt_pid != 0) {
+ ret = FindTtxtInfoInPMT(card_no, pmt_pid, 0, info, &foundinfo);
+ } else {
+ // SID not found, try searching VID in all SIDS
+ if(vpid != 0) {
+ int done;
+ for(i = 0, done = 0; i < numsects && !done; i++) {
+ int numdescrs;
+ struct PAT_sect *s = (struct PAT_sect *) patsects[i];
+
+ numdescrs = ((ntohs(s->syntax_len) & 0x3FF) - 7) / 4;
+
+ for(j = 0; j < numdescrs && !done; j++) {
+ uint16_t pno = ntohs(s->d[j].program_number);
+
+ if(pno == 0)
+ continue; // network pid
+
+ pmt_pid = ntohs(s->d[j].res_PMTPID) & 0x1fff;
+
+ ret = FindTtxtInfoInPMT(card_no, pmt_pid, vpid, info, &foundinfo);
+ if(ret) {
+ done = 1;
+ }
+ if(foundinfo)
+ done = 1;
+ }
+ }
+ }
+ }
+
+bail:
+ FreeSects(patsects);
+ return ret;
+}
+
+
+void FreeTtxtInfoData(struct ttxtinfo *info)
+{
+ int i;
+
+ for(i = 0; i < info->pidcount; i++) {
+ free((void *) info->p[i].i);
+ }
+
+ free((void *) info->p);
+}
+
+
+struct ttxtpidinfo *FindSubs(struct ttxtinfo *info, char *lang, int HI, int *pid, int *pageno)
+{
+ struct ttxtpidinfo *foundNonHIInfo = NULL;
+ int foundNonHIPid = 0;
+ int foundNonHIPage = -1;
+
+ 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)) {
+ *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]);
+ }
+ }
+ }
+ }
+
+ 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(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]);
+ 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],
+ type == TTXT_INITIAL_PAGE ? "(Initial Page)" :
+ type == TTXT_SUBTITLE_PAGE ? "(Subtitles)" :
+ type == TTXT_ADDITIONAL_INFO_PAGE ? "(Additional Info Page)" :
+ type == TTXT_PROGRAMME_SCHEDULE_PAGE ? "(Programme Schedule Page)" :
+ type == TTXT_SUBTITLE_HEARING_IMPAIRED_PAGE ? "(Hearing Impaired)" : "(Unknown type)");
+ }
+ }
+ }
+
+ *pid = 0;
+ *pageno = -1;
+ return NULL;
+}
+
+
+#if 0
+int XX_GetTtxtSubtitleInfo(uint16_t pid, int card_no, struct ttxtinfo *info)
+{
+ int len;
+ char buf[4096];
+ struct PMT_sect *p = (struct PMT_sect *) buf;
+ char *sp;
+
+ memset((char *) info, 0, sizeof(*info));
+
+ len = GetSection(pid, 2, 0xFF, card_no, buf, sizeof(buf));
+
+ //dump_hex("Sect: ", (uint8_t *) buf, len);
+
+ if(len < 0)
+ return len;
+
+ if(len != ((ntohs(p->syntax_len) & 0xfff) + 3)) {
+ fprintf(stderr, "bad lengt: %x / %x!\n", len, ntohs(p->syntax_len));
+ return -1;
+ }
+
+ // also skip extra program info
+ sp = ((char *) &(p->s)) + (ntohs(p->res_program_info_length) & 0xfff);
+
+ while(sp < buf + len - 4) {
+ struct PMT_stream *s = (struct PMT_stream *) sp;
+
+ //printf("type: %d, pid: %d, len: %d\n", s->stream_type, ntohs(s->res_PID) & 0x1fff, ntohs(s->res_ES_info_len) & 0xfff);
+ //dump_hex("descr: ", sp, ntohs(s->res_ES_info_len) & 0xfff);
+
+ if(s->stream_type == 6) { // PES private data
+ uint8_t *descr;
+
+ for(descr = s->descrs; descr < s->descrs + (ntohs(s->res_ES_info_len) & 0xfff); descr += descr[1] + 2) {
+ //dump_hex("descr: ", descr, descr[1] + 2);
+ if(descr[0] == DESCR_TELETEXT) {
+ struct ttxt_descr *t = (struct ttxt_descr *) descr;
+ int i, count = t->length / sizeof(t->d[0]);
+ for(i = 0; i < count; i++) {
+ addpageinfo(info, ntohs(s->res_PID) & 0x1fff, t, i);
+ //printf("%c%c%c: type %d, page: %01x%02x\n", t->d[i].lang[0], t->d[i].lang[1], t->d[i].lang[2], t->d[i].type_mag >> 3,
+ //t->d[i].type_mag & 0x7, t->d[i].page_no);
+ }
+ }
+ }
+ }
+
+ sp += (ntohs(s->res_ES_info_len) & 0xfff) + 5;
+ }
+
+ return 0;
+}
+#endif
+
+#if 0
+//XXX temporary for testing!
+int get_subs(char *lang, int sid, int vpid, int cardno, int *pid, int *page) {
+ struct ttxtinfo info;
+ int i, j;
+ int result = -1;
+
+ GetTtxtInfo(cardno, sid, vpid, &info);
+
+ for(i = 0; i < info.pidcount; i++) {
+ //printf("PID: %d ", info.p[i].pid);
+ for(j = 0; j < info.p[i].pagecount; j++) {
+ //printf(" %c%c%c: %01x%02x (%d) ",
+ // info.p[i].i[j].lang[0], info.p[i].i[j].lang[1], info.p[i].i[j].lang[2],
+ // info.p[i].i[j].mag, info.p[i].i[j].page, info.p[i].i[j].type);
+ 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] &&
+ (info.p[i].i[j].type == TTXT_SUBTITLE_PAGE || info.p[i].i[j].type == TTXT_SUBTITLE_HEARING_IMPAIRED_PAGE)) {
+ *pid = info.p[i].pid;
+ *page = info.p[i].i[j].mag * 0x100 + info.p[i].i[j].page;
+ result = 0;
+ }
+ }
+ //printf("\n");
+ }
+
+ return result;
+}
+#endif
diff --git a/siinfo.h b/siinfo.h
new file mode 100644
index 0000000..16ac445
--- /dev/null
+++ b/siinfo.h
@@ -0,0 +1,36 @@
+
+
+#define TTXT_INITIAL_PAGE 1
+#define TTXT_SUBTITLE_PAGE 2
+#define TTXT_ADDITIONAL_INFO_PAGE 3
+#define TTXT_PROGRAMME_SCHEDULE_PAGE 4
+#define TTXT_SUBTITLE_HEARING_IMPAIRED_PAGE 5
+
+struct ttxtpageinfo {
+ char lang[3];
+ uint8_t type;
+ uint8_t mag;
+ uint8_t page;
+};
+
+struct ttxtpidinfo {
+ uint16_t pid;
+ int pagecount;
+ struct ttxtpageinfo *i;
+};
+
+struct ttxtinfo {
+ int pidcount;
+ struct ttxtpidinfo *p;
+};
+
+/*
+ * find the ttxt_info in the PMT via the PAT, try first with the SID
+ * and if that fails with the VPID
+ * return <> 0 on error;
+ */
+int GetTtxtInfo(int card_no, uint16_t sid, uint16_t vpid, struct ttxtinfo *info);
+
+void FreeTtxtInfoData(struct ttxtinfo *info);
+
+struct ttxtpidinfo *FindSubs(struct ttxtinfo *info, char *lang, int HI, int *pid, int *pageno);
diff --git a/teletext-chars.h b/teletext-chars.h
new file mode 100644
index 0000000..1ebc10b
--- /dev/null
+++ b/teletext-chars.h
@@ -0,0 +1,80 @@
+
+/*
+ * Character mappings
+ * An attempt to map Teletext characters into ISO-8859-1.
+ * Trying to use similar looking or similar meaning
+ * characters.
+ */
+
+
+/*
+ * G0 and G2 national option table given Triplet 1 bits 14-11 and Control bits from C12-14
+ * ETSI EN 300 706 Table 33
+ */
+uint8_t laG0_nat_opts_lookup[16][8] = {
+ {1, 4, 11, 5, 3, 8, 0, 1},
+ {7, 4, 11, 5, 3, 1, 0, 1},
+ {1, 4, 11, 5, 3, 8, 12, 1},
+ {1, 1, 1, 1, 1, 10, 1, 9},
+ {1, 4, 2, 6, 1, 1, 0, 1},
+ {1, 1, 1, 1, 1, 1, 1, 1}, // 5 - reserved
+ {1, 1, 1, 1, 1, 1, 12, 1},
+ {1, 1, 1, 1, 1, 1, 1, 1}, // 7 - reserved
+ {1, 1, 1, 1, 3, 1, 1, 1},
+ {1, 1, 1, 1, 1, 1, 1, 1}, // 9 - reserved
+ {1, 1, 1, 1, 1, 1, 1, 1},
+ {1, 1, 1, 1, 1, 1, 1, 1}, // 11 - reserved
+ {1, 1, 1, 1, 1, 1, 1, 1}, // 12 - reserved
+ {1, 1, 1, 1, 1, 1, 1, 1}, // 13 - reserved
+ {1, 1, 1, 1, 1, 1, 1, 1}, // 14 - reserved
+ {1, 1, 1, 1, 1, 1, 1, 1} // 15 - reserved
+};
+
+
+uint8_t laG0_nat_replace_map[128] = {
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 5, 6, 7, 8,
+9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 0
+};
+
+
+/*
+ * Latin National Option Sub-Sets
+ * ETSI EN 300 706 Table 36
+ */
+
+uint8_t laG0_nat_opts[13][14] = {
+{0, '#', 'u', 'c', 't', 'z', 'ý', 'í', 'r', 'é', 'á', 'e', 'ú', 's'}, // 0 - Czech/Slovak
+{0, '£', '$', '@', '-', '½', '-', '|', '#', '-', '¼', '#', '¾', '÷'}, // 1 - English
+{0, '#', 'õ', 'S', 'Ä', 'Ö', 'Z', 'Ü', 'Õ', 's', 'ä', 'ö', 'z', 'ü'}, // 2 - Estonian
+{0, 'é', 'ï', 'à', 'ë', 'ê', 'ù', 'î', '#', 'è', 'â', 'ô', 'û', 'ç'}, // 3 - French
+{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
+{0, 'ç', '$', 'i', 'á', 'é', 'í', 'ó', 'ú', '¿', 'ü', 'ñ', 'è', 'à'}, // 8 - Portuguese/Spanish
+{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
+};
+
+/*
+ * Latin G2 Set
+ * ETSI EN 300 706 Table 37
+ */
+uint8_t laG2_la1_table[] = {
+'?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?',
+'?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?',
+' ', '¡', '¢', '£', '$', '¥', '#', '§', '¤', '´', '"', '«', '-', '|', '-', '|',
+'°', '±', '²', '³', '×', 'µ', '¶', '·', '÷', '\'', '"', '»', '¼', '½', '¾', '¿',
+' ', '`', '´', '^', '~', '-', '?', '·', '¨', '.', 'º', '¸', '_', '"', '?', '?',
+'-', '¹', '®', '©', 'T', '?', 'C', 'p', '?', ' ', ' ', ' ', '?', '?', '?', '?',
+'?', 'Æ', 'D', 'a', '?', '?', ' ', 'I', 'L', 'L', 'Ø', 'Ö', 'o', 'P', 'n', 'n',
+'k', 'æ', 'd', 'd', 'h', 'i', 'i', 'l', 'l', 'ø', 'ö', 'ß', 'p', 't', 'n', '?'
+};
diff --git a/teletext-tables.h b/teletext-tables.h
new file mode 100644
index 0000000..b1e6a13
--- /dev/null
+++ b/teletext-tables.h
@@ -0,0 +1,81 @@
+/* This file is copied from Ralph Metzler's vbidecode package. */
+
+/*
+ tables.h - some data conversion tables for vbidecode
+*/
+
+#ifndef _TABLES_H
+#define _TABLES_H
+
+unsigned char unhamtab[256] = {
+ 0x01, 0xff, 0x81, 0x01, 0xff, 0x00, 0x01, 0xff,
+ 0xff, 0x02, 0x01, 0xff, 0x0a, 0xff, 0xff, 0x07,
+ 0xff, 0x00, 0x01, 0xff, 0x00, 0x80, 0xff, 0x00,
+ 0x06, 0xff, 0xff, 0x0b, 0xff, 0x00, 0x03, 0xff,
+ 0xff, 0x0c, 0x01, 0xff, 0x04, 0xff, 0xff, 0x07,
+ 0x06, 0xff, 0xff, 0x07, 0xff, 0x07, 0x07, 0x87,
+ 0x06, 0xff, 0xff, 0x05, 0xff, 0x00, 0x0d, 0xff,
+ 0x86, 0x06, 0x06, 0xff, 0x06, 0xff, 0xff, 0x07,
+ 0xff, 0x02, 0x01, 0xff, 0x04, 0xff, 0xff, 0x09,
+ 0x02, 0x82, 0xff, 0x02, 0xff, 0x02, 0x03, 0xff,
+ 0x08, 0xff, 0xff, 0x05, 0xff, 0x00, 0x03, 0xff,
+ 0xff, 0x02, 0x03, 0xff, 0x03, 0xff, 0x83, 0x03,
+ 0x04, 0xff, 0xff, 0x05, 0x84, 0x04, 0x04, 0xff,
+ 0xff, 0x02, 0x0f, 0xff, 0x04, 0xff, 0xff, 0x07,
+ 0xff, 0x05, 0x05, 0x85, 0x04, 0xff, 0xff, 0x05,
+ 0x06, 0xff, 0xff, 0x05, 0xff, 0x0e, 0x03, 0xff,
+ 0xff, 0x0c, 0x01, 0xff, 0x0a, 0xff, 0xff, 0x09,
+ 0x0a, 0xff, 0xff, 0x0b, 0x8a, 0x0a, 0x0a, 0xff,
+ 0x08, 0xff, 0xff, 0x0b, 0xff, 0x00, 0x0d, 0xff,
+ 0xff, 0x0b, 0x0b, 0x8b, 0x0a, 0xff, 0xff, 0x0b,
+ 0x0c, 0x8c, 0xff, 0x0c, 0xff, 0x0c, 0x0d, 0xff,
+ 0xff, 0x0c, 0x0f, 0xff, 0x0a, 0xff, 0xff, 0x07,
+ 0xff, 0x0c, 0x0d, 0xff, 0x0d, 0xff, 0x8d, 0x0d,
+ 0x06, 0xff, 0xff, 0x0b, 0xff, 0x0e, 0x0d, 0xff,
+ 0x08, 0xff, 0xff, 0x09, 0xff, 0x09, 0x09, 0x89,
+ 0xff, 0x02, 0x0f, 0xff, 0x0a, 0xff, 0xff, 0x09,
+ 0x88, 0x08, 0x08, 0xff, 0x08, 0xff, 0xff, 0x09,
+ 0x08, 0xff, 0xff, 0x0b, 0xff, 0x0e, 0x03, 0xff,
+ 0xff, 0x0c, 0x0f, 0xff, 0x04, 0xff, 0xff, 0x09,
+ 0x0f, 0xff, 0x8f, 0x0f, 0xff, 0x0e, 0x0f, 0xff,
+ 0x08, 0xff, 0xff, 0x05, 0xff, 0x0e, 0x0d, 0xff,
+ 0xff, 0x0e, 0x0f, 0xff, 0x0e, 0x8e, 0xff, 0x0e,
+};
+
+// invert tab for last 42 bytes of data in packets
+unsigned char invtab[256] = {
+ 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
+ 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
+ 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
+ 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
+ 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
+ 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
+ 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
+ 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
+ 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
+ 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
+ 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
+ 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
+ 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
+ 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
+ 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
+ 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
+ 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
+ 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
+ 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
+ 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
+ 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
+ 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
+ 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
+ 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
+ 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
+ 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
+ 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
+ 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
+ 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
+ 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
+ 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
+ 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff,
+};
+
+#endif
diff --git a/teletext.c b/teletext.c
new file mode 100644
index 0000000..13ea178
--- /dev/null
+++ b/teletext.c
@@ -0,0 +1,724 @@
+
+#include <stdio.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <netinet/in.h>
+#include <ctype.h>
+#include <string.h>
+
+//#include <linux/dvb/dmx.h>
+//#include <vdr/osd.h>
+
+#include "teletext.h"
+#include "teletext-tables.h"
+#include "teletext-chars.h"
+#include "utils.h"
+
+
+// FROM vbidecode
+// unham 2 bytes into 1, report 2 bit errors but ignore them
+unsigned char unham(unsigned char a,unsigned char b)
+{
+ unsigned char c1,c2;
+
+ c1=unhamtab[a];
+ c2=unhamtab[b];
+// if ((c1|c2)&0x40)
+// fprintf(stderr,"bad ham!");
+ return (c2<<4)|(0x0f&c1);
+}
+
+
+// ham 8/4 the byte in into two bytes pointed to by out
+// should make a table instead
+void ham8_4byte(uint8_t in, uint8_t *out)
+{
+ out[0] = ham8_4nibble(in & 0xF);
+ out[1] = ham8_4nibble(in >> 4);
+
+ if (1) { // XXX debug
+ int a;
+ if(unham(out[0], out[1]) != in) {
+ fprintf(stderr, "ham8_4: 1 - result not correct %02x -> %02x %02x!\n", in, out[0], out[1]);
+ }
+ a = unhamtab[out[0]];
+ a ^= 0x80;
+ if(a & 0xf0 || (a != (in & 0xF))) {
+ fprintf(stderr, "ham8_4: 2 - result not correct %02x -> %02x %02x, %02x!\n", in, out[0], out[1], a);
+ }
+ a = unhamtab[out[1]];
+ a ^= 0x80;
+ if(a & 0xf0 || (a != (in >> 4))) {
+ fprintf(stderr, "ham8_4: 3 - result not correct %02x -> %02x %02x, %02x!\n", in, out[0], out[1], a);
+ }
+ }
+}
+
+// should be a table instead
+int parity(uint8_t x) {
+ int res = 1;
+ int count = x & 0xf0 ? 8 : 4;
+
+ while(count--) {
+ if(x & 0x01)
+ res = !res;
+ x >>= 1;
+ }
+
+ return res;
+}
+
+// ham 8/4 the nibble in into the byte pointed to by out
+// should make a table instead
+// D4 P4 D3 P3 D2 P2 D1 P1
+// P4 = all the rest
+uint8_t ham8_4nibble(uint8_t in)
+{
+ uint8_t o = 0;
+
+ // insert the data bits
+ o |= (in << 4) & 0x80;
+ o |= (in << 3) & 0x20;
+ o |= (in << 2) & 0x08;
+ o |= (in << 1) & 0x02;
+
+ if(parity(in & 0x0d)) // P1 = 1 . D1 . D3 . D4
+ o |= 0x01;
+ if(parity(in & 0x0b)) // P2 = 1 . D1 . D2 . D4
+ o |= 0x04;
+ if(parity(in & 0x07)) // P3 = 1 . D1 . D2 . D3
+ o |= 0x10;
+ if(parity(o & 0xbf)) // P4 = 1 . P1 . D1 . ... . D3 . D4
+ o |= 0x40;
+
+ return o;
+}
+
+
+/*
+ * Map Latin G0 teletext characters into a ISO-8859-1 approximation.
+ * Trying to use similar looking or similar meaning characters.
+ * Gtriplet - 4 bits = triplet-1 bits 14-11 (14-8) of a X/28 or M/29 packet, if unknown - use 0
+ * natopts - 3 bits = national_options field of a Y/0 packet (or triplet-1 bits 10-8 as above?)
+ * inchar - 7 bits = characted to remap
+ * Also strips parity
+ */
+
+uint8_t ttxt_laG0_la1_char(int Gtriplet, int natopts, uint8_t inchar)
+{
+ int no = laG0_nat_opts_lookup[Gtriplet & 0xf][natopts & 0x7];
+ uint8_t c = inchar & 0x7f;
+
+ //dprint("\n%x/%c/%x/%x\n", c, c, laG0_nat_replace_map[c], laG0_nat_opts[no][laG0_nat_replace_map[c]]);
+
+ if(!laG0_nat_replace_map[c])
+ return c;
+ else
+ return laG0_nat_opts[no][laG0_nat_replace_map[c]];
+}
+
+/*
+ * Map Latin G2 teletext characters into a ISO-8859-1 approximation.
+ * Trying to use similar looking or similar meaning characters.
+ */
+uint8_t ttxt_laG2_la1_char(uint8_t inchar)
+{
+ return laG2_la1_table[inchar & 0x7f];
+}
+
+void dump_hex(char *msg, const uint8_t *p, int len)
+{
+ int i;
+ printf("%s:", msg);
+ for(i = 0; i < len; i++) {
+ printf(" %02x", p[i]);
+ }
+ printf("\n");
+}
+
+
+void
+print_code(uint8_t c, int natopts)
+{
+ if(c >= 0x20) {
+ dprint("%c", ttxt_laG0_la1_char(0, natopts, c));
+ } else
+ switch(c) {
+ case 0x0:
+ case 0x1:
+ case 0x2:
+ case 0x3:
+ case 0x4:
+ case 0x5:
+ case 0x6:
+ case 0x7:
+ dprint("<fc%d>", c);
+ break;
+ case 0x08:
+ dprint("<flash>");
+ break;
+ case 0x09:
+ dprint("<Steady>");
+ break;
+ case 0x0a:
+ dprint("]");
+ break;
+ case 0x0b:
+ dprint("[");
+ break;
+ case 0x0c:
+ dprint("<normal size>");
+ break;
+ case 0x0d:
+ dprint("<dh>");
+ break;
+ case 0x0e:
+ dprint("<dbl width>");
+ break;
+ case 0x0f:
+ dprint("<dbl size>");
+ break;
+ case 0x10:
+ case 0x11:
+ case 0x12:
+ case 0x13:
+ case 0x14:
+ case 0x15:
+ case 0x16:
+ case 0x17:
+ dprint("<mosc%d>", c-0x10);
+ break;
+ case 0x18:
+ dprint("<conceal>");
+ break;
+ case 0x19:
+ dprint("<cont mosaic>");
+ break;
+ case 0x1a:
+ dprint("<sep mosaic>");
+ break;
+ case 0x1b:
+ dprint("<charset switch>");
+ break;
+ case 0x1c:
+ dprint("<black bg>");
+ break;
+ case 0x1d:
+ dprint("<new bg>");
+ break;
+ case 0x1e:
+ dprint("<hold mosaic>");
+ break;
+ case 0x1f:
+ dprint("<rele mosaic>");
+ break;
+ default:
+ dprint("<U%02x>", c);
+ }
+}
+
+void
+print_page(struct ttxt_page *p)
+{
+ int r, c, e;
+
+ dprint("\n\n%03x %s%s%s%s%snatchars: %d\n",
+ (p->mag << 8) + p->no,
+ p->flags & erasepage ? "erasepage, " : "",
+ p->flags & newsflash ? "Newsflash, " : "",
+ p->flags & subtitle ? "Subtitle, " : "",
+ p->flags & suppress_header ? "SuppressHeader, " : "",
+ p->flags & inhibit_display ? "InihinitDisplay, " : "",
+ p->national_charset);
+
+ for(r = 0; r < 26; r++) {
+ int do_display = 0;
+
+ for(e = 39; e > 0; e--) {
+ if(p->data[r][e]) {
+ do_display = 1;
+ break;
+ }
+ }
+
+ if(do_display) {
+ dprint("%02d ", r);
+ for(c = 0; c <= e; c++) {
+ char z = p->data[r][c] & 0x7f;
+ print_code(z, p->national_charset);
+ //dprint("%c", isprint(z) ? z : z == 0 ? '\'' : '.');
+ }
+ dprint("\n");
+ }
+ }
+}
+
+
+void print_line(char *p)
+{
+ int mp;
+ int mag; // X in ETSI EN 300 706
+ int packet; // Y
+ struct ttxt_data_field *d = (struct ttxt_data_field *) p;
+
+ mp = unham(invtab[d->mag_addr_ham[0]], invtab[d->mag_addr_ham[1]]);
+ mag = mp & 0x7;
+ packet = (mp >> 3) & 0x1f;
+
+ dprint("I%d Y%02d M%d ", d->data_unit_id, packet, mag);
+
+ // packet 0, change page no in this magazine
+ if(packet == 0) {
+ int no;
+ uint8_t buf[8];
+
+ for(int i = 0; i < 8; i++)
+ buf[i] = invtab[d->data[i]];
+
+ no = unham(buf[0], buf[1]);
+
+ dprint("P%02x ", no);
+
+ dprint("<");
+ for(int i = 8; i < 40; i++)
+ print_code(invtab[d->data[i]] & 0x7f, 0);
+ dprint(">");
+
+ if(buf[3] & 0x80) dprint(", erase page");
+ if(buf[5] & 0x20) dprint(", newsflash");
+ if(buf[5] & 0x80) dprint(", subtitle");
+ if(buf[6] & 0x02) dprint(", suppress header");
+ if(buf[6] & 0x08) dprint(", update indicator");
+ if(buf[6] & 0x20) dprint(", interrupted sequence");
+ if(buf[6] & 0x80) dprint(", inhibit display");
+ if(buf[7] & 0x02) dprint(", magazine serial");
+
+ dprint(", charset=%d", ((buf[7] & 0x80) >> 5) + ((buf[7] & 0x20) >> 4) + ((buf[7] & 0x08) >> 3));
+ } else {
+ dprint("<");
+ for(int i = 0; i < 40; i++)
+ print_code(invtab[d->data[i]] & 0x7f, 0);
+ dprint(">");
+ }
+
+ dprint("\n");
+}
+
+
+#if 0
+void
+show_page(struct ttxt_page *p)
+{
+ static cOsdBase *o = NULL;
+ static tWindowHandle w1 = 0;
+ static tWindowHandle w2 = 0;
+ static int open1 = 0, open2 = 0;
+ char *s1 = NULL, *s2 = NULL;
+ int i, j;
+
+ for(i = 1; i < TTXT_ROWS; i++) {
+ char *s = NULL;
+ if(p->data[i][0]) {
+ int do_incr = 0;
+ dprint("Checking row %d\n", i);
+ for(j = 0; j < 40; j++) {
+ p->data[i][j] = p->data[i][j] & 0x7f;
+ switch(p->data[i][j]) {
+ case 0x0d: // double height
+ case 0x0f: // double size
+ do_incr = 1;
+ break;
+ case 0x0a: // box end
+ p->data[i][j] = '\0';
+ break;
+ case 0x0b: // box begin
+ s = (char *) &(p->data[i][j+1]);
+ dprint("Using row %d\n", i);
+ break;
+ case '[':
+ p->data[i][j] = 'Ä';
+ break;
+ case ']':
+ p->data[i][j] = 'Å';
+ break;
+ case '\\':
+ p->data[i][j] = 'Ö';
+ break;
+ case '{':
+ p->data[i][j] = 'ä';
+ break;
+ case '}':
+ p->data[i][j] = 'å';
+ break;
+ case '|':
+ p->data[i][j] = 'ö';
+ break;
+ }
+ }
+ if(do_incr)
+ i++;
+ dprint("\n");
+ }
+
+ if(s)
+ if(!s1)
+ s1 = s;
+ else if(!s2)
+ s2 = s;
+ }
+
+ if(s1 && !s2) {
+ s2 = s1;
+ s1 = NULL;
+ }
+
+ if(s1)
+ dprint("S1: %s\n", s1);
+ else
+ dprint("S1: <none>\n");
+ if(s2)
+ dprint("S2: %s\n", s2);
+ else
+ dprint("S2: <none>\n");
+
+ if(o != NULL) {
+ if(open1) {
+ o->Clear(w1);
+ open1 = 0;
+ }
+ if(open2) {
+ o->Clear(w2);
+ open2 = 0;
+ }
+
+ o->Clear();
+ delete o;
+ o = NULL;
+ }
+
+ if(!s1 && !s2)
+ return;
+
+ o = cOsd::OpenRaw(125, 450);
+
+ if(o == NULL)
+ return;
+
+ o->SetFont(fontOsd);
+
+ if(s1) {
+ int w = o->Width((char *) s1);
+ dprint("w1: %d\n", w);
+ w = 450;
+ int h = 36;
+ w1 = o->Create(0, 0, w, h, 2);
+#if 0
+ o->AddColor(clrBackground, w1);
+ o->AddColor(clrWhite, w1);
+#endif
+ o->Fill(0, 0, w, h, clrBackground, w1);
+ o->Text(15, 4, (char *) s1, clrWhite, clrBackground, w1);
+ open1 = 1;
+ }
+
+ if(s2) {
+ int w = o->Width((char *) s2);
+ dprint("w2: %d\n", w);
+ w = 452;
+ int h = 36;
+ w2 = o->Create(0, 45, w, h, 2);
+#if 0
+ o->AddColor(clrBackground, w2);
+ o->AddColor(clrWhite, w2);
+#endif
+ o->Fill(0, 45, w, 45 + h, clrBackground, w2);
+ o->Text(15, 45 + 4, (char *) s2, clrWhite, clrBackground, w2);
+ open2 = 1;
+ }
+
+ o->Flush();
+}
+#endif
+
+#if 0
+void
+ttxt_packet_in(int data_unit_id, int mag, int pack, uint8_t *data)
+{
+ static int valid = 0;
+ static struct ttxt_page p;
+ int i;
+
+ if(pack == 0) {
+ if(valid)
+ if(p.mag == TTXT_MAG && p.no == TTXT_PAGE)
+ if(p.subtitle)
+ //print_page(&p);
+ /* if(p.mag == 1) */{
+ show_page(&p);
+ print_page(&p);
+ }
+
+ memset(&p, 0, sizeof(p));
+ p.data_unit_id = data_unit_id;
+ p.mag = mag;
+ p.no = unham(data[0], data[1]);
+
+ if(data[3] & 0x80) { // Erase Page
+ memset(p.data, 0, sizeof(p.data));
+ p.erasepage = 1;
+ }
+ if(data[5] & 0x20) // Newsflash
+ p.newsflash = 1;
+ if(data[5] & 0x80) // Subtitle
+ p.subtitle = 1;
+ if(data[6] & 0x02) // Suppress Header
+ p.suppress_header = 1;
+ //if(data[6] & 0x08) // Update Indicator
+ //if(data[6] & 0x20) // Interrupted Sequence
+ if(data[6] & 0x80) // Inhibit Display
+ p.inhibit_display = 1;
+ //if(data[7] & 0x02) // Magazine Serial
+
+ p.national_charset =
+ ((data[7] & 0x80) >> 5) + ((data[7] & 0x20) >> 4) + ((data[7] & 0x08) >> 3);
+
+ valid = 1;
+
+ for(i = 0; i < 32; i++)
+ p.data[0][i] = data[i+8];
+ } else if(pack < 26) {
+ for(i = 0; i < 42; i++)
+ p.data[pack][i] = data[i];
+ } else {
+ if((valid && p.mag == TTXT_MAG && p.no == TTXT_PAGE) ||
+ pack == 28)
+ dprint("mag: %d, pack: %d, page: %02x\n", p.mag, pack, p.no);
+ }
+}
+#endif
+
+#if 0
+void
+ttxt_pes_data_in(struct ttxt_pes_data_field *t, int len)
+{
+ int i;
+
+ if(len < 1)
+ return;
+
+ if(t->data_identifier < 0x10 || t->data_identifier > 0x1F) {
+ dprint("Unknown data identifier: %02x\n", t->data_identifier);
+ return;
+ }
+
+ for(i = 0; (uint8_t *) (&(t->d[i+1])) < (((uint8_t *) t) + len); i++) {
+ struct ttxt_data_field *d = &(t->d[i]);
+ int i;
+ uint8_t *p;
+ int uh;
+ int mag;
+ int pack;
+
+ // if(d->data_unit_id != ttxt_subtitle)
+ // continue;
+
+ p = (uint8_t *) &(d->mag_addr_ham);
+ for(i = 0; i < 42; i++) {
+ p[i] = invtab[p[i]];
+ }
+
+ uh = unham(d->mag_addr_ham[0], d->mag_addr_ham[1]);
+ mag = uh & 0x7;
+ pack = (uh >> 3) & 0x1f;
+
+ // dump_hex("line", (uint8_t *) d, 20);
+ // dprint("id: %d, line: %d, mag %d, pack: %d\n", d->data_unit_id, TTXT_LINE_NO(*d), mag, pack);
+
+ ttxt_packet_in(d->data_unit_id, mag, pack, d->data);
+ }
+}
+
+
+void
+set_filt(int fd,uint16_t tt_pid, dmx_pes_type_t t)
+{
+ struct dmx_pes_filter_params pesFilterParams;
+
+ memset(&pesFilterParams, 0, sizeof(pesFilterParams));
+
+ pesFilterParams.pid = tt_pid;
+ pesFilterParams.input = DMX_IN_FRONTEND;
+ pesFilterParams.output = DMX_OUT_TAP;
+ pesFilterParams.pes_type = t;
+ pesFilterParams.flags = DMX_IMMEDIATE_START;
+
+ if (ioctl(fd, DMX_SET_PES_FILTER, &pesFilterParams) < 0)
+ perror("DMX SET PES FILTER:");
+
+ // XXX set buffer size too?
+}
+#endif
+
+#if 0
+#define BUFSIZE 2000
+
+// external entry point
+void ttxt_decode(int fd, int tpid, int *doRun)
+{
+ uint8_t buf[BUFSIZE];
+ int n;
+ int hunt = 1;
+
+ set_filt(fd, tpid, DMX_PES_OTHER);
+
+ while (*doRun) {
+ restart:
+ if(hunt) {
+ int i;
+
+ dprint("hunting...\n");
+ n = read(fd, buf, 199);
+ if(n < 0) {
+ perror("ttxt_decode:read: ");
+ goto restart;
+ }
+
+ for(i = 0; i < (n - 3); i++) {
+ if(buf[i] == 0 && buf[i+1] == 0 && buf[i+2] == 1 && buf[i+3] == 0xbd) {
+ struct ttxt_pes_header *p = (struct ttxt_pes_header *) (buf + i);
+ size_t skip = ntohs(p->len) + 6 - (n - i);
+
+ if(skip > 0) {
+ read(fd, buf, skip < sizeof(buf) ? skip : sizeof(buf));
+ if(n < 0) {
+ perror("ttxt_decode:read: ");
+ goto restart;
+ }
+ hunt = 0;
+ break;
+ }
+ }
+ }
+ } else {
+ struct ttxt_pes_header *p = (struct ttxt_pes_header *) buf;
+
+ n = read(fd, buf, 6);
+ if(n < 0) {
+ perror("ttxt_decode:read: ");
+ goto restart;
+ }
+
+ if(p->start[0] != 0 || p->start[1] != 0 || p->start[2] != 1 ||
+ p->stream_id != 0xbd || // private_stream_1
+ ntohs(p->len) > (sizeof(buf) - 6)) {
+ dprint("Lost PES packet sync, hunting...\n");
+ hunt = 1;
+ continue;
+ }
+
+ n = read(fd, buf + 6, ntohs(p->len));
+ if(n < 0) {
+ perror("ttxt_decode:read: ");
+ goto restart;
+ }
+
+ // dprint("len: %d\n", n);
+
+ if(n != ntohs(p->len) ||
+ p->header_len != 0x24) { // header should allways be 0x24 long (EN 300 472)
+ dprint("Error reading PES packet, hunting...\n");
+ hunt = 1;
+ continue;
+ }
+
+ ttxt_pes_data_in((struct ttxt_pes_data_field *) (buf + 45), n + 6 - 45);
+ }
+
+ // fwrite(buf,1,n,stdout);
+ }
+
+ dprint("ttxt_decode DONE!\n");
+}
+#endif
+
+
+#if 0
+int
+main(int argc, char **argv)
+{
+ int fd;
+ int pid;
+ uint8_t buf[BUFSIZE];
+ int n;
+ int hunt = 1;
+
+ if (argc!=2) {
+ fprintf(stderr,"USAGE: %s PID\n", argv[0]);
+ return -1;
+ }
+
+ pid = atoi(argv[1]);
+
+ if((fd = open("/dev/dvb/adapter0/demux0", O_RDWR)) < 0){
+ perror("Error opening demux device: ");
+ return -1;
+ }
+
+ set_filt(fd, pid, DMX_PES_OTHER);
+
+ for (;;) {
+ if(hunt) {
+ int i;
+
+ dprint("hunting...\n");
+ n = read(fd, buf, 199);
+
+ for(i = 0; i < (n - 3); i++) {
+ if(buf[i] == 0 && buf[i+1] == 0 && buf[i+2] == 1 && buf[i+3] == 0xbd) {
+ struct ttxt_pes_header *p = (struct ttxt_pes_header *) (buf + i);
+ int skip = ntohs(p->len) + 6 - (n - i);
+
+ if(skip > 0) {
+ read(fd, buf, skip < sizeof(buf) ? skip : sizeof(buf));
+ hunt = 0;
+ break;
+ }
+ }
+ }
+ } else {
+ struct ttxt_pes_header *p = (struct ttxt_pes_header *) buf;
+
+ n = read(fd, buf, 6);
+
+ if(p->start[0] != 0 || p->start[1] != 0 || p->start[2] != 1 ||
+ p->stream_id != 0xbd || // private_stream_1
+ ntohs(p->len) > (sizeof(buf) - 6)) {
+ dprint("Lost PES packet sync, hunting...\n");
+ hunt = 1;
+ continue;
+ }
+
+ n = read(fd, buf + 6, ntohs(p->len));
+
+ // dprint("len: %d\n", n);
+
+ if(n != ntohs(p->len) ||
+ p->header_len != 0x24) { // header should allways be 0x24 long (EN 300 472)
+ dprint("Error reading PES packet, hunting...\n");
+ hunt = 1;
+ continue;
+ }
+
+ ttxt_pes_data_in((struct ttxt_pes_data_field *) (buf + 45), n + 6 - 45);
+ }
+
+ // fwrite(buf,1,n,stdout);
+ }
+
+ return 0;
+}
+#endif
diff --git a/teletext.h b/teletext.h
new file mode 100644
index 0000000..fa1b519
--- /dev/null
+++ b/teletext.h
@@ -0,0 +1,101 @@
+
+#ifndef __TS_TELETEXT_H
+#define __TS_TELETEXT_H
+
+
+#define PACK __attribute__ ((__packed__))
+
+struct ttxt_pes_header {
+ uint8_t start[3];
+ uint8_t stream_id PACK;
+ uint16_t len PACK;
+
+ // private_stream_1 packets (and others)
+ uint8_t flags[2] PACK;
+ uint8_t header_len PACK;
+};
+
+
+enum {
+ ttxt_non_subtitle = 2,
+ ttxt_subtitle = 3,
+ ttxt_stuff = 0xff
+};
+
+#define TTXT_LINE_NO(x) ((x).par_loff & 0x1f)
+struct ttxt_data_field {
+ uint8_t data_unit_id PACK;
+ uint8_t data_unit_length PACK;
+ uint8_t par_loff PACK;
+ uint8_t framing_code PACK;
+ uint8_t mag_addr_ham[2] PACK;
+ uint8_t data[40] PACK;
+};
+
+
+struct ttxt_pes_data_field {
+ uint8_t data_identifier PACK;
+ struct ttxt_data_field d[1] PACK;
+};
+
+
+// Really 25 + row 0
+#define TTXT_DISPLAYABLE_ROWS 26
+
+enum pageflags {
+ erasepage = 0x01,
+ newsflash = 0x02,
+ subtitle = 0x04,
+ suppress_header = 0x08,
+ inhibit_display = 0x10 // rows 1 - 24 are not to be displayed
+};
+
+struct ttxt_page {
+ uint8_t mag;
+ uint8_t no;
+ uint8_t flags;
+ uint8_t national_charset;
+ uint8_t data[TTXT_DISPLAYABLE_ROWS][40];
+};
+
+// FROM vbidecode
+// unham 2 bytes into 1, report 2 bit errors but ignore them
+unsigned char unham(unsigned char a,unsigned char b);
+
+#define UNHAM_INV(a, b) unham(invtab[a], invtab[b])
+
+// ham 8/4 the byte in into two bytes pointed to by out
+void ham8_4byte(uint8_t in, uint8_t *out);
+// ham 8/4 the nibble in into the byte pointed to by out
+uint8_t ham8_4nibble(uint8_t in);
+
+// odd parity status for last 7 bits of byte
+int parity(uint8_t x);
+
+/*
+ * Map Latin G0 teletext characters into a ISO-8859-1 approximation.
+ * Trying to use similar looking or similar meaning characters.
+ * Gtriplet - 4 bits = triplet-1 bits 14-11 (14-8) of a X/28 or M/29 packet, if unknown - use 0
+ * natopts - 3 bits = national_options field of a Y/0 packet (or triplet-1 bits 10-8 as above?)
+ * inchar - 7 bits = characted to remap
+ * Also strips parity
+ */
+uint8_t ttxt_laG0_la1_char(int Gtriplet, int natopts, uint8_t inchar);
+
+/*
+ * Map Latin G2 teletext characters into a ISO-8859-1 approximation.
+ * Trying to use similar looking or similar meaning characters.
+ */
+uint8_t ttxt_laG2_la1_char(uint8_t inchar);
+
+
+
+// debug stuff
+void print_page(struct ttxt_page *p);
+void print_line(char *p);
+void dump_hex(char *msg, const uint8_t *p, int len);
+
+// invert tab for last 42 bytes in packets
+extern unsigned char invtab[256];
+
+#endif __TS_TELETEXT_H
diff --git a/ttxtsubs.c b/ttxtsubs.c
new file mode 100644
index 0000000..da9c2f1
--- /dev/null
+++ b/ttxtsubs.c
@@ -0,0 +1,357 @@
+/*
+ * ttxtsubs.c: A plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id: ttxtsubs.c,v 1.11 2003/03/07 05:52:58 ragge Exp $
+ */
+
+#include <vdr/plugin.h>
+#include <vdr/status.h>
+#include <vdr/vdrttxtsubshooks.h>
+#include <vdr/menuitems.h>
+#include <vdr/config.h>
+
+#include "ttxtsubsdisplayer.h"
+#include "ttxtsubsrecorder.h"
+#include "utils.h"
+#include "siinfo.h"
+#include "ttxtsubs.h"
+
+static const char *VERSION = "0.0.1";
+static const char *DESCRIPTION = "Teletext subtitles";
+//static const char *MAINMENUENTRY = "Ttxtsubs";
+
+// ISO 639-2 language codes in VDR order
+// XXX should be replaced by something that allows for other languages and for real language names!
+// <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", //Swedish
+ "rom", //Romanian
+ "hun", //Hungarian
+ // Not in translations!
+ "dan" //Danish
+ };
+int gNumLanguages = sizeof(gLanguages) / sizeof(gLanguages[0]);
+
+class cPluginTtxtsubs : public cPlugin, public cStatus, public cVDRTtxtsubsHookListener {
+public:
+ cPluginTtxtsubs(void);
+ virtual ~cPluginTtxtsubs();
+
+ // -- cPlugin
+ virtual const char *Version(void) { return VERSION; }
+ virtual const char *Description(void) { return DESCRIPTION; }
+ virtual const char *CommandLineHelp(void);
+ virtual bool ProcessArgs(int argc, char *argv[]);
+ virtual bool Start(void);
+ virtual void Housekeeping(void);
+ //virtual const char *MainMenuEntry(void) { return MAINMENUENTRY; }
+ 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);
+ virtual void Replaying(const cControl *Control, const char *Name);
+ // The given player control has started replaying Name. Name is the name of the
+ // recording, without any directory path. In case of a player that can't provide
+ // a name, Name can be a string that identifies the player type (like, e.g., "DVD").
+ // If Name is NULL, the replay has ended.
+ // virtual void OsdClear(void) { ShowTtxt(); }
+ // virtual void OsdTitle(const char *Title) { HideTtxt(); }
+ // virtual void OsdStatusMessage(const char *Message) { HideTtxt(); }
+ // virtual void OsdHelpKeys(const char *Red, const char *Green, const char *Yellow, const char *Blue) { HideTtxt(); }
+ // virtual void OsdCurrentItem(const char *Text) { HideTtxt(); }
+ // virtual void OsdTextItem(const char *Text, bool Scroll) { HideTtxt(); }
+ // virtual void OsdChannel(const char *Text) { HideTtxt(); }
+ // virtual void OsdProgramme(time_t PresentTime, const char *PresentTitle, const char *PresentSubtitle, time_t FollowingTime, const char *FollowingTitle, const char *FollowingSubtitle) { HideTtxt(); }
+
+ // -- cVDRTtxtsubsHookListener
+ virtual void HideOSD(void) { HideTtxt(); }
+ virtual void ShowOSD(void) { ShowTtxt(); }
+ virtual void PlayerTeletextData(uint8_t *p, int length);
+ virtual cTtxtSubsRecorderBase *NewTtxtSubsRecorder(cDevice *dev, const cChannel *ch);
+
+ // -- internal
+ private:
+ void StartTtxtLive(const cDevice *Device, int pid, int page);
+ void StartTtxtPlay(int page);
+ void StopTtxt(void);
+ void ShowTtxt(void);
+ void HideTtxt(void);
+
+private:
+ // Add any member variables or functions you may need here.
+ cTtxtSubsDisplayer *mRec;
+
+ // Setup items
+ char mLanguage[4];
+ int mHearingImpaired;
+ int mRecord;
+
+ // ugly hack for now
+ int mPage;
+};
+
+class cMenuSetupTtxtsubs : public cMenuSetupPage {
+ public:
+ cMenuSetupTtxtsubs(cPluginTtxtsubs *ttxtsubs);
+ protected:
+ virtual void Store(void);
+ private:
+ cPluginTtxtsubs *mTtxtsubs;
+ char mLanguage[4];
+ int mLanguageNo;
+ int mHearingImpaired;
+ int mRecord;
+};
+
+
+cPluginTtxtsubs::cPluginTtxtsubs(void)
+ :
+ mRec(NULL),
+ mHearingImpaired(0),
+ mRecord(1),
+ mPage(0x199)
+{
+ // 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);
+}
+
+cPluginTtxtsubs::~cPluginTtxtsubs()
+{
+ // Clean up after yourself!
+}
+
+const char *cPluginTtxtsubs::CommandLineHelp(void)
+{
+ // Return a string that describes all known command line options.
+ return NULL;
+}
+
+bool cPluginTtxtsubs::ProcessArgs(int argc, char *argv[])
+{
+ // Implement command line argument processing here if applicable.
+ return true;
+}
+
+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);
+ } else {
+ strncpy(mLanguage, gLanguages[n], 4);
+ mLanguage[3] = '\0';
+ }
+ }
+
+ //dprint("cPluginTtxtsubs::Start\n");
+
+ HookAttach();
+
+ return true;
+}
+
+void cPluginTtxtsubs::Housekeeping(void)
+{
+ // Perform any cleanup or other regular tasks.
+}
+
+cOsdObject *cPluginTtxtsubs::MainMenuAction(void)
+{
+ // Perform the action when selected from the main VDR menu.
+ return NULL;
+}
+
+cMenuSetupPage *cPluginTtxtsubs::SetupMenu(void)
+{
+ return new cMenuSetupTtxtsubs(this);
+}
+
+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);
+ else
+ return false;
+ return true;
+}
+
+void cPluginTtxtsubs::ChannelSwitch(const cDevice *Device, int ChannelNumber)
+{
+ if(ChannelNumber) {
+ cChannel *c = Channels.GetByNumber(ChannelNumber);
+ if(c) {
+ struct ttxtinfo info;
+ int pid, page;
+
+ if(GetTtxtInfo(Device->DeviceNumber(), c->Sid(), c->Vpid(), &info)) {
+ fprintf(stderr, "ttxtsubs: Error: GetTtxtInfo failed!\n");
+ } else {
+ if(FindSubs(&info, mLanguage, mHearingImpaired, &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);
+ }
+ FreeTtxtInfoData(&info);
+ }
+ }
+ } else
+ StopTtxt();
+}
+
+void cPluginTtxtsubs::Replaying(const cControl *Control, const char *Name)
+{
+ StopTtxt();
+ StartTtxtPlay(mPage); // XXX should get page in some other way!
+}
+
+void cPluginTtxtsubs::PlayerTeletextData(uint8_t *p, int length)
+{
+ cTtxtSubsPlayer *r = dynamic_cast<cTtxtSubsPlayer *>(mRec);
+
+ if(!r) {
+ fprintf(stderr, "ttxtsubs: ERROR: not a cTtxtSubsPlayer!\n");
+ return;
+ }
+
+ r->PES_data(p, length);
+}
+
+cTtxtSubsRecorderBase *cPluginTtxtsubs::NewTtxtSubsRecorder(cDevice *dev, const cChannel *ch)
+{
+ if(mRecord)
+ return new cTtxtSubsRecorder(dev, ch, mLanguage, mHearingImpaired);
+ else
+ return NULL;
+}
+
+
+// -- internal
+
+void cPluginTtxtsubs::StartTtxtLive(const cDevice *Device, int pid, int page)
+{
+ //dprint("cPluginTtxtsubs::StartTtxtLive\n");
+
+ if(!mRec) {
+ cTtxtSubsLiveReceiver *r;
+ //dprint("teletext subtitles started on pid %d\n", pid);
+ mRec = r = new cTtxtSubsLiveReceiver(pid, page);
+ if(!cDevice::PrimaryDevice()->AttachReceiver(r))
+ fprintf(stderr, "ttxtsubs: Error: AttachReceiver failed!\n"); //
+ } else
+ fprintf(stderr, "ttxtsubs: Error: StartTtxtLive called when already started!\n");
+}
+
+void cPluginTtxtsubs::StartTtxtPlay(int backup_page)
+{
+ //dprint("cPluginTtxtsubs::StartTtxtPlay\n");
+
+ if(!mRec) {
+ dprint("ttxtsubs: teletext subtitles replayer started with initial page %03x\n", backup_page);
+ mRec = new cTtxtSubsPlayer(mLanguage, mHearingImpaired, backup_page);
+ } else
+ fprintf(stderr, "ttxtsubs: Error: StartTtxtPlay called when already started!\n");
+}
+
+void cPluginTtxtsubs::StopTtxt(void)
+{
+ //dprint("cPluginTtxtsubs::StopTtxt\n");
+
+ if(mRec) {
+ HideTtxt();
+ delete mRec;
+ mRec = NULL;
+ }
+}
+
+void cPluginTtxtsubs::ShowTtxt(void)
+{
+ if(mRec)
+ mRec->ShowDisplay();
+}
+
+void cPluginTtxtsubs::HideTtxt(void)
+{
+ if(mRec)
+ mRec->HideDisplay();
+}
+
+// ----- cMenuSetupTtxtsubs -----
+
+cMenuSetupTtxtsubs::cMenuSetupTtxtsubs(cPluginTtxtsubs *ttxtsubs)
+ :
+ mTtxtsubs(ttxtsubs),
+ mLanguageNo(-1),
+ mHearingImpaired(mTtxtsubs->HearingImpaired()),
+ mRecord(mTtxtsubs->Record())
+{
+ memcpy(mLanguage, ttxtsubs->Language(), 3);
+ mLanguage[3] = '\0';
+
+ for(int i = 0; i < gNumLanguages; i++) {
+ if(!memcmp(mLanguage, gLanguages[i], 3)) {
+ mLanguageNo = i;
+ break;
+ }
+ }
+
+ 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")));
+}
+
+void cMenuSetupTtxtsubs::Store(void)
+{
+ if(mLanguageNo >= 0) {
+ SetupStore("Language", gLanguages[mLanguageNo]);
+ mTtxtsubs->SetLanguage(gLanguages[mLanguageNo]);
+ } else {
+ SetupStore("Language", mLanguage);
+ mTtxtsubs->SetLanguage(mLanguage);
+ }
+
+ SetupStore("HearingImpaired", mHearingImpaired);
+ mTtxtsubs->SetHearingImpaired(mHearingImpaired);
+
+ SetupStore("Record", mRecord);
+ mTtxtsubs->SetRecord(mRecord);
+}
+
+
+VDRPLUGINCREATOR(cPluginTtxtsubs); // Don't touch this!
diff --git a/ttxtsubs.h b/ttxtsubs.h
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/ttxtsubs.h
@@ -0,0 +1 @@
+
diff --git a/ttxtsubsdisplay.c b/ttxtsubsdisplay.c
new file mode 100644
index 0000000..370b4c9
--- /dev/null
+++ b/ttxtsubsdisplay.c
@@ -0,0 +1,331 @@
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/time.h>
+#include <time.h>
+
+#include <vdr/osd.h>
+#include <vdr/osdbase.h>
+#include <vdr/thread.h>
+
+#include "ttxtsubsdisplay.h"
+#include "utils.h"
+
+// pageState
+enum {
+ invalid = 0,
+ collecting,
+ interimshow,
+ finished
+};
+
+
+cTtxtSubsDisplay::cTtxtSubsDisplay(void)
+ :
+ mPageState(invalid),
+ mMag(0),
+ mNo(0),
+ mDoDisplay(0),
+ mOsd(NULL),
+ mOsdLock(),
+ mLastDataTime(NULL)
+{
+ memset(&page.data, 0, sizeof(page.data));
+ mLastDataTime = (struct timeval *) calloc(1, sizeof(*mLastDataTime));
+}
+
+
+cTtxtSubsDisplay::~cTtxtSubsDisplay(void)
+{
+ if(mLastDataTime)
+ free(mLastDataTime);
+ ClearOSD();
+}
+
+
+void cTtxtSubsDisplay::SetPage(int Pageno)
+{
+ Clear();
+
+ mMag = (Pageno >> 8) & 0xF;
+ mNo = Pageno & 0xFF;
+}
+
+
+void cTtxtSubsDisplay::Hide(void)
+{
+ //dprint("cTtxtSubsDisplay::Hide\n");
+ mDoDisplay = 0;
+ ClearOSD();
+}
+
+
+void cTtxtSubsDisplay::Show(void)
+{
+ //dprint("cTtxtSubsDisplay::Show\n");
+ mDoDisplay = 1;
+ ShowOSD();
+}
+
+
+void cTtxtSubsDisplay::Clear(void)
+{
+ mPageState = invalid;
+ ClearOSD();
+}
+
+
+void cTtxtSubsDisplay::TtxtData(const uint8_t *Data)
+{
+ int mp;
+ int mag; // X in ETSI EN 300 706
+ int packet; // Y
+ struct ttxt_data_field *d;
+
+ // idle time - if we have been sitting on a half finished page for a while - show it
+ if(Data == NULL && mPageState == collecting) {
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ // add second diff to usecs
+ tv.tv_usec += 1000000 * (tv.tv_sec - mLastDataTime->tv_sec);
+
+ if((tv.tv_usec - mLastDataTime->tv_usec) > 500000) {
+ //dprint("Interimshow!\n");
+ mPageState = interimshow;
+ ClearOSD();
+ ShowOSD();
+ }
+ }
+
+ if(Data == NULL)
+ return;
+
+ //print_line((char *) Data);
+
+ d = (struct ttxt_data_field *) Data;
+
+ mp = unham(invtab[d->mag_addr_ham[0]], invtab[d->mag_addr_ham[1]]);
+ mag = mp & 0x7;
+ packet = (mp >> 3) & 0x1f;
+
+ //dprint("cTtxtSubsDisplay::TtxtData: mag:%d packet: %d\n", mag, packet); // XXX
+
+ if(packet == 0) {
+ int i, no;
+ uint8_t fi[8]; // flags field inverted
+
+ for(i = 0; i < 8; i++)
+ fi[i] = invtab[d->data[i]];
+
+ if(mPageState == collecting) {
+ mPageState = finished;
+ ClearOSD();
+ ShowOSD();
+ }
+ if(mPageState == interimshow)
+ mPageState = finished;
+
+ no = unham(fi[0], fi[1]);
+
+ //dprint("cTtxtSubsDisplay::TtxtData: page:%d%02x, packet: %d\n", mag, no, packet); // XXX
+ //print_line((char *) Data);
+
+ if(mag == mMag && no == mNo) {
+ //dprint("cTtxtSubsDisplay::TtxtData: page:%d%02x, packet: %d\n", mag, no, packet); // XXX
+ page.mag = mag;
+ page.no = no;
+ page.flags = 0;
+ page.national_charset = 0;
+
+ if(fi[3] & 0x80) { // Newsflash
+ page.flags |= erasepage;
+ memset(&page.data, 0, sizeof(page.data)); // only if erasepage is set?
+ }
+ if(fi[5] & 0x20) // Subtitle
+ page.flags |= newsflash;
+ if(fi[5] & 0x80) // Suppress Header
+ page.flags |= subtitle;
+ // if(fi[6] & 0x08) // Update Indicator
+ // if(fi[6] & 0x20) // Interrupted Sequence
+ if(fi[6] & 0x80) // Inhibit Display
+ page.flags |= inhibit_display;
+ // if(fi[7] & 0x02) // Magazine Serial
+
+ page.national_charset = ((fi[7] & 0x80) >> 5) +
+ ((fi[7] & 0x20) >> 4) + ((fi[7] & 0x08) >> 3);
+
+ mPageState = collecting;
+ gettimeofday(mLastDataTime, NULL);
+
+ for(i = 0; i < 32; i++)
+ page.data[0][i] = invtab[d->data[i+8]];
+ }
+ } else if(mag == page.mag && packet < TTXT_DISPLAYABLE_ROWS &&
+ (mPageState == collecting || mPageState == interimshow)) {
+ // mag == page.mag: The magazines can be sent interleaved
+ int i;
+ for(i = 0; i < 40; i++)
+ page.data[packet][i] = invtab[d->data[i]];
+
+ mPageState = collecting;
+ gettimeofday(mLastDataTime, NULL);
+
+ //dprint("row: %d ", packet);
+ //dump_hex("", page.data[packet], 40);
+ } else {
+ // packets with national characters information: X/28/0 format 1, X/28/1, X/28/4, M/29/0 M/29/4,
+ if(packet == 28 || packet == 29)
+ dprint("mag: %d, packet: %d, page: %02x, state: %d\n", page.mag, packet, page.no, mPageState);
+
+ //if(packet == 26) {
+ // dprint("mag: %d, packet: %d, page: %02x, state: %d\n", page.mag, packet, page.no, mPageState);
+ // dump_hex("", page.data[packet], 40);
+ //}
+ }
+}
+
+
+static char *
+ttxt2la1(uint8_t *p, char *buf, int natopts)
+{
+ int i, j = 0, skip = 1;
+ for(i = 0; i < 40; i++) {
+ uint8_t c = p[i] & 0x7f;
+
+ if(c == 0x0b) { // box begin
+ skip = 0;
+ if(j != 0) // if we have chars in buf
+ buf[j++] = ' ';
+ continue;
+ }
+
+ if(c == 0x0a) { // box end
+ skip = 1;
+ continue;
+ }
+
+ if(skip)
+ continue;
+
+ if(j == 0 && c == 20) // skip leading spaces
+ continue;
+
+ if(c >= 0x20) {
+ buf[j++] = ttxt_laG0_la1_char(0, natopts, c);
+ }
+ }
+
+ while(j > 0 && buf[j-1] == ' ') // strip extra spaces
+ j--;
+
+ buf[j++] = '\0';
+ if(strlen(buf))
+ return buf;
+ else
+ return NULL;
+}
+
+#define SCREENLEFT 125
+#define SCREENBOT 540
+#if 0
+#define ROWINCR 45
+#define ROWH 36
+#define TEXTY 4
+#else
+#define ROWINCR 43
+#define ROWH 34
+#define TEXTY 3
+#endif
+#define TEXTX 15
+#define SCREENTOP (SCREENBOT - MAXTTXTROWS * ROWINCR)
+
+void cTtxtSubsDisplay::ShowOSD(void)
+{
+ int i, y;
+ int rowcount = 0;
+ char buf[TTXT_DISPLAYABLE_ROWS][41];
+ int doneWidthWorkaround = 0;
+
+ cMutexLock lock(&mOsdLock);
+
+ if(!mDoDisplay) {
+ //dprint("NOT displaying subtitles because of other OSD activities!\n");
+ return;
+ }
+
+ if(mPageState != interimshow && mPageState != finished) {
+ //dprint("NOT displaying subtitles because page state: %d!\n", mPageState);
+ return;
+ }
+
+ if(mOsd != NULL)
+ ClearOSD();
+
+ //print_page(&page);
+
+ for(i = 1; i < 24; i++) {
+ if(page.data[i][0] != 0) // anything on this line?
+ if(ttxt2la1(page.data[i], buf[rowcount], page.national_charset))
+ rowcount++;
+ }
+
+#if 0
+ mOsd = cOsd::OpenRaw(SCREENLEFT, SCREENTOP);
+#else
+ mOsd = cDevice::PrimaryDevice()->NewOsd(SCREENLEFT, SCREENTOP);
+#endif
+ if(mOsd == NULL) {
+ dprint("Error: cOsd::OpenRaw returned NULL!\n");
+ return;
+ }
+
+ mOsd->SetFont(fontOsd);
+
+ if(rowcount > MAXTTXTROWS)
+ rowcount = MAXTTXTROWS;
+
+ y = SCREENBOT - SCREENTOP - ROWH - (ROWINCR * (rowcount-1));
+ for(i = 0; i < rowcount; i++) {
+ tWindowHandle wind;
+ int w = 0; // XXX should be text width - not %4!
+
+ // XXX Width calculations doesn't work before we have created a window...
+ if(!doneWidthWorkaround) {
+ wind = mOsd->Create(0, y, 4, ROWH, 2);
+ w = mOsd->Width(buf[i]) + 2 * TEXTX;
+ mOsd->Clear(wind);
+ mOsd->Hide(wind);
+ doneWidthWorkaround = 1;
+ } else
+ 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, clrBackground, wind);
+ mOsd->Text(TEXTX, y + TEXTY, buf[i], clrWhite, clrBackground, wind);
+
+ y += ROWINCR;
+ }
+
+ mOsd->Flush();
+}
+
+
+void cTtxtSubsDisplay::ClearOSD(void)
+{
+ // dprint("\nClearOSD!\n");
+ cMutexLock lock(&mOsdLock);
+
+ if(mOsd) {
+
+ mOsd->Clear(ALL_WINDOWS);
+ mOsd->Hide(ALL_WINDOWS);
+ mOsd->Flush();
+ delete mOsd;
+ mOsd = NULL;
+ }
+}
diff --git a/ttxtsubsdisplay.h b/ttxtsubsdisplay.h
new file mode 100644
index 0000000..42905ff
--- /dev/null
+++ b/ttxtsubsdisplay.h
@@ -0,0 +1,35 @@
+
+#include "teletext.h"
+
+struct ttxt_data_field;
+struct timeval;
+
+class cOsdBase;
+
+#define MAXTTXTROWS 5
+
+class cTtxtSubsDisplay {
+ public:
+ cTtxtSubsDisplay(void);
+ ~cTtxtSubsDisplay(void);
+
+ void SetPage(int Pageno);
+ void Hide(void);
+ void Show(void);
+ void Clear(void);
+ void TtxtData(const uint8_t *);
+
+ protected:
+ void ShowOSD();
+ void ClearOSD(void);
+
+ private:
+ int mPageState;
+ int mMag;
+ int mNo;
+ int mDoDisplay;
+ struct ttxt_page page;
+ cOsdBase *mOsd;
+ cMutex mOsdLock;
+ struct timeval *mLastDataTime;
+};
diff --git a/ttxtsubsdisplayer.c b/ttxtsubsdisplayer.c
new file mode 100644
index 0000000..6983cd2
--- /dev/null
+++ b/ttxtsubsdisplayer.c
@@ -0,0 +1,256 @@
+
+#include "ttxtsubsdisplayer.h"
+#include "ttxtsubsdisplay.h"
+#include "utils.h"
+#include "ttxtsubs.h"
+#include "siinfo.h"
+
+// ----- class cTtxtSubsDisplayer -----
+
+cTtxtSubsDisplayer::cTtxtSubsDisplayer(int textpage)
+ :
+ mDisp(NULL),
+ mGetMutex(),
+ mGetCond(),
+ mRingBuf(18800, true),
+ mRun(0)
+{
+ mDisp = new cTtxtSubsDisplay();
+ mDisp->SetPage(textpage);
+ ShowDisplay();
+
+ mRun = 1;
+ this->Start(); // start thread
+}
+
+cTtxtSubsDisplayer::~cTtxtSubsDisplayer(void)
+{
+ // stop thread
+ mRun = false;
+ mGetCond.Broadcast();
+ this->Cancel(5);
+
+ if(mDisp) {
+ delete mDisp;
+ mDisp = NULL;
+ }
+}
+
+// handled messages in ring buffer - runs in a thread of its own
+void cTtxtSubsDisplayer::Action(void)
+{
+ while(mRun) {
+ const cFrame *f;
+
+ f = mRingBuf.Get();
+
+ if(f) {
+ mDisp->TtxtData(f->Data());
+ mRingBuf.Drop(f);
+ } else {
+ // wait for more data
+ mGetMutex.Lock();
+ mGetCond.TimedWait(mGetMutex, 500);
+ mDisp->TtxtData(NULL); // give display some idle time
+ mGetMutex.Unlock();
+ }
+ }
+}
+
+void cTtxtSubsDisplayer::ShowDisplay(void)
+{
+ mDisp->Show();
+}
+
+void cTtxtSubsDisplayer::HideDisplay(void)
+{
+ mDisp->Hide();
+}
+
+
+// ----- class cTtxtSubsLiveReceiver -----
+
+cTtxtSubsLiveReceiver::cTtxtSubsLiveReceiver(int Pid, int textpage)
+ :
+ cReceiver(0, -1, 1, Pid),
+ cTtxtSubsDisplayer(textpage)
+{
+}
+
+void cTtxtSubsLiveReceiver::Activate(bool On)
+{
+ //dprint("cTtxtSubsLiveReceiver::Activate: On:%d\n", On);
+}
+
+// Take TS packets and break out the teletext data
+// Buffer the data for processing in a separate thread
+// XXX We should do some filtering here to avoid unneccessary load!
+void cTtxtSubsLiveReceiver::Receive(uchar *Data, int Length)
+{
+ int i;
+
+ if(Length != 188) // should never happen
+ return;
+
+ if(Data[1] & 0x80) // transport_error_indicator
+ return;
+
+ // payload_unit_start_indicator
+ for(i = (Data[1] & 0x40) ? 1 : 0; i < 4; i++) {
+ if(0xff == Data[4 + i*46]) // stuffing data
+ continue;
+
+ cFrame *f = new cFrame(Data + 4 + i*46, 46);
+ mRingBuf.Put(f);
+ mGetCond.Broadcast();
+ }
+}
+
+// ----- class cTtxtSubsPlayer -----
+
+cTtxtSubsPlayer::cTtxtSubsPlayer(char *lang, int HI, int backup_textpage)
+ :
+ cTtxtSubsDisplayer(backup_textpage),
+ mHearingImpaired(HI),
+ mHasFilteredStream(0),
+ mFoundLangPage(0),
+ mLangInfoState(0)
+{
+ memcpy(mLanguage, lang, 3);
+ mLanguage[3] = '\0';
+}
+
+// Take PES packets and break out the teletext data
+// Buffer the data for processing in a separate thread
+// XXX We should do some filtering here to avoid unneccessary load!
+void cTtxtSubsPlayer::PES_data(uchar *p, int Length)
+{
+ int i;
+
+ //printf("cTtxtSubsPlayer: len: %d\n", Length); // XXX
+
+ if(Length < 46 || p[0] != 0 || p[1] != 0 || p[2] != 1 || p[3] != 0xbd || p[8] != 0x24 ||
+ p[45] < 0x10 || p[45] >= 0x20) {
+ fprintf(stderr, "cTtxtSubsPlayer::PES_data: bad indata!\n");
+ }
+
+ // Recorded teletext typically has payload type 0x10.
+ // We use the payload type 0x1f to indicate that we have our own
+ // filtered stream, if we find that we only use that.
+
+ if(p[45] == 0x1f && !mHasFilteredStream)
+ mHasFilteredStream = 1;
+
+ if(mHasFilteredStream && p[45] != 0x1f)
+ return; // other teletext data, ignore
+
+ if(mHasFilteredStream && !mFoundLangPage)
+ SearchLanguagePage(p, Length);
+
+ // payload_unit_start_indicator
+ for(i = 1; (i*46) < Length ; i++) {
+ if(0xff == p[i*46]) // stuffing data
+ continue;
+
+ cFrame *f = new cFrame(p + i*46, 46);
+ mRingBuf.Put(f);
+ mGetCond.Broadcast();
+ }
+}
+
+static void copy_inv_strip_par(uint8_t *dest, uint8_t *src, size_t len)
+{
+ size_t i;
+ for(i = 0; i < len; i++)
+ dest[i] = invtab[src[i]] & 0x7f;
+}
+
+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;
+
+ struct ttxt_data_field *d;
+ for(d = ((struct ttxt_data_field *) p) + 1; ((uint8_t *) d) < (p + len + 45); d++) {
+ int mp = UNHAM_INV(d->mag_addr_ham[0], d->mag_addr_ham[1]);
+ //int mag = mp & 0x7;
+ int packet = (mp >> 3) & 0x1f;
+ uint8_t buf[40];
+
+ if(d->data_unit_id != 0x02)
+ break; // a subtitle line
+
+ switch(foundlines) {
+ case 0:
+ if(packet != 0) // need a Y0 first
+ return;
+ foundlines++;
+ break;
+ case 1:
+ if(packet != 1) // need a Y1
+ return;
+ copy_inv_strip_par(buf, d->data, sizeof(buf));
+ if(memcmp((char *) buf, infoline, strlen(infoline)))
+ return;
+ foundlines++;
+ 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;
+ 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] &&
+ ((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] == ' ') {
+ unsigned int page =
+ ((buf[i+4] - '0') << 8) +
+ ((buf[i+5] - '0') << 4) +
+ (buf[i+6] - '0');
+ if(page >= 0x100 && page <= 0x900) {
+ if(page >= 0x800)
+ page -= 0x800;
+
+ if(((!mHearingImpaired) && (buf[3+i] == ' ')) ||
+ (mHearingImpaired && (buf[3+i] == 'h'))) {
+ 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
+ }
+ }
+ }
+ }
+ 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
new file mode 100644
index 0000000..00127ff
--- /dev/null
+++ b/ttxtsubsdisplayer.h
@@ -0,0 +1,47 @@
+
+#include <vdr/receiver.h>
+#include <vdr/ringbuffer.h>
+
+class cTtxtSubsDisplay;
+
+class cTtxtSubsDisplayer : public cThread {
+ public:
+ cTtxtSubsDisplayer(int textpage);
+ ~cTtxtSubsDisplayer(void);
+ void ShowDisplay(void);
+ void HideDisplay(void);
+
+ protected:
+ virtual void Action(void);
+
+ protected:
+ cTtxtSubsDisplay *mDisp;
+ cMutex mGetMutex;
+ cCondVar mGetCond;
+ cRingBufferFrame mRingBuf;
+ int mRun;
+};
+
+class cTtxtSubsLiveReceiver : public cReceiver, public cTtxtSubsDisplayer {
+ public:
+ cTtxtSubsLiveReceiver(int Pid, int textpage);
+
+ protected:
+ virtual void Activate(bool On);
+ virtual void Receive(uchar *Data, int Length);
+};
+
+class cTtxtSubsPlayer : public cTtxtSubsDisplayer {
+ public:
+ cTtxtSubsPlayer(char *lang, int HI, 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 mLangInfoState;
+};
diff --git a/ttxtsubsfilter.c b/ttxtsubsfilter.c
new file mode 100644
index 0000000..a50a3fb
--- /dev/null
+++ b/ttxtsubsfilter.c
@@ -0,0 +1,158 @@
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "ttxtsubsfilter.h"
+#include "teletext.h"
+
+cTtxtSubsFilter::cTtxtSubsFilter(void)
+ :
+ mCount(0),
+ mPages(0)
+{
+ int i;
+ for(i = 0; i < 8; i++)
+ mMagPage[i] = -1;
+}
+
+
+cTtxtSubsFilter::~cTtxtSubsFilter(void)
+{
+ FreePages();
+}
+
+
+void cTtxtSubsFilter::SetPages(int count, uint16_t *pages)
+{
+ FreePages();
+
+ mPages = (uint16_t *) malloc(count * sizeof(uint16_t));
+
+ memcpy((char *) mPages, (char *) pages, count * sizeof(uint16_t));
+
+ mCount = count;
+
+ if(0) {
+ int i;
+ fprintf(stderr, "SetPages: %d, ", count);
+ for(i = 0; i < count; i++)
+ fprintf(stderr, "%03x ", pages[i]);
+ fprintf(stderr, "\n");
+ }
+}
+
+// XXX IF MERGING PAGES FROM SEVERAL PIDS (which we currently don't)
+// Need to insert a line Y0 if
+// - there has been data from other streams inserted and the next line is not a Y0
+// there has been data from other magazines and the next line for this mag is not a Y0
+// indata and outdata are pointers to a ttxt data row buffers,
+// if return value <> 0 outdata countains data to pass on
+int cTtxtSubsFilter::Filter(char *indata, char *outdata)
+{
+ int mp;
+ int mag; // X in ETSI EN 300 706
+ int packet; // Y
+ int result = 0;
+
+ struct ttxt_data_field *d = (struct ttxt_data_field *) indata;
+
+ mp = UNHAM_INV(d->mag_addr_ham[0], d->mag_addr_ham[1]);
+ mag = mp & 0x7;
+ packet = (mp >> 3) & 0x1f;
+
+ if (0) { // XXX
+ uint8_t buf[2];
+ ham8_4byte(mp, buf);
+ fprintf(stderr, "unham %02x %02x -> %02x, ham: %02x %02x\n",
+ invtab[d->mag_addr_ham[0]], invtab[d->mag_addr_ham[1]],
+ mp,
+ buf[0], buf[1]);
+ }
+
+ // packet 0, change page no in this magazine
+ if(packet == 0) {
+ uint8_t no;
+ int newindex;
+
+ no = UNHAM_INV(d->data[0], d->data[1]);
+
+ mMagPage[mag] = newindex = Find(mCount, mPages, (mag << 8) | no);
+
+ if(newindex != -1) {
+ //fprintf(stderr, "Filter: %x%02x (%d)\n", mag, no, newindex); // XXX
+
+ MakeY0(outdata, indata, mPages[newindex]);
+ result = 1;
+ }
+ } else if(packet >= 1 && packet <= 25) {
+ if(mMagPage[mag] != -1) {
+ memcpy(outdata, indata, sizeof(ttxt_data_field));
+ result = 1;
+ }
+ }
+
+ return result;
+}
+
+
+void cTtxtSubsFilter::FreePages(void)
+{
+ mCount = 0;
+
+ if(mPages) {
+ free(mPages);
+ mPages = NULL;
+ }
+}
+
+
+int cTtxtSubsFilter::Find(int count, uint16_t *pages, uint16_t pageno){
+
+ int i;
+ for(i = 0; i < count; i++) {
+ if(pages[i] == pageno)
+ return i;
+ }
+
+ return -1;
+}
+
+
+// make a new Y0 line with the flags in
+void cTtxtSubsFilter::MakeY0(char *outdata, char *indata, uint16_t newpageno)
+{
+ struct ttxt_data_field *i = (struct ttxt_data_field *) indata;
+ struct ttxt_data_field *o = (struct ttxt_data_field *) outdata;
+ uint8_t hambuf[2];
+
+ // new magazine number (Y = 0)
+ ham8_4byte((newpageno >> 8) & 0x7, hambuf);
+ o->mag_addr_ham[0] = invtab[hambuf[0]];
+ o->mag_addr_ham[1] = invtab[hambuf[1]];
+
+ // new page number
+ ham8_4byte(newpageno & 0xff, hambuf);
+ o->data[0] = invtab[hambuf[0]];
+ o->data[1] = invtab[hambuf[1]];
+
+ // copy flags
+ memcpy(o->data + 2, i->data + 2, 6);
+
+ // new text
+ char txtbuf[32];
+ size_t txtlen;
+ size_t n;
+
+ txtlen = snprintf(txtbuf, 32, "%03x", newpageno < 0x100 ? newpageno + 0x800 : newpageno);
+
+ for(n = 0; n < txtlen; n++) {
+ if(parity(txtbuf[n]))
+ o->data[n + 8] = invtab[txtbuf[n] | 0x80]; // XXX wrong parity?
+ else
+ o->data[n + 8] = invtab[txtbuf[n]]; // XXX wrong parity?
+ }
+ for(; n < 32; n++)
+ o->data[n + 8] = invtab[' ']; // space already has right parity
+}
diff --git a/ttxtsubsfilter.h b/ttxtsubsfilter.h
new file mode 100644
index 0000000..3e09b20
--- /dev/null
+++ b/ttxtsubsfilter.h
@@ -0,0 +1,27 @@
+
+// ----- cTtxtSubsFilter -----
+
+// Filters out the pages on one teletext stream
+class cTtxtSubsFilter
+{
+ public:
+ cTtxtSubsFilter(void);
+ virtual ~cTtxtSubsFilter(void);
+
+ void SetPages(int count, uint16_t *pages);
+
+ // indata and outdata are pointers to a ttxt data row buffers,
+ // if return value <> 0 outdata countains data to pass on
+ int Filter(char *indata, char *outdata);
+
+ private:
+ void FreePages(void);
+ int Find(int count, uint16_t *pages, uint16_t pageno);
+ void MakeY0(char *outdata, char *indata, uint16_t newpageno);
+
+ int mCount; // number of pages to filter out
+ uint16_t *mPages; // array of page numbers
+ int mMagPage[8]; /* index into pages for each mag number, or -1 if
+ not currently collecting pages for that mag */
+};
+
diff --git a/ttxtsubsreceiver.c b/ttxtsubsreceiver.c
new file mode 100644
index 0000000..a497fd6
--- /dev/null
+++ b/ttxtsubsreceiver.c
@@ -0,0 +1,253 @@
+
+#include <stdlib.h>
+#include <vdr/receiver.h>
+#include <vdr/ringbuffer.h>
+
+#include "siinfo.h"
+#include "ttxtsubsfilter.h"
+#include "ttxtsubsreceiver.h"
+#include "teletext.h"
+
+#define MAXINDEXPAGELINES 24
+
+// ----- cTtxtSubsReceiver -----
+
+cTtxtSubsReceiver::cTtxtSubsReceiver(int Ca, struct ttxtpidinfo *PI)
+ :
+ cReceiver(Ca, -1, 1, PI->pid),
+ mGetMutex(),
+ mGetCond(),
+ mRingBuf(46 * 500, true),
+ mPI(*PI),
+ mIndexPageLines(0),
+ mIndexPageCol(0),
+ mIndexPageNo(0)
+{
+ int count = 0;
+ uint16_t *pages = (uint16_t *) malloc(sizeof(uint16_t) * mPI.pagecount);
+
+ // find a free page to put the index page on
+ mIndexPageNo = 0x100;
+ int again;
+ do {
+ again = 0;
+ for(int i = 0; i < count; i++) {
+ if(pages[i] == mIndexPageNo) {
+ mIndexPageNo++;
+ if((mIndexPageNo & 0x000f) == 0x000a)
+ mIndexPageNo += 6;
+ again = 1;
+ }
+ }
+ } while(again);
+
+ // get pages to record
+ for(int i = 0; i < mPI.pagecount; i++) {
+ // only subtitle pages
+ if(mPI.i[i].type != TTXT_SUBTITLE_PAGE &&
+ mPI.i[i].type != TTXT_SUBTITLE_HEARING_IMPAIRED_PAGE)
+ continue;
+
+ pages[count] = (mPI.i[i].mag << 8) + mPI.i[i].page;
+
+ AddIndexInfo(mPI.i[i].lang, mPI.i[i].type, pages[count]);
+
+ count++;
+ }
+
+ mFilter.SetPages(count, pages);
+ EndIndexInfo();
+
+ free(pages);
+}
+
+
+cTtxtSubsReceiver::~cTtxtSubsReceiver()
+{
+ // get any waiting threads going
+ mGetCond.Broadcast();
+}
+
+
+// returns pointer buf if there is new data
+uint8_t *cTtxtSubsReceiver::Get(uint8_t *buf)
+{
+ const cFrame *f;
+
+ f = mRingBuf.Get();
+ if(f) {
+ memcpy(buf, f->Data(), 46);
+ mRingBuf.Drop(f);
+ // fprintf(stderr, "cTtxtSubsReceiver::Get: returned data!\n");
+ return buf;
+ } else {
+ return NULL;
+ }
+}
+
+
+void cTtxtSubsReceiver::IndexPage(char ***lines, int *linecount)
+{
+ static char *lineptrs[MAXINDEXPAGELINES];
+
+ for(int i = 0; i < mIndexPageLines; i++)
+ lineptrs[i] = (char *) mIndexPage[i];
+
+ *linecount = mIndexPageLines;
+ *lines = lineptrs;
+}
+
+
+// wait for more data
+void cTtxtSubsReceiver::Wait(void)
+{
+ mGetMutex.Lock();
+ mGetCond.Wait(mGetMutex);
+ mGetMutex.Unlock();
+}
+
+
+void cTtxtSubsReceiver::Activate(bool On)
+{
+}
+
+
+// Take TS packets and break out the teletext data
+// Buffer the data for processing in a separate thread
+// XXX We should do some filtering here to avoid unneccessary load!
+void cTtxtSubsReceiver::Receive(uchar *Data, int Length)
+{
+ int i;
+
+ if(Length != 188) // should never happen
+ return;
+
+ if(Data[1] & 0x80) // transport_error_indicator
+ return;
+
+ // 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)) {
+ // fprintf(stderr, "Forward Packet:\n");
+ // print_line((char *) Data + 4 + i*46);
+ // print_line(buf);
+
+ cFrame *f = new cFrame((uchar *) buf, 46);
+ mRingBuf.Put(f);
+ mGetCond.Broadcast();
+ }
+ }
+}
+
+static void init_line(uint8_t *buf, uint8_t Y, uint8_t mag)
+{
+ struct ttxt_data_field *d = (struct ttxt_data_field *) buf;
+ uint8_t magbuf[2];
+
+ d->data_unit_id = 0x02; // EBU Teletxt non-subtitle data
+ d->data_unit_length = 0x2c;
+ d->par_loff = 0xc0; // reserved bits + unknown line
+ d->framing_code = 0xe4;
+ ham8_4byte((Y << 3) | (mag & 0x7), magbuf);
+ d->mag_addr_ham[0] = invtab[magbuf[0]];
+ d->mag_addr_ham[1] = invtab[magbuf[1]];
+}
+
+static void copy_inv_par(uint8_t *dest, uint8_t *src, int count)
+{
+ for(int i = 0; i < count; i++)
+ if(parity(src[i]))
+ dest[i] = invtab[src[i] | 0x80];
+ else
+ dest[i] = invtab[src[i]];
+}
+
+void cTtxtSubsReceiver::AddIndexInfo(char *lang, int type, uint16_t page)
+{
+ //fprintf(stderr, "AddIndexInfo: %c%c%c/%d/%03x\n", lang[0], lang[1], lang[2], type, page); // XXX
+
+ if(mIndexPageLines == MAXINDEXPAGELINES) {
+ fprintf(stderr, "cTtxtSubsReceiver::AddIndexInfo: Index page full!\n");
+ return;
+ }
+
+ if(mIndexPageLines == 0) { // make Y0 and info text
+ struct ttxt_data_field *d = (struct ttxt_data_field *) mIndexPage[0];
+ uint8_t buf[40];
+ size_t txtlen;
+
+ init_line(mIndexPage[mIndexPageLines], mIndexPageLines, mIndexPageNo >> 8);
+
+ d->data[0] = invtab[ham8_4nibble(mIndexPageNo & 0x000f)];
+ d->data[1] = invtab[ham8_4nibble((mIndexPageNo >> 4) & 0x000f)];
+ uint8_t ham_inv_zero = invtab[ham8_4nibble(0)];
+ d->data[2] = ham_inv_zero;
+ d->data[3] = ham_inv_zero;
+ d->data[4] = ham_inv_zero;
+ d->data[5] = ham_inv_zero;
+ d->data[6] = ham_inv_zero;
+ d->data[7] = ham_inv_zero;
+
+ txtlen = snprintf((char *) buf, 32, "%03x", mIndexPageNo);
+ copy_inv_par(d->data + 8, buf, txtlen);
+ for(int i = txtlen; i < 32; i++)
+ d->data[i+8] = invtab[' ']; // space already has correct parity
+
+ //print_line((char *) mIndexPage[mIndexPageLines]); // XXX
+
+ mIndexPageLines++;
+
+ init_line(mIndexPage[mIndexPageLines], mIndexPageLines, mIndexPageNo >> 8);
+ d = (struct ttxt_data_field *) mIndexPage[mIndexPageLines];
+ char *header = "Subtitles Index Page";
+ txtlen = strlen(header);
+ copy_inv_par(d->data, (uint8_t *) header, txtlen);
+ for(int i = txtlen; i < 40; i++)
+ d->data[i] = invtab[' ']; // space already has correct parity
+
+ //print_line((char *) mIndexPage[mIndexPageLines]); // XXX
+
+ mIndexPageLines++;
+ }
+
+ struct ttxt_data_field *d = (struct ttxt_data_field *) mIndexPage[mIndexPageLines];
+ uint8_t buf[10];
+
+ if(mIndexPageCol == 0) {
+ init_line(mIndexPage[mIndexPageLines], mIndexPageLines, mIndexPageNo >> 8);
+ for(int i = 0; i < 40; i++)
+ d->data[i+8] = invtab[' ']; // space already has correct parity
+ }
+
+ if(page < 0x100)
+ page += 0x800;
+
+ snprintf((char *) buf, 10, "%c%c%c%c%03x ", lang[0], lang[1], lang[2],
+ type == TTXT_SUBTITLE_PAGE ? ' ' :
+ type == TTXT_SUBTITLE_HEARING_IMPAIRED_PAGE ? 'h' : 'x',
+ page);
+
+ copy_inv_par(d->data+ mIndexPageCol*8, buf, 8);
+
+ //print_line((char *) mIndexPage[mIndexPageLines]); // XXX
+
+ mIndexPageCol++;
+
+ if(mIndexPageCol == 5) {
+ mIndexPageCol = 0;
+ mIndexPageLines++;
+ }
+}
+
+void cTtxtSubsReceiver::EndIndexInfo(void)
+{
+ if(mIndexPageCol != 0) {
+ mIndexPageCol = 0;
+ mIndexPageLines++;
+ }
+}
diff --git a/ttxtsubsreceiver.h b/ttxtsubsreceiver.h
new file mode 100644
index 0000000..a88aa65
--- /dev/null
+++ b/ttxtsubsreceiver.h
@@ -0,0 +1,34 @@
+
+class cRingBufferFrame;
+
+class cTtxtSubsReceiver : public cReceiver
+{
+ public:
+ cTtxtSubsReceiver(int Ca, struct ttxtpidinfo *PI);
+ virtual ~cTtxtSubsReceiver();
+
+ // returns pointer buf if there is new data
+ uint8_t *Get(uint8_t *buf);
+ // wait for more data
+ void Wait(void);
+
+ void IndexPage(char ***lines, int *linecount);
+
+ protected:
+ virtual void Activate(bool On);
+ virtual void Receive(uchar *Data, int Length);
+
+ virtual void AddIndexInfo(char *lang, int type, uint16_t page);
+ virtual void EndIndexInfo(void);
+
+ private:
+ cMutex mGetMutex;
+ cCondVar mGetCond;
+ cRingBufferFrame mRingBuf;
+ struct ttxtpidinfo mPI;
+ cTtxtSubsFilter mFilter;
+ uint8_t mIndexPage[24][46];
+ uint8_t mIndexPageLines;
+ uint8_t mIndexPageCol;
+ uint16_t mIndexPageNo;
+};
diff --git a/ttxtsubsrecorder.c b/ttxtsubsrecorder.c
new file mode 100644
index 0000000..b6719af
--- /dev/null
+++ b/ttxtsubsrecorder.c
@@ -0,0 +1,167 @@
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <vdr/vdrttxtsubshooks.h>
+#include <vdr/device.h>
+#include <vdr/channels.h>
+#include <vdr/receiver.h>
+#include <vdr/ringbuffer.h>
+
+#include "siinfo.h"
+#include "ttxtsubsfilter.h"
+#include "ttxtsubsrecorder.h"
+#include "ttxtsubsreceiver.h"
+#include "ttxtsubs.h"
+#include "teletext.h"
+
+#define MAXPACKETSIZE 20000
+
+// ----- cTtxtSubsRecorder -----
+
+cTtxtSubsRecorder::cTtxtSubsRecorder(cDevice *dev, const cChannel *ch, char *lang, int HI)
+ :
+ mDev(dev),
+ mSid(ch->Sid()),
+ mVid(ch->Vpid()),
+ mTtxtinfo(NULL),
+ mReceiver(NULL),
+ mPacketBuffer(NULL)
+{
+ mTtxtinfo = (struct ttxtinfo *) malloc(sizeof(*mTtxtinfo));
+ struct ttxtpidinfo *pi = NULL;
+ int pid, page;
+
+ if(GetTtxtInfo(dev->DeviceNumber(), ch->Sid(), ch->Vpid(), mTtxtinfo)) {
+ fprintf(stderr, "cTtxtSubsRecorder::cTtxtSubsRecorder: GetTtxtSubtitleInfo error!\n");
+ } else {
+ pi = FindSubs(mTtxtinfo, lang, HI, &pid, &page);
+
+ if(!pi && mTtxtinfo->pidcount > 0) {
+ pi = &(mTtxtinfo->p[0]);
+ fprintf(stderr, "Selected language not found, just recording first teletext pid found.\n");
+ }
+
+ if(pi) {
+ mReceiver = new cTtxtSubsReceiver(ch->Ca(), pi);
+ mPacketBuffer = (uint8_t *) malloc(MAXPACKETSIZE);
+ } else {
+ fprintf(stderr, "No teletext pid found, not recording any (obviously).\n");
+ }
+ }
+}
+
+cTtxtSubsRecorder::~cTtxtSubsRecorder(void)
+{
+ if(mReceiver) {
+ delete mReceiver;
+ mReceiver = NULL;
+ }
+
+ if(mTtxtinfo) {
+ FreeTtxtInfoData(mTtxtinfo);
+ free(mTtxtinfo);
+ }
+
+ if(mPacketBuffer)
+ free(mPacketBuffer);
+}
+
+uint8_t *cTtxtSubsRecorder::GetPacket(uint8_t **outbuf, size_t *lenp)
+{
+ int done = 0;
+ size_t len;
+ uint8_t *b = (uint8_t *) mPacketBuffer;
+ uint8_t line[46];
+
+ *outbuf = NULL;
+
+ if(!mReceiver)
+ return *outbuf;
+
+ len = 46; // skip PES header area
+
+ if(mReceiver->Get(line)) {
+ // 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...
+
+ int mp;
+ //int mag; // X in ETSI EN 300 706
+ int packet; // Y
+
+ struct ttxt_data_field *d = (struct ttxt_data_field *) line;
+
+ mp = UNHAM_INV(d->mag_addr_ham[0], d->mag_addr_ham[1]);
+ //mag = mp & 0x7;
+ packet = (mp >> 3) & 0x1f;
+
+ if(packet == 0) {
+ char **iplines;
+ int iplcount;
+ mReceiver->IndexPage(&iplines, &iplcount);
+ for(int i = 0; i < iplcount; i++) {
+ memcpy(b + len, (char *) iplines[i], 46);
+ len += 46;
+ }
+ }
+
+ // insert the line
+ memcpy(b + len, line, 46);
+ len += 46;
+ }
+
+ // get the rest of the lines
+ while((len < (MAXPACKETSIZE - 184)) && !done) {
+ if(mReceiver->Get(b + len)) {
+ len += 46;
+ } else
+ done = 1;
+ }
+
+ if(len > 46) { // we have data, make a PES packet according to ETSI EN 300 472
+ // fill out packet with stuffing data to 184 byte boundary
+ size_t stufflen = 184 - (len % 184);
+ if(stufflen == 184)
+ stufflen = 0;
+ memset((char *) b + len, 0xff, stufflen);
+
+ len += stufflen;
+
+ // fill in header
+ uint8_t header[] = {0x00, 0x00, 0x01, 0xbd, // private data stream 1
+ 0x00, 0x00, // len
+ 0x80, // 10 and flags
+ 0x00, // PTS_DTS_flags and other flags
+ 0x24}; // PES_header_data_length
+
+ memcpy((char *) b, (char *) header, sizeof(header));
+ b[4] = (len-6) >> 8;
+ b[5] = (len-6) & 0xff;
+ 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
+
+ *outbuf = b;
+ *lenp = len;
+ }
+
+#if 0
+ if(*outbuf) { // XXX
+ fprintf(stderr, "cTtxtSubsRecorder::GetPacket: len: %d\n", len);
+ for(size_t i = 46; i < len; i +=46) {
+ struct ttxt_data_field *d = (struct ttxt_data_field *) b + i;
+ if(d->data_unit_id != 0xff)
+ print_line((char *) b + i);
+ }
+ }
+#endif
+
+ return *outbuf;
+}
+
+void cTtxtSubsRecorder::DeviceAttach(void)
+{
+ if(mReceiver)
+ mDev->AttachReceiver(mReceiver);
+}
+
diff --git a/ttxtsubsrecorder.h b/ttxtsubsrecorder.h
new file mode 100644
index 0000000..bb9182a
--- /dev/null
+++ b/ttxtsubsrecorder.h
@@ -0,0 +1,23 @@
+
+class cDevice;
+class cChannel;
+class cTtxtSubsReceiver;
+struct ttxtinfo;
+
+class cTtxtSubsRecorder : public cTtxtSubsRecorderBase {
+ public:
+ cTtxtSubsRecorder(cDevice *dev, const cChannel *ch, char *lang, int HI);
+ virtual ~cTtxtSubsRecorder();
+
+ // returns a PES packet if there is data to add to the recording
+ virtual uint8_t *GetPacket(uint8_t **buf, size_t *len);
+ virtual void DeviceAttach(void);
+
+ private:
+ cDevice *mDev;
+ int mSid;
+ int mVid;
+ ttxtinfo *mTtxtinfo;
+ cTtxtSubsReceiver *mReceiver;
+ uint8_t *mPacketBuffer;
+};
diff --git a/utils.h b/utils.h
new file mode 100644
index 0000000..a473d1b
--- /dev/null
+++ b/utils.h
@@ -0,0 +1,2 @@
+
+#define dprint(format, args...) fprintf(stderr, format, ## args)
diff --git a/vdrttxtsubshooks.c b/vdrttxtsubshooks.c
new file mode 100644
index 0000000..4c1836b
--- /dev/null
+++ b/vdrttxtsubshooks.c
@@ -0,0 +1,44 @@
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+
+#include "vdrttxtsubshooks.h"
+
+// XXX Really should be a list...
+static cVDRTtxtsubsHookListener *gListener;
+
+// ------ class cVDRTtxtsubsHookProxy ------
+
+class cVDRTtxtsubsHookProxy : public cVDRTtxtsubsHookListener
+{
+ public:
+ virtual void HideOSD(void) { if(gListener) gListener->HideOSD(); };
+ virtual void ShowOSD(void) { if(gListener) gListener->ShowOSD(); };
+ virtual void PlayerTeletextData(uint8_t *p, int length)
+ { if(gListener) gListener->PlayerTeletextData(p, length); };
+ virtual cTtxtSubsRecorderBase *NewTtxtSubsRecorder(cDevice *dev, const cChannel *ch)
+ { if(gListener) return gListener->NewTtxtSubsRecorder(dev, ch); else return NULL; };
+};
+
+
+// ------ class cVDRTtxtsubsHookListener ------
+
+cVDRTtxtsubsHookListener::~cVDRTtxtsubsHookListener()
+{
+ gListener = 0;
+}
+
+void cVDRTtxtsubsHookListener::HookAttach(void)
+{
+ gListener = this;
+ //printf("cVDRTtxtsubsHookListener::HookAttach\n");
+}
+
+static cVDRTtxtsubsHookProxy gProxy;
+
+cVDRTtxtsubsHookListener *cVDRTtxtsubsHookListener::Hook(void)
+{
+ return &gProxy;
+}
+
diff --git a/vdrttxtsubshooks.h b/vdrttxtsubshooks.h
new file mode 100644
index 0000000..b2a1dcd
--- /dev/null
+++ b/vdrttxtsubshooks.h
@@ -0,0 +1,36 @@
+
+#ifndef __VDRTTXTSUBSHOOKS_H
+#define __VDRTTXTSUBSHOOKS_H
+
+class cDevice;
+class cChannel;
+
+#define VDRTTXTSUBSHOOKS
+
+class cTtxtSubsRecorderBase {
+ public:
+ virtual ~cTtxtSubsRecorderBase() {};
+
+ // returns a PES packet if there is data to add to the recording
+ virtual uint8_t *GetPacket(uint8_t **buf, size_t *len) { return NULL; };
+ virtual void DeviceAttach(void) {};
+};
+
+class cVDRTtxtsubsHookListener {
+ public:
+ cVDRTtxtsubsHookListener(void) {};
+ virtual ~cVDRTtxtsubsHookListener();
+
+ void HookAttach(void);
+
+ virtual void HideOSD(void) {};
+ virtual void ShowOSD(void) {};
+ virtual void PlayerTeletextData(uint8_t *p, int length) {};
+ virtual cTtxtSubsRecorderBase *NewTtxtSubsRecorder(cDevice *dev, const cChannel *ch)
+ { return NULL; };
+
+ // used by VDR to call hook listeners
+ static cVDRTtxtsubsHookListener *Hook(void);
+};
+
+#endif __VDRTTXTSUBSHOOKS_H