diff -ruN vdr-1.3.30/CONTRIBUTORS vdr-1.3.31/CONTRIBUTORS
--- vdr-1.3.30/CONTRIBUTORS	2005-08-21 16:10:12.000000000 +0200
+++ vdr-1.3.31/CONTRIBUTORS	2005-08-28 16:18:24.000000000 +0200
@@ -976,6 +976,8 @@
  for making cDvbPlayer::Goto() append a Sequence End Code to get the image shown
  immediately with softdevices
  for fixing cDvbSpuBitmap::putPixel()
+ for implementing cAudioRepacker in remux.c
+ for modifying handling of audio packets for radio channels in remux.c
 
 Richard Robson <richard_robson@beeb.net>
  for reporting freezing replay if a timer starts while in Transfer Mode from the
@@ -1212,6 +1214,7 @@
  is waiting
  for improving resetting CAM connections
  for fixing handling EPG data for time shifted events
+ for fixing detecting short channel names for "Kabel Deutschland"
 
 Jürgen Schmitz <j.schmitz@web.de>
  for reporting a bug in displaying the current channel when switching via the SVDRP
@@ -1436,7 +1439,20 @@
 Thomas Günther <tom1@toms-cafe.de>
  for fixing handling the frame number display if '7' is pressed before the first
  editing mark, or '9' after the last one
+ for suggesting to move cMenuEditTimer and cMenuEvent to menu.h so that plugins
+ can use it
 
 David Woodhouse <dwmw2@infradead.org>
  for his help in replacing the get/put_unaligned() macros from asm/unaligned.h with
  own inline functions to avoid problems on platforms that don't provide these
+
+Marcus Hilbrich <s4440288@mail.inf.tu-dresden.de>
+ for a bug report that lead to fixing the EPG scan, so that it doesn't use the
+ primary device if that is currently in Transfer-Mode from itself
+
+Hardy Flor <HFlor@web.de>
+ for a patch that was used as a base to implement SVDRP commands for plugins
+ for implementing the SVDRP command PLAY
+
+Harald Milz <hm@seneca.muc.de>
+ for his CUTR patch, which was used as a base to implement the SVDRP command EDIT
diff -ruN vdr-1.3.30/HISTORY vdr-1.3.31/HISTORY
--- vdr-1.3.30/HISTORY	2005-08-21 16:15:00.000000000 +0200
+++ vdr-1.3.31/HISTORY	2005-08-28 16:16:51.000000000 +0200
@@ -3716,3 +3716,33 @@
 - Replaced the get/put_unaligned() macros from <asm/unaligned.h> with own inline
   functions to avoid problems on platforms that don't provide these (thanks to
   David Woodhouse for his help).
+
+2005-08-28: Version 1.3.31
+
+- Added missing German OSD texts for 'Audio language'.
+- The Setup/CICAM menu now only contains the devices that actually have a CI and
+  dynamically detects the number of slots a CI provides.
+- Implemented cAudioRepacker for better handling of audio PES packets (thanks to
+  Reinhard Nissl).
+- Modified handling of audio packets for radio channels in remux.c (thanks to
+  Reinhard Nissl).
+- Updated the Danish OSD texts (thanks to Mogens Elneff).
+- Fixed the EPG scan, so that it doesn't use the primary device if that is
+  currently in Transfer-Mode from itself (thanks to Marcus Hilbrich for a bug
+  report that lead to this).
+- Removed the TUNER_LOCK_TIMEOUT in cDevice::AttachReceiver() since it caused more
+  trouble than it fixed.
+- Fixed detecting short channel names for "Kabel Deutschland", who uses a comma
+  as delimiter (thanks to Marco Schlüßler).
+- Moved cMenuEditTimer and cMenuEvent to menu.h so that plugins can use it (suggested
+  by Thomas Günther).
+- The new static function cString::sprintf() can be used to easily create a formatted
+  string.
+- Plugins can now implement their own SVDRP commands (based on a patch from Hardy
+  Flor). See PLUGINS.html, section "SVDRP commands" for details. The SVDRP commands
+  of a plugin are accessed through the new SVDRP command PLUG.
+  See PLUGINS/src/svdrpdemo for an example of how to use this feature.
+- The new SVDRP command PLAY can be used to start replaying a recording (thanks to
+  Hardy Flor).
+- The new SVDRP command EDIT can be used to start the editing process of a recording
+  (based on the CUTR patch by Harald Milz).
diff -ruN vdr-1.3.30/PLUGINS/src/svdrpdemo/COPYING vdr-1.3.31/PLUGINS/src/svdrpdemo/COPYING
--- vdr-1.3.30/PLUGINS/src/svdrpdemo/COPYING	1970-01-01 01:00:00.000000000 +0100
+++ vdr-1.3.31/PLUGINS/src/svdrpdemo/COPYING	2005-08-27 13:26:49.000000000 +0200
@@ -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 -ruN vdr-1.3.30/PLUGINS/src/svdrpdemo/HISTORY vdr-1.3.31/PLUGINS/src/svdrpdemo/HISTORY
--- vdr-1.3.30/PLUGINS/src/svdrpdemo/HISTORY	1970-01-01 01:00:00.000000000 +0100
+++ vdr-1.3.31/PLUGINS/src/svdrpdemo/HISTORY	2005-08-27 13:26:49.000000000 +0200
@@ -0,0 +1,6 @@
+VDR Plugin 'svdrpdemo' Revision History
+---------------------------------------
+
+2005-08-27: Version 0.0.1
+
+- Initial revision.
diff -ruN vdr-1.3.30/PLUGINS/src/svdrpdemo/Makefile vdr-1.3.31/PLUGINS/src/svdrpdemo/Makefile
--- vdr-1.3.30/PLUGINS/src/svdrpdemo/Makefile	1970-01-01 01:00:00.000000000 +0100
+++ vdr-1.3.31/PLUGINS/src/svdrpdemo/Makefile	2005-08-27 13:26:49.000000000 +0200
@@ -0,0 +1,82 @@
+#
+# Makefile for a Video Disk Recorder plugin
+#
+# $Id: Makefile 1.1 2005/08/27 11:26:49 kls 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 = svdrpdemo
+
+### 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)
+
+### Includes and Defines (add further entries here):
+
+INCLUDES += -I$(VDRDIR)/include -I$(DVBDIR)/include
+
+DEFINES += -D_GNU_SOURCE -DPLUGIN_NAME_I18N='"$(PLUGIN)"'
+
+### The object files (add further files here):
+
+OBJS = $(PLUGIN).o
+
+### Implicit rules:
+
+%.o: %.c
+	$(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) $<
+
+# Dependencies:
+
+MAKEDEP = $(CXX) -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 * $(TMPDIR)/$(ARCHIVE)
+	@tar czf $(PACKAGE).tgz -C $(TMPDIR) $(ARCHIVE)
+	@-rm -rf $(TMPDIR)/$(ARCHIVE)
+	@echo Distribution package created as $(PACKAGE).tgz
+
+clean:
+	@-rm -f $(OBJS) $(DEPFILE) *.so *.tgz core* *~
diff -ruN vdr-1.3.30/PLUGINS/src/svdrpdemo/README vdr-1.3.31/PLUGINS/src/svdrpdemo/README
--- vdr-1.3.30/PLUGINS/src/svdrpdemo/README	1970-01-01 01:00:00.000000000 +0100
+++ vdr-1.3.31/PLUGINS/src/svdrpdemo/README	2005-08-27 13:29:10.000000000 +0200
@@ -0,0 +1,13 @@
+This is a "plugin" for the Video Disk Recorder (VDR).
+
+Written by:                  Klaus Schmidinger <Klaus.Schmidinger@cadsoft.de>
+
+Project's homepage:          http://www.cadsoft.de/vdr
+
+Latest version available at: http://www.cadsoft.de/vdr
+
+See the file COPYING for license information.
+
+Description:
+
+This plugin shows how to add SVDRP support to a plugin.
diff -ruN vdr-1.3.30/PLUGINS/src/svdrpdemo/svdrpdemo.c vdr-1.3.31/PLUGINS/src/svdrpdemo/svdrpdemo.c
--- vdr-1.3.30/PLUGINS/src/svdrpdemo/svdrpdemo.c	1970-01-01 01:00:00.000000000 +0100
+++ vdr-1.3.31/PLUGINS/src/svdrpdemo/svdrpdemo.c	2005-08-27 18:28:58.000000000 +0200
@@ -0,0 +1,60 @@
+/*
+ * svdrpdemo.c: A plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id: svdrpdemo.c 1.1 2005/08/27 16:28:58 kls Exp $
+ */
+
+#include <vdr/plugin.h>
+
+static const char *VERSION        = "0.0.1";
+static const char *DESCRIPTION    = "How to add SVDRP support to a plugin";
+static const char *MAINMENUENTRY  = NULL;
+
+class cPluginSvdrpdemo : public cPlugin {
+private:
+  // Add any member variables or functions you may need here.
+public:
+  virtual const char *Version(void) { return VERSION; }
+  virtual const char *Description(void) { return DESCRIPTION; }
+  virtual const char **SVDRPHelpPages(void);
+  virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode);
+  };
+
+const char **cPluginSvdrpdemo::SVDRPHelpPages(void)
+{
+  static const char *HelpPages[] = {
+    "DATE\n"
+    "    Print the current date.",
+    "TIME [ raw ]\n"
+    "    Print the current time.\n"
+    "    If the optional keyword 'raw' is given, the result will be the\n"
+    "    raw time_t data.",
+    NULL
+    };
+  return HelpPages;
+}
+
+cString cPluginSvdrpdemo::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode)
+{
+  if (strcasecmp(Command, "DATE") == 0) {
+     // we use the default reply code here
+     return DateString(time(NULL));
+     }
+  else if (strcasecmp(Command, "TIME") == 0) {
+     ReplyCode = 901;
+     if (*Option) {
+        if (strcasecmp(Option, "RAW") == 0)
+           return cString::sprintf("%ld\nThis is the number of seconds since the epoch\nand a demo of a multi-line reply", time(NULL));
+        else {
+           ReplyCode = 504;
+           return cString::sprintf("Unknown option: \"%s\"", Option);
+           }
+        }
+     return TimeString(time(NULL));
+     }
+  return NULL;
+}
+
+VDRPLUGINCREATOR(cPluginSvdrpdemo); // Don't touch this!
diff -ruN vdr-1.3.30/PLUGINS.html vdr-1.3.31/PLUGINS.html
--- vdr-1.3.30/PLUGINS.html	2005-08-21 16:20:09.000000000 +0200
+++ vdr-1.3.31/PLUGINS.html	2005-08-28 16:28:53.000000000 +0200
@@ -14,18 +14,18 @@
 <a href="http://www.cadsoft.de/vdr">www.cadsoft.de/vdr</a>
 </center>
 <p>
-<!--X1.3.19--><table width=100%><tr><td bgcolor=#0000AA>&nbsp;</td><td width=100%>
-Important modifications introduced in version 1.3.19 are marked like this.
-<!--X1.3.19--></td></tr></table>
-<!--X1.3.20--><table width=100%><tr><td bgcolor=#00AA00>&nbsp;</td><td width=100%>
+<!--X1.3.20--><table width=100%><tr><td bgcolor=#0000AA>&nbsp;</td><td width=100%>
 Important modifications introduced in version 1.3.20 are marked like this.
 <!--X1.3.20--></td></tr></table>
-<!--X1.3.21--><table width=100%><tr><td bgcolor=#AA0000>&nbsp;</td><td width=100%>
+<!--X1.3.21--><table width=100%><tr><td bgcolor=#00AA00>&nbsp;</td><td width=100%>
 Important modifications introduced in version 1.3.21 are marked like this.
 <!--X1.3.21--></td></tr></table>
-<!--X1.3.30--><table width=100%><tr><td bgcolor=#FF0000>&nbsp;</td><td width=100%>
+<!--X1.3.30--><table width=100%><tr><td bgcolor=#AA0000>&nbsp;</td><td width=100%>
 Important modifications introduced in version 1.3.30 are marked like this.
 <!--X1.3.30--></td></tr></table>
+<!--X1.3.31--><table width=100%><tr><td bgcolor=#FF0000>&nbsp;</td><td width=100%>
+Important modifications introduced in version 1.3.31 are marked like this.
+<!--X1.3.31--></td></tr></table>
 <p>
 VDR provides an easy to use plugin interface that allows additional functionality
 to be added to the program by implementing a dynamically loadable library file.
@@ -58,7 +58,7 @@
 <li><a href="#Command line arguments">Command line arguments</a>
 <li><a href="#Command line help">Command line help</a>
 <li><a href="#Getting started">Getting started</a>
-<!--X1.3.20--><table width=100%><tr><td bgcolor=#00AA00>&nbsp;</td><td width=100%>
+<!--X1.3.20--><table width=100%><tr><td bgcolor=#0000AA>&nbsp;</td><td width=100%>
 <li><a href="#Shutting down">Shutting down</a>
 <!--X1.3.20--></td></tr></table>
 <li><a href="#Main menu entry">Main menu entry</a>
@@ -68,9 +68,12 @@
 <li><a href="#The Setup menu">The Setup menu</a>
 <li><a href="#Configuration files">Configuration files</a>
 <li><a href="#Internationalization">Internationalization</a>
-<!--X1.3.30--><table width=100%><tr><td bgcolor=#FF0000>&nbsp;</td><td width=100%>
+<!--X1.3.30--><table width=100%><tr><td bgcolor=#AA0000>&nbsp;</td><td width=100%>
 <li><a href="#Custom services">Custom services</a>
 <!--X1.3.30--></td></tr></table>
+<!--X1.3.31--><table width=100%><tr><td bgcolor=#FF0000>&nbsp;</td><td width=100%>
+<li><a href="#SVDRP commands">SVDRP commands</a>
+<!--X1.3.31--></td></tr></table>
 <li><a href="#Loading plugins into VDR">Loading plugins into VDR</a>
 <li><a href="#Building the distribution package">Building the distribution package</a>
 </ul>
@@ -84,7 +87,7 @@
 <li><a href="#Skins">Skins</a>
 <li><a href="#Themes">Themes</a>
 <li><a href="#Devices">Devices</a>
-<!--X1.3.21--><table width=100%><tr><td bgcolor=#AA0000>&nbsp;</td><td width=100%>
+<!--X1.3.21--><table width=100%><tr><td bgcolor=#00AA00>&nbsp;</td><td width=100%>
 <li><a href="#Audio">Audio</a>
 <!--X1.3.21--></td></tr></table>
 <li><a href="#Remote Control">Remote Control</a>
@@ -311,7 +314,7 @@
 command line help - and after that immediately destroys them again.
 <p>
 The <b>destructor</b> has to clean up any data created by the plugin.
-<!--X1.3.20--><table width=100%><tr><td bgcolor=#00AA00>&nbsp;</td><td width=100%>
+<!--X1.3.20--><table width=100%><tr><td bgcolor=#0000AA>&nbsp;</td><td width=100%>
 Any threads the plugin may have created shall be stopped in the
 <a href="#Shutting down"><tt>Stop()</tt></a> function.
 <!--X1.3.20--></td></tr></table>
@@ -509,7 +512,7 @@
 If the plugin doesn't implement any background functionality or internationalized
 texts, it doesn't need to implement either of these functions.
 
-<!--X1.3.20--><table width=100%><tr><td bgcolor=#00AA00>&nbsp;</td><td width=100%>
+<!--X1.3.20--><table width=100%><tr><td bgcolor=#0000AA>&nbsp;</td><td width=100%>
 <a name="Shutting down"><hr><h2>Shutting down</h2>
 
 <center><i><b>Stop it, right there!</b></i></center><p>
@@ -869,7 +872,7 @@
 and then in the global VDR texts. So a plugin can make use of texts defined by the
 core VDR code.
 
-<!--X1.3.30--><table width=100%><tr><td bgcolor=#FF0000>&nbsp;</td><td width=100%>
+<!--X1.3.30--><table width=100%><tr><td bgcolor=#AA0000>&nbsp;</td><td width=100%>
 <a name="Custom services"><hr><h2>Custom services</h2>
 
 <center><i><b>What can I do for you?</b></i></center><p>
@@ -934,12 +937,106 @@
 The function returns a pointer to the plugin that handled the request, or <tt>NULL</tt>
 if no plugin handled it.
 <p>
-To send a messages to all plugins, a plugin can call the function
+To send a message to all plugins, a plugin can call the function
 <tt>cPluginManager::CallAllServices()</tt>. This function returns <tt>true</tt> if
 any plugin handled the request, or <tt>false</tt> if no plugin handled the request.
 
 <!--X1.3.30--></td></tr></table>
 
+<!--X1.3.31--><table width=100%><tr><td bgcolor=#FF0000>&nbsp;</td><td width=100%>
+<a name="SVDRP commands"><hr><h2>SVDRP commands</h2>
+
+<center><i><b>Infinite Diversity in Infinite Combinations</b></i></center><p>
+
+A plugin can implement its own SVDRP commands through the two functions
+
+<p><table><tr><td bgcolor=#F0F0F0><pre>
+virtual const char **SVDRPHelpPages(void);
+virtual cString SVDRPCommand(const char *Cmd, const char *Option, int &ReplyCode);
+</pre></td></tr></table><p>
+
+The <tt>SVDRPHelpPages()</tt> function must return a pointer to a list of help
+strings for all of the plugin's SVDRP commands, like this
+
+<p><table><tr><td bgcolor=#F0F0F0><pre>
+const char **cPluginSvdrpdemo::SVDRPHelpPages(void)
+{
+  static const char *HelpPages[] = {
+    "DATE\n"
+    "    Print the current date.",
+    "TIME [ raw ]\n"
+    "    Print the current time.\n"
+    "    If the optional keyword 'raw' is given, the result will be the\n"
+    "    raw time_t data.",
+    NULL
+    };
+  return HelpPages;
+}
+</pre></td></tr></table><p>
+
+Note that the first line of each entry contains the actual command and its
+parameters, while the following lines explain what the command does and what
+the parameters (if any) mean. All lines of the explanation shall be indented
+by exactly 4 blanks (no tabs), and none of them shall be longer than 79 characters
+(to avoid messy output on 80 character wide terminals). The last entry in the
+list must be NULL.
+<p>
+The command names <tt>HELP</tt> and <tt>MAIN</tt> are reserverd and cannot
+be used by a plugin.
+<p>
+The actual processing of SVDRP commands for a plugin is done in its
+<tt>SVDRPCommand()</tt> function.
+Here's an example of such a function, which implements the commands advertised in
+the above help texts:
+
+<p><table><tr><td bgcolor=#F0F0F0><pre>
+cString cPluginSvdrpdemo::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode)
+{
+  if (strcasecmp(Command, "DATE") == 0) {
+     // we use the default reply code here
+     return DateString(time(NULL));
+     }
+  else if (strcasecmp(Command, "TIME") == 0) {
+     ReplyCode = 901;
+     if (*Option) {
+        if (strcasecmp(Option, "RAW") == 0)
+           return cString::sprintf("%ld\nThis is the number of seconds since the epoch\n"
+                                   "and a demo of a multi-line reply", time(NULL));
+        else {
+           ReplyCode = 504;
+           return cString::sprintf("Unknown option: \"%s\"", Option);
+           }
+        }
+     return TimeString(time(NULL));
+     }
+  return NULL;
+}
+</pre></td></tr></table><p>
+
+The command is given to this function in the <tt>Command</tt> parameter, and any optional parameters
+are given in the <tt>Option</tt> string. <tt>Command</tt> always points to an actual, non-empty string, while
+<tt>Option</tt> may point to an empty string (it is never NULL, though).
+<p>
+If a plugin doesn't implement the given command, it shall return NULL, and VDR will
+automatically issue a proper error message. If it encounters an unknown or invalid
+option, it shall set the <tt>ReplyCode</tt> to one of the codes defined in <tt>VDR/svdrp.c</tt>
+and return a proper error message.
+<p>
+The default <tt>ReplyCode</tt> is 900, and if the plugin doesn't care about reply
+codes, it doesn't have to set it to anything else (unless there is an error, of
+course). The codes in the range 901..999 are reserved for plugins that want
+to use special reply codes. Any plugin can use any of these values and doesn't
+have to coordinate this with any other plugin, since the caller knows which
+plugin was called, and will therefore process the values according to the
+particular plugin's definitions.
+<p>
+The returned string may consist of several lines, separated by the newline character
+('<tt>\n</tt>'). Each of these lines will be preceeded with the <tt>ReplyCode</tt>
+when presenting them to the caller, and the continuation character ('<tt>-</tt>')
+will be set for all but the last one.
+
+<!--X1.3.31--></td></tr></table>
+
 <a name="Loading plugins into VDR"><hr><h2>Loading plugins into VDR</h2>
 
 <center><i><b>Saddling up!</b></i></center><p>
@@ -1276,9 +1373,7 @@
   };
 
 cMyReceiver::cMyReceiver(int Pid)
-<!--X1.3.19--><table width=100%><tr><td bgcolor=#0000AA>&nbsp;</td><td width=100%>
 :cReceiver(0, -1, Pid)
-<!--X1.3.19--></td></tr></table>
 {
 }
 
@@ -1735,7 +1830,7 @@
   virtual void Action(void);
 public:
   cMyAudio(void);
-<!--X1.3.21--><table width=100%><tr><td bgcolor=#AA0000>&nbsp;</td><td width=100%>
+<!--X1.3.21--><table width=100%><tr><td bgcolor=#00AA00>&nbsp;</td><td width=100%>
   virtual void Play(const uchar *Data, int Length, uchar Id);
 <!--X1.3.21--></td></tr></table>
   virtual void Mute(bool On);
diff -ruN vdr-1.3.30/channels.conf vdr-1.3.31/channels.conf
--- vdr-1.3.30/channels.conf	2005-08-21 16:21:34.000000000 +0200
+++ vdr-1.3.31/channels.conf	2005-08-28 16:30:35.000000000 +0200
@@ -5,18 +5,18 @@
 Das Erste;ARD:11836:hC34:S19.2E:27500:101:102=deu:104:0:28106:1:1101:0
 Bayerisches FS;ARD:11836:hC34:S19.2E:27500:201:202=deu:204:0:28107:1:1101:0
 hr-fernsehen;ARD:11836:hC34:S19.2E:27500:301:302=deu:304:0:28108:1:1101:0
-NDR FS MV;ARD:12109:hC34:S19.2E:27500:2401:2402=deu:2404:0:28224:1:1073:0
+NDR FS MV;ARD:12109:hC34:S19.2E:27500:301:302=deu:2404:0:28224:1:1073:0
 SR SÜDWEST Ferns.;ARD:11836:hC34:S19.2E:27500:501:502=deu:504:0:28110:1:1101:0
 WDR Köln;ARD:11836:hC34:S19.2E:27500:601:602=deu:604:0:28111:1:1101:0
-BR-alpha;ARD:11836:hC34:S19.2E:27500:701:702=deu;703:704:0:28112:1:1101:0
+BR-alpha;ARD:11836:hC34:S19.2E:27500:701:702=deu:704:0:28112:1:1101:0
 SÜDWEST Ferns. BW;ARD:11836:hC34:S19.2E:27500:801:802=deu:804:0:28113:1:1101:0
 Phoenix;ARD:11836:hC34:S19.2E:27500:901:902=deu:904:0:28114:1:1101:0
 ZDF;ZDFvision:11953:hC34:S19.2E:27500:110:120=deu,121=2ch;125=dd:130:0:28006:1:1079:0
-3sat;ZDFvision:11953:hC34:S19.2E:27500:210:220=deu,221=2ch:230:0:28007:1:1079:0
+3sat;ZDFvision:11953:hC34:S19.2E:27500:210:220=deu,221=2ch;225=dd:230:0:28007:1:1079:0
 KiKa;ZDFvision:11953:hC34:S19.2E:27500:310:320=deu:330:0:28008:1:1079:0
 arte;ARD:11836:hC34:S19.2E:27500:401:402=deu,403=fra:404:0:28109:1:1101:0
-ORF1;ORF:12692:hC56:S19.2E:22000:160:161=deu;163=deu:165:1762,D05,1702,1801:13001:1:1117:0
-ORF2;ORF:12692:hC56:S19.2E:22000:500:501=deu;503=deu:505:1762,D05,1702,1801:13002:1:1117:0
+ORF1;ORF:12692:hC56:S19.2E:22000:160:161=deu;163=deu:165:3:13001:1:1117:0
+ORF2;ORF:12692:hC56:S19.2E:22000:500:501=deu;503=deu:505:3:13002:1:1117:0
 ZDFinfokanal;ZDFvision:11953:hC34:S19.2E:27500:610:620=deu:130:0:28011:1:1079:0
 CNN Int.;CNN:11778:vC34:S19.2E:27500:165:100=eng:47:0:28522:1:1068:0
 Super RTL,S RTL;RTL World:12187:hC34:S19.2E:27500:165:120=deu:65:0:12040:1:1089:0
@@ -27,7 +27,7 @@
 HSE24,HSE24;BetaDigital:12480:vC34:S19.2E:27500:1279:1280=deu:37:0:40:133:33:0
 Bloomberg TV Germany;Bloomberg:12551:vC56:S19.2E:22000:162:99=deu:0:0:12160:1:1108:0
 EURONEWS;CSAT:11817:vC34:S19.2E:27500:163:92=fra,93=eng,94=ita,95=esl,91=rus,98=por,99=deu:0:0:8004:1:1070:0
-rbb Brandenburg;ARD:12109:hC34:S19.2E:27500:601:602=deu:604:0:28205:1:1073:0
+rbb Brandenburg;ARD:12109:hC34:S19.2E:27500:501:502=deu:504:0:28205:1:1073:0
 Sky News:11597:vC56:S19.2E:22000:305+131:306=eng:0:0:28707:1:1026:0
 Veronica/JETIX;CANALDIGITAAL:12574:hC56:S19.2E:22000:518+8190:92=dut:38:622,100:5020:53:1109:0
 BVN;CANALDIGITAAL:12574:hC56:S19.2E:22000:515+8190:96=dut:36:0:5025:53:1109:0
@@ -44,9 +44,9 @@
 rbb Berlin;ARD:12109:hC34:S19.2E:27500:601:602=deu:604:0:28206:1:1073:0
 :Premiere World
 PREMIERE START,START;PREMIERE:11797:hC34:S19.2E:27500:255:256=deu:32:1:8:133:2:0
-PREMIERE 1,PREM 1;PREMIERE:11797:hC34:S19.2E:27500:511:512=deu,513=deu;515=deu:32:1722,1702,1801:10:133:2:0
+PREMIERE 1,PREM 1;PREMIERE:11797:hC34:S19.2E:27500:511:512=deu,513=deu;515=deu:32:1:10:133:2:0
 PREMIERE 2,PREM 2;PREMIERE:11797:hC34:S19.2E:27500:1791:1792=deu,1793=deu;1795=deu:32:1801,1722,1702:11:133:2:0
-PREMIERE 3,PREM 3;PREMIERE:11797:hC34:S19.2E:27500:2303:2304=deu:32:1722,1801,1702:43:133:2:0
+PREMIERE 3,PREM 3;PREMIERE:11797:hC34:S19.2E:27500:2303:2304=deu,2305=deu:32:1722,1801,1702:43:133:2:0
 PREMIERE 4,PREM 4;PREMIERE:11797:hC34:S19.2E:27500:767:768=deu:32:1801,1722,1702:9:133:2:0
 PREMIERE 5,PREM 5;PREMIERE:11797:hC34:S19.2E:27500:1279:1280=deu:32:1722,1801,1702:29:133:2:0
 PREMIERE 6,PREM 6;PREMIERE:11797:hC34:S19.2E:27500:1535:1536=deu:32:1702,1801,1722:41:133:2:0
@@ -108,7 +108,7 @@
 Sky Cinema 2;BSkyB:12285:vC23:S28.2E:27500:517+8190:645=eng,665=NAR:581:960,961:4802:2:2030:0
 :@900 Some 'seed' channels
 Chelsea TV;BskyB:11778:vC23:S28.2E:27500:2308+2304:2309=eng:0:960,961:9307:2:2004:0
-WDR Münster;ARD:12421:hC34:S19.2E:27500:101:102=deu:104:0:28310:1:1201:0
+WDR Münster;ARD:12421:hC34:S19.2E:27500:701:702=deu:104:0:28310:1:1201:0
 Animal Plnt+;BSkyB:12070:hC23:S28.2E:27500:2314+2307:2315=eng:0:960,961:50002:2:2019:0
 S1T;BSkyB:12285:vC23:S28.2E:27500:513+8190:641=eng,661=NAR:577:960,961:4409:2:2030:0
 CNN;BSkyB:12051:vC23:S28.2E:27500:2313:2315=eng:2314:0:7140:2:2018:0
diff -ruN vdr-1.3.30/config.h vdr-1.3.31/config.h
--- vdr-1.3.30/config.h	2005-08-20 12:29:35.000000000 +0200
+++ vdr-1.3.31/config.h	2005-08-26 14:28:40.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: config.h 1.224 2005/08/20 10:29:35 kls Exp $
+ * $Id: config.h 1.225 2005/08/26 12:28:40 kls Exp $
  */
 
 #ifndef __CONFIG_H
@@ -20,8 +20,8 @@
 #include "i18n.h"
 #include "tools.h"
 
-#define VDRVERSION  "1.3.30"
-#define VDRVERSNUM   10330  // Version * 10000 + Major * 100 + Minor
+#define VDRVERSION  "1.3.31"
+#define VDRVERSNUM   10331  // Version * 10000 + Major * 100 + Minor
 
 #define MAXPRIORITY 99
 #define MAXLIFETIME 99
diff -ruN vdr-1.3.30/device.c vdr-1.3.31/device.c
--- vdr-1.3.30/device.c	2005-08-21 10:56:49.000000000 +0200
+++ vdr-1.3.31/device.c	2005-08-27 11:01:09.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: device.c 1.106 2005/08/21 08:56:49 kls Exp $
+ * $Id: device.c 1.107 2005/08/27 09:01:09 kls Exp $
  */
 
 #include "device.h"
@@ -139,7 +139,6 @@
 
 // The default priority for non-primary devices:
 #define DEFAULTPRIORITY  -1
-#define TUNER_LOCK_TIMEOUT 5000 // ms
 
 int cDevice::numDevices = 0;
 int cDevice::useDevice = 0;
@@ -1186,10 +1185,6 @@
      return false;
   if (Receiver->device == this)
      return true;
-  if (!HasLock(TUNER_LOCK_TIMEOUT)) {
-     esyslog("ERROR: device %d has no lock, can't attach receiver!", CardIndex() + 1);
-     return false;
-     }
   cMutexLock MutexLock(&mutexReceiver);
   for (int i = 0; i < MAXRECEIVERS; i++) {
       if (!receiver[i]) {
diff -ruN vdr-1.3.30/eitscan.c vdr-1.3.31/eitscan.c
--- vdr-1.3.30/eitscan.c	2005-08-07 13:29:54.000000000 +0200
+++ vdr-1.3.31/eitscan.c	2005-08-26 17:37:06.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: eitscan.c 1.27 2005/08/07 11:29:54 kls Exp $
+ * $Id: eitscan.c 1.28 2005/08/26 15:37:06 kls Exp $
  */
 
 #include "eitscan.h"
@@ -150,7 +150,7 @@
                          if (!Channel->Ca() || Channel->Ca() == Device->DeviceNumber() + 1 || Channel->Ca() >= 0x0100) {
                             if (Device->ProvidesTransponder(Channel)) {
                                if (!Device->Receiving()) {
-                                  bool IsPrimaryDeviceReplaying = Device == cDevice::PrimaryDevice() && Device->Replaying();
+                                  bool IsPrimaryDeviceReplaying = Device == cDevice::PrimaryDevice() && Device->Replaying() && cTransferControl::ReceiverDevice() != cDevice::PrimaryDevice();
                                   if (Device != cDevice::ActualDevice() || (Device->ProvidesTransponderExclusively(Channel) && (IsPrimaryDeviceReplaying || now - lastActivity > Setup.EPGScanTimeout * 3600))) {
                                      if (!IsPrimaryDeviceReplaying && Device == cDevice::ActualDevice() && !currentChannel) {
                                         if (cTransferControl::ReceiverDevice())
diff -ruN vdr-1.3.30/i18n.c vdr-1.3.31/i18n.c
--- vdr-1.3.30/i18n.c	2005-08-14 14:03:47.000000000 +0200
+++ vdr-1.3.31/i18n.c	2005-08-26 15:39:07.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: i18n.c 1.198 2005/08/14 12:03:47 kls Exp $
+ * $Id: i18n.c 1.200 2005/08/26 13:39:07 kls Exp $
  *
  * Translations provided by:
  *
@@ -271,7 +271,7 @@
     "",// TODO
     "",// TODO
     "",// TODO
-    "",// TODO
+    "Optagelses info",
   },
   { "Setup",
     "Einstellungen",
@@ -2924,7 +2924,7 @@
     "¿àÞÚàãâÚÐ áâàÐÝØæ ÜÕÝî",
     "Listaj po stranicama",
     "Lehekülje kerimine",
-    "Rul sider",
+    "Scroll sidevis",
   },
   { "Setup.OSD$Scroll wraps",
     "Rundum scrollen",
@@ -2945,7 +2945,7 @@
     "",// TODO
     "S kraja skoèi na poèetak",
     "",// TODO
-    "",// TODO
+    "Scroll rundt",
   },
   { "Setup.OSD$Sort timers",
     "Timer sortieren",
@@ -3389,7 +3389,7 @@
     "tilf. ny transp.",
   },
   { "Setup.DVB$Audio languages",  // note the plural
-    "",//TODO
+    "Audio Sprachen",
     "",//TODO
     "",//TODO
     "Audio talen",
@@ -3410,7 +3410,7 @@
     "Audio sprog (ant.)",
   },
   { "Setup.DVB$Audio language",  // note the singular
-    "",//TODO
+    "Audio Sprache",
     "",//TODO
     "",//TODO
     "Audio taal",
@@ -5407,7 +5407,7 @@
     "",// TODO
     "Bez titla",
     "",// TODO
-    "",// TODO
+    "Ingen titel",
   },
   { NULL }
   };
diff -ruN vdr-1.3.30/menu.c vdr-1.3.31/menu.c
--- vdr-1.3.30/menu.c	2005-08-14 17:14:29.000000000 +0200
+++ vdr-1.3.31/menu.c	2005-08-27 11:37:23.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: menu.c 1.355 2005/08/14 15:14:29 kls Exp $
+ * $Id: menu.c 1.357 2005/08/27 09:37:23 kls Exp $
  */
 
 #include "menu.h"
@@ -19,7 +19,6 @@
 #include "eitscan.h"
 #include "i18n.h"
 #include "interface.h"
-#include "menuitems.h"
 #include "plugin.h"
 #include "recording.h"
 #include "remote.h"
@@ -605,20 +604,6 @@
 
 // --- cMenuEditTimer --------------------------------------------------------
 
-class cMenuEditTimer : public cOsdMenu {
-private:
-  cTimer *timer;
-  cTimer data;
-  int channel;
-  bool addIfConfirmed;
-  cMenuEditDateItem *firstday;
-  void SetFirstDayItem(void);
-public:
-  cMenuEditTimer(cTimer *Timer, bool New = false);
-  virtual ~cMenuEditTimer();
-  virtual eOSState ProcessKey(eKeys Key);
-  };
-
 cMenuEditTimer::cMenuEditTimer(cTimer *Timer, bool New)
 :cOsdMenu(tr("Edit timer"), 12)
 {
@@ -904,15 +889,6 @@
 
 // --- cMenuEvent ------------------------------------------------------------
 
-class cMenuEvent : public cOsdMenu {
-private:
-  const cEvent *event;
-public:
-  cMenuEvent(const cEvent *Event, bool CanSwitch = false);
-  virtual void Display(void);
-  virtual eOSState ProcessKey(eKeys Key);
-};
-
 cMenuEvent::cMenuEvent(const cEvent *Event, bool CanSwitch)
 :cOsdMenu(tr("Event"))
 {
@@ -2122,11 +2098,28 @@
 
 // --- cMenuSetupCICAM -------------------------------------------------------
 
+class cMenuSetupCICAMItem : public cOsdItem {
+private:
+  cCiHandler *ciHandler;
+  int slot;
+public:
+  cMenuSetupCICAMItem(int Device, cCiHandler *CiHandler, int Slot);
+  cCiHandler *CiHandler(void) { return ciHandler; }
+  int Slot(void) { return slot; }
+  };
+
+cMenuSetupCICAMItem::cMenuSetupCICAMItem(int Device, cCiHandler *CiHandler, int Slot)
+{
+  ciHandler = CiHandler;
+  slot = Slot;
+  char buffer[32];
+  const char *CamName = CiHandler->GetCamName(slot);
+  snprintf(buffer, sizeof(buffer), "%s%d %d\t%s", tr("Setup.CICAM$CICAM DVB"), Device + 1, slot + 1, CamName ? CamName : "-");
+  SetText(buffer);
+}
+
 class cMenuSetupCICAM : public cMenuSetupBase {
 private:
-  int helpKeys;
-  void SetHelpKeys(void);
-  cCiHandler *GetCurrentCiHandler(int *Slot = NULL);
   eOSState Menu(void);
   eOSState Reset(void);
 public:
@@ -2136,66 +2129,43 @@
 
 cMenuSetupCICAM::cMenuSetupCICAM(void)
 {
-  helpKeys = -1;
   SetSection(tr("CICAM"));
   for (int d = 0; d < cDevice::NumDevices(); d++) {
       cDevice *Device = cDevice::GetDevice(d);
-      cCiHandler *CiHandler = Device->CiHandler();
-      for (int Slot = 0; Slot < 2; Slot++) {
-          char buffer[32];
-          int CardIndex = Device->CardIndex();
-          const char *CamName = CiHandler ? CiHandler->GetCamName(Slot) : NULL;
-          if (!CamName)
-             CamName = "-";
-          snprintf(buffer, sizeof(buffer), "%s%d %d\t%s", tr("Setup.CICAM$CICAM DVB"), CardIndex + 1, Slot + 1, CamName);
-          Add(new cOsdItem(buffer));
-          }
+      if (Device) {
+         cCiHandler *CiHandler = Device->CiHandler();
+         if (CiHandler) {
+            for (int Slot = 0; Slot < CiHandler->NumSlots(); Slot++)
+                Add(new cMenuSetupCICAMItem(Device->CardIndex(), CiHandler, Slot));
+            }
+         }
       }
-  SetHelpKeys();
-}
-
-cCiHandler *cMenuSetupCICAM::GetCurrentCiHandler(int *Slot)
-{
-  cDevice *Device = cDevice::GetDevice(Current() / 2);
-  if (Slot)
-     *Slot = Current() % 2;
-  return Device ? Device->CiHandler() : NULL;
-}
-
-void cMenuSetupCICAM::SetHelpKeys(void)
-{
-  int NewHelpKeys = helpKeys;
-  NewHelpKeys = GetCurrentCiHandler() ? 1 : 0;
-  if (NewHelpKeys != helpKeys) {
-     switch (NewHelpKeys) {
-       case 0: SetHelp(NULL); break;
-       case 1: SetHelp(tr("Menu"), tr("Reset"));
-       }
-     helpKeys = NewHelpKeys;
-     }
+  SetHelp(tr("Menu"), tr("Reset"));
 }
 
 eOSState cMenuSetupCICAM::Menu(void)
 {
-  int Slot = 0;
-  cCiHandler *CiHandler = GetCurrentCiHandler(&Slot);
-  if (CiHandler && CiHandler->EnterMenu(Slot))
-     return osEnd; // the CAM menu will be executed explicitly from the main loop
-  else
-     Skins.Message(mtError, tr("Can't open CAM menu!"));
+  cMenuSetupCICAMItem *item = (cMenuSetupCICAMItem *)Get(Current());
+  if (item) {
+     if (item->CiHandler()->EnterMenu(item->Slot()))
+        return osEnd; // the CAM menu will be executed explicitly from the main loop
+     else
+        Skins.Message(mtError, tr("Can't open CAM menu!"));
+     }
   return osContinue;
 }
 
 eOSState cMenuSetupCICAM::Reset(void)
 {
-  int Slot = 0;
-  cCiHandler *CiHandler = GetCurrentCiHandler(&Slot);
-  if (CiHandler && CiHandler->Reset(Slot)) {
-     Skins.Message(mtInfo, tr("CAM has been reset"));
-     return osEnd;
+  cMenuSetupCICAMItem *item = (cMenuSetupCICAMItem *)Get(Current());
+  if (item) {
+     if (item->CiHandler()->Reset(item->Slot())) {
+        Skins.Message(mtInfo, tr("CAM has been reset"));
+        return osEnd;
+        }
+     else
+        Skins.Message(mtError, tr("Can't reset CAM!"));
      }
-  else
-     Skins.Message(mtError, tr("Can't reset CAM!"));
   return osContinue;
 }
 
@@ -2205,17 +2175,11 @@
 
   if (state == osUnknown) {
      switch (Key) {
-       case kRed:    if (helpKeys == 1)
-                        return Menu();
-                     break;
-       case kGreen:  if (helpKeys == 1)
-                        return Reset();
-                     break;
+       case kRed:    return Menu();
+       case kGreen:  return Reset();
        default: break;
        }
      }
-  if (Key != kNone)
-     SetHelpKeys();
   return state;
 }
 
diff -ruN vdr-1.3.30/menu.h vdr-1.3.31/menu.h
--- vdr-1.3.30/menu.h	2005-05-15 16:34:54.000000000 +0200
+++ vdr-1.3.31/menu.h	2005-08-27 11:37:33.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: menu.h 1.70 2005/05/15 14:34:54 kls Exp $
+ * $Id: menu.h 1.71 2005/08/27 09:37:33 kls Exp $
  */
 
 #ifndef __MENU_H
@@ -15,6 +15,7 @@
 #include "epg.h"
 #include "osdbase.h"
 #include "dvbplayer.h"
+#include "menuitems.h"
 #include "recorder.h"
 #include "skins.h"
 
@@ -29,6 +30,29 @@
   virtual eOSState ProcessKey(eKeys Key);
   };
 
+class cMenuEditTimer : public cOsdMenu {
+private:
+  cTimer *timer;
+  cTimer data;
+  int channel;
+  bool addIfConfirmed;
+  cMenuEditDateItem *firstday;
+  void SetFirstDayItem(void);
+public:
+  cMenuEditTimer(cTimer *Timer, bool New = false);
+  virtual ~cMenuEditTimer();
+  virtual eOSState ProcessKey(eKeys Key);
+  };
+
+class cMenuEvent : public cOsdMenu {
+private:
+  const cEvent *event;
+public:
+  cMenuEvent(const cEvent *Event, bool CanSwitch = false);
+  virtual void Display(void);
+  virtual eOSState ProcessKey(eKeys Key);
+  };
+
 class cMenuMain : public cOsdMenu {
 private:
   time_t lastActivity;
diff -ruN vdr-1.3.30/newplugin vdr-1.3.31/newplugin
--- vdr-1.3.30/newplugin	2005-08-21 11:32:08.000000000 +0200
+++ vdr-1.3.31/newplugin	2005-08-27 18:13:29.000000000 +0200
@@ -12,7 +12,7 @@
 # See the main source file 'vdr.c' for copyright information and
 # how to reach the author.
 #
-# $Id: newplugin 1.19 2005/08/21 09:32:08 kls Exp $
+# $Id: newplugin 1.20 2005/08/27 16:13:29 kls Exp $
 
 $PLUGIN_NAME = $ARGV[0] || die "Usage: newplugin <name>\n";
 
@@ -171,6 +171,8 @@
   virtual cMenuSetupPage *SetupMenu(void);
   virtual bool SetupParse(const char *Name, const char *Value);
   virtual bool Service(const char *Id, void *Data = NULL);
+  virtual const char **SVDRPHelpPages(void);
+  virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode);
   };
 
 cPlugin${PLUGIN_CLASS}::cPlugin$PLUGIN_CLASS(void)
@@ -243,6 +245,18 @@
   return false;
 }
 
+const char **cPlugin${PLUGIN_CLASS}::SVDRPHelpPages(vo<F3>id)
+{
+  // Return help text for SVDRP commands this plugin implements
+  return NULL;
+}
+
+const char *cPlugin${PLUGIN_CLASS}::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode)
+{
+  // Process SVDRP commands this plugin implements
+  return NULL;
+}
+
 VDRPLUGINCREATOR(cPlugin$PLUGIN_CLASS); // Don't touch this!
 };
 
diff -ruN vdr-1.3.30/plugin.c vdr-1.3.31/plugin.c
--- vdr-1.3.30/plugin.c	2005-08-21 11:35:28.000000000 +0200
+++ vdr-1.3.31/plugin.c	2005-08-27 18:13:24.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: plugin.c 1.14 2005/08/21 09:35:28 kls Exp $
+ * $Id: plugin.c 1.15 2005/08/27 16:13:24 kls Exp $
  */
 
 #include "plugin.h"
@@ -104,6 +104,16 @@
   return false;
 }
 
+const char **cPlugin::SVDRPHelpPages(void)
+{
+  return NULL;
+}
+
+cString cPlugin::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode)
+{
+  return NULL;
+}
+
 void cPlugin::RegisterI18n(const tI18nPhrase * const Phrases)
 {
   I18nRegister(Phrases, Name());
diff -ruN vdr-1.3.30/plugin.h vdr-1.3.31/plugin.h
--- vdr-1.3.30/plugin.h	2005-08-21 11:32:08.000000000 +0200
+++ vdr-1.3.31/plugin.h	2005-08-27 18:13:17.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: plugin.h 1.9 2005/08/21 09:32:08 kls Exp $
+ * $Id: plugin.h 1.10 2005/08/27 16:13:17 kls Exp $
  */
 
 #ifndef __PLUGIN_H
@@ -51,6 +51,8 @@
   void RegisterI18n(const tI18nPhrase * const Phrases);
 
   virtual bool Service(const char *Id, void *Data = NULL);
+  virtual const char **SVDRPHelpPages(void);
+  virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode);
 
   static void SetConfigDirectory(const char *Dir);
   static const char *ConfigDirectory(const char *PluginName = NULL);
diff -ruN vdr-1.3.30/remux.c vdr-1.3.31/remux.c
--- vdr-1.3.30/remux.c	2005-08-21 10:58:58.000000000 +0200
+++ vdr-1.3.31/remux.c	2005-08-28 13:46:44.000000000 +0200
@@ -8,10 +8,10 @@
  * the Linux DVB driver's 'tuxplayer' example and were rewritten to suit
  * VDR's needs.
  *
- * The cDolbyRepacker code was originally written by Reinhard Nissl <rnissl@gmx.de>,
+ * The cRepacker family's code was originally written by Reinhard Nissl <rnissl@gmx.de>,
  * and adapted to the VDR coding style by Klaus.Schmidinger@cadsoft.de.
  *
- * $Id: remux.c 1.37 2005/08/21 08:58:58 kls Exp $
+ * $Id: remux.c 1.42 2005/08/28 11:46:44 kls Exp $
  */
 
 #include "remux.h"
@@ -20,49 +20,23 @@
 #include "thread.h"
 #include "tools.h"
 
-// --- cRepacker -------------------------------------------------------------
-
-class cRepacker {
-protected:
-  int maxPacketSize;
-  uint8_t subStreamId;
-  static void DroppedData(const char *Reason, int Count) { esyslog("%s (dropped %d bytes)", Reason, Count); }
-  static int Put(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count)
-  {
-    int n = ResultBuffer->Put(Data, Count);
-    if (n != Count)
-       esyslog("ERROR: result buffer overflow, dropped %d out of %d byte", Count - n, Count);
-    return n;
-  }
-  static int AnalyzePesHeader(const uchar *Data, int Count, int &PesPayloadOffset, bool *ContinuationHeader = 0);
-public:
-  cRepacker(void) { maxPacketSize = 6 + 65535; subStreamId = 0; }
-  virtual ~cRepacker() {}
-  virtual void Reset(void) {}
-  virtual void Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) = 0;
-  virtual int BreakAt(const uchar *Data, int Count) = 0;
-  virtual int QuerySnoopSize(void) { return 0; }
-  void SetMaxPacketSize(int MaxPacketSize) { maxPacketSize = MaxPacketSize; }
-  void SetSubStreamId(uint8_t SubStreamId) { subStreamId = SubStreamId; }
-  };
-
-int cRepacker::AnalyzePesHeader(const uchar *Data, int Count, int &PesPayloadOffset, bool *ContinuationHeader)
+ePesHeader AnalyzePesHeader(const uchar *Data, int Count, int &PesPayloadOffset, bool *ContinuationHeader)
 {
   if (Count < 7)
-     return -1; // too short
+     return phNeedMoreData; // too short
 
   if ((Data[6] & 0xC0) == 0x80) { // MPEG 2
      if (Count < 9)
-        return -1; // too short
+        return phNeedMoreData; // too short
 
      PesPayloadOffset = 6 + 3 + Data[8];
      if (Count < PesPayloadOffset)
-        return -1; // too short
+        return phNeedMoreData; // too short
 
      if (ContinuationHeader)
         *ContinuationHeader = ((Data[6] == 0x80) && !Data[7] && !Data[8]);
 
-     return 2; // MPEG 2
+     return phMPEG2; // MPEG 2
      }
 
   // check for MPEG 1 ...
@@ -74,7 +48,7 @@
          break;
 
       if (Count <= ++PesPayloadOffset)
-         return -1; // too short
+         return phNeedMoreData; // too short
       }
 
   // skip STD_buffer_scale/size
@@ -82,7 +56,7 @@
      PesPayloadOffset += 2;
 
      if (Count <= PesPayloadOffset)
-        return -1; // too short
+        return phNeedMoreData; // too short
      }
 
   if (ContinuationHeader)
@@ -104,18 +78,50 @@
         *ContinuationHeader = true;
      }
   else
-     return 0; // unknown
+     return phInvalid; // unknown
 
   if (Count < PesPayloadOffset)
-     return -1; // too short
+     return phNeedMoreData; // too short
 
-  return 1; // MPEG 1
+  return phMPEG1; // MPEG 1
 }
 
-// --- cVideoRepacker --------------------------------------------------------
+// --- cRepacker -------------------------------------------------------------
 
-class cVideoRepacker : public cRepacker {
-private:
+class cRepacker {
+protected:
+  bool initiallySyncing;
+  int maxPacketSize;
+  uint8_t subStreamId;
+  static void DroppedData(const char *Reason, int Count) { esyslog("%s (dropped %d bytes)", Reason, Count); }
+public:
+  static int Put(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count, int CapacityNeeded);
+  cRepacker(void) { initiallySyncing = true; maxPacketSize = 6 + 65535; subStreamId = 0; }
+  virtual ~cRepacker() {}
+  virtual void Reset(void) { /* initiallySyncing = true; */ }
+  virtual void Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) = 0;
+  virtual int BreakAt(const uchar *Data, int Count) = 0;
+  virtual int QuerySnoopSize(void) { return 0; }
+  void SetMaxPacketSize(int MaxPacketSize) { maxPacketSize = MaxPacketSize; }
+  void SetSubStreamId(uint8_t SubStreamId) { subStreamId = SubStreamId; }
+  };
+
+int cRepacker::Put(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count, int CapacityNeeded)
+{
+  if (CapacityNeeded >= Count && ResultBuffer->Free() < CapacityNeeded) {
+     esyslog("ERROR: possible result buffer overflow, dropped %d out of %d byte", CapacityNeeded, CapacityNeeded);
+     return 0;
+     }
+  int n = ResultBuffer->Put(Data, Count);
+  if (n != Count)
+     esyslog("ERROR: result buffer overflow, dropped %d out of %d byte", Count - n, Count);
+  return n;
+}
+
+// --- cCommonRepacker --------------------------------------------------------
+
+class cCommonRepacker : public cRepacker {
+protected:
   int skippedBytes;
   int packetTodo;
   uchar fragmentData[6 + 65535 + 3];
@@ -125,40 +131,25 @@
   uchar pesHeaderBackup[6 + 3 + 255];
   int pesHeaderBackupLen;
   uint32_t scanner;
-  enum eState {
-    syncing,
-    findPicture,
-    scanPicture
-    } state;
   uint32_t localScanner;
   int localStart;
   bool PushOutPacket(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count);
-public:
-  cVideoRepacker(void);
-  virtual void Reset(void);
-  virtual void Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count);
-  virtual int BreakAt(const uchar *Data, int Count);
   virtual int QuerySnoopSize() { return 4; }
+  virtual void Reset(void);
   };
 
-cVideoRepacker::cVideoRepacker(void)
-{
-  Reset();
-}
-
-void cVideoRepacker::Reset(void)
+void cCommonRepacker::Reset(void)
 {
+  cRepacker::Reset();
   skippedBytes = 0;
   packetTodo = maxPacketSize - 6 - 3;
   fragmentLen = 0;
   pesHeaderLen = 0;
   pesHeaderBackupLen = 0;
-  scanner = 0xFFFFFFFF;
-  state = syncing;
   localStart = -1;
 }
 
-bool cVideoRepacker::PushOutPacket(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count)
+bool cCommonRepacker::PushOutPacket(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count)
 {
   // enter packet length into PES header ...
   if (fragmentLen > 0) { // ... which is contained in the fragment buffer
@@ -166,11 +157,17 @@
      int PacketLen = fragmentLen + Count - 6;
      fragmentData[ 4 ] = PacketLen >> 8;
      fragmentData[ 5 ] = PacketLen & 0xFF;
+     // just skip packets with no payload
+     int PesPayloadOffset = 0;
+     if (AnalyzePesHeader(fragmentData, fragmentLen, PesPayloadOffset) <= phInvalid)
+        esyslog("cCommonRepacker: invalid PES packet encountered in fragment buffer!");
+     else if (6 + PacketLen <= PesPayloadOffset)
+        return true; // skip empty packet
      // amount of data to put into result buffer: a negative Count value means
      // to strip off any partially contained start code.
      int Bite = fragmentLen + (Count >= 0 ? 0 : Count);
      // put data into result buffer
-     int n = Put(ResultBuffer, fragmentData, Bite);
+     int n = Put(ResultBuffer, fragmentData, Bite, 6 + PacketLen);
      fragmentLen = 0;
      if (n != Bite)
         return false;
@@ -179,11 +176,17 @@
      int PacketLen = pesHeaderLen + Count - 6;
      pesHeader[ 4 ] = PacketLen >> 8;
      pesHeader[ 5 ] = PacketLen & 0xFF;
+     // just skip packets with no payload
+     int PesPayloadOffset = 0;
+     if (AnalyzePesHeader(pesHeader, pesHeaderLen, PesPayloadOffset) <= phInvalid)
+        esyslog("cCommonRepacker: invalid PES packet encountered in header buffer!");
+     else if (6 + PacketLen <= PesPayloadOffset)
+        return true; // skip empty packet
      // amount of data to put into result buffer: a negative Count value means
      // to strip off any partially contained start code.
      int Bite = pesHeaderLen + (Count >= 0 ? 0 : Count);
      // put data into result buffer
-     int n = Put(ResultBuffer, pesHeader, Bite);
+     int n = Put(ResultBuffer, pesHeader, Bite, 6 + PacketLen);
      pesHeaderLen = 0;
      if (n != Bite)
         return false;
@@ -193,7 +196,7 @@
      // amount of data to put into result buffer
      int Bite = Count;
      // put data into result buffer
-     int n = Put(ResultBuffer, Data, Bite);
+     int n = Put(ResultBuffer, Data, Bite, Bite);
      if (n != Bite)
         return false;
      }
@@ -201,6 +204,34 @@
   return true;
 }
 
+// --- cVideoRepacker --------------------------------------------------------
+
+class cVideoRepacker : public cCommonRepacker {
+private:
+  enum eState {
+    syncing,
+    findPicture,
+    scanPicture
+    } state;
+public:
+  cVideoRepacker(void);
+  virtual void Reset(void);
+  virtual void Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count);
+  virtual int BreakAt(const uchar *Data, int Count);
+  };
+
+cVideoRepacker::cVideoRepacker(void)
+{
+  Reset();
+}
+
+void cVideoRepacker::Reset(void)
+{
+  cCommonRepacker::Reset();
+  scanner = 0xFFFFFFFF;
+  state = syncing;
+}
+
 void cVideoRepacker::Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count)
 {
   // synchronisation is detected some bytes after frame start.
@@ -211,8 +242,8 @@
 
   int pesPayloadOffset = 0;
   bool continuationHeader = false;
-  int mpegLevel = AnalyzePesHeader(Data, Count, pesPayloadOffset, &continuationHeader);
-  if (mpegLevel <= 0) {
+  ePesHeader mpegLevel = AnalyzePesHeader(Data, Count, pesPayloadOffset, &continuationHeader);
+  if (mpegLevel <= phInvalid) {
      DroppedData("cVideoRepacker: no valid PES packet header found", Count);
      return;
      }
@@ -264,16 +295,14 @@
                      // the byte count get's negative then the current buffer ends in a
                      // partitial start code that must be stripped off, as it shall be put
                      // in the next packet.
-                     if (!PushOutPacket(ResultBuffer, payload, data - 3 - payload)) {
-                        DroppedData("cVideoRepacker: result buffer overflow", Count - (done - 3));
-                        return;
-                        }
+                     PushOutPacket(ResultBuffer, payload, data - 3 - payload);
                      // go on with syncing to the next picture
                      state = syncing;
                      }
                   if (state == syncing) {
-                     // report that syncing dropped some bytes
-                     if (skippedBytes > SkippedBytesLimit)
+                     if (initiallySyncing) // omit report for the typical initial case
+                        initiallySyncing = false;
+                     else if (skippedBytes > SkippedBytesLimit) // report that syncing dropped some bytes
                         esyslog("cVideoRepacker: skipped %d bytes to sync on next picture", skippedBytes - SkippedBytesLimit);
                      skippedBytes = 0;
                      // if there is a PES header available, then use it ...
@@ -298,7 +327,7 @@
                         pesHeader[pesHeaderLen++] = 0x00; // length still unknown
                         pesHeader[pesHeaderLen++] = 0x00; // length still unknown
 
-                        if (mpegLevel == 2) {
+                        if (mpegLevel == phMPEG2) {
                            pesHeader[pesHeaderLen++] = 0x80;
                            pesHeader[pesHeaderLen++] = 0x00;
                            pesHeader[pesHeaderLen++] = 0x00;
@@ -379,10 +408,7 @@
               const uchar *excessData = fragmentData + fragmentLen + bite;
               // a negative byte count means to drop some bytes from the current
               // fragment's tail, to not exceed the maximum packet size.
-              if (!PushOutPacket(ResultBuffer, payload, bite)) {
-                 DroppedData("cVideoRepacker: result buffer overflow", Count - done);
-                 return;
-                 }
+              PushOutPacket(ResultBuffer, payload, bite);
               // create a continuation PES header
               pesHeaderLen = 0;
               pesHeader[pesHeaderLen++] = 0x00;
@@ -392,7 +418,7 @@
               pesHeader[pesHeaderLen++] = 0x00; // length still unknown
               pesHeader[pesHeaderLen++] = 0x00; // length still unknown
 
-              if (mpegLevel == 2) {
+              if (mpegLevel == phMPEG2) {
                  pesHeader[pesHeaderLen++] = 0x80;
                  pesHeader[pesHeaderLen++] = 0x00;
                  pesHeader[pesHeaderLen++] = 0x00;
@@ -434,7 +460,8 @@
      }
   // report that syncing dropped some bytes
   if (skippedBytes > SkippedBytesLimit) {
-     esyslog("cVideoRepacker: skipped %d bytes while syncing on next picture", skippedBytes - SkippedBytesLimit);
+     if (!initiallySyncing) // omit report for the typical initial case
+        esyslog("cVideoRepacker: skipped %d bytes while syncing on next picture", skippedBytes - SkippedBytesLimit);
      skippedBytes = SkippedBytesLimit;
      }
 }
@@ -443,7 +470,7 @@
 {
   int PesPayloadOffset = 0;
 
-  if (AnalyzePesHeader(Data, Count, PesPayloadOffset) <= 0)
+  if (AnalyzePesHeader(Data, Count, PesPayloadOffset) <= phInvalid)
      return -1; // not enough data for test
 
   // just detect end of picture
@@ -475,6 +502,381 @@
   return PesPayloadOffset + packetTodo + 4;
 }
 
+// --- cAudioRepacker --------------------------------------------------------
+
+class cAudioRepacker : public cCommonRepacker {
+private:
+  static int bitRates[2][3][16];
+  enum eState {
+    syncing,
+    scanFrame
+    } state;
+  int frameTodo;
+  int frameSize;
+  static bool IsValidAudioHeader(uint32_t Header, bool Mpeg2, int *FrameSize = NULL);
+public:
+  cAudioRepacker(void);
+  virtual void Reset(void);
+  virtual void Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count);
+  virtual int BreakAt(const uchar *Data, int Count);
+  };
+
+int cAudioRepacker::bitRates[2][3][16] = { // all values are specified as kbits/s
+  // MPEG 1, Layer I
+  0,  32,  64,  96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, -1,
+  // MPEG 1, Layer II
+  0,  32,  48,  56,  64,  80,  96, 112, 128, 160, 192, 224, 256, 320, 384, -1,
+  // MPEG 1, Layer III
+  0,  32,  40,  48,  56,  64,  80,  96, 112, 128, 160, 192, 224, 256, 320, -1,
+  // MPEG 2, Layer I
+  0,  32,  48,  56,  64,  80,  96, 112, 128, 144, 160, 176, 192, 224, 256, -1,
+  // MPEG 2, Layer II/III
+  0,   8,  16,  24,  32,  40,  48,  56,  64,  80,  96, 112, 128, 144, 160, -1,
+  // MPEG 2, Layer II/III
+  0,   8,  16,  24,  32,  40,  48,  56,  64,  80,  96, 112, 128, 144, 160, -1
+  };
+
+cAudioRepacker::cAudioRepacker(void)
+{
+  Reset();
+}
+
+void cAudioRepacker::Reset(void)
+{
+  cCommonRepacker::Reset();
+  scanner = 0;
+  state = syncing;
+  frameTodo = 0;
+  frameSize = 0;
+}
+
+bool cAudioRepacker::IsValidAudioHeader(uint32_t Header, bool Mpeg2, int *FrameSize)
+{
+  int syncword           = (Header & 0xFFF00000) >> 20;
+  int id                 = (Header & 0x00080000) >> 19;
+  int layer              = (Header & 0x00060000) >> 17;
+//int protection_bit     = (Header & 0x00010000) >> 16;
+  int bitrate_index      = (Header & 0x0000F000) >> 12;
+  int sampling_frequency = (Header & 0x00000C00) >> 10;
+  int padding_bit        = (Header & 0x00000200) >>  9;
+//int private_bit        = (Header & 0x00000100) >>  8;
+//int mode               = (Header & 0x000000C0) >>  6;
+//int mode_extension     = (Header & 0x00000030) >>  4;
+//int copyright          = (Header & 0x00000008) >>  3;
+//int orignal_copy       = (Header & 0x00000004) >>  2;
+  int emphasis           = (Header & 0x00000003);
+
+  if (syncword != 0xFFF)
+     return false;
+
+  if (id == 0 && !Mpeg2) // reserved in MPEG 1
+     return false;
+
+  if (layer == 0) // reserved
+     return false;
+
+  if (bitrate_index == 0xF) // forbidden
+     return false;
+
+  if (sampling_frequency == 3) // reserved
+     return false;
+
+  if (emphasis == 2) // reserved
+     return false;
+
+  if (FrameSize) {
+     if (bitrate_index == 0)
+        *FrameSize = 0;
+     else {
+        static int samplingFrequencies[2][4] = { // all values are specified in Hz
+          // MPEG 1
+          44100, 48000, 32000, -1,
+          // MPEG 2
+          22050, 24000, 16000, -1
+          };
+
+        static int slots_per_frame[2][3] = {
+          // MPEG 1, Layer I, II, III
+          12, 144, 144,
+          // MPEG 2, Layer I, II, III
+          12, 144,  72
+          };
+
+        int mpegIndex = 1 - id;
+        int layerIndex = 3 - layer;
+
+        // Layer I (i. e., layerIndex == 0) has a larger slot size
+        int slotSize = (layerIndex == 0) ? 4 : 1; // bytes
+
+        int br = 1000 * bitRates[mpegIndex][layerIndex][bitrate_index]; // bits/s
+        int sf = samplingFrequencies[mpegIndex][sampling_frequency];
+
+        int N = slots_per_frame[mpegIndex][layerIndex] * br / sf; // slots
+
+        *FrameSize = (N + padding_bit) * slotSize; // bytes
+        }
+     }
+
+  return true;
+}
+
+void cAudioRepacker::Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count)
+{
+  // synchronisation is detected some bytes after frame start.
+  const int SkippedBytesLimit = 4;
+
+  // reset local scanner
+  localStart = -1;
+
+  int pesPayloadOffset = 0;
+  bool continuationHeader = false;
+  ePesHeader mpegLevel = AnalyzePesHeader(Data, Count, pesPayloadOffset, &continuationHeader);
+  if (mpegLevel <= phInvalid) {
+     DroppedData("cAudioRepacker: no valid PES packet header found", Count);
+     return;
+     }
+  if (!continuationHeader) {
+     // backup PES header
+     pesHeaderBackupLen = pesPayloadOffset;
+     memcpy(pesHeaderBackup, Data, pesHeaderBackupLen);
+     }
+
+  // skip PES header
+  int done = pesPayloadOffset;
+  int todo = Count - done;
+  const uchar *data = Data + done;
+  // remember start of the data
+  const uchar *payload = data;
+
+  while (todo > 0) {
+        // collect number of skipped bytes while syncing
+        if (state <= syncing)
+           skippedBytes++;
+        else if (frameTodo > 0) {
+           frameTodo--;
+           if (frameTodo == 0 && state == scanFrame) {
+              // the current audio frame is is done now. So push out the packet to
+              // start a new packet for the next audio frame.
+              PushOutPacket(ResultBuffer, payload, data - payload);
+              // go on with syncing to the next audio frame
+              state = syncing;
+              }
+           }
+        // did we reach an audio frame header?
+        scanner <<= 8;
+        scanner |= *data;
+        if ((scanner & 0xFFF00000) == 0xFFF00000) {
+           if (frameTodo <= 0 && IsValidAudioHeader(scanner, mpegLevel == phMPEG2, &frameSize)) {
+              if (state == scanFrame) {
+                 // As a new audio frame starts here, the previous one is done. So push
+                 // out the packet to start a new packet for the next audio frame. If
+                 // the byte count gets negative then the current buffer ends in a
+                 // partitial audio frame header that must be stripped off, as it shall
+                 // be put in the next packet.
+                 PushOutPacket(ResultBuffer, payload, data - 3 - payload);
+                 // go on with syncing to the next audio frame
+                 state = syncing;
+                 }
+              if (state == syncing) {
+                 if (initiallySyncing) // omit report for the typical initial case
+                    initiallySyncing = false;
+                 else if (skippedBytes > SkippedBytesLimit) // report that syncing dropped some bytes
+                    esyslog("cAudioRepacker: skipped %d bytes to sync on next audio frame", skippedBytes - SkippedBytesLimit);
+                 skippedBytes = 0;
+                 // if there is a PES header available, then use it ...
+                 if (pesHeaderBackupLen > 0) {
+                    // ISO 13818-1 says:
+                    // In the case of audio, if a PTS is present in a PES packet header
+                    // it shall refer to the access unit commencing in the PES packet. An
+                    // audio access unit commences in a PES packet if the first byte of
+                    // the audio access unit is present in the PES packet.
+                    memcpy(pesHeader, pesHeaderBackup, pesHeaderBackupLen);
+                    pesHeaderLen = pesHeaderBackupLen;
+                    pesHeaderBackupLen = 0;
+                    }
+                 else {
+                    // ... otherwise create a continuation PES header
+                    pesHeaderLen = 0;
+                    pesHeader[pesHeaderLen++] = 0x00;
+                    pesHeader[pesHeaderLen++] = 0x00;
+                    pesHeader[pesHeaderLen++] = 0x01;
+                    pesHeader[pesHeaderLen++] = Data[3]; // audio stream ID
+                    pesHeader[pesHeaderLen++] = 0x00; // length still unknown
+                    pesHeader[pesHeaderLen++] = 0x00; // length still unknown
+
+                    if (mpegLevel == phMPEG2) {
+                       pesHeader[pesHeaderLen++] = 0x80;
+                       pesHeader[pesHeaderLen++] = 0x00;
+                       pesHeader[pesHeaderLen++] = 0x00;
+                       }
+                    else
+                       pesHeader[pesHeaderLen++] = 0x0F;
+                    }
+                 // append the first three bytes of the audio frame header
+                 pesHeader[pesHeaderLen++] = 0xFF;
+                 pesHeader[pesHeaderLen++] = (scanner >> 16) & 0xFF;
+                 pesHeader[pesHeaderLen++] = (scanner >>  8) & 0xFF;
+                 // the next packet's payload will begin with the fourth byte of
+                 // the audio frame header (= the actual byte)
+                 payload = data;
+                 // maximum we can hold in one PES packet
+                 packetTodo = maxPacketSize - pesHeaderLen;
+                 // expected remainder of audio frame: so far we have read 3 bytes from the frame header 
+                 frameTodo = frameSize - 3;
+                 // go on with collecting the frame's data
+                 ((int &)state)++;
+                 }
+              }
+           }
+        data++;
+        done++;
+        todo--;
+        // do we have to start a new packet as there is no more space left?
+        if (--packetTodo <= 0) {
+           // We connot start a new packet here if the current might end in an audio
+           // frame header and this header shall possibly be put in the next packet. So
+           // overfill the current packet until we can safely detect that we won't
+           // break an audio frame header into pieces:
+           //
+           // A) the last four bytes were an audio frame header.
+           // B) the last three bytes introduce an audio frame header.
+           // C) the last two bytes introduce an audio frame header.
+           // D) the last byte introduces an audio frame header.
+           //
+           // Todo : Data                          : Rule : Result
+           // -----:-------------------------------:------:-------
+           //      : XX XX FF Fz zz zz|YY YY YY YY :      :
+           //    0 :                ^^|            : A    : push
+           // -----:-------------------------------:------:-------
+           //      : XX XX XX FF Fz zz|zz YY YY YY :      :
+           //    0 :                ^^|            : B    : wait
+           //   -1 :                  |^^          : A    : push
+           // -----:-------------------------------:------:-------
+           //      : XX XX XX XX FF Fz|zz zz YY YY :      :
+           //    0 :                ^^|            : C    : wait
+           //   -1 :                  |^^          : B    : wait
+           //   -2 :                  |   ^^       : A    : push
+           // -----:-------------------------------:------:-------
+           //      : XX XX XX XX XX FF|Fz zz zz YY :      :
+           //    0 :                ^^|            : D    : wait
+           //   -1 :                  |^^          : C    : wait
+           //   -2 :                  |   ^^       : B    : wait
+           //   -3 :                  |      ^^    : A    : push
+           // -----:-------------------------------:------:-------
+           bool A = ((scanner & 0xFFF00000) == 0xFFF00000);
+           bool B = ((scanner &   0xFFF000) ==   0xFFF000);
+           bool C = ((scanner &     0xFFF0) ==     0xFFF0);
+           bool D = ((scanner &       0xFF) ==       0xFF);
+           if (A || (!B && !C && !D)) {
+              // Actually we cannot push out an overfull packet. So we'll have to
+              // adjust the byte count and payload start as necessary. If the byte
+              // count gets negative we'll have to append the excess from fragment's
+              // tail to the next PES header.
+              int bite = data + packetTodo - payload;
+              const uchar *excessData = fragmentData + fragmentLen + bite;
+              // A negative byte count means to drop some bytes from the current
+              // fragment's tail, to not exceed the maximum packet size.
+              PushOutPacket(ResultBuffer, payload, bite);
+              // create a continuation PES header
+              pesHeaderLen = 0;
+              pesHeader[pesHeaderLen++] = 0x00;
+              pesHeader[pesHeaderLen++] = 0x00;
+              pesHeader[pesHeaderLen++] = 0x01;
+              pesHeader[pesHeaderLen++] = Data[3]; // audio stream ID
+              pesHeader[pesHeaderLen++] = 0x00; // length still unknown
+              pesHeader[pesHeaderLen++] = 0x00; // length still unknown
+
+              if (mpegLevel == phMPEG2) {
+                 pesHeader[pesHeaderLen++] = 0x80;
+                 pesHeader[pesHeaderLen++] = 0x00;
+                 pesHeader[pesHeaderLen++] = 0x00;
+                 }
+              else
+                 pesHeader[pesHeaderLen++] = 0x0F;
+
+              // copy any excess data
+              while (bite++ < 0) {
+                    // append the excess data here
+                    pesHeader[pesHeaderLen++] = *excessData++;
+                    packetTodo++;
+                    }
+              // the next packet's payload will begin here
+              payload = data + packetTodo;
+              // as there is no length information available, assume the
+              // maximum we can hold in one PES packet
+              packetTodo += maxPacketSize - pesHeaderLen;
+              }
+           }
+        }
+  // The packet is done. Now store any remaining data into fragment buffer
+  // if we are no longer syncing.
+  if (state != syncing) {
+     // append the PES header ...
+     int bite = pesHeaderLen;
+     pesHeaderLen = 0;
+     if (bite > 0) {
+        memcpy(fragmentData + fragmentLen, pesHeader, bite);
+        fragmentLen += bite;
+        }
+     // append payload. It may contain part of an audio frame header at it's
+     // end, which will be removed when the next packet gets processed.
+     bite = data - payload;
+     if (bite > 0) {
+        memcpy(fragmentData + fragmentLen, payload, bite);
+        fragmentLen += bite;
+        }
+     }
+  // report that syncing dropped some bytes
+  if (skippedBytes > SkippedBytesLimit) {
+     if (!initiallySyncing) // omit report for the typical initial case
+        esyslog("cAudioRepacker: skipped %d bytes while syncing on next audio frame", skippedBytes - SkippedBytesLimit);
+     skippedBytes = SkippedBytesLimit;
+     }
+}
+
+int cAudioRepacker::BreakAt(const uchar *Data, int Count)
+{
+  int PesPayloadOffset = 0;
+
+  ePesHeader MpegLevel = AnalyzePesHeader(Data, Count, PesPayloadOffset);
+  if (MpegLevel <= phInvalid)
+     return -1; // not enough data for test
+
+  // determine amount of data to fill up packet and to append next audio frame header
+  int packetRemainder = PesPayloadOffset + packetTodo + 4;
+
+  // just detect end of an audio frame
+  if (state == scanFrame) {
+     // when remaining audio frame size is known, then omit scanning
+     if (frameTodo > 0) {
+        // determine amount of data to fill up audio frame and to append next audio frame header
+        int remaining = PesPayloadOffset + frameTodo + 4;
+        if (remaining < packetRemainder)
+           return remaining;
+        return packetRemainder;
+        }
+     // setup local scanner
+     if (localStart < 0) {
+        localScanner = scanner;
+        localStart = 0;
+        }
+     // start where we've stopped at the last run
+     const uchar *data = Data + PesPayloadOffset + localStart;
+     const uchar *limit = Data + Count;
+     // scan data
+     while (data < limit) {
+           localStart++;
+           localScanner <<= 8;
+           localScanner |= *data++;
+           // check whether the next audio frame follows
+           if (((localScanner & 0xFFF00000) == 0xFFF00000) && IsValidAudioHeader(localScanner, MpegLevel == phMPEG2))
+              return data - Data;
+           }
+     }
+  // just fill up packet and append next audio frame header
+  return packetRemainder;
+}
+
 // --- cDolbyRepacker --------------------------------------------------------
 
 class cDolbyRepacker : public cRepacker {
@@ -501,8 +903,8 @@
   int skippedBytes;
   void ResetPesHeader(bool ContinuationFrame = false);
   void AppendSubStreamID(bool ContinuationFrame = false);
-  bool FinishRemainder(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Done, int &Bite);
-  bool StartNewPacket(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Done, int &Bite);
+  bool FinishRemainder(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Bite);
+  bool StartNewPacket(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Bite);
 public:
   cDolbyRepacker(void);
   virtual void Reset(void);
@@ -569,6 +971,7 @@
 
 void cDolbyRepacker::Reset(void)
 {
+  cRepacker::Reset();
   ResetPesHeader();
   state = find_0b;
   ac3todo = 0;
@@ -580,26 +983,24 @@
   skippedBytes = 0;
 }
 
-bool cDolbyRepacker::FinishRemainder(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Done, int &Bite)
+bool cDolbyRepacker::FinishRemainder(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Bite)
 {
+  bool success = true;
   // enough data available to put PES packet into buffer?
   if (fragmentTodo <= Todo) {
      // output a previous fragment first
      if (fragmentLen > 0) {
         Bite = fragmentLen;
-        int n = Put(ResultBuffer, fragmentData, Bite);
-        if (Bite != n) {
-           Reset();
-           return false;
-           }
+        int n = Put(ResultBuffer, fragmentData, Bite, fragmentLen + fragmentTodo);
+        if (Bite != n)
+           success = false;
         fragmentLen = 0;
         }
      Bite = fragmentTodo;
-     int n = Put(ResultBuffer, Data, Bite);
-     if (Bite != n) {
-        Reset();
-        Done += n;
-        return false;
+     if (success) {
+        int n = Put(ResultBuffer, Data, Bite, Bite);
+        if (Bite != n)
+           success = false;
         }
      fragmentTodo = 0;
      // ac3 frame completely processed?
@@ -609,19 +1010,16 @@
   else {
      // copy the fragment into separate buffer for later processing
      Bite = Todo;
-     if (fragmentLen + Bite > (int)sizeof(fragmentData)) {
-        Reset();
-        return false;
-        }
      memcpy(fragmentData + fragmentLen, Data, Bite);
      fragmentLen += Bite;
      fragmentTodo -= Bite;
      }
-  return true;
+  return success;
 }
 
-bool cDolbyRepacker::StartNewPacket(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Done, int &Bite)
+bool cDolbyRepacker::StartNewPacket(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Bite)
 {
+  bool success = true;
   int packetLen = pesHeaderLen + ac3todo;
   // limit packet to maximum size
   if (packetLen > maxPacketSize)
@@ -631,17 +1029,14 @@
   Bite = pesHeaderLen;
   // enough data available to put PES packet into buffer?
   if (packetLen - pesHeaderLen <= Todo) {
-     int n = Put(ResultBuffer, pesHeader, Bite);
-     if (Bite != n) {
-        Reset();
-        return false;
-        }
+     int n = Put(ResultBuffer, pesHeader, Bite, packetLen);
+     if (Bite != n)
+        success = false;
      Bite = packetLen - pesHeaderLen;
-     n = Put(ResultBuffer, Data, Bite);
-     if (Bite != n) {
-        Reset();
-        Done += n;
-        return false;
+     if (success) {
+        n = Put(ResultBuffer, Data, Bite, Bite);
+        if (Bite != n)
+           success = false;
         }
      // ac3 frame completely processed?
      if (Bite >= ac3todo)
@@ -650,24 +1045,16 @@
   else {
      fragmentTodo = packetLen;
      // copy the pesheader into separate buffer for later processing
-     if (fragmentLen + Bite > (int)sizeof(fragmentData)) {
-        Reset();
-        return false;
-        }
      memcpy(fragmentData + fragmentLen, pesHeader, Bite);
      fragmentLen += Bite;
      fragmentTodo -= Bite;
      // copy the fragment into separate buffer for later processing
      Bite = Todo;
-     if (fragmentLen + Bite > (int)sizeof(fragmentData)) {
-        Reset();
-        return false;
-        }
      memcpy(fragmentData + fragmentLen, Data, Bite);
      fragmentLen += Bite;
      fragmentTodo -= Bite;
      }
-  return true;
+  return success;
 }
 
 void cDolbyRepacker::Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count)
@@ -761,8 +1148,9 @@
                   state = find_0b;
                   continue;
                   }
-               // report that syncing dropped some bytes
-               if (skippedBytes > SkippedBytesLimit)
+               if (initiallySyncing) // omit report for the typical initial case
+                  initiallySyncing = false;
+               else if (skippedBytes > SkippedBytesLimit) // report that syncing dropped some bytes
                   esyslog("cDolbyRepacker: skipped %d bytes to sync on next AC3 frame", skippedBytes - SkippedBytesLimit);
                skippedBytes = 0;
                // append read data to header for common output processing
@@ -776,18 +1164,11 @@
           case output_packet: {
                int bite = 0;
                // finish remainder of ac3 frame?
-               if (fragmentTodo > 0) {
-                  if (!FinishRemainder(ResultBuffer, data, todo, done, bite)) {
-                     DroppedData("cDolbyRepacker: result buffer overflow", Count - done);
-                     return;
-                     }
-                  }
+               if (fragmentTodo > 0)
+                  FinishRemainder(ResultBuffer, data, todo, bite);
                else {
                   // start a new packet
-                  if (!StartNewPacket(ResultBuffer, data, todo, done, bite)) {
-                     DroppedData("cDolbyRepacker: result buffer overflow", Count - done);
-                     return;
-                     }
+                  StartNewPacket(ResultBuffer, data, todo, bite);
                   // prepare for next (continuation) packet
                   ResetPesHeader(state == output_packet);
                   }
@@ -800,7 +1181,8 @@
         }
   // report that syncing dropped some bytes
   if (skippedBytes > SkippedBytesLimit) {
-     esyslog("cDolbyRepacker: skipped %d bytes while syncing on next AC3 frame", skippedBytes - 4);
+     if (!initiallySyncing) // omit report for the typical initial case
+        esyslog("cDolbyRepacker: skipped %d bytes while syncing on next AC3 frame", skippedBytes - 4);
      skippedBytes = SkippedBytesLimit;
      }
 }
@@ -890,7 +1272,8 @@
   uint8_t hlength;
   int mpeg;
   uint8_t check;
-  int which;
+  int mpeg1_required;
+  int mpeg1_stuffing;
   bool done;
   cRingBufferLinear *resultBuffer;
   int tsErrors;
@@ -956,11 +1339,8 @@
 {
   if (repacker)
      repacker->Repack(resultBuffer, Data, Count);
-  else {
-     int n = resultBuffer->Put(Data, Count);
-     if (n != Count)
-        esyslog("ERROR: result buffer overflow, dropped %d out of %d byte", Count - n, Count);
-     }
+  else
+     cRepacker::Put(resultBuffer, Data, Count, Count);
 }
 
 void cTS2PES::reset_ipack(void)
@@ -973,7 +1353,8 @@
   hlength = 0;
   mpeg = 0;
   check = 0;
-  which = 0;
+  mpeg1_required = 0;
+  mpeg1_stuffing = 0;
   done = false;
   count = 0;
 }
@@ -1057,7 +1438,7 @@
 {
   int c = 0;
 
-  while (c < Count && (mpeg == 0 || (mpeg == 1 && found < 7) || (mpeg == 2 && found < 9)) && (found < 5 || !done)) {
+  while (c < Count && (mpeg == 0 || (mpeg == 1 && found < mpeg1_required) || (mpeg == 2 && found < 9)) && (found < 5 || !done)) {
         switch (found ) {
           case 0:
           case 1:
@@ -1103,6 +1484,7 @@
                      plength = ntohs(*pl);
                      c += 2;
                      found += 2;
+                     mpeg1_stuffing = 0;
                      }
                   else {
                      plen[0] = Buf[c];
@@ -1115,32 +1497,54 @@
                     unsigned short *pl = (unsigned short *)plen;
                     plength = ntohs(*pl);
                     found++;
+                    mpeg1_stuffing = 0;
                   }
                   break;
           case 6:
                   if (!done) {
                      flag1 = Buf[c++];
                      found++;
-                     if ((flag1 & 0xC0) == 0x80 )
-                        mpeg = 2;
-                     else {
-                        hlength = 0;
-                        which = 0;
-                        mpeg = 1;
-                        flag2 = 0;
+                     if (mpeg1_stuffing == 0) { // first stuffing iteration: determine MPEG level
+                        if ((flag1 & 0xC0) == 0x80)
+                           mpeg = 2;
+                        else {
+                           mpeg = 1;
+                           mpeg1_required = 7;
+                           }
+                        }
+                     if (mpeg == 1) {
+                        if (flag1 == 0xFF) { // MPEG1 stuffing
+                           if (++mpeg1_stuffing > 16)
+                              found = 0; // invalid MPEG1 header
+                           else { // ignore stuffing
+                              found--;
+                              if (plength > 0)
+                                 plength--;
+                              }
+                           }
+                        else if ((flag1 & 0xC0) == 0x40) // STD_buffer_scale/size
+                           mpeg1_required += 2;
+                        else if (flag1 != 0x0F && (flag1 & 0xF0) != 0x20 && (flag1 & 0xF0) != 0x30)
+                           found = 0; // invalid MPEG1 header
+                        else {
+                           flag2 = 0;
+                           hlength = 0;
+                           }
                         }
                      }
                   break;
           case 7:
-                  if (!done && mpeg == 2) {
+                  if (!done && (mpeg == 2 || mpeg1_required > 7)) {
                      flag2 = Buf[c++];
                      found++;
                      }
                   break;
           case 8:
-                  if (!done && mpeg == 2) {
+                  if (!done && (mpeg == 2 || mpeg1_required > 7)) {
                      hlength = Buf[c++];
                      found++;
+                     if (mpeg == 1 && hlength != 0x0F && (hlength & 0xF0) != 0x20 && (hlength & 0xF0) != 0x30)
+                        found = 0; // invalid MPEG1 header
                      }
                   break;
           default:
@@ -1151,7 +1555,7 @@
   if (!plength)
      plength = MMAX_PLENGTH - 6;
 
-  if (done || ((mpeg == 2 && found >= 9) || (mpeg == 1 && found >= 7))) {
+  if (done || ((mpeg == 2 && found >= 9) || (mpeg == 1 && found >= mpeg1_required))) {
      switch (cid) {
        case AUDIO_STREAM_S ... AUDIO_STREAM_E:
        case VIDEO_STREAM_S ... VIDEO_STREAM_E:
@@ -1163,8 +1567,13 @@
                write_ipack(&hlength, 1);
                }
 
-            if (mpeg == 1 && found == 7)
+            if (mpeg == 1 && found == mpeg1_required) {
                write_ipack(&flag1, 1);
+               if (mpeg1_required > 7) {
+                  write_ipack(&flag2, 1);
+                  write_ipack(&hlength, 1);
+                  }
+               }
 
             if (mpeg == 2 && (flag2 & PTS_ONLY) && found < 14) {
                while (c < Count && found < 14) {
@@ -1277,7 +1686,12 @@
   if (APids) {
      int n = 0;
      while (*APids && numTracks < MAXTRACKS && n < MAXAPIDS)
+#define TEST_cAudioRepacker
+#ifdef TEST_cAudioRepacker
+           ts2pes[numTracks++] = new cTS2PES(*APids++, resultBuffer, IPACKS, 0xC0 + n++, 0x00, new cAudioRepacker);
+#else
            ts2pes[numTracks++] = new cTS2PES(*APids++, resultBuffer, IPACKS, 0xC0 + n++);
+#endif
      }
   if (DPids) {
      int n = 0;
@@ -1321,10 +1735,9 @@
   // If the return value is -1 the packet was not completely in the buffer.
   int Length = GetPacketLength(Data, Count, Offset);
   if (Length > 0) {
-     if (Length >= 8) {
-        int i = Offset + 8; // the minimum length of the video packet header
-        i += Data[i] + 1;   // possible additional header bytes
-        for (; i < Offset + Length - 5; i++) {
+     int PesPayloadOffset = 0;
+     if (AnalyzePesHeader(Data + Offset, Length, PesPayloadOffset) >= phMPEG1) {
+        for (int i = Offset + PesPayloadOffset; i < Offset + Length - 5; i++) {
             if (Data[i] == 0 && Data[i + 1] == 0 && Data[i + 2] == 1) {
                switch (Data[i + 3]) {
                  case SC_PICTURE: PictureType = (Data[i + 5] >> 3) & 0x07;
@@ -1409,19 +1822,6 @@
   return resultBuffer->Get(Count);
 #endif
 
-  // Special VPID case to enable recording radio channels:
-
-  if (isRadio) {
-     // XXX actually '0' should be enough, but '1' must be used with encrypted channels (driver bug?)
-     // XXX also allowing 0x1FFF to not break Michael Paar's original patch,
-     // XXX but it would probably be best to only use '0'
-     // Force syncing of radio channels to avoid "no useful data" error
-     synced = true;
-     if (PictureType)
-        *PictureType = I_FRAME;
-     return resultBuffer->Get(Count);
-     }
-
   // Check for frame borders:
 
   if (PictureType)
@@ -1466,6 +1866,18 @@
                l = GetPacketLength(data, resultCount, i);
                if (l < 0)
                   return resultData;
+               if (isRadio) {
+                  if (!synced) {
+                     if (PictureType)
+                        *PictureType = I_FRAME;
+                     resultSkipped = i; // will drop everything before this position
+                     synced = true;
+                     }
+                  else if (Count)
+                     return resultData;
+                  else if (PictureType)
+                     *PictureType = I_FRAME;
+                  }
                }
             if (synced) {
                if (!Count)
@@ -1499,8 +1911,9 @@
 
 void cRemux::SetBrokenLink(uchar *Data, int Length)
 {
-  if (Length > 9 && Data[0] == 0 && Data[1] == 0 && Data[2] == 1 && (Data[3] & 0xF0) == VIDEO_STREAM_S) {
-     for (int i = Data[8] + 9; i < Length - 7; i++) { // +9 to skip video packet header
+  int PesPayloadOffset = 0;
+  if (AnalyzePesHeader(Data, Length, PesPayloadOffset) >= phMPEG1 && (Data[3] & 0xF0) == VIDEO_STREAM_S) {
+     for (int i = PesPayloadOffset; i < Length - 7; i++) {
          if (Data[i] == 0 && Data[i + 1] == 0 && Data[i + 2] == 1 && Data[i + 3] == 0xB8) {
             if (!(Data[i + 7] & 0x40)) // set flag only if GOP is not closed
                Data[i + 7] |= 0x20;
diff -ruN vdr-1.3.30/remux.h vdr-1.3.31/remux.h
--- vdr-1.3.30/remux.h	2005-08-07 12:28:07.000000000 +0200
+++ vdr-1.3.31/remux.h	2005-08-26 15:22:19.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: remux.h 1.14 2005/08/07 10:28:07 kls Exp $
+ * $Id: remux.h 1.15 2005/08/26 13:22:19 kls Exp $
  */
 
 #ifndef __REMUX_H
@@ -15,6 +15,15 @@
 #include "ringbuffer.h"
 #include "tools.h"
 
+enum ePesHeader {
+  phNeedMoreData = -1,
+  phInvalid = 0,
+  phMPEG1 = 1,
+  phMPEG2 = 2
+  };
+
+ePesHeader AnalyzePesHeader(const uchar *Data, int Count, int &PesPayloadOffset, bool *ContinuationHeader = NULL);
+
 // Picture types:
 #define NO_PICTURE 0
 #define I_FRAME    1
diff -ruN vdr-1.3.30/sdt.c vdr-1.3.31/sdt.c
--- vdr-1.3.30/sdt.c	2005-05-14 11:39:46.000000000 +0200
+++ vdr-1.3.31/sdt.c	2005-08-27 11:27:47.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: sdt.c 1.14 2005/05/14 09:39:46 kls Exp $
+ * $Id: sdt.c 1.15 2005/08/27 09:27:47 kls Exp $
  */
 
 #include "sdt.h"
@@ -64,11 +64,14 @@
                         char *ps = compactspace(ShortNameBuf);
                         if (!*ps && cSource::IsCable(Source())) {
                            // Some cable providers don't mark short channel names according to the
-                           // standard, but rather go their own way and use "name>short name":
-                           char *p = strchr(pn, '>');
+                           // standard, but rather go their own way and use "name>short name" or
+                           // "name, short name":
+                           char *p = strchr(pn, '>'); // fix for UPC Wien
+                           if (!p)
+                              p = strchr(pn, ','); // fix for "Kabel Deutschland"
                            if (p && p > pn) {
                               *p++ = 0;
-                              strcpy(ShortNameBuf, p);
+                              strcpy(ShortNameBuf, skipspace(p));
                               }
                            }
                         sd->providerName.getText(ProviderNameBuf, sizeof(ProviderNameBuf));
diff -ruN vdr-1.3.30/svdrp.c vdr-1.3.31/svdrp.c
--- vdr-1.3.30/svdrp.c	2005-08-07 16:20:41.000000000 +0200
+++ vdr-1.3.31/svdrp.c	2005-08-28 16:12:00.000000000 +0200
@@ -10,7 +10,7 @@
  * and interact with the Video Disk Recorder - or write a full featured
  * graphical interface that sits on top of an SVDRP connection.
  *
- * $Id: svdrp.c 1.74 2005/08/07 14:20:41 kls Exp $
+ * $Id: svdrp.c 1.77 2005/08/28 14:12:00 kls Exp $
  */
 
 #include "svdrp.h"
@@ -28,10 +28,12 @@
 #include <unistd.h>
 #include "channels.h"
 #include "config.h"
+#include "cutter.h"
 #include "device.h"
 #include "eitscan.h"
 #include "keys.h"
 #include "menu.h"
+#include "plugin.h"
 #include "remote.h"
 #include "timers.h"
 #include "tools.h"
@@ -194,6 +196,10 @@
   "    RECORDING - BE SURE YOU KNOW WHAT YOU ARE DOING!",
   "DELT <number>\n"
   "    Delete timer.",
+  "EDIT <number>\n"
+  "    Edit the recording with the given number. Before a recording can be\n"
+  "    edited, an LSTR command must have been executed in order to retrieve\n"
+  "    the recording numbers.",
   "GRAB <filename> [ jpeg | pnm [ <quality> [ <sizex> <sizey> ] ] ]\n"
   "    Grab the current frame and save it to the given file. Images can\n"
   "    be stored as JPEG (default) or PNM, at the given quality (default\n"
@@ -253,6 +259,22 @@
   "    zero, this means that the timer is currently recording and has started\n"
   "    at the given time. The first value in the resulting line is the number\n"
   "    of the timer.",
+  "PLAY <number> [ begin | <position> ]\n"
+  "    Play the recording with the given number. Before a recording can be\n"
+  "    played, an LSTR command must have been executed in order to retrieve\n"
+  "    the recording numbers.\n"
+  "    The keyword 'begin' plays the recording from its very beginning, while\n"
+  "    a <position> (given as hh:mm:ss[.ff] or framenumber) starts at that\n"
+  "    position. If neither 'begin' nor a <position> are given, replay is resumed\n"
+  "    at the position where any previous replay was stopped, or from the beginning\n"
+  "    by default. To control or stop the replay session, use the usual remote\n"
+  "    control keypresses via the HITK command.",
+  "PLUG <name> [ <command> [ <options> ]]\n"
+  "    Send a command to a plugin.\n"
+  "    The PLUG command without any parameters lists all plugins.\n"
+  "    If only a name is given, all commands known to that plugin are listed.\n"
+  "    If a command is given (optionally followed by parameters), that command\n"
+  "    is sent to the plugin, and the result will be displayed.",
   "PUTE\n"
   "    Put data into the EPG list. The data entered has to strictly follow the\n"
   "    format defined in vdr(5) for the 'epg.data' file.  A '.' on a line\n"
@@ -294,6 +316,8 @@
  504 Command parameter not implemented
  550 Requested action not taken
  554 Transaction failed
+ 900 Default plugin reply code
+ 901..999 Plugin specific reply codes
 
 */
 
@@ -315,15 +339,16 @@
   return NULL;
 }
 
-const char *GetHelpPage(const char *Cmd)
+const char *GetHelpPage(const char *Cmd, const char **p)
 {
-  const char **p = HelpPages;
-  while (*p) {
-        const char *t = GetHelpTopic(*p);
-        if (strcasecmp(Cmd, t) == 0)
-           return *p;
-        p++;
-        }
+  if (p) {
+     while (*p) {
+           const char *t = GetHelpTopic(*p);
+           if (strcasecmp(Cmd, t) == 0)
+              return *p;
+           p++;
+           }
+     }
   return NULL;
 }
 
@@ -376,14 +401,14 @@
         va_start(ap, fmt);
         char *buffer;
         vasprintf(&buffer, fmt, ap);
-        char *nl = strchr(buffer, '\n');
-        if (Code > 0 && nl && *(nl + 1)) // trailing newlines don't count!
-           Code = -Code;
-        char number[16];
-        sprintf(number, "%03d%c", abs(Code), Code < 0 ? '-' : ' ');
         const char *s = buffer;
         while (s && *s) {
               const char *n = strchr(s, '\n');
+              char cont = ' ';
+              if (Code < 0 || n && *(n + 1)) // trailing newlines don't count!
+                 cont = '-';
+              char number[16];
+              sprintf(number, "%03d%c", abs(Code), cont);
               if (!(Send(number) && Send(s, n ? n - s : -1) && Send("\r\n"))) {
                  Close();
                  break;
@@ -400,6 +425,32 @@
      }
 }
 
+void cSVDRP::PrintHelpTopics(const char **hp)
+{
+  int NumPages = 0;
+  if (hp) {
+     while (*hp) {
+           NumPages++;
+           hp++;
+           }
+     hp -= NumPages;
+     }
+  const int TopicsPerLine = 5;
+  int x = 0;
+  for (int y = 0; (y * TopicsPerLine + x) < NumPages; y++) {
+      char buffer[TopicsPerLine * MAXHELPTOPIC + 5];
+      char *q = buffer;
+      q += sprintf(q, "    ");
+      for (x = 0; x < TopicsPerLine && (y * TopicsPerLine + x) < NumPages; x++) {
+          const char *topic = GetHelpTopic(hp[(y * TopicsPerLine + x)]);
+          if (topic)
+             q += sprintf(q, "%*s", -MAXHELPTOPIC, topic);
+          }
+      x = 0;
+      Reply(-214, buffer);
+      }
+}
+
 void cSVDRP::CmdCHAN(const char *Option)
 {
   if (*Option) {
@@ -561,6 +612,36 @@
      Reply(501, "Missing timer number");
 }
 
+void cSVDRP::CmdEDIT(const char *Option)
+{
+  if (*Option) {
+     if (isnumber(Option)) {
+        cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1);
+        if (recording) {
+           cMarks Marks;
+           if (Marks.Load(recording->FileName()) && Marks.Count()) {
+              if (!cCutter::Active()) {
+                 if (cCutter::Start(recording->FileName()))
+                    Reply(250, "Editing recording \"%s\" [%s]", Option, recording->Title());
+                 else
+                    Reply(554, "Can't start editing process");
+                 }
+              else
+                 Reply(554, "Editing process already active");
+              }
+           else
+              Reply(554, "No editing marks defined");
+           }
+        else
+           Reply(550, "Recording \"%s\" not found%s", Option, Recordings.Count() ? "" : " (use LSTR before editing)");
+        }
+     else
+        Reply(501, "Error in recording number \"%s\"", Option);
+     }
+  else
+     Reply(501, "Missing recording number");
+}
+
 void cSVDRP::CmdGRAB(const char *Option)
 {
   char *FileName = NULL;
@@ -626,7 +707,7 @@
 void cSVDRP::CmdHELP(const char *Option)
 {
   if (*Option) {
-     const char *hp = GetHelpPage(Option);
+     const char *hp = GetHelpPage(Option, HelpPages);
      if (hp)
         Reply(214, hp);
      else {
@@ -637,24 +718,13 @@
   else {
      Reply(-214, "This is VDR version %s", VDRVERSION);
      Reply(-214, "Topics:");
-     const char **hp = HelpPages;
-     int NumPages = 0;
-     while (*hp) {
-           NumPages++;
-           hp++;
-           }
-     const int TopicsPerLine = 5;
-     int x = 0;
-     for (int y = 0; (y * TopicsPerLine + x) < NumPages; y++) {
-         char buffer[TopicsPerLine * (MAXHELPTOPIC + 5)];
-         char *q = buffer;
-         for (x = 0; x < TopicsPerLine && (y * TopicsPerLine + x) < NumPages; x++) {
-             const char *topic = GetHelpTopic(HelpPages[(y * TopicsPerLine + x)]);
-             if (topic)
-                q += sprintf(q, "    %s", topic);
-             }
-         x = 0;
-         Reply(-214, buffer);
+     PrintHelpTopics(HelpPages);
+     cPlugin *plugin;
+     for (int i = 0; (plugin = cPluginManager::GetPlugin(i)) != NULL; i++) {
+         const char **hp = plugin->SVDRPHelpPages();
+         if (hp)
+            Reply(-214, "Plugin %s v%s - %s", plugin->Name(), plugin->Version(), plugin->Description());
+         PrintHelpTopics(hp);
          }
      Reply(-214, "To report bugs in the implementation send email to");
      Reply(-214, "    vdr-bugs@cadsoft.de");
@@ -1042,6 +1112,120 @@
      Reply(550, "No active timers");
 }
 
+void cSVDRP::CmdPLAY(const char *Option)
+{
+  if (*Option) {
+     char *opt = strdup(Option);
+     char *num = skipspace(opt);
+     char *option = num;
+     while (*option && !isspace(*option))
+           option++;
+     char c = *option;
+     *option = 0;
+     if (isnumber(num)) {
+        cRecording *recording = Recordings.Get(strtol(num, NULL, 10) - 1);
+        if (recording) {
+           if (c)
+              option = skipspace(++option);
+           cReplayControl::SetRecording(NULL, NULL);
+           cControl::Shutdown();
+           if (*option) {
+              int pos = 0;
+              if (strcasecmp(option, "BEGIN") != 0) {
+                 int h, m = 0, s = 0, f = 1;
+                 int x = sscanf(option, "%d:%d:%d.%d", &h, &m, &s, &f);
+                 if (x == 1)
+                    pos = h;
+                 else if (x >= 3)
+                    pos = (h * 3600 + m * 60 + s) * FRAMESPERSEC + f - 1;
+                 }
+              cResumeFile resume(recording->FileName());
+              if (pos <= 0)
+                 resume.Delete();
+              else
+                 resume.Save(pos);
+              }
+           cReplayControl::SetRecording(recording->FileName(), recording->Title());
+           cControl::Launch(new cReplayControl);
+           cControl::Attach();
+           Reply(250, "Playing recording \"%s\" [%s]", num, recording->Title());
+           }
+        else
+           Reply(550, "Recording \"%s\" not found%s", num, Recordings.Count() ? "" : " (use LSTR before playing)");
+        }
+     else
+        Reply(501, "Error in recording number \"%s\"", num);
+     free(opt);
+     }
+  else
+     Reply(501, "Missing recording number");
+}
+
+void cSVDRP::CmdPLUG(const char *Option)
+{
+  if (*Option) {
+     char *opt = strdup(Option);
+     char *name = skipspace(opt);
+     char *option = name;
+     while (*option && !isspace(*option))
+        option++;
+     char c = *option;
+     *option = 0;
+     cPlugin *plugin = cPluginManager::GetPlugin(name);
+     if (plugin) {
+        if (c)
+           option = skipspace(++option);
+        char *cmd = option;
+        while (*option && !isspace(*option))
+              option++;
+        if (*option) {
+           *option++ = 0;
+           option = skipspace(option);
+           }
+        if (!*cmd || strcasecmp(cmd, "HELP") == 0) {
+           if (*cmd && *option) {
+              const char *hp = GetHelpPage(option, plugin->SVDRPHelpPages());
+              if (hp) {
+                 Reply(-214, hp);
+                 Reply(214, "End of HELP info");
+                 }
+              else
+                 Reply(504, "HELP topic \"%s\" for plugin \"%s\" unknown", option, plugin->Name());
+              }
+           else {
+              Reply(-214, "Plugin %s v%s - %s", plugin->Name(), plugin->Version(), plugin->Description());
+              const char **hp = plugin->SVDRPHelpPages();
+              if (hp) {
+                 Reply(-214, "SVDRP commands:");
+                 PrintHelpTopics(hp);
+                 Reply(214, "End of HELP info");
+                 }
+              else
+                 Reply(214, "This plugin has no SVDRP commands");
+              }
+           }
+        else {
+           int ReplyCode = 900;
+           cString s = plugin->SVDRPCommand(cmd, option, ReplyCode);
+           if (s)
+              Reply(abs(ReplyCode), *s);
+           else
+              Reply(500, "Command unrecognized: \"%s\"", cmd);
+           }
+        }
+     else
+        Reply(550, "Plugin \"%s\" not found (use PLUG for a list of plugins)", name);
+     free(opt);
+     }
+  else {
+     Reply(-214, "Available plugins:");
+     cPlugin *plugin;
+     for (int i = 0; (plugin = cPluginManager::GetPlugin(i)) != NULL; i++)
+         Reply(-214, "%s v%s - %s", plugin->Name(), plugin->Version(), plugin->Description());
+     Reply(214, "End of plugin list");
+     }
+}
+
 void cSVDRP::CmdPUTE(const char *Option)
 {
   delete PUTEhandler;
@@ -1152,6 +1336,7 @@
   else if (CMD("DELC"))  CmdDELC(s);
   else if (CMD("DELR"))  CmdDELR(s);
   else if (CMD("DELT"))  CmdDELT(s);
+  else if (CMD("EDIT"))  CmdEDIT(s);
   else if (CMD("GRAB"))  CmdGRAB(s);
   else if (CMD("HELP"))  CmdHELP(s);
   else if (CMD("HITK"))  CmdHITK(s);
@@ -1167,6 +1352,8 @@
   else if (CMD("NEWC"))  CmdNEWC(s);
   else if (CMD("NEWT"))  CmdNEWT(s);
   else if (CMD("NEXT"))  CmdNEXT(s);
+  else if (CMD("PLAY"))  CmdPLAY(s);
+  else if (CMD("PLUG"))  CmdPLUG(s);
   else if (CMD("PUTE"))  CmdPUTE(s);
   else if (CMD("SCAN"))  CmdSCAN(s);
   else if (CMD("STAT"))  CmdSTAT(s);
diff -ruN vdr-1.3.30/svdrp.h vdr-1.3.31/svdrp.h
--- vdr-1.3.30/svdrp.h	2004-01-17 14:30:52.000000000 +0100
+++ vdr-1.3.31/svdrp.h	2005-08-28 16:10:32.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: svdrp.h 1.20 2004/01/17 13:30:52 kls Exp $
+ * $Id: svdrp.h 1.23 2005/08/28 14:10:32 kls Exp $
  */
 
 #ifndef __SVDRP_H
@@ -52,11 +52,13 @@
   void Close(bool Timeout = false);
   bool Send(const char *s, int length = -1);
   void Reply(int Code, const char *fmt, ...);
+  void PrintHelpTopics(const char **hp);
   void CmdCHAN(const char *Option);
   void CmdCLRE(const char *Option);
   void CmdDELC(const char *Option);
   void CmdDELR(const char *Option);
   void CmdDELT(const char *Option);
+  void CmdEDIT(const char *Option);
   void CmdGRAB(const char *Option);
   void CmdHELP(const char *Option);
   void CmdHITK(const char *Option);
@@ -72,6 +74,8 @@
   void CmdNEWC(const char *Option);
   void CmdNEWT(const char *Option);
   void CmdNEXT(const char *Option);
+  void CmdPLAY(const char *Option);
+  void CmdPLUG(const char *Option);
   void CmdPUTE(const char *Option);
   void CmdSCAN(const char *Option);
   void CmdSTAT(const char *Option);
diff -ruN vdr-1.3.30/tools.c vdr-1.3.31/tools.c
--- vdr-1.3.30/tools.c	2005-08-06 11:53:21.000000000 +0200
+++ vdr-1.3.31/tools.c	2005-08-27 16:43:55.000000000 +0200
@@ -4,13 +4,14 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: tools.c 1.96 2005/08/06 09:53:21 kls Exp $
+ * $Id: tools.c 1.97 2005/08/27 14:43:55 kls Exp $
  */
 
 #include "tools.h"
 #include <ctype.h>
 #include <dirent.h>
 #include <errno.h>
+#include <stdarg.h>
 #include <stdlib.h>
 #include <sys/time.h>
 #include <sys/vfs.h>
@@ -530,6 +531,15 @@
   return *this;
 }
 
+cString cString::sprintf(const char *fmt, ...)
+{
+  va_list ap;
+  va_start(ap, fmt);
+  char *buffer;
+  vasprintf(&buffer, fmt, ap);
+  return cString(buffer, true);
+}
+
 cString WeekDayName(int WeekDay)
 {
   char buffer[4];
diff -ruN vdr-1.3.30/tools.h vdr-1.3.31/tools.h
--- vdr-1.3.30/tools.h	2005-08-21 16:06:38.000000000 +0200
+++ vdr-1.3.31/tools.h	2005-08-27 16:40:08.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: tools.h 1.74 2005/08/21 14:06:38 kls Exp $
+ * $Id: tools.h 1.75 2005/08/27 14:40:08 kls Exp $
  */
 
 #ifndef __TOOLS_H
@@ -81,6 +81,7 @@
   operator const char * () const { return s; } // for use in (const char *) context
   const char * operator*() const { return s; } // for use in (const void *) context (printf() etc.)
   cString &operator=(const cString &String);
+  static cString sprintf(const char *fmt, ...);
   };
 
 ssize_t safe_read(int filedes, void *buffer, size_t size);
