diff -ruN vdr-2.1.1/CONTRIBUTORS vdr-2.1.2/CONTRIBUTORS
--- vdr-2.1.1/CONTRIBUTORS	2013-08-23 12:54:49.000000000 +0200
+++ vdr-2.1.2/CONTRIBUTORS	2013-10-16 11:43:47.000000000 +0200
@@ -618,6 +618,8 @@
  for suggesting to read the epg.data file in a separate thread
  for some improvements to allowing the parameters PATH and NAME to the --dirnames
  command line option to be left empty to use the default values if only ENC shall be set
+ for reporting an inconsistent behavior between opening the Recordings menu manually
+ via the main menu and by pressing the Recordings key
 
 Jeremy Hall <jhall@UU.NET>
  for fixing an incomplete initialization of the filter parameters in eit.c
@@ -1169,6 +1171,8 @@
  for fixing the call to ChannelString() in cSkinLCARSDisplayChannel::SetChannel()
  for a patch that was used to rename the "plp id" to a more general "stream id"
  and add support for DVB-S2 "Input Stream Identifier" (ISI)
+ for helping to debug and understand subtitle page refreshes
+ for a patch that was used to implement the SVDRP command RENR
 
 Ralf Klueber <ralf.klueber@vodafone.com>
  for reporting a bug in cutting a recording if there is only a single editing mark
@@ -1491,6 +1495,8 @@
  for fixing handling the '0' key for switching between the last two channels
  for making cPatFilter::Process() check whether the channel exists before setting
  the PMT filter
+ for the Rotor plugin, from which code was used by a patch that was used as a base for
+ implementing support for positioners
 
 St�phane Est�-Gracias <sestegra@free.fr>
  for fixing a typo in libsi/si.h
@@ -1699,6 +1705,7 @@
  for reporting references to old *.vdr file names in MANUAL
  for reporting that the video stream type was set to 2 even if the vpid was 0
  for updates to 'sources.conf'
+ for reporting a wrong initialization of Setup.PositionerSwing
 
 Milos Kapoun <m.kapoun@cra.cz>
  for suggesting to skip code table info in SI data
@@ -2545,6 +2552,8 @@
  for suggesting to add user defined key kUser0
  for suggesting to perform absolute jumps when replaying a recording (via the Red key)
  only if an actual value has been entered
+ for suggesting to make the Yellow button in the main menu not act as "Pause" if
+ "Pause key handling" is set to "do not pause live video"
 
 J�rg Wendel <vdr-ml@jwendel.de>
  for reporting that cPlugin::Active() was called too often
@@ -2961,6 +2970,7 @@
 Dave Pickles <dave@pickles.me.uk>
  for adding support for "content identifier descriptor" and "default authority
  descriptor" to 'libsi'
+ for reporting that old EPG events are not cleaned up in case no epg data file is given
 
 Holger Dengler <holger.dengler@gmx.de>
  for making the isnumber() function check the given pointer for NULL
@@ -3009,6 +3019,11 @@
  for suggesting to increase the size of the TS buffer to 5MB and that of the Recorder
  buffer to 20MB to better handle HD recordings
  for fixing setting the video format in the dvbhdffdevice
+ for reporting a problem with setting the system time from the TDT in case devices
+ are tuned to the same transponder on different sources, and these broadcast different
+ time data
+ for reporting a problem with unjustified "video data stream broken" errors in case
+ the system time is changed while a recording is active
 
 Christian Ruppert <idl0r@gentoo.org>
  for some improvements to the Makefiles
@@ -3169,3 +3184,21 @@
 Manfred V�lkel <mvoelkel@digitaldevices.de>
  for suggesting to make all bonded devices (except for the master) turn off their LNB
  power completely to avoid problems when receiving vertically polarized transponders
+
+Thomas Maass <mase@setho.org>
+ for reporting a difference in the internal sequence of actions when pressing the Blue
+ and the Back key, respectively, during replay
+
+Martin Prochnow <nordlicht@martins-kabuff.de>
+ for writing the "extrecmenu" plugin, which inspired the implementation of editing
+ recording properties
+
+Eike Edener <eike@edener.de>
+ for reporting a bug in writing group separators to channels.conf that contain a comma
+
+Harald Koenig <koenig@tat.physik.uni-tuebingen.de>
+ for making the function cRecordings::MBperMinute() only take into account recordings
+ with less than 5 seconds per megabyte, to filter out radio recordings
+
+Guido Cordaro <guido.cordaro@tiscali.it>
+ for adding maximum signal strength value for TechniSat SkyStar 2 DVB-S rev 2.3P
diff -ruN vdr-2.1.1/HISTORY vdr-2.1.2/HISTORY
--- vdr-2.1.1/HISTORY	2013-08-25 12:16:43.000000000 +0200
+++ vdr-2.1.2/HISTORY	2013-10-19 12:04:51.000000000 +0200
@@ -7845,7 +7845,8 @@
 - Fixed a crash in the LCARS skin's main menu in case there is no current channel
   (reported by Dominique Dumont).
 - Added basic support for positioners to control steerable satellite dishes (based on
-  a patch from Seppo Ingalsuo and Ales Jurik).
+  a patch from Seppo Ingalsuo and Ales Jurik, which in turn used code from Thomas
+  Bergwinkl's Rotor plugin).
   + Supports GotoN (aka "DiSEqC 1.2") and GotoX (aka "USALS").
   + The new DiSEqC command code 'P' can be used to instruct a positioner to move the
     dish to the required satellite position. When a 'P' code is processed, further
@@ -7899,3 +7900,112 @@
   A recommended method for a relatively safe disk setup in a VDR system is to use two
   1TB (or larger) disks and use them as a RAID-1 (mirrored). That way, if one disk
   fails, you can replace it without data loss.
+
+2013-09-01: Version 2.0.3
+
+- Fixed asserting free disk space in the cutter.
+- No longer trying to delete old recordings in AssertFreeDiskSpace() if the given
+  Priority is less than 1.
+- Fixed handling LIRC events in case repeated events are lost.
+- Fixed a possible crash when shutting down VDR while subtitles are being displayed
+  (reported by Ville Skytt�).
+- cDevice::IsPrimaryDevice() now also checks whether the primary device actually has
+  a decoder and returns false otherwise. This should improve device allocation on
+  systems that are only used as a receiver and don't actually display anything.
+- Increased the value of MAXRETRIES to 20 to reduce the probability of disturbances
+  in transfer mode.
+- All bonded devices (except for the master) now turn off their LNB power completely
+  to avoid problems when receiving vertically polarized transponders (suggested by
+  Manfred V�lkel and Oliver Endriss).
+- Fixed cleaning up old EPG events in case no epg data file is given (reported by
+  Dave Pickles).
+
+2013-10-19: Version 2.1.2
+
+- Updated the Finnish OSD texts (thanks to Rolf Ahrenberg).
+- Fixed displaying DVB subtitles (thanks to Rolf Ahrenberg for helping to debug and
+  understand subtitle page refreshes):
+  + Fixed handling DVB subtitle fill region codes for 2 and 8 bpp.
+  + Fixed handling pages without an explicit END_OF_DISPLAY_SET_SEGMENT.
+    The FINISHPAGE_HACK is no longer necessary.
+  + Fixed handling "page refreshes". The data is now parsed and stored closer to the
+    DVB standard specs, introducing "object refs" and "region refs".
+  + The debug output now goes into an HTML file named dbg-log.htm and shows the actual
+    bitmaps (dbg-nnn.jpg) used to display the subtitles. That way it is much easier to
+    see what's actually going on.
+  + Fixed handling subtitles encoded as a string of characters (the very first
+    character was always skipped).
+- Fixed wrong initialization of Setup.PositionerSwing (reported by Arthur Konovalov).
+- Updated the Estonian OSD texts (thanks to Arthur Konovalov).
+- Fixed cleaning up old EPG events in case no epg data file is given (reported by
+  Dave Pickles).
+- Unified the internal sequence of actions when pressing the Blue and the Back key,
+  respectively, during replay (reported by Thomas Maass).
+- The Yellow button in the main menu no longer acts as "Pause" if "Pause key handling"
+  is set to "do not pause live video" (suggested by Ulf Kiener).
+- The code for distributing recordings over several video directories has been
+  removed. VDR now by default assumes that the video directory is one big disk.
+  If you absolutely need to use several separate disks to store recordings, you can
+  write a plugin that uses the new cVideoDirectory API to implement the necessary
+  functionality (see PLUGINS.html, section "The video directory"). You can copy the
+  respective code from previous versions of videodir.c.
+  IMPORTANT NOTE: If you write a plugin that implements a distributed video directory,
+  =============== be sure to make cVideoDirectory::Rename() follow symbolic links!
+                  This functionality was never implemented in VDR and it therefore
+                  used a workaround in cutter.c. See the section marked with
+                  // XXX this can be removed once RenameVideoFile() follows symlinks
+                  in previous versions of cutter.c.
+  + CloseVideoFile() is obsolete and has been removed.
+  + The functions OpenVideoFile(), RenameVideoFile(), RemoveVideoFile(), VideoFileSpaceAvailable(),
+    VideoDiskSpace(), RemoveEmptyVideoDirectories(), IsOnVideoDirectoryFileSystem() and
+    PrefixVideoFileName() are now static members of cVideoDirectory and need to be called
+    with the proper prefix.
+  + The name of the video directory is now available through cVideoDirectory::Name().
+- Added renaming and moving recordings and folders, editing a recording's priority and
+  lifetime, and queueing cutting jobs (inspired by the "extrecmenu" plugin from Martin
+  Prochnow).
+  + The "Recording info" menu now has a new Blue button named "Edit", which opens a
+    dialog in which several properties of the selected recording can be changed. It can
+    be renamed or moved into another folder and its priority and lifetime can be
+    modified (inspired by the "extrecmenu" plugin from Martin Prochnow).
+    The new blue "Edit" button in the "Recordings" menu opens a dialog in which a folder
+    can be renamed or moved. See MANUAL, section "Managing folders".
+  + In the "Edit recording" menu the Yellow button ("Delete marks") allows you to delete
+    all editing marks of the selected recording.
+  + cCutter is no longer a static class. Cutting requests should now be invoked by
+    calling RecordingsHandler.Add(ruCut, FileName). See the new cRecordingsHandler
+    class in recording.h.
+  + Cutting jobs are now placed in a queue (together with any move or copy jobs) and
+    are processed one by one.
+  + The new SVDRP command RENR can be used to rename a recording (suggested by Rolf
+    Ahrenberg).
+  + Note that in several places in the source code a "copy" operation is mentioned,
+    however there is no user interface for this, yet.
+- Changed some variable names in positioner.c to match the names used in the page with
+  the explanation on vdr-portal.de.
+- Updated the Italian OSD texts (thanks to Diego Pierotto).
+- Fixed writing group separators to channels.conf that contain a comma (reported by
+  Eike Edener).
+- Now also checking the source (in addition to the transponder) when setting the
+  system time from the TDT, which avoids problems in case devices are tuned to the
+  same transponder on different sources, and these broadcast different time data
+  (reported by Torsten Lang).
+- Changed cRecorder::Action() to use cTimeMs instead of time() to avoid problems with
+  unjustified "video data stream broken" errors in case the system time is changed
+  while a recording is active (reported by Torsten Lang).
+- Revised the section on "Learning the remote control keys" in the INSTALL file to
+  avoid the impression that there actually is a default remote.conf file, and to
+  not use any alphabetic keys for special functions, so that they remain available
+  for textual input.
+- The function cRecordings::MBperMinute() now only takes into account recordings with
+  less than 5 seconds per megabyte, in an attempt to filter out radio recordings
+  (thanks to Harald Koenig). The result of this function was way off any realistic
+  value in case there are many radio recordings in the video directory.
+- Added maximum signal strength value for TechniSat SkyStar 2 DVB-S rev 2.3P (thanks
+  to Guido Cordaro).
+- Fixed an inconsistent behavior between opening the Recordings menu manually via the
+  main menu and by pressing the Recordings key. In the latter case it automatically
+  opened all sub folders to position the cursor to the last replayed recording, which
+  is unexpected at this point (reported by Helmut Auer). You can still navigate to
+  the last replayed recording (if any) by pressing Ok repeatedly in the Recordings
+  menu.
diff -ruN vdr-2.1.1/INSTALL vdr-2.1.2/INSTALL
--- vdr-2.1.1/INSTALL	2013-08-23 14:41:56.000000000 +0200
+++ vdr-2.1.2/INSTALL	2013-10-12 14:51:35.000000000 +0200
@@ -439,19 +439,17 @@
 
 The recommended PC key assignments are:
 
-  Up, Down, Left, Right     Crsr keys in numeric block
-  Menu                      'Home' in numeric block
+  Up, Down, Left, Right     Cursor keys
+  Menu                      'Home'
   Ok                        'Enter'
-  Back                      'End' in numeric block
+  Back                      'Backspace'
   Red, Green, Yellow, Blue  'F1'..'F4'
-  0..9                      '0'..'9' in top row
-  Power                     'P'
-  Volume+/-                 '+', '-'
-  Mute                      'm'
+  0..9                      '0'..'9'
+  Volume+/-                 'PgUp', 'PgDn'
+  Mute                      'F10'
 
-If you prefer different key assignments, or if the default doesn't work for
-your keyboard, simply delete the file 'remote.conf' and restart 'vdr' to get
-into learning mode.
+If you want to change your key assignments later, simply delete the file
+'remote.conf' and restart 'vdr' to get into learning mode.
 
 Generating source code documentation:
 -------------------------------------
diff -ruN vdr-2.1.1/MANUAL vdr-2.1.2/MANUAL
--- vdr-2.1.1/MANUAL	2013-08-21 12:47:04.000000000 +0200
+++ vdr-2.1.2/MANUAL	2013-10-10 14:03:23.000000000 +0200
@@ -504,6 +504,30 @@
   folder, or enters a sub folder. Once a folder has been selected, the entire
   path of the timer's file name will be replaced with the selected folder.
 
+  In the "Recordings" menu the folders of existing recordings can be renamed or
+  moved by pressing the "Blue" key ("Edit") while the cursor is positioned on
+  a folder. This will open a menu in which the folder's name and location (the
+  "parent" folder) can be edited. If such an operation will result in moving
+  more than one recording, you will be asked for confirmation.
+  The name, folder, priority and lifetime of an individual recording can be
+  changed by pressing the "Blue" key ("Info") while the cursor is positioned
+  on a recording, and in the resulting Info menu pressing the "Blue" key again
+  to bring up the "Edit recording" menu.
+  In the "Edit recording" menu the Red button ("Folder") allows you to select one
+  of your predefined folders. The Green button has multiple functions, depending
+  on what is currently going on with the recording. It can either stop or cancel
+  a cut, move or copy operation. If the button reads "Stop..." it means that the
+  respective operation is already happening, while "Cancel..." means that the
+  operation is still pending execution. If no operation is currently happening
+  and the recording has editing marks, the Button will read "Cut" and triggers
+  cutting the recording (same as pressing '2' while replaying the recording).
+  The Yellow button ("Delete marks") allows you to delete all editing marks from
+  the selected recording (if there are any and the recording is not currently
+  being cut). To directly edit the folder or name of the recording, position the
+  cursor to the respective line and press the Right key to start editing (press
+  Ok to confirm the edit, or Back to return to the previous value). Once you are
+  finished with editing the recording properties, press Ok to confirm the changes.
+
 * Parameters in the "Setup" menu
 
   Select "Setup" from the "VDR" menu to enter the setup menu. From there you can
diff -ruN vdr-2.1.1/PLUGINS/src/dvbhddevice/HISTORY vdr-2.1.2/PLUGINS/src/dvbhddevice/HISTORY
--- vdr-2.1.1/PLUGINS/src/dvbhddevice/HISTORY	2013-08-23 12:10:22.000000000 +0200
+++ vdr-2.1.2/PLUGINS/src/dvbhddevice/HISTORY	2013-08-26 10:46:21.000000000 +0200
@@ -80,3 +80,7 @@
 
 - Fixed aspect ratio and position of scaled video.
 - Added yellow button in main menu to send CEC TV-Off command.
+
+2013-08-26: Version 2.1.2
+
+- Updated the Finnish OSD texts (thanks to Rolf Ahrenberg).
diff -ruN vdr-2.1.1/PLUGINS/src/dvbhddevice/dvbhddevice.c vdr-2.1.2/PLUGINS/src/dvbhddevice/dvbhddevice.c
--- vdr-2.1.1/PLUGINS/src/dvbhddevice/dvbhddevice.c	2013-04-11 10:35:48.000000000 +0200
+++ vdr-2.1.2/PLUGINS/src/dvbhddevice/dvbhddevice.c	2013-08-26 10:45:37.000000000 +0200
@@ -10,7 +10,7 @@
 #include "menu.h"
 #include "setup.h"
 
-static const char *VERSION        = "2.1.1";
+static const char *VERSION        = "2.1.2";
 static const char *DESCRIPTION    = trNOOP("HD Full Featured DVB device");
 static const char *MAINMENUENTRY  = "dvbhddevice";
 
diff -ruN vdr-2.1.1/PLUGINS/src/dvbhddevice/po/fi_FI.po vdr-2.1.2/PLUGINS/src/dvbhddevice/po/fi_FI.po
--- vdr-2.1.1/PLUGINS/src/dvbhddevice/po/fi_FI.po	2013-08-23 12:11:11.000000000 +0200
+++ vdr-2.1.2/PLUGINS/src/dvbhddevice/po/fi_FI.po	2013-08-26 10:47:20.000000000 +0200
@@ -26,7 +26,7 @@
 msgstr "TV päälle"
 
 msgid "TV off"
-msgstr ""
+msgstr "TV kiinni"
 
 msgid "Automatic"
 msgstr "automaattinen"
diff -ruN vdr-2.1.1/PLUGINS/src/dvbhddevice/po/it_IT.po vdr-2.1.2/PLUGINS/src/dvbhddevice/po/it_IT.po
--- vdr-2.1.1/PLUGINS/src/dvbhddevice/po/it_IT.po	2013-08-23 12:11:11.000000000 +0200
+++ vdr-2.1.2/PLUGINS/src/dvbhddevice/po/it_IT.po	2013-10-13 16:55:49.000000000 +0200
@@ -9,7 +9,7 @@
 "Project-Id-Version: vdr-dvbhddevice 0.0.4\n"
 "Report-Msgid-Bugs-To: <see README>\n"
 "POT-Creation-Date: 2013-08-23 12:10+0200\n"
-"PO-Revision-Date: 2013-02-18 23:42+0100\n"
+"PO-Revision-Date: 2013-09-19 00:00+0100\n"
 "Last-Translator: Diego Pierotto <vdr-italian@tiscali.it>\n"
 "Language-Team:  <see README>\n"
 "Language: it\n"
@@ -30,7 +30,7 @@
 msgstr "TV accesa"
 
 msgid "TV off"
-msgstr ""
+msgstr "TV spenta"
 
 msgid "Automatic"
 msgstr "Automatica"
diff -ruN vdr-2.1.1/PLUGINS.html vdr-2.1.2/PLUGINS.html
--- vdr-2.1.1/PLUGINS.html	2013-06-10 16:31:11.000000000 +0200
+++ vdr-2.1.2/PLUGINS.html	2013-09-11 10:42:43.000000000 +0200
@@ -104,6 +104,7 @@
 <li><a href="#Remote Control">Remote Control</a>
 <li><a href="#Conditional Access">Conditional Access</a>
 <li><a href="#Electronic Program Guide">Electronic Program Guide</a>
+<li><modified><a href="#The video directory">The video directory</a></modified>
 </ul>
 </ul>
 
@@ -2042,8 +2043,6 @@
 #include &lt;vdr/positioner.h&gt;
 
 class cMyPositioner : public cPositioner {
-private:
-  void SendDiseqc(uint8_t *Codes, int NumCodes);
 public:
   cMyPositioner(void);
   virtual void Drive(ePositionerDirection Direction);
@@ -2302,5 +2301,41 @@
 <p>
 See <tt>VDR/epg.h</tt> for details.
 
+<div class="modified">
+<hr><h2><a name="The video directory">The video directory</a></h2>
+
+<div class="blurb">Bits and pieces...</div><p>
+
+By default VDR assumes that the video directory consists of one large
+volume, on which it can store its recordings. If you want to distribute your
+recordings over several physical drives, you can derive from <tt>cVideoDirectory</tt>,
+as in
+
+<p><table><tr><td class="code"><pre>
+#include &lt;vdr/videodir.h&gt;
+
+class cMyVideoDirectory : public cVideoDirectory {
+public:
+  cMyVideoDirectory(void);
+  virtual ~cMyVideoDirectory();
+  virtual int FreeMB(int *UsedMB = NULL);
+  virtual bool Register(const char *FileName);
+  virtual bool Rename(const char *OldName, const char *NewName);
+  virtual bool Move(const char *FromName, const char *ToName);
+  virtual bool Remove(const char *Name);
+  virtual void Cleanup(const char *IgnoreFiles[] = NULL);
+  virtual bool Contains(const char *Name);
+  };
+</pre></td></tr></table><p>
+
+See the description in <tt>videodir.h</tt> for details.
+<p>
+You should create your derived video directory object in the
+<a href="#Getting started"><tt>Start()</tt></a> function of your plugin.
+Note that the object has to be created on the heap (using <tt>new</tt>),
+and you shall not delete it at any point (it will be deleted automatically
+when the program ends).
+</div modified>
+
 </body>
 </html>
diff -ruN vdr-2.1.1/channels.c vdr-2.1.2/channels.c
--- vdr-2.1.1/channels.c	2012-07-14 14:34:47.000000000 +0200
+++ vdr-2.1.2/channels.c	2013-10-11 13:03:26.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: channels.c 3.0 2012/07/14 12:34:47 kls Exp $
+ * $Id: channels.c 3.1 2013/10/11 11:03:26 kls Exp $
  */
 
 #include "channels.h"
@@ -503,12 +503,14 @@
   char FullName[strlen(Channel->name) + 1 + strlen(Channel->shortName) + 1 + strlen(Channel->provider) + 1 + 10]; // +10: paranoia
   char *q = FullName;
   q += sprintf(q, "%s", Channel->name);
-  if (!isempty(Channel->shortName))
-     q += sprintf(q, ",%s", Channel->shortName);
-  else if (strchr(Channel->name, ','))
-     q += sprintf(q, ",");
-  if (!isempty(Channel->provider))
-     q += sprintf(q, ";%s", Channel->provider);
+  if (!Channel->groupSep) {
+     if (!isempty(Channel->shortName))
+        q += sprintf(q, ",%s", Channel->shortName);
+     else if (strchr(Channel->name, ','))
+        q += sprintf(q, ",");
+     if (!isempty(Channel->provider))
+        q += sprintf(q, ";%s", Channel->provider);
+     }
   *q = 0;
   strreplace(FullName, ':', '|');
   cString buffer;
diff -ruN vdr-2.1.1/config.c vdr-2.1.2/config.c
--- vdr-2.1.1/config.c	2013-05-23 14:41:06.000000000 +0200
+++ vdr-2.1.2/config.c	2013-08-31 14:41:28.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: config.c 3.1 2013/05/23 12:41:06 kls Exp $
+ * $Id: config.c 3.2 2013/08/31 12:41:28 kls Exp $
  */
 
 #include "config.h"
@@ -393,7 +393,7 @@
   SiteLat = 0;
   SiteLon = 0;
   PositionerSpeed = 15;
-  PositionerSwing = 65;
+  PositionerSwing = 650;
   PositionerLastLon = 0;
   SetSystemTime = 0;
   TimeSource = 0;
diff -ruN vdr-2.1.1/config.h vdr-2.1.2/config.h
--- vdr-2.1.1/config.h	2013-05-23 14:40:19.000000000 +0200
+++ vdr-2.1.2/config.h	2013-10-19 13:32:15.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 3.2 2013/05/23 12:40:19 kls Exp $
+ * $Id: config.h 3.4 2013/10/19 11:32:15 kls Exp $
  */
 
 #ifndef __CONFIG_H
@@ -22,13 +22,13 @@
 
 // VDR's own version number:
 
-#define VDRVERSION  "2.1.1"
-#define VDRVERSNUM   20101  // Version * 10000 + Major * 100 + Minor
+#define VDRVERSION  "2.1.2"
+#define VDRVERSNUM   20102  // Version * 10000 + Major * 100 + Minor
 
 // The plugin API's version number:
 
-#define APIVERSION  "2.1.1"
-#define APIVERSNUM   20101  // Version * 10000 + Major * 100 + Minor
+#define APIVERSION  "2.1.2"
+#define APIVERSNUM   20102  // Version * 10000 + Major * 100 + Minor
 
 // When loading plugins, VDR searches them by their APIVERSION, which
 // may be smaller than VDRVERSION in case there have been no changes to
diff -ruN vdr-2.1.1/cutter.c vdr-2.1.2/cutter.c
--- vdr-2.1.1/cutter.c	2013-08-21 15:15:24.000000000 +0200
+++ vdr-2.1.2/cutter.c	2013-10-02 15:18:02.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: cutter.c 3.2 2013/08/21 13:15:24 kls Exp $
+ * $Id: cutter.c 3.4 2013/10/02 13:18:02 kls Exp $
  */
 
 #include "cutter.h"
@@ -642,46 +642,47 @@
 
 // --- cCutter ---------------------------------------------------------------
 
-cMutex cCutter::mutex;
-cString cCutter::originalVersionName;
-cString cCutter::editedVersionName;
-cCuttingThread *cCutter::cuttingThread = NULL;
-bool cCutter::error = false;
-bool cCutter::ended = false;
+cCutter::cCutter(const char *FileName)
+{
+  cuttingThread = NULL;
+  error = false;
+  originalVersionName = FileName;
+}
 
-bool cCutter::Start(const char *FileName)
+cCutter::~cCutter()
 {
-  cMutexLock MutexLock(&mutex);
-  if (!cuttingThread) {
-     error = false;
-     ended = false;
-     originalVersionName = FileName;
-     cRecording Recording(FileName);
+  Stop();
+}
 
-     cMarks FromMarks;
-     FromMarks.Load(FileName, Recording.FramesPerSecond(), Recording.IsPesRecording());
-     if (cMark *First = FromMarks.GetNextBegin())
+cString cCutter::EditedFileName(const char *FileName)
+{
+  cRecording Recording(FileName);
+  cMarks Marks;
+  if (Marks.Load(FileName, Recording.FramesPerSecond(), Recording.IsPesRecording())) {
+     if (cMark *First = Marks.GetNextBegin())
         Recording.SetStartTime(Recording.Start() + (int(First->Position() / Recording.FramesPerSecond() + 30) / 60) * 60);
+     return Recording.PrefixFileName('%');
+     }
+  return NULL;
+}
 
-     const char *evn = Recording.PrefixFileName('%');
-     if (evn && RemoveVideoFile(evn) && MakeDirs(evn, true)) {
-        // XXX this can be removed once RenameVideoFile() follows symlinks (see videodir.c)
-        // remove a possible deleted recording with the same name to avoid symlink mixups:
-        char *s = strdup(evn);
-        char *e = strrchr(s, '.');
-        if (e) {
-           if (strcmp(e, ".rec") == 0) {
-              strcpy(e, ".del");
-              RemoveVideoFile(s);
+bool cCutter::Start(void)
+{
+  if (!cuttingThread) {
+     error = false;
+     if (*originalVersionName) {
+        cRecording Recording(originalVersionName);
+        editedVersionName = EditedFileName(originalVersionName);
+        if (*editedVersionName) {
+           if (strcmp(originalVersionName, editedVersionName) != 0) { // names must be different!
+              if (cVideoDirectory::RemoveVideoFile(editedVersionName) && MakeDirs(editedVersionName, true)) {
+                 Recording.WriteInfo(editedVersionName);
+                 Recordings.AddByName(editedVersionName, false);
+                 cuttingThread = new cCuttingThread(originalVersionName, editedVersionName);
+                 return true;
+                 }
               }
            }
-        free(s);
-        // XXX
-        editedVersionName = evn;
-        Recording.WriteInfo();
-        Recordings.AddByName(editedVersionName, false);
-        cuttingThread = new cCuttingThread(FileName, editedVersionName);
-        return true;
         }
      }
   return false;
@@ -689,7 +690,6 @@
 
 void cCutter::Stop(void)
 {
-  cMutexLock MutexLock(&mutex);
   bool Interrupted = cuttingThread && cuttingThread->Active();
   const char *Error = cuttingThread ? cuttingThread->Error() : NULL;
   delete cuttingThread;
@@ -701,42 +701,27 @@
         esyslog("ERROR: '%s' during editing process", Error);
      if (cReplayControl::NowReplaying() && strcmp(cReplayControl::NowReplaying(), editedVersionName) == 0)
         cControl::Shutdown();
-     RemoveVideoFile(editedVersionName);
+     cVideoDirectory::RemoveVideoFile(editedVersionName);
      Recordings.DelByName(editedVersionName);
      }
 }
 
-bool cCutter::Active(const char *FileName)
+bool cCutter::Active(void)
 {
-  cMutexLock MutexLock(&mutex);
   if (cuttingThread) {
      if (cuttingThread->Active())
-        return !FileName || strcmp(FileName, originalVersionName) == 0 || strcmp(FileName, editedVersionName) == 0;
+        return true;
      error = cuttingThread->Error();
      Stop();
      if (!error)
         cRecordingUserCommand::InvokeCommand(RUC_EDITEDRECORDING, editedVersionName, originalVersionName);
-     originalVersionName = NULL;
-     editedVersionName = NULL;
-     ended = true;
      }
   return false;
 }
 
 bool cCutter::Error(void)
 {
-  cMutexLock MutexLock(&mutex);
-  bool result = error;
-  error = false;
-  return result;
-}
-
-bool cCutter::Ended(void)
-{
-  cMutexLock MutexLock(&mutex);
-  bool result = ended;
-  ended = false;
-  return result;
+  return error;
 }
 
 #define CUTTINGCHECKINTERVAL 500 // ms between checks for the active cutting process
@@ -749,10 +734,13 @@
         cMarks Marks;
         if (Marks.Load(FileName, Recording.FramesPerSecond(), Recording.IsPesRecording()) && Marks.Count()) {
            if (Marks.GetNumSequences()) {
-              if (cCutter::Start(FileName)) {
-                 while (cCutter::Active())
+              cCutter Cutter(FileName);
+              if (Cutter.Start()) {
+                 while (Cutter.Active())
                        cCondWait::SleepMs(CUTTINGCHECKINTERVAL);
-                 return true;
+                 if (!Cutter.Error())
+                    return true;
+                 fprintf(stderr, "error while cutting\n");
                  }
               else
                  fprintf(stderr, "can't start editing process\n");
diff -ruN vdr-2.1.1/cutter.h vdr-2.1.2/cutter.h
--- vdr-2.1.1/cutter.h	2012-02-16 13:05:33.000000000 +0100
+++ vdr-2.1.2/cutter.h	2013-10-05 13:34:55.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: cutter.h 3.0 2012/02/16 12:05:33 kls Exp $
+ * $Id: cutter.h 3.1 2013/10/05 11:34:55 kls Exp $
  */
 
 #ifndef __CUTTER_H
@@ -17,21 +17,31 @@
 
 class cCutter {
 private:
-  static cMutex mutex;
-  static cString originalVersionName;
-  static cString editedVersionName;
-  static cCuttingThread *cuttingThread;
-  static bool error;
-  static bool ended;
+  cString originalVersionName;
+  cString editedVersionName;
+  cCuttingThread *cuttingThread;
+  bool error;
 public:
-  static bool Start(const char *FileName);
-  static void Stop(void);
-  static bool Active(const char *FileName = NULL);
-         ///< Returns true if the cutter is currently active.
-         ///< If a FileName is given, true is only returned if either the
-         ///< original or the edited file name is equal to FileName.
-  static bool Error(void);
-  static bool Ended(void);
+  cCutter(const char *FileName);
+      ///< Sets up a new cutter for the given FileName, which must be the full path
+      ///< name of an existing recording directory.
+  ~cCutter();
+  static cString EditedFileName(const char *FileName);
+      ///< Returns the full path name of the edited version of the recording with
+      ///< the given FileName. This static function can be used independent of any
+      ///< cCutter object, to determine the file name beforehand.
+      ///< Returns NULL in case of error.
+  bool Start(void);
+      ///< Starts the actual cutting process.
+      ///< Returns true if successful.
+      ///< If Start() is called while the cutting process is already active, nothing
+      ///< happens and false will be returned.
+  void Stop(void);
+      ///< Stops an ongoing cutting process.
+  bool Active(void);
+      ///< Returns true if the cutter is currently active.
+  bool Error(void);
+      ///< Returns true if an error occurred while cutting the recording.
   };
 
 bool CutRecording(const char *FileName);
diff -ruN vdr-2.1.1/device.h vdr-2.1.2/device.h
--- vdr-2.1.1/device.h	2013-08-22 13:57:34.000000000 +0200
+++ vdr-2.1.2/device.h	2013-10-09 10:25:16.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: device.h 3.2 2013/08/22 11:57:34 kls Exp $
+ * $Id: device.h 3.3 2013/10/09 08:25:16 kls Exp $
  */
 
 #ifndef __DEVICE_H
@@ -201,7 +201,7 @@
   int CardIndex(void) const { return cardIndex; }
          ///< Returns the card index of this device (0 ... MAXDEVICES - 1).
   int DeviceNumber(void) const;
-         ///< Returns the number of this device (0 ... numDevices).
+         ///< Returns the number of this device (0 ... numDevices - 1).
   virtual cString DeviceType(void) const;
          ///< Returns a string identifying the type of this device (like "DVB-S").
          ///< If this device can receive different delivery systems, the returned
diff -ruN vdr-2.1.1/dvbdevice.c vdr-2.1.2/dvbdevice.c
--- vdr-2.1.1/dvbdevice.c	2013-08-23 11:19:43.000000000 +0200
+++ vdr-2.1.2/dvbdevice.c	2013-10-13 16:41:57.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: dvbdevice.c 3.3 2013/08/23 09:19:43 kls Exp $
+ * $Id: dvbdevice.c 3.4 2013/10/13 14:41:57 kls Exp $
  */
 
 #include "dvbdevice.h"
@@ -558,6 +558,8 @@
     case 0x13C21019: // TT-budget S2-3200 (DVB-S/DVB-S2)
     case 0x1AE40001: // TechniSat SkyStar HD2 (DVB-S/DVB-S2)
                      MaxSignal = 670; break;
+    case 0x13D02103: // TechniSat SkyStar 2 DVB-S rev 2.3P
+                     MaxSignal = 0x4925; break;
     }
   int s = int(Signal) * 100 / MaxSignal;
   if (s > 100)
diff -ruN vdr-2.1.1/dvbsubtitle.c vdr-2.1.2/dvbsubtitle.c
--- vdr-2.1.1/dvbsubtitle.c	2013-02-22 16:25:25.000000000 +0100
+++ vdr-2.1.2/dvbsubtitle.c	2013-09-07 12:39:46.000000000 +0200
@@ -5,9 +5,9 @@
  * how to reach the author.
  *
  * Original author: Marco Schluessler <marco@lordzodiac.de>
- * With some input from the "subtitle plugin" by Pekka Virtanen <pekka.virtanen@sci.fi>
+ * With some input from the "subtitles plugin" by Pekka Virtanen <pekka.virtanen@sci.fi>
  *
- * $Id: dvbsubtitle.c 3.0 2013/02/22 15:25:25 kls Exp $
+ * $Id: dvbsubtitle.c 3.4 2013/09/07 10:39:46 kls Exp $
  */
 
 #include "dvbsubtitle.h"
@@ -16,8 +16,6 @@
 #include "device.h"
 #include "libsi/si.h"
 
-//#define FINISHPAGE_HACK
-
 #define PAGE_COMPOSITION_SEGMENT    0x10
 #define REGION_COMPOSITION_SEGMENT  0x11
 #define CLUT_DEFINITION_SEGMENT     0x12
@@ -27,36 +25,129 @@
 #define END_OF_DISPLAY_SET_SEGMENT  0x80
 #define STUFFING_SEGMENT            0xFF
 
-// Set these to 'true' for debug output:
-static bool DebugConverter = false;
-static bool DebugSegments = false;
-static bool DebugPages = false;
-static bool DebugRegions = false;
-static bool DebugObjects = false;
-static bool DebugCluts = false;
-
-#define dbgconverter(a...) if (DebugConverter) fprintf(stderr, a)
-#define dbgsegments(a...) if (DebugSegments) fprintf(stderr, a)
-#define dbgpages(a...) if (DebugPages) fprintf(stderr, a)
-#define dbgregions(a...) if (DebugRegions) fprintf(stderr, a)
-#define dbgobjects(a...) if (DebugObjects) fprintf(stderr, a)
-#define dbgcluts(a...) if (DebugCluts) fprintf(stderr, a)
+// Set these to 'true' for debug output, which is written into the file dbg-log.htm
+// in the current working directory. The HTML file shows the actual bitmaps (dbg-nnn.jpg)
+// used to display the subtitles.
+static bool DebugNormal    = false; // shows pages, regions and objects
+static bool DebugVerbose   = false; // shows everything
+static bool DebugDisplay   = DebugVerbose || DebugNormal;
+static bool DebugPages     = DebugVerbose || DebugNormal;
+static bool DebugRegions   = DebugVerbose || DebugNormal;
+static bool DebugObjects   = DebugVerbose || DebugNormal;
+static bool DebugBitmaps   = DebugVerbose || DebugNormal;
+static bool DebugConverter = DebugVerbose;
+static bool DebugSegments  = DebugVerbose;
+static bool DebugPixel     = DebugVerbose;
+static bool DebugCluts     = DebugVerbose;
+static bool DebugOutput    = DebugVerbose;
+
+#define dbgdisplay(a...)   if (DebugDisplay)   SD.WriteHtml(a)
+#define dbgpages(a...)     if (DebugPages)     SD.WriteHtml(a)
+#define dbgregions(a...)   if (DebugRegions)   SD.WriteHtml(a)
+#define dbgobjects(a...)   if (DebugObjects)   SD.WriteHtml(a)
+#define dbgbitmaps(a...)   if (DebugBitmaps)   SD.WriteHtml(a)
+#define dbgconverter(a...) if (DebugConverter) SD.WriteHtml(a)
+#define dbgsegments(a...)  if (DebugSegments)  SD.WriteHtml(a)
+#define dbgpixel(a...)     if (DebugPixel)     SD.WriteHtml(a)
+#define dbgcluts(a...)     if (DebugCluts)     SD.WriteHtml(a)
+#define dbgoutput(a...)    if (DebugOutput)    SD.WriteHtml(a)
+
+#define DBGMAXBITMAPS  100 // debug output will be stopped after this many bitmaps
+#define DBGBITMAPWIDTH 400
+
+// --- cSubtitleDebug --------------------------------------------------------
+
+class cSubtitleDebug {
+private:
+  cMutex mutex;
+  int imgCnt;
+  int64_t firstPts;
+  bool newFile;
+  double factor;
+public:
+  cSubtitleDebug(void) { Reset(); }
+  void Reset(void);
+  bool Active(void) { return imgCnt < DBGMAXBITMAPS; }
+  int64_t FirstPts(void) { return firstPts; }
+  void SetFirstPts(int64_t FirstPts) { if (firstPts < 0) firstPts = FirstPts; }
+  void SetFactor(double Factor) { factor = Factor; }
+  cString WriteJpeg(const cBitmap *Bitmap, int MaxX = 0, int MaxY = 0);
+  void WriteHtml(const char *Format, ...);
+  };
+
+void cSubtitleDebug::Reset(void)
+{
+  imgCnt = 0;
+  firstPts = -1;
+  newFile = true;
+  factor = 1.0;
+}
+
+cString cSubtitleDebug::WriteJpeg(const cBitmap *Bitmap, int MaxX, int MaxY)
+{
+  if (!Active())
+     return NULL;
+  cMutexLock MutexLock(&mutex);
+  cBitmap *Scaled = Bitmap->Scaled(factor, factor, true);
+  int w = MaxX ? int(round(MaxX * factor)) : Scaled->Width();
+  int h = MaxY ? int(round(MaxY * factor)) : Scaled->Height();
+  uchar mem[w * h * 3];
+  for (int x = 0; x < w; x++) {
+      for (int y = 0; y < h; y++) {
+          tColor c = Scaled->GetColor(x, y);
+          int o = (y * w + x) * 3;
+          mem[o++] = (c & 0x00FF0000) >> 16;
+          mem[o++] = (c & 0x0000FF00) >> 8;
+          mem[o]   = (c & 0x000000FF);
+          }
+      }
+  delete Scaled;
+  int Size = 0;
+  uchar *Jpeg = RgbToJpeg(mem, w, h, Size);
+  cString ImgName = cString::sprintf("dbg-%03d.jpg", imgCnt++);
+  int f = open(ImgName, O_WRONLY | O_CREAT, DEFFILEMODE);
+  if (f >= 0) {
+     if (write(f, Jpeg, Size) < 0)
+        LOG_ERROR_STR(*ImgName);
+     close(f);
+     }
+  free(Jpeg);
+  return ImgName;
+}
+
+void cSubtitleDebug::WriteHtml(const char *Format, ...)
+{
+  if (!Active())
+     return;
+  cMutexLock MutexLock(&mutex);
+  if (FILE *f = fopen("dbg-log.htm", newFile ? "w" : "a")) {
+     va_list ap;
+     va_start(ap, Format);
+     vfprintf(f, Format, ap);
+     va_end(ap);
+     fclose(f);
+     newFile = false;
+     }
+}
+
+static cSubtitleDebug SD;
 
 // --- cSubtitleClut ---------------------------------------------------------
 
 class cSubtitleClut : public cListObject {
 private:
   int clutId;
-  int version;
+  int clutVersionNumber;
   cPalette palette2;
   cPalette palette4;
   cPalette palette8;
+  tColor yuv2rgb(int Y, int Cb, int Cr);
+  void SetColor(int Bpp, int Index, tColor Color);
 public:
   cSubtitleClut(int ClutId);
+  void Parse(cBitStream &bs);
   int ClutId(void) { return clutId; }
-  int Version(void) { return version; }
-  void SetVersion(int Version) { version = Version; }
-  void SetColor(int Bpp, int Index, tColor Color);
+  int ClutVersionNumber(void) { return clutVersionNumber; }
   const cPalette *GetPalette(int Bpp);
   };
 
@@ -67,7 +158,7 @@
 {
   int a = 0, r = 0, g = 0, b = 0;
   clutId = ClutId;
-  version = -1;
+  clutVersionNumber = -1;
   // ETSI EN 300 743 10.3: 4-entry CLUT default contents
   palette2.SetColor(0, ArgbToColor(  0,   0,   0,   0));
   palette2.SetColor(1, ArgbToColor(255, 255, 255, 255));
@@ -129,6 +220,67 @@
       }
 }
 
+void cSubtitleClut::Parse(cBitStream &bs)
+{
+  int Version = bs.GetBits(4);
+  if (clutVersionNumber == Version)
+     return; // no update
+  clutVersionNumber = Version;
+  bs.SkipBits(4); // reserved
+  dbgcluts("<b>clut</b> id %d version %d<br>\n", clutId, clutVersionNumber);
+  while (!bs.IsEOF()) {
+        uchar clutEntryId = bs.GetBits(8);
+        bool entryClut2Flag = bs.GetBit();
+        bool entryClut4Flag = bs.GetBit();
+        bool entryClut8Flag = bs.GetBit();
+        bs.SkipBits(4); // reserved
+        uchar yval;
+        uchar crval;
+        uchar cbval;
+        uchar tval;
+        if (bs.GetBit()) { // full_range_flag
+           yval  = bs.GetBits(8);
+           crval = bs.GetBits(8);
+           cbval = bs.GetBits(8);
+           tval  = bs.GetBits(8);
+           }
+        else {
+           yval  = bs.GetBits(6) << 2;
+           crval = bs.GetBits(4) << 4;
+           cbval = bs.GetBits(4) << 4;
+           tval  = bs.GetBits(2) << 6;
+           }
+        tColor value = 0;
+        if (yval) {
+           value = yuv2rgb(yval, cbval, crval);
+           value |= ((10 - (clutEntryId ? Setup.SubtitleFgTransparency : Setup.SubtitleBgTransparency)) * (255 - tval) / 10) << 24;
+           }
+        dbgcluts("%2d %d %d %d %08X<br>\n", clutEntryId, entryClut2Flag ? 2 : 0, entryClut4Flag ? 4 : 0, entryClut8Flag ? 8 : 0, value);
+        if (entryClut2Flag)
+           SetColor(2, clutEntryId, value);
+        if (entryClut4Flag)
+           SetColor(4, clutEntryId, value);
+        if (entryClut8Flag)
+           SetColor(8, clutEntryId, value);
+        }
+}
+
+tColor cSubtitleClut::yuv2rgb(int Y, int Cb, int Cr)
+{
+  int Ey, Epb, Epr;
+  int Eg, Eb, Er;
+
+  Ey = (Y - 16);
+  Epb = (Cb - 128);
+  Epr = (Cr - 128);
+  /* ITU-R 709 */
+  Er = constrain((298 * Ey             + 460 * Epr) / 256, 0, 255);
+  Eg = constrain((298 * Ey -  55 * Epb - 137 * Epr) / 256, 0, 255);
+  Eb = constrain((298 * Ey + 543 * Epb            ) / 256, 0, 255);
+
+  return (Er << 16) | (Eg << 8) | Eb;
+}
+
 void cSubtitleClut::SetColor(int Bpp, int Index, tColor Color)
 {
   switch (Bpp) {
@@ -155,86 +307,129 @@
 class cSubtitleObject : public cListObject {
 private:
   int objectId;
-  int version;
-  int codingMethod;
+  int objectVersionNumber;
+  int objectCodingMethod;
   bool nonModifyingColorFlag;
-  uchar backgroundPixelCode;
-  uchar foregroundPixelCode;
-  int providerFlag;
-  int px;
-  int py;
-  cBitmap *bitmap;
-  char textData[Utf8BufSize(256)]; // number of character codes is an 8-bit field
-  void DrawLine(int x, int y, tIndex Index, int Length);
-  bool Decode2BppCodeString(cBitStream *bs, int&x, int y, const uint8_t *MapTable);
-  bool Decode4BppCodeString(cBitStream *bs, int&x, int y, const uint8_t *MapTable);
-  bool Decode8BppCodeString(cBitStream *bs, int&x, int y);
+  int topLength;
+  int botLength;
+  uchar *topData;
+  uchar *botData;
+  char *txtData;
+  int lineHeight;
+  void DrawLine(cBitmap *Bitmap, int x, int y, tIndex Index, int Length);
+  bool Decode2BppCodeString(cBitmap *Bitmap, int px, int py, cBitStream *bs, int&x, int y, const uint8_t *MapTable);
+  bool Decode4BppCodeString(cBitmap *Bitmap, int px, int py, cBitStream *bs, int&x, int y, const uint8_t *MapTable);
+  bool Decode8BppCodeString(cBitmap *Bitmap, int px, int py, cBitStream *bs, int&x, int y);
+  void DecodeSubBlock(cBitmap *Bitmap, int px, int py, const uchar *Data, int Length, bool Even);
+  void DecodeCharacterString(const uchar *Data, int NumberOfCodes);
 public:
-  cSubtitleObject(int ObjectId, cBitmap *Bitmap);
+  cSubtitleObject(int ObjectId);
+  ~cSubtitleObject();
+  void Parse(cBitStream &bs);
   int ObjectId(void) { return objectId; }
-  int Version(void) { return version; }
-  int CodingMethod(void) { return codingMethod; }
-  uchar BackgroundPixelCode(void) { return backgroundPixelCode; }
-  uchar ForegroundPixelCode(void) { return foregroundPixelCode; }
-  const char *TextData(void) { return &textData[0]; }
-  int X(void) { return px; }
-  int Y(void) { return py; }
+  int ObjectVersionNumber(void) { return objectVersionNumber; }
+  int ObjectCodingMethod(void) { return objectCodingMethod; }
   bool NonModifyingColorFlag(void) { return nonModifyingColorFlag; }
-  void DecodeCharacterString(const uchar *Data, int NumberOfCodes);
-  void DecodeSubBlock(const uchar *Data, int Length, bool Even);
-  void SetVersion(int Version) { version = Version; }
-  void SetBackgroundPixelCode(uchar BackgroundPixelCode) { backgroundPixelCode = BackgroundPixelCode; }
-  void SetForegroundPixelCode(uchar ForegroundPixelCode) { foregroundPixelCode = ForegroundPixelCode; }
-  void SetNonModifyingColorFlag(bool NonModifyingColorFlag) { nonModifyingColorFlag = NonModifyingColorFlag; }
-  void SetCodingMethod(int CodingMethod) { codingMethod = CodingMethod; }
-  void SetPosition(int x, int y) { px = x; py = y; }
-  void SetProviderFlag(int ProviderFlag) { providerFlag = ProviderFlag; }
+  void Render(cBitmap *Bitmap, int px, int py, tIndex IndexFg, tIndex IndexBg);
   };
 
-cSubtitleObject::cSubtitleObject(int ObjectId, cBitmap *Bitmap)
+cSubtitleObject::cSubtitleObject(int ObjectId)
 {
   objectId = ObjectId;
-  version = -1;
-  codingMethod = -1;
+  objectVersionNumber = -1;
+  objectCodingMethod = -1;
   nonModifyingColorFlag = false;
-  backgroundPixelCode = 0;
-  foregroundPixelCode = 0;
-  providerFlag = -1;
-  px = py = 0;
-  bitmap = Bitmap;
-  memset(textData, 0, sizeof(textData));
+  topLength = 0;
+  botLength = 0;
+  topData = NULL;
+  botData = NULL;
+  txtData = NULL;
+  lineHeight = 26; // configurable subtitling font size?
+}
+
+cSubtitleObject::~cSubtitleObject()
+{
+  free(topData);
+  free(botData);
+  free(txtData);
+}
+
+void cSubtitleObject::Parse(cBitStream &bs)
+{
+  int Version = bs.GetBits(4);
+  if (objectVersionNumber == Version)
+     return; // no update
+  objectVersionNumber = Version;
+  objectCodingMethod = bs.GetBits(2);
+  nonModifyingColorFlag = bs.GetBit();
+  bs.SkipBit(); // reserved
+  dbgobjects("<b>object</b> id %d version %d method %d modify %d", objectId, objectVersionNumber, objectCodingMethod, nonModifyingColorFlag); // no "<br>\n" here, DecodeCharacterString() may add data
+  if (objectCodingMethod == 0) { // coding of pixels
+     topLength = bs.GetBits(16);
+     botLength = bs.GetBits(16);
+     free(topData);
+     if ((topData = MALLOC(uchar, topLength)) != NULL)
+        memcpy(topData, bs.GetData(), topLength);
+     else
+        topLength = 0;
+     free(botData);
+     if ((botData = MALLOC(uchar, botLength)) != NULL)
+        memcpy(botData, bs.GetData() + topLength, botLength);
+     else
+        botLength = 0;
+     bs.WordAlign();
+     }
+  else if (objectCodingMethod == 1) { // coded as a string of characters
+     int numberOfCodes = bs.GetBits(8);
+     DecodeCharacterString(bs.GetData(), numberOfCodes);
+     }
+  dbgobjects("<br>\n");
+  if (DebugObjects) {
+     // We can't get the actual clut here, so we use a default one. This may lead to
+     // funny colors, but we just want to get a rough idea of what's in the object, anyway.
+     cSubtitleClut Clut(0);
+     cBitmap b(1920, 1080, 8);
+     b.Replace(*Clut.GetPalette(b.Bpp()));
+     b.Clean();
+     Render(&b, 0, 0, 0, 1);
+     int x1, y1, x2, y2;
+     if (b.Dirty(x1, y1, x2, y2)) {
+        cString ImgName = SD.WriteJpeg(&b, x2, y2);
+        dbgobjects("<img src=\"%s\"><br>\n", *ImgName);
+        }
+     }
 }
 
 void cSubtitleObject::DecodeCharacterString(const uchar *Data, int NumberOfCodes)
 {
+  // "ETSI EN 300 743 V1.3.1 (2006-11)", chapter 7.2.5 "Object data segment" specifies
+  // character_code to be a 16-bit index number into the character table identified
+  // in the subtitle_descriptor. However, the "subtitling_descriptor" <sic> according to
+  // "ETSI EN 300 468 V1.13.1 (2012-04)" doesn't contain a "character table identifier".
+  // It only contains a three letter language code, without any specification as to how
+  // this is related to a specific character table.
+  // Apparently the first "code" in textual subtitles contains the character table
+  // identifier, and all codes are 8-bit only. So let's first make Data a string of
+  // 8-bit characters:
   if (NumberOfCodes > 0) {
+     char txt[NumberOfCodes + 1];
+     for (int i = 0; i < NumberOfCodes; i++)
+         txt[i] = Data[i * 2 + 1];
+     txt[NumberOfCodes] = 0;
      bool singleByte;
-     const uchar *from = &Data[1];
-     int len = NumberOfCodes * 2 - 1;
-     cCharSetConv conv(SI::getCharacterTable(from, len, &singleByte));
-     if (singleByte) {
-        char txt[NumberOfCodes + 1];
-        char *p = txt;
-        for (int i = 2; i < NumberOfCodes; ++i) {
-            uchar c = Data[i * 2 + 1] & 0xFF;
-            if (c == 0)
-               break;
-            if (' ' <= c && c <= '~' || c == '\n' || 0xA0 <= c)
-               *(p++) = c;
-            else if (c == 0x8A)
-               *(p++) = '\n';
-            }
-        *p = 0;
-        const char *s = conv.Convert(txt);
-        Utf8Strn0Cpy(textData, s, Utf8StrLen(s));
-        }
-     else {
-        // TODO: add proper multibyte support for "UTF-16", "EUC-KR", "GB2312", "GBK", "UTF-8"
-        }
+     const uchar *from = (uchar *)txt;
+     int len = NumberOfCodes;
+     const char *CharacterTable = SI::getCharacterTable(from, len, &singleByte);
+     dbgobjects(" table %s single %d raw '%s'", CharacterTable, singleByte, from);
+     cCharSetConv conv(CharacterTable, cCharSetConv::SystemCharacterTable());
+     const char *s = conv.Convert((const char *)from);
+     dbgobjects(" conv '%s'", s);
+     free(txtData);
+     txtData = strdup(s);
      }
 }
 
-void cSubtitleObject::DecodeSubBlock(const uchar *Data, int Length, bool Even)
+void cSubtitleObject::DecodeSubBlock(cBitmap *Bitmap, int px, int py, const uchar *Data, int Length, bool Even)
 {
   int x = 0;
   int y = Even ? 0 : 1;
@@ -246,69 +441,65 @@
   while (!bs.IsEOF()) {
         switch (bs.GetBits(8)) {
           case 0x10:
-               dbgobjects("2-bit / pixel code string\n");
-               switch (bitmap->Bpp()) {
+               dbgpixel("2-bit / pixel code string<br>\n");
+               switch (Bitmap->Bpp()) {
                  case 8:  mapTable = map2to8; break;
                  case 4:  mapTable = map2to4; break;
                  default: mapTable = NULL;    break;
                  }
-               while (Decode2BppCodeString(&bs, x, y, mapTable) && !bs.IsEOF())
+               while (Decode2BppCodeString(Bitmap, px, py, &bs, x, y, mapTable) && !bs.IsEOF())
                      ;
                bs.ByteAlign();
                break;
           case 0x11:
-               dbgobjects("4-bit / pixel code string\n");
-               switch (bitmap->Bpp()) {
+               dbgpixel("4-bit / pixel code string<br>\n");
+               switch (Bitmap->Bpp()) {
                  case 8:  mapTable = map4to8; break;
                  default: mapTable = NULL;    break;
                  }
-               while (Decode4BppCodeString(&bs, x, y, mapTable) && !bs.IsEOF())
+               while (Decode4BppCodeString(Bitmap, px, py, &bs, x, y, mapTable) && !bs.IsEOF())
                      ;
                bs.ByteAlign();
                break;
           case 0x12:
-               dbgobjects("8-bit / pixel code string\n");
-               while (Decode8BppCodeString(&bs, x, y) && !bs.IsEOF())
+               dbgpixel("8-bit / pixel code string<br>\n");
+               while (Decode8BppCodeString(Bitmap, px, py, &bs, x, y) && !bs.IsEOF())
                      ;
                break;
           case 0x20:
-               dbgobjects("sub block 2 to 4 map\n");
-               map2to4[0] = bs.GetBits(4);
-               map2to4[1] = bs.GetBits(4);
-               map2to4[2] = bs.GetBits(4);
-               map2to4[3] = bs.GetBits(4);
+               dbgpixel("sub block 2 to 4 map<br>\n");
+               for (int i = 0; i < 4; ++i)
+                   map2to4[i] = bs.GetBits(4);
                break;
           case 0x21:
-               dbgobjects("sub block 2 to 8 map\n");
+               dbgpixel("sub block 2 to 8 map<br>\n");
                for (int i = 0; i < 4; ++i)
                    map2to8[i] = bs.GetBits(8);
                break;
           case 0x22:
-               dbgobjects("sub block 4 to 8 map\n");
+               dbgpixel("sub block 4 to 8 map<br>\n");
                for (int i = 0; i < 16; ++i)
                    map4to8[i] = bs.GetBits(8);
                break;
           case 0xF0:
-               dbgobjects("end of object line\n");
+               dbgpixel("end of object line<br>\n");
                x = 0;
                y += 2;
                break;
-          default: dbgobjects("unknown sub block %s %d\n", __FUNCTION__, __LINE__);
+          default: dbgpixel("unknown sub block %s %d<br>\n", __FUNCTION__, __LINE__);
           }
         }
 }
 
-void cSubtitleObject::DrawLine(int x, int y, tIndex Index, int Length)
+void cSubtitleObject::DrawLine(cBitmap *Bitmap, int x, int y, tIndex Index, int Length)
 {
   if (nonModifyingColorFlag && Index == 1)
      return;
-  x += px;
-  y += py;
   for (int pos = x; pos < x + Length; pos++)
-      bitmap->SetIndex(pos, y, Index);
+      Bitmap->SetIndex(pos, y, Index);
 }
 
-bool cSubtitleObject::Decode2BppCodeString(cBitStream *bs, int &x, int y, const uint8_t *MapTable)
+bool cSubtitleObject::Decode2BppCodeString(cBitmap *Bitmap, int px, int py, cBitStream *bs, int &x, int y, const uint8_t *MapTable)
 {
   int rl = 0;
   int color = 0;
@@ -343,12 +534,12 @@
      }
   if (MapTable)
      color = MapTable[color];
-  DrawLine(x, y, color, rl);
+  DrawLine(Bitmap, px + x, py + y, color, rl);
   x += rl;
   return true;
 }
 
-bool cSubtitleObject::Decode4BppCodeString(cBitStream *bs, int &x, int y, const uint8_t *MapTable)
+bool cSubtitleObject::Decode4BppCodeString(cBitmap *Bitmap, int px, int py, cBitStream *bs, int &x, int y, const uint8_t *MapTable)
 {
   int rl = 0;
   int color = 0;
@@ -388,12 +579,12 @@
      }
   if (MapTable)
      color = MapTable[color];
-  DrawLine(x, y, color, rl);
+  DrawLine(Bitmap, px + x, py + y, color, rl);
   x += rl;
   return true;
 }
 
-bool cSubtitleObject::Decode8BppCodeString(cBitStream *bs, int &x, int y)
+bool cSubtitleObject::Decode8BppCodeString(cBitmap *Bitmap, int px, int py, cBitStream *bs, int &x, int y)
 {
   int rl = 0;
   int color = 0;
@@ -413,105 +604,203 @@
      else
         return false;
      }
-  DrawLine(x, y, color, rl);
+  DrawLine(Bitmap, px + x, py + y, color, rl);
   x += rl;
   return true;
 }
 
+void cSubtitleObject::Render(cBitmap *Bitmap, int px, int py, tIndex IndexFg, tIndex IndexBg)
+{
+  if (objectCodingMethod == 0) { // coding of pixels
+     DecodeSubBlock(Bitmap, px, py, topData, topLength, true);
+     if (botLength)
+        DecodeSubBlock(Bitmap, px, py, botData, botLength, false);
+     else
+        DecodeSubBlock(Bitmap, px, py, topData, topLength, false);
+     }
+  else if (objectCodingMethod == 1) { // coded as a string of characters
+     if (txtData) {
+        //TODO couldn't we draw the text directly into Bitmap?
+        cFont *font = cFont::CreateFont(Setup.FontOsd, Setup.FontOsdSize);
+        cBitmap tmp(font->Width(txtData), font->Height(), Bitmap->Bpp());
+        double factor = (double)lineHeight / font->Height();
+        tmp.DrawText(0, 0, txtData, Bitmap->Color(IndexFg), Bitmap->Color(IndexBg), font);
+        cBitmap *scaled = tmp.Scaled(factor, factor, true);
+        Bitmap->DrawBitmap(px, py, *scaled);
+        delete scaled;
+        delete font;
+        }
+     }
+}
+
+// --- cSubtitleObjects ------------------------------------------------------
+
+class cSubtitleObjects : public cList<cSubtitleObject> {
+public:
+  cSubtitleObject *GetObjectById(int ObjectId, bool New = false);
+  };
+
+cSubtitleObject *cSubtitleObjects::GetObjectById(int ObjectId, bool New)
+{
+  for (cSubtitleObject *so = First(); so; so = Next(so)) {
+      if (so->ObjectId() == ObjectId)
+         return so;
+      }
+  if (!New)
+     return NULL;
+  cSubtitleObject *Object = new cSubtitleObject(ObjectId);
+  Add(Object);
+  return Object;
+}
+
+// --- cSubtitleObjectRef ----------------------------------------------------
+
+class cSubtitleObjectRef : public cListObject {
+private:
+  int objectId;
+  int objectType;
+  int objectProviderFlag;
+  int objectHorizontalPosition;
+  int objectVerticalPosition;
+  int foregroundPixelCode;
+  int backgroundPixelCode;
+public:
+  cSubtitleObjectRef(cBitStream &bs);
+  int ObjectId(void) { return objectId; }
+  int ObjectType(void) { return objectType; }
+  int ObjectProviderFlag(void) { return objectProviderFlag; }
+  int ObjectHorizontalPosition(void) { return objectHorizontalPosition; }
+  int ObjectVerticalPosition(void) { return objectVerticalPosition; }
+  int ForegroundPixelCode(void) { return foregroundPixelCode; }
+  int BackgroundPixelCode(void) { return backgroundPixelCode; }
+  };
+
+cSubtitleObjectRef::cSubtitleObjectRef(cBitStream &bs)
+{
+  objectId = bs.GetBits(16);
+  objectType = bs.GetBits(2);
+  objectProviderFlag = bs.GetBits(2);
+  objectHorizontalPosition = bs.GetBits(12);
+  bs.SkipBits(4); // reserved
+  objectVerticalPosition = bs.GetBits(12);
+  if (objectType == 0x01 || objectType == 0x02) {
+     foregroundPixelCode = bs.GetBits(8);
+     backgroundPixelCode = bs.GetBits(8);
+     }
+  else {
+     foregroundPixelCode = 0;
+     backgroundPixelCode = 0;
+     }
+  dbgregions("<b>objectref</b> id %d type %d flag %d x %d y %d fg %d bg %d<br>\n", objectId, objectType, objectProviderFlag, objectHorizontalPosition, objectVerticalPosition, foregroundPixelCode, backgroundPixelCode);
+}
+
 // --- cSubtitleRegion -------------------------------------------------------
 
-class cSubtitleRegion : public cListObject, public cBitmap {
+class cSubtitleRegion : public cListObject {
 private:
   int regionId;
-  int version;
+  int regionVersionNumber;
+  bool regionFillFlag;
+  int regionWidth;
+  int regionHeight;
+  int regionLevelOfCompatibility;
+  int regionDepth;
   int clutId;
-  int horizontalAddress;
-  int verticalAddress;
-  int level;
-  int lineHeight;
-  cList<cSubtitleObject> objects;
+  int region8bitPixelCode;
+  int region4bitPixelCode;
+  int region2bitPixelCode;
+  cList<cSubtitleObjectRef> objectRefs;
 public:
   cSubtitleRegion(int RegionId);
+  void Parse(cBitStream &bs);
   int RegionId(void) { return regionId; }
-  int Version(void) { return version; }
+  int RegionVersionNumber(void) { return regionVersionNumber; }
+  bool RegionFillFlag(void) { return regionFillFlag; }
+  int RegionWidth(void) { return regionWidth; }
+  int RegionHeight(void) { return regionHeight; }
+  int RegionLevelOfCompatibility(void) { return regionLevelOfCompatibility; }
+  int RegionDepth(void) { return regionDepth; }
   int ClutId(void) { return clutId; }
-  int Level(void) { return level; }
-  int Depth(void) { return Bpp(); }
-  void FillRegion(tIndex Index);
-  cSubtitleObject *GetObjectById(int ObjectId, bool New = false);
-  int HorizontalAddress(void) { return horizontalAddress; }
-  int VerticalAddress(void) { return verticalAddress; }
-  void SetVersion(int Version) { version = Version; }
-  void SetClutId(int ClutId) { clutId = ClutId; }
-  void SetLevel(int Level);
-  void SetDepth(int Depth);
-  void SetHorizontalAddress(int HorizontalAddress) { horizontalAddress = HorizontalAddress; }
-  void SetVerticalAddress(int VerticalAddress) { verticalAddress = VerticalAddress; }
-  void UpdateTextData(cSubtitleClut *Clut);
+  void Render(cBitmap *Bitmap, cSubtitleObjects *Objects);
   };
 
 cSubtitleRegion::cSubtitleRegion(int RegionId)
-:cBitmap(1, 1, 4)
 {
   regionId = RegionId;
-  version = -1;
+  regionVersionNumber = -1;
+  regionFillFlag = false;
+  regionWidth = 0;
+  regionHeight = 0;
+  regionLevelOfCompatibility = 0;
+  regionDepth = 0;
   clutId = -1;
-  horizontalAddress = 0;
-  verticalAddress = 0;
-  level = 0;
-  lineHeight = 26; // configurable subtitling font size
-}
-
-void cSubtitleRegion::FillRegion(tIndex Index)
-{
-  dbgregions("FillRegion %d\n", Index);
-  for (int y = 0; y < Height(); y++) {
-      for (int x = 0; x < Width(); x++)
-          SetIndex(x, y, Index);
-      }
-}
-
-cSubtitleObject *cSubtitleRegion::GetObjectById(int ObjectId, bool New)
-{
-  cSubtitleObject *result = NULL;
-  for (cSubtitleObject *so = objects.First(); so; so = objects.Next(so)) {
-      if (so->ObjectId() == ObjectId)
-         result = so;
-      }
-  if (!result && New) {
-     result = new cSubtitleObject(ObjectId, this);
-     objects.Add(result);
-     }
-  return result;
-}
-
-void cSubtitleRegion::UpdateTextData(cSubtitleClut *Clut)
-{
-  const cPalette *palette = Clut ? Clut->GetPalette(Depth()) : NULL;
-  for (cSubtitleObject *so = objects.First(); so && palette; so = objects.Next(so)) {
-      if (Utf8StrLen(so->TextData()) > 0) {
-         cFont *font = cFont::CreateFont(Setup.FontOsd, Setup.FontOsdSize);
-         cBitmap tmp(font->Width(so->TextData()), font->Height(), Depth());
-         double factor = (double)lineHeight / font->Height();
-         tmp.DrawText(0, 0, so->TextData(), palette->Color(so->ForegroundPixelCode()), palette->Color(so->BackgroundPixelCode()), font);
-         cBitmap *scaled = tmp.Scaled(factor, factor, true);
-         DrawBitmap(so->X(), so->Y(), *scaled);
-         delete scaled;
-         delete font;
+  region8bitPixelCode = 0;
+  region4bitPixelCode = 0;
+  region2bitPixelCode = 0;
+}
+
+void cSubtitleRegion::Parse(cBitStream &bs)
+{
+  int Version = bs.GetBits(4);
+  if (regionVersionNumber == Version)
+     return; // no update
+  regionVersionNumber = Version;
+  regionFillFlag = bs.GetBit();
+  bs.SkipBits(3); // reserved
+  regionWidth = bs.GetBits(16);
+  regionHeight = bs.GetBits(16);
+  regionLevelOfCompatibility = 1 << bs.GetBits(3); // stored as "number of bits per pixel"
+  regionDepth = 1 << bs.GetBits(3); // stored as "number of bits per pixel"
+  bs.SkipBits(2); // reserved
+  clutId = bs.GetBits(8);
+  region8bitPixelCode = bs.GetBits(8);
+  region4bitPixelCode = bs.GetBits(4);
+  region2bitPixelCode = bs.GetBits(2);
+  bs.SkipBits(2); // reserved
+  dbgregions("<b>region</b> id %d version %d fill %d width %d height %d level %d depth %d clutId %d<br>\n", regionId, regionVersionNumber, regionFillFlag, regionWidth, regionHeight, regionLevelOfCompatibility, regionDepth, clutId);
+  // no objectRefs.Clear() here!
+  while (!bs.IsEOF())
+        objectRefs.Add(new cSubtitleObjectRef(bs));
+}
+
+void cSubtitleRegion::Render(cBitmap *Bitmap, cSubtitleObjects *Objects)
+{
+  if (regionFillFlag) {
+     switch (Bitmap->Bpp()) {
+       case 2: Bitmap->Fill(region2bitPixelCode); break;
+       case 4: Bitmap->Fill(region4bitPixelCode); break;
+       case 8: Bitmap->Fill(region8bitPixelCode); break;
+       default: dbgregions("unknown bpp %d (%s %d)<br>\n", Bitmap->Bpp(), __FUNCTION__, __LINE__);
+       }
+     }
+  for (cSubtitleObjectRef *sor = objectRefs.First(); sor; sor = objectRefs.Next(sor)) {
+      if (cSubtitleObject *so = Objects->GetObjectById(sor->ObjectId())) {
+         so->Render(Bitmap, sor->ObjectHorizontalPosition(), sor->ObjectVerticalPosition(), sor->ForegroundPixelCode(), sor->BackgroundPixelCode());
          }
       }
 }
 
-void cSubtitleRegion::SetLevel(int Level)
-{
-  if (Level > 0 && Level < 4)
-     level = 1 << Level;
-}
+// --- cSubtitleRegionRef ----------------------------------------------------
+
+class cSubtitleRegionRef : public cListObject {
+private:
+  int regionId;
+  int regionHorizontalAddress;
+  int regionVerticalAddress;
+public:
+  cSubtitleRegionRef(cBitStream &bs);
+  int RegionId(void) { return regionId; }
+  int RegionHorizontalAddress(void) { return regionHorizontalAddress; }
+  int RegionVerticalAddress(void) { return regionVerticalAddress; }
+  };
 
-void cSubtitleRegion::SetDepth(int Depth)
+cSubtitleRegionRef::cSubtitleRegionRef(cBitStream &bs)
 {
-  if (Depth > 0 && Depth < 4)
-     SetBpp(1 << Depth);
+  regionId = bs.GetBits(8);
+  bs.SkipBits(8); // reserved
+  regionHorizontalAddress = bs.GetBits(16);
+  regionVerticalAddress = bs.GetBits(16);
+  dbgpages("<b>regionref</b> id %d tx %d y %d<br>\n", regionId, regionHorizontalAddress, regionVerticalAddress);
 }
 
 // --- cDvbSubtitlePage ------------------------------------------------------
@@ -519,119 +808,132 @@
 class cDvbSubtitlePage : public cListObject {
 private:
   int pageId;
-  int version;
-  int state;
+  int pageTimeout;
+  int pageVersionNumber;
+  int pageState;
   int64_t pts;
-  int timeout;
+  bool pending;
+  cSubtitleObjects objects;
   cList<cSubtitleClut> cluts;
-public:
   cList<cSubtitleRegion> regions;
+  cList<cSubtitleRegionRef> regionRefs;
+public:
   cDvbSubtitlePage(int PageId);
-  virtual ~cDvbSubtitlePage();
+  void Parse(int64_t Pts, cBitStream &bs);
   int PageId(void) { return pageId; }
-  int Version(void) { return version; }
-  int State(void) { return state; }
-  tArea *GetAreas(double FactorX, double FactorY);
+  int PageTimeout(void) { return pageTimeout; }
+  int PageVersionNumber(void) { return pageVersionNumber; }
+  int PageState(void) { return pageState; }
+  int64_t Pts(void) const { return pts; }
+  bool Pending(void) { return pending; }
+  cSubtitleObjects *Objects(void) { return &objects; }
+  tArea *GetAreas(int &NumAreas, double FactorX, double FactorY);
+  cSubtitleObject *GetObjectById(int ObjectId, bool New = false);
   cSubtitleClut *GetClutById(int ClutId, bool New = false);
-  cSubtitleObject *GetObjectById(int ObjectId);
   cSubtitleRegion *GetRegionById(int RegionId, bool New = false);
-  int64_t Pts(void) const { return pts; }
-  int Timeout(void) { return timeout; }
-  void SetVersion(int Version) { version = Version; }
-  void SetPts(int64_t Pts) { pts = Pts; }
-  void SetState(int State);
-  void SetTimeout(int Timeout) { timeout = Timeout; }
+  cSubtitleRegionRef *GetRegionRefByIndex(int RegionRefIndex) { return regionRefs.Get(RegionRefIndex); }
+  void SetPending(bool Pending) { pending = Pending; }
   };
 
 cDvbSubtitlePage::cDvbSubtitlePage(int PageId)
 {
   pageId = PageId;
-  version = -1;
-  state = -1;
-  pts = 0;
-  timeout = 0;
-}
-
-cDvbSubtitlePage::~cDvbSubtitlePage()
-{
+  pageTimeout = 0;
+  pageVersionNumber = -1;
+  pageState = -1;
+  pts = -1;
+  pending = false;
+}
+
+void cDvbSubtitlePage::Parse(int64_t Pts, cBitStream &bs)
+{
+  if (Pts >= 0)
+     pts = Pts;
+  pageTimeout = bs.GetBits(8);
+  int Version = bs.GetBits(4);
+  if (pageVersionNumber == Version)
+     return; // no update
+  pageVersionNumber = Version;
+  pageState = bs.GetBits(2);
+  switch (pageState) {
+    case 0: // normal case - page update
+         break;
+    case 1: // acquisition point - page refresh
+         regions.Clear();
+         objects.Clear();
+         break;
+    case 2: // mode change - new page
+         regions.Clear();
+         cluts.Clear();
+         objects.Clear();
+         break;
+    case 3: // reserved
+         break;
+    default: dbgpages("unknown page state: %d<br>\n", pageState);
+    }
+  bs.SkipBits(2); // reserved
+  dbgpages("<hr>\n<b>page</b> id %d version %d pts %"PRId64" timeout %d state %d<br>\n", pageId, pageVersionNumber, pts, pageTimeout, pageState);
+  regionRefs.Clear();
+  while (!bs.IsEOF())
+        regionRefs.Add(new cSubtitleRegionRef(bs));
+  pending = true;
 }
 
-tArea *cDvbSubtitlePage::GetAreas(double FactorX, double FactorY)
+tArea *cDvbSubtitlePage::GetAreas(int &NumAreas, double FactorX, double FactorY)
 {
   if (regions.Count() > 0) {
-     tArea *Areas = new tArea[regions.Count()];
+     NumAreas = regionRefs.Count();
+     tArea *Areas = new tArea[NumAreas];
      tArea *a = Areas;
-     for (cSubtitleRegion *sr = regions.First(); sr; sr = regions.Next(sr)) {
-         a->x1 = int(round(FactorX * sr->HorizontalAddress()));
-         a->y1 = int(round(FactorY * sr->VerticalAddress()));
-         a->x2 = int(round(FactorX * (sr->HorizontalAddress() + sr->Width() - 1)));
-         a->y2 = int(round(FactorY * (sr->VerticalAddress() + sr->Height() - 1)));
-         a->bpp = sr->Bpp();
-         while ((a->Width() & 3) != 0)
-               a->x2++; // aligns width to a multiple of 4, so 2, 4 and 8 bpp will work
+     for (cSubtitleRegionRef *srr = regionRefs.First(); srr; srr = regionRefs.Next(srr)) {
+         if (cSubtitleRegion *sr = GetRegionById(srr->RegionId())) {
+            a->x1 = int(round(FactorX * srr->RegionHorizontalAddress()));
+            a->y1 = int(round(FactorY * srr->RegionVerticalAddress()));
+            a->x2 = int(round(FactorX * (srr->RegionHorizontalAddress() + sr->RegionWidth() - 1)));
+            a->y2 = int(round(FactorY * (srr->RegionVerticalAddress() + sr->RegionHeight() - 1)));
+            a->bpp = sr->RegionDepth();
+            while ((a->Width() & 3) != 0)
+                  a->x2++; // aligns width to a multiple of 4, so 2, 4 and 8 bpp will work
+            }
+         else
+            a->x1 = a->y1 = a->x2 = a->y2 = a->bpp = 0;
          a++;
          }
      return Areas;
      }
+  NumAreas = 0;
   return NULL;
 }
 
 cSubtitleClut *cDvbSubtitlePage::GetClutById(int ClutId, bool New)
 {
-  cSubtitleClut *result = NULL;
   for (cSubtitleClut *sc = cluts.First(); sc; sc = cluts.Next(sc)) {
       if (sc->ClutId() == ClutId)
-         result = sc;
+         return sc;
       }
-  if (!result && New) {
-     result = new cSubtitleClut(ClutId);
-     cluts.Add(result);
-     }
-  return result;
+  if (!New)
+     return NULL;
+  cSubtitleClut *Clut = new cSubtitleClut(ClutId);
+  cluts.Add(Clut);
+  return Clut;
 }
 
 cSubtitleRegion *cDvbSubtitlePage::GetRegionById(int RegionId, bool New)
 {
-  cSubtitleRegion *result = NULL;
   for (cSubtitleRegion *sr = regions.First(); sr; sr = regions.Next(sr)) {
       if (sr->RegionId() == RegionId)
-         result = sr;
+         return sr;
       }
-  if (!result && New) {
-     result = new cSubtitleRegion(RegionId);
-     regions.Add(result);
-     }
-  return result;
+  if (!New)
+     return NULL;
+  cSubtitleRegion *Region = new cSubtitleRegion(RegionId);
+  regions.Add(Region);
+  return Region;
 }
 
-cSubtitleObject *cDvbSubtitlePage::GetObjectById(int ObjectId)
+cSubtitleObject *cDvbSubtitlePage::GetObjectById(int ObjectId, bool New)
 {
-  cSubtitleObject *result = NULL;
-  for (cSubtitleRegion *sr = regions.First(); sr && !result; sr = regions.Next(sr))
-      result = sr->GetObjectById(ObjectId);
-  return result;
-}
-
-void cDvbSubtitlePage::SetState(int State)
-{
-  state = State;
-  switch (state) {
-    case 0: // normal case - page update
-         dbgpages("page update\n");
-         break;
-    case 1: // acquisition point - page refresh
-         dbgpages("page refresh\n");
-         regions.Clear();
-         break;
-    case 2: // mode change - new page
-         dbgpages("new Page\n");
-         regions.Clear();
-         cluts.Clear();
-         break;
-    case 3: // reserved
-         break;
-    default: dbgpages("unknown page state (%s %d)\n", __FUNCTION__, __LINE__);
-    }
+  return objects.GetObjectById(ObjectId, New);
 }
 
 // --- cDvbSubtitleAssembler -------------------------------------------------
@@ -714,6 +1016,7 @@
 
 class cDvbSubtitleBitmaps : public cListObject {
 private:
+  int state;
   int64_t pts;
   int timeout;
   tArea *areas;
@@ -722,16 +1025,20 @@
   double osdFactorY;
   cVector<cBitmap *> bitmaps;
 public:
-  cDvbSubtitleBitmaps(int64_t Pts, int Timeout, tArea *Areas, int NumAreas, double OsdFactorX, double OsdFactorY);
+  cDvbSubtitleBitmaps(int State, int64_t Pts, int Timeout, tArea *Areas, int NumAreas, double OsdFactorX, double OsdFactorY);
   ~cDvbSubtitleBitmaps();
+  int State(void) { return state; }
   int64_t Pts(void) { return pts; }
   int Timeout(void) { return timeout; }
   void AddBitmap(cBitmap *Bitmap);
+  bool HasBitmaps(void) { return bitmaps.Size(); }
   void Draw(cOsd *Osd);
+  void DbgDump(int WindowWidth, int WindowHeight);
   };
 
-cDvbSubtitleBitmaps::cDvbSubtitleBitmaps(int64_t Pts, int Timeout, tArea *Areas, int NumAreas, double OsdFactorX, double OsdFactorY)
+cDvbSubtitleBitmaps::cDvbSubtitleBitmaps(int State, int64_t Pts, int Timeout, tArea *Areas, int NumAreas, double OsdFactorX, double OsdFactorY)
 {
+  state = State;
   pts = Pts;
   timeout = Timeout;
   areas = Areas;
@@ -765,11 +1072,11 @@
          }
      if (Osd->CanHandleAreas(areas, numAreas) != oeOk) {
         for (int i = 0; i < numAreas; i++)
-            Bpp[i] = areas[i].bpp = Bpp[i];
+            areas[i].bpp = Bpp[i];
         AntiAlias = false;
         }
      }
-  if (Osd->SetAreas(areas, numAreas) == oeOk) {
+  if (State() == 0 || Osd->SetAreas(areas, numAreas) == oeOk) {
      for (int i = 0; i < bitmaps.Size(); i++) {
          cBitmap *b = bitmaps[i];
          if (Scale)
@@ -782,6 +1089,37 @@
      }
 }
 
+void cDvbSubtitleBitmaps::DbgDump(int WindowWidth, int WindowHeight)
+{
+  if (!SD.Active())
+     return;
+  SD.SetFirstPts(Pts());
+  double STC = double(cDevice::PrimaryDevice()->GetSTC() - SD.FirstPts()) / 90000;
+  double Start = double(Pts() - SD.FirstPts()) / 90000;
+  double Duration = Timeout();
+  double End = Start + Duration;
+  cBitmap Bitmap(WindowWidth, WindowHeight, 8);
+#define DBGBACKGROUND 0xA0A0A0
+  Bitmap.DrawRectangle(0, 0, WindowWidth - 1, WindowHeight - 1, DBGBACKGROUND);
+  for (int i = 0; i < bitmaps.Size(); i++) {
+      cBitmap *b = bitmaps[i];
+      Bitmap.DrawBitmap(b->X0(), b->Y0(), *b);
+      }
+  cString ImgName = SD.WriteJpeg(&Bitmap);
+#define BORDER //" border=1"
+  SD.WriteHtml("<p>%s<br>", State() == 0 ? "page update" : State() == 1 ? "page refresh" : State() == 2 ? "new page" : "???");
+  SD.WriteHtml("<table" BORDER "><tr><td>");
+  SD.WriteHtml("%.2f", STC);
+  SD.WriteHtml("</td><td>");
+  SD.WriteHtml("<img src=\"%s\">", *ImgName);
+  SD.WriteHtml("</td><td style=\"height:100%%\"><table" BORDER " style=\"height:100%%\">");
+  SD.WriteHtml("<tr><td valign=top><b>%.2f</b></td></tr>", Start);
+  SD.WriteHtml("<tr><td valign=middle>%.2f</td></tr>", Duration);
+  SD.WriteHtml("<tr><td valign=bottom>%.2f</td></tr>", End);
+  SD.WriteHtml("</table></td>");
+  SD.WriteHtml("</tr></table>\n");
+}
+
 // --- cDvbSubtitleConverter -------------------------------------------------
 
 int cDvbSubtitleConverter::setupLevel = 0;
@@ -799,6 +1137,7 @@
   windowVerticalOffset = 0;
   pages = new cList<cDvbSubtitlePage>;
   bitmaps = new cList<cDvbSubtitleBitmaps>;
+  SD.Reset();
   Start();
 }
 
@@ -818,7 +1157,7 @@
 
 void cDvbSubtitleConverter::Reset(void)
 {
-  dbgconverter("Converter reset -----------------------\n");
+  dbgconverter("converter reset -----------------------<br>\n");
   dvbSubtitleAssembler->Reset();
   Lock();
   pages->Clear();
@@ -848,9 +1187,9 @@
         }
 
      if (Length > PayloadOffset + SubstreamHeaderLength) {
-        int64_t pts = PesHasPts(Data) ? PesGetPts(Data) : 0;
-        if (pts)
-           dbgconverter("Converter PTS: %"PRId64"\n", pts);
+        int64_t pts = PesHasPts(Data) ? PesGetPts(Data) : -1;
+        if (pts >= 0)
+           dbgconverter("converter PTS: %"PRId64"<br>\n", pts);
         const uchar *data = Data + PayloadOffset + SubstreamHeaderLength; // skip substream header
         int length = Length - PayloadOffset - SubstreamHeaderLength; // skip substream header
         if (ResetSubtitleAssembler)
@@ -884,9 +1223,9 @@
   if (Data && Length > 8) {
      int PayloadOffset = PesPayloadOffset(Data);
      if (Length > PayloadOffset) {
-        int64_t pts = PesGetPts(Data);
-        if (pts)
-           dbgconverter("Converter PTS: %"PRId64"\n", pts);
+        int64_t pts = PesHasPts(Data) ? PesGetPts(Data) : -1;
+        if (pts >= 0)
+           dbgconverter("converter PTS: %"PRId64"<br>\n", pts);
         const uchar *data = Data + PayloadOffset;
         int length = Length - PayloadOffset;
         if (length > 3) {
@@ -914,7 +1253,6 @@
 }
 
 #define LimitTo32Bit(n) ((n) & 0x00000000FFFFFFFFL)
-#define MAXDELTA 40000 // max. reasonable PTS/STC delta in ms
 
 void cDvbSubtitleConverter::Action(void)
 {
@@ -927,55 +1265,47 @@
            if (osd) {
               int NewSetupLevel = setupLevel;
               if (Timeout.TimedOut() || LastSetupLevel != NewSetupLevel) {
+                 dbgoutput("closing osd<br>\n");
                  DELETENULL(osd);
                  }
               LastSetupLevel = NewSetupLevel;
               }
-           if (cDvbSubtitleBitmaps *sb = bitmaps->First()) {
-              int64_t STC = cDevice::PrimaryDevice()->GetSTC();
-              int64_t Delta = LimitTo32Bit(sb->Pts()) - LimitTo32Bit(STC); // some devices only deliver 32 bits
-              if (Delta > (int64_t(1) << 31))
-                 Delta -= (int64_t(1) << 32);
-              else if (Delta < -((int64_t(1) << 31) - 1))
-                 Delta += (int64_t(1) << 32);
-              Delta /= 90; // STC and PTS are in 1/90000s
-              if (Delta <= MAXDELTA) {
-                 if (Delta <= 0) {
-                    dbgconverter("Got %d bitmaps, showing #%d\n", bitmaps->Count(), sb->Index() + 1);
-                    if (AssertOsd()) {
-                       sb->Draw(osd);
-                       Timeout.Set(sb->Timeout() * 1000);
-                       dbgconverter("PTS: %"PRId64"  STC: %"PRId64" (%"PRId64") timeout: %d\n", sb->Pts(), cDevice::PrimaryDevice()->GetSTC(), Delta, sb->Timeout());
-                       }
-                    bitmaps->Del(sb);
-                    }
-                 else if (Delta < WaitMs)
-                    WaitMs = Delta;
-                 }
-              else
-                 bitmaps->Del(sb);
-              }
+           for (cDvbSubtitleBitmaps *sb = bitmaps->First(); sb; sb = bitmaps->Next(sb)) {
+               // Calculate the Delta between the STC (the current timestamp of the video)
+               // and the bitmap's PTS (the timestamp when the bitmap shall be presented).
+               // A negative Delta means that the bitmap will be presented in the future:
+               int64_t STC = cDevice::PrimaryDevice()->GetSTC();
+               int64_t Delta = LimitTo32Bit(STC) - LimitTo32Bit(sb->Pts()); // some devices only deliver 32 bits
+               if (Delta > (int64_t(1) << 31))
+                  Delta -= (int64_t(1) << 32);
+               else if (Delta < -((int64_t(1) << 31) - 1))
+                  Delta += (int64_t(1) << 32);
+               Delta /= 90; // STC and PTS are in 1/90000s
+               if (Delta >= 0) { // found a bitmap that shall be displayed...
+                  if (Delta < sb->Timeout() * 1000) { // ...and has not timed out yet
+                     if (!sb->HasBitmaps()) {
+                        Timeout.Set();
+                        WaitMs = 0;
+                        }
+                     else if (AssertOsd()) {
+                        dbgoutput("showing bitmap #%d of %d<br>\n", sb->Index() + 1, bitmaps->Count());
+                        sb->Draw(osd);
+                        Timeout.Set(sb->Timeout() * 1000);
+                        dbgconverter("PTS: %"PRId64"  STC: %"PRId64" (%"PRId64") timeout: %d<br>\n", sb->Pts(), STC, Delta, sb->Timeout());
+                        }
+                     }
+                  else
+                     WaitMs = 0; // bitmap already timed out, so try next one immediately
+                  dbgoutput("deleting bitmap #%d of %d<br>\n", sb->Index() + 1, bitmaps->Count());
+                  bitmaps->Del(sb);
+                  break;
+                  }
+               }
            }
         cCondWait::SleepMs(WaitMs);
         }
 }
 
-tColor cDvbSubtitleConverter::yuv2rgb(int Y, int Cb, int Cr)
-{
-  int Ey, Epb, Epr;
-  int Eg, Eb, Er;
-
-  Ey = (Y - 16);
-  Epb = (Cb - 128);
-  Epr = (Cr - 128);
-  /* ITU-R 709 */
-  Er = constrain((298 * Ey             + 460 * Epr) / 256, 0, 255);
-  Eg = constrain((298 * Ey -  55 * Epb - 137 * Epr) / 256, 0, 255);
-  Eb = constrain((298 * Ey + 543 * Epb            ) / 256, 0, 255);
-
-  return (Er << 16) | (Eg << 8) | Eb;
-}
-
 void cDvbSubtitleConverter::SetOsdData(void)
 {
   int OsdWidth, OsdHeight;
@@ -1006,6 +1336,19 @@
   return osd != NULL;
 }
 
+cDvbSubtitlePage *cDvbSubtitleConverter::GetPageById(int PageId, bool New)
+{
+  for (cDvbSubtitlePage *sp = pages->First(); sp; sp = pages->Next(sp)) {
+      if (sp->PageId() == PageId)
+         return sp;
+      }
+  if (!New)
+     return NULL;
+  cDvbSubtitlePage *Page = new cDvbSubtitlePage(PageId);
+  pages->Add(Page);
+  return Page;
+}
+
 int cDvbSubtitleConverter::ExtractSegment(const uchar *Data, int Length, int64_t Pts)
 {
   cBitStream bs(Data, Length * 8);
@@ -1013,177 +1356,42 @@
      int segmentType = bs.GetBits(8);
      if (segmentType == STUFFING_SEGMENT)
         return -1;
-     int pageId = bs.GetBits(16);
+     LOCK_THREAD;
+     cDvbSubtitlePage *page = GetPageById(bs.GetBits(16), true);
      int segmentLength = bs.GetBits(16);
      if (!bs.SetLength(bs.Index() + segmentLength * 8))
         return -1;
-     cDvbSubtitlePage *page = NULL;
-     LOCK_THREAD;
-     for (cDvbSubtitlePage *sp = pages->First(); sp; sp = pages->Next(sp)) {
-         if (sp->PageId() == pageId) {
-            page = sp;
-            break;
-            }
-         }
-     if (!page) {
-        page = new cDvbSubtitlePage(pageId);
-        pages->Add(page);
-        dbgpages("Create SubtitlePage %d (total pages = %d)\n", pageId, pages->Count());
-        }
-     if (Pts)
-        page->SetPts(Pts);
      switch (segmentType) {
        case PAGE_COMPOSITION_SEGMENT: {
-            dbgsegments("PAGE_COMPOSITION_SEGMENT\n");
-            int pageTimeout = bs.GetBits(8);
-            int pageVersion = bs.GetBits(4);
-            if (pageVersion == page->Version())
-               break; // no update
-            page->SetVersion(pageVersion);
-            page->SetTimeout(pageTimeout);
-            page->SetState(bs.GetBits(2));
-            bs.SkipBits(2); // reserved
-            dbgpages("Update page id %d version %d pts %"PRId64" timeout %d state %d\n", pageId, page->Version(), page->Pts(), page->Timeout(), page->State());
-            while (!bs.IsEOF()) {
-                  cSubtitleRegion *region = page->GetRegionById(bs.GetBits(8), true);
-                  bs.SkipBits(8); // reserved
-                  region->SetHorizontalAddress(bs.GetBits(16));
-                  region->SetVerticalAddress(bs.GetBits(16));
-                  }
+            if (page->Pending()) {
+               dbgsegments("END_OF_DISPLAY_SET_SEGMENT (simulated)<br>\n");
+               FinishPage(page);
+               }
+            dbgsegments("PAGE_COMPOSITION_SEGMENT<br>\n");
+            page->Parse(Pts, bs);
+            SD.SetFactor(double(DBGBITMAPWIDTH) / windowWidth);
             break;
             }
        case REGION_COMPOSITION_SEGMENT: {
-            dbgsegments("REGION_COMPOSITION_SEGMENT\n");
-            cSubtitleRegion *region = page->GetRegionById(bs.GetBits(8));
-            if (!region)
-               break;
-            int regionVersion = bs.GetBits(4);
-            if (regionVersion == region->Version())
-               break; // no update
-            region->SetVersion(regionVersion);
-            bool regionFillFlag = bs.GetBit();
-            bs.SkipBits(3); // reserved
-            int regionWidth = bs.GetBits(16);
-            if (regionWidth < 1)
-               regionWidth = 1;
-            int regionHeight = bs.GetBits(16);
-            if (regionHeight < 1)
-               regionHeight = 1;
-            region->SetSize(regionWidth, regionHeight);
-            region->SetLevel(bs.GetBits(3));
-            region->SetDepth(bs.GetBits(3));
-            bs.SkipBits(2); // reserved
-            region->SetClutId(bs.GetBits(8));
-            dbgregions("Region pageId %d id %d version %d fill %d width %d height %d level %d depth %d clutId %d\n", pageId, region->RegionId(), region->Version(), regionFillFlag, regionWidth, regionHeight, region->Level(), region->Depth(), region->ClutId());
-            int region8bitPixelCode = bs.GetBits(8);
-            int region4bitPixelCode = bs.GetBits(4);
-            int region2bitPixelCode = bs.GetBits(2);
-            bs.SkipBits(2); // reserved
-            if (regionFillFlag) {
-               switch (region->Bpp()) {
-                 case 2: region->FillRegion(region8bitPixelCode); break;
-                 case 4: region->FillRegion(region4bitPixelCode); break;
-                 case 8: region->FillRegion(region2bitPixelCode); break;
-                 default: dbgregions("unknown bpp %d (%s %d)\n", region->Bpp(), __FUNCTION__, __LINE__);
-                 }
-               }
-            while (!bs.IsEOF()) {
-                  cSubtitleObject *object = region->GetObjectById(bs.GetBits(16), true);
-                  int objectType = bs.GetBits(2);
-                  object->SetCodingMethod(objectType);
-                  object->SetProviderFlag(bs.GetBits(2));
-                  int objectHorizontalPosition = bs.GetBits(12);
-                  bs.SkipBits(4); // reserved
-                  int objectVerticalPosition = bs.GetBits(12);
-                  object->SetPosition(objectHorizontalPosition, objectVerticalPosition);
-                  if (objectType == 0x01 || objectType == 0x02) {
-                     object->SetForegroundPixelCode(bs.GetBits(8));
-                     object->SetBackgroundPixelCode(bs.GetBits(8));
-                     }
-                  }
+            dbgsegments("REGION_COMPOSITION_SEGMENT<br>\n");
+            cSubtitleRegion *region = page->GetRegionById(bs.GetBits(8), true);
+            region->Parse(bs);
             break;
             }
        case CLUT_DEFINITION_SEGMENT: {
-            dbgsegments("CLUT_DEFINITION_SEGMENT\n");
+            dbgsegments("CLUT_DEFINITION_SEGMENT<br>\n");
             cSubtitleClut *clut = page->GetClutById(bs.GetBits(8), true);
-            int clutVersion = bs.GetBits(4);
-            if (clutVersion == clut->Version())
-               break; // no update
-            clut->SetVersion(clutVersion);
-            bs.SkipBits(4); // reserved
-            dbgcluts("Clut pageId %d id %d version %d\n", pageId, clut->ClutId(), clut->Version());
-            while (!bs.IsEOF()) {
-                  uchar clutEntryId = bs.GetBits(8);
-                  bool entryClut2Flag = bs.GetBit();
-                  bool entryClut4Flag = bs.GetBit();
-                  bool entryClut8Flag = bs.GetBit();
-                  bs.SkipBits(4); // reserved
-                  uchar yval;
-                  uchar crval;
-                  uchar cbval;
-                  uchar tval;
-                  if (bs.GetBit()) { // full_range_flag
-                     yval  = bs.GetBits(8);
-                     crval = bs.GetBits(8);
-                     cbval = bs.GetBits(8);
-                     tval  = bs.GetBits(8);
-                     }
-                  else {
-                     yval  = bs.GetBits(6) << 2;
-                     crval = bs.GetBits(4) << 4;
-                     cbval = bs.GetBits(4) << 4;
-                     tval  = bs.GetBits(2) << 6;
-                     }
-                  tColor value = 0;
-                  if (yval) {
-                     value = yuv2rgb(yval, cbval, crval);
-                     value |= ((10 - (clutEntryId ? Setup.SubtitleFgTransparency : Setup.SubtitleBgTransparency)) * (255 - tval) / 10) << 24;
-                     }
-                  dbgcluts("%2d %d %d %d %08X\n", clutEntryId, entryClut2Flag ? 2 : 0, entryClut4Flag ? 4 : 0, entryClut8Flag ? 8 : 0, value);
-                  if (entryClut2Flag)
-                     clut->SetColor(2, clutEntryId, value);
-                  if (entryClut4Flag)
-                     clut->SetColor(4, clutEntryId, value);
-                  if (entryClut8Flag)
-                     clut->SetColor(8, clutEntryId, value);
-                  }
-            dbgcluts("\n");
+            clut->Parse(bs);
             break;
             }
        case OBJECT_DATA_SEGMENT: {
-            dbgsegments("OBJECT_DATA_SEGMENT\n");
-            cSubtitleObject *object = page->GetObjectById(bs.GetBits(16));
-            if (!object)
-               break;
-            int objectVersion = bs.GetBits(4);
-            if (objectVersion == object->Version())
-               break; // no update
-            object->SetVersion(objectVersion);
-            int codingMethod = bs.GetBits(2);
-            object->SetNonModifyingColorFlag(bs.GetBit());
-            bs.SkipBit(); // reserved
-            dbgobjects("Object pageId %d id %d version %d method %d modify %d\n", pageId, object->ObjectId(), object->Version(), object->CodingMethod(), object->NonModifyingColorFlag());
-            if (codingMethod == 0) { // coding of pixels
-               int topFieldLength = bs.GetBits(16);
-               int bottomFieldLength = bs.GetBits(16);
-               object->DecodeSubBlock(bs.GetData(), topFieldLength, true);
-               if (bottomFieldLength)
-                  object->DecodeSubBlock(bs.GetData() + topFieldLength, bottomFieldLength, false);
-               else
-                  object->DecodeSubBlock(bs.GetData(), topFieldLength, false);
-               bs.WordAlign();
-               }
-            else if (codingMethod == 1) { // coded as a string of characters
-               int numberOfCodes = bs.GetBits(8);
-               object->DecodeCharacterString(bs.GetData(), numberOfCodes);
-               }
-#ifdef FINISHPAGE_HACK
-            FinishPage(page); // flush to OSD right away
-#endif
+            dbgsegments("OBJECT_DATA_SEGMENT<br>\n");
+            cSubtitleObject *object = page->GetObjectById(bs.GetBits(16), true);
+            object->Parse(bs);
             break;
             }
        case DISPLAY_DEFINITION_SEGMENT: {
-            dbgsegments("DISPLAY_DEFINITION_SEGMENT\n");
+            dbgsegments("DISPLAY_DEFINITION_SEGMENT<br>\n");
             int version = bs.GetBits(4);
             if (version != ddsVersionNumber) {
                bool displayWindowFlag = bs.GetBit();
@@ -1199,13 +1407,13 @@
                   windowHeight           = bs.GetBits(16) - windowVerticalOffset + 1;   // displayWindowVerticalPositionMaximum
                   }
                SetOsdData();
-               SetupChanged();
                ddsVersionNumber = version;
+               dbgdisplay("<b>display</b> version %d flag %d width %d height %d ofshor %d ofsver %d<br>\n", ddsVersionNumber, displayWindowFlag, windowWidth, windowHeight, windowHorizontalOffset, windowVerticalOffset);
                }
             break;
             }
        case DISPARITY_SIGNALING_SEGMENT: {
-            dbgsegments("DISPARITY_SIGNALING_SEGMENT\n");
+            dbgsegments("DISPARITY_SIGNALING_SEGMENT<br>\n");
             bs.SkipBits(4); // dss_version_number
             bool disparity_shift_update_sequence_page_flag = bs.GetBit();
             bs.SkipBits(3); // reserved
@@ -1246,12 +1454,13 @@
             break;
             }
        case END_OF_DISPLAY_SET_SEGMENT: {
-            dbgsegments("END_OF_DISPLAY_SET_SEGMENT\n");
+            dbgsegments("END_OF_DISPLAY_SET_SEGMENT<br>\n");
             FinishPage(page);
+            page->SetPending(false);
             break;
             }
        default:
-            dbgsegments("*** unknown segment type: %02X\n", segmentType);
+            dbgsegments("*** unknown segment type: %02X<br>\n", segmentType);
        }
      return bs.Length() / 8;
      }
@@ -1262,11 +1471,12 @@
 {
   if (!AssertOsd())
      return;
-  tArea *Areas = Page->GetAreas(osdFactorX, osdFactorY);
-  int NumAreas = Page->regions.Count();
+  int NumAreas;
+  tArea *Areas = Page->GetAreas(NumAreas, osdFactorX, osdFactorY);
   int Bpp = 8;
   bool Reduced = false;
   while (osd && osd->CanHandleAreas(Areas, NumAreas) != oeOk) {
+        dbgoutput("CanHandleAreas: %d<br>\n", osd->CanHandleAreas(Areas, NumAreas));
         int HalfBpp = Bpp / 2;
         if (HalfBpp >= 2) {
            for (int i = 0; i < NumAreas; i++) {
@@ -1280,37 +1490,35 @@
         else
            return; // unable to draw bitmaps
         }
-  cDvbSubtitleBitmaps *Bitmaps = new cDvbSubtitleBitmaps(Page->Pts(), Page->Timeout(), Areas, NumAreas, osdFactorX, osdFactorY);
+  cDvbSubtitleBitmaps *Bitmaps = new cDvbSubtitleBitmaps(Page->PageState(), Page->Pts(), Page->PageTimeout(), Areas, NumAreas, osdFactorX, osdFactorY);
   bitmaps->Add(Bitmaps);
   for (int i = 0; i < NumAreas; i++) {
-      cSubtitleRegion *sr = Page->regions.Get(i);
-      cSubtitleClut *clut = Page->GetClutById(sr->ClutId());
-      if (!clut)
-         continue;
-      sr->Replace(*clut->GetPalette(sr->Bpp()));
-      sr->UpdateTextData(clut);
-      if (Reduced) {
-         if (sr->Bpp() != Areas[i].bpp) {
-            if (sr->Level() <= Areas[i].bpp) {
-               //TODO this is untested - didn't have any such subtitle stream
-               cSubtitleClut *Clut = Page->GetClutById(sr->ClutId());
-               if (Clut) {
-                  dbgregions("reduce region %d bpp %d level %d area bpp %d\n", sr->RegionId(), sr->Bpp(), sr->Level(), Areas[i].bpp);
-                  sr->ReduceBpp(*Clut->GetPalette(sr->Bpp()));
+      if (cSubtitleRegionRef *srr = Page->GetRegionRefByIndex(i)) {
+         if (cSubtitleRegion *sr = Page->GetRegionById(srr->RegionId())) {
+            if (cSubtitleClut *clut = Page->GetClutById(sr->ClutId())) {
+               cBitmap *bm = new cBitmap(sr->RegionWidth(), sr->RegionHeight(), sr->RegionDepth());
+               bm->Replace(*clut->GetPalette(sr->RegionDepth()));
+               sr->Render(bm, Page->Objects());
+               if (Reduced) {
+                  if (sr->RegionDepth() != Areas[i].bpp) {
+                     if (sr->RegionLevelOfCompatibility() <= Areas[i].bpp) {
+                        //TODO this is untested - didn't have any such subtitle stream
+                        cSubtitleClut *Clut = Page->GetClutById(sr->ClutId());
+                        dbgregions("reduce region %d bpp %d level %d area bpp %d<br>\n", sr->RegionId(), sr->RegionDepth(), sr->RegionLevelOfCompatibility(), Areas[i].bpp);
+                        bm->ReduceBpp(*Clut->GetPalette(sr->RegionDepth()));
+                        }
+                     else {
+                        dbgregions("condense region %d bpp %d level %d area bpp %d<br>\n", sr->RegionId(), sr->RegionDepth(), sr->RegionLevelOfCompatibility(), Areas[i].bpp);
+                        bm->ShrinkBpp(Areas[i].bpp);
+                        }
+                     }
                   }
-               }
-            else {
-               dbgregions("condense region %d bpp %d level %d area bpp %d\n", sr->RegionId(), sr->Bpp(), sr->Level(), Areas[i].bpp);
-               sr->ShrinkBpp(Areas[i].bpp);
+               bm->SetOffset(srr->RegionHorizontalAddress(), srr->RegionVerticalAddress());
+               Bitmaps->AddBitmap(bm);
                }
             }
          }
-      int posX = sr->HorizontalAddress();
-      int posY = sr->VerticalAddress();
-      if (sr->Width() > 0 && sr->Height() > 0) {
-         cBitmap *bm = new cBitmap(sr->Width(), sr->Height(), sr->Bpp(), posX, posY);
-         bm->DrawBitmap(posX, posY, *sr);
-         Bitmaps->AddBitmap(bm);
-         }
       }
+  if (DebugPages)
+     Bitmaps->DbgDump(windowWidth, windowHeight);
 }
diff -ruN vdr-2.1.1/dvbsubtitle.h vdr-2.1.2/dvbsubtitle.h
--- vdr-2.1.1/dvbsubtitle.h	2012-03-11 14:34:12.000000000 +0100
+++ vdr-2.1.2/dvbsubtitle.h	2013-09-06 12:53:30.000000000 +0200
@@ -6,7 +6,7 @@
  *
  * Original author: Marco Schluessler <marco@lordzodiac.de>
  *
- * $Id: dvbsubtitle.h 3.0 2012/03/11 13:34:12 kls Exp $
+ * $Id: dvbsubtitle.h 3.1 2013/09/06 10:53:30 kls Exp $
  */
 
 #ifndef __DVBSUBTITLE_H
@@ -39,7 +39,7 @@
   double osdFactorY;
   cList<cDvbSubtitlePage> *pages;
   cList<cDvbSubtitleBitmaps> *bitmaps;
-  tColor yuv2rgb(int Y, int Cb, int Cr);
+  cDvbSubtitlePage *GetPageById(int PageId, bool New = false);
   void SetOsdData(void);
   bool AssertOsd(void);
   int ExtractSegment(const uchar *Data, int Length, int64_t Pts);
diff -ruN vdr-2.1.1/eit.c vdr-2.1.2/eit.c
--- vdr-2.1.1/eit.c	2013-08-23 12:52:27.000000000 +0200
+++ vdr-2.1.2/eit.c	2013-10-12 13:10:11.000000000 +0200
@@ -8,7 +8,7 @@
  * Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
  * Adapted to 'libsi' for VDR 1.3.0 by Marcel Wiesweg <marcel.wiesweg@gmx.de>.
  *
- * $Id: eit.c 3.1 2013/08/23 10:52:27 kls Exp $
+ * $Id: eit.c 3.2 2013/10/12 11:10:11 kls Exp $
  */
 
 #include "eit.h"
@@ -406,7 +406,7 @@
          }
          break;
     case 0x14: {
-         if (Setup.SetSystemTime && Setup.TimeTransponder && ISTRANSPONDER(Transponder(), Setup.TimeTransponder))
+         if (Setup.SetSystemTime && Setup.TimeSource == Source() && Setup.TimeTransponder && ISTRANSPONDER(Transponder(), Setup.TimeTransponder))
             cTDT TDT(Data);
          }
          break;
diff -ruN vdr-2.1.1/epg.c vdr-2.1.2/epg.c
--- vdr-2.1.1/epg.c	2013-08-23 12:46:33.000000000 +0200
+++ vdr-2.1.2/epg.c	2013-08-31 15:21:09.000000000 +0200
@@ -7,7 +7,7 @@
  * Original version (as used in VDR before 1.3.0) written by
  * Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
  *
- * $Id: epg.c 3.1 2013/08/23 10:46:33 kls Exp $
+ * $Id: epg.c 3.2 2013/08/31 13:21:09 kls Exp $
  */
 
 #include "epg.h"
@@ -1140,16 +1140,19 @@
 class cEpgDataWriter : public cThread {
 private:
   cMutex mutex;
+  bool dump;
 protected:
   virtual void Action(void);
 public:
   cEpgDataWriter(void);
+  void SetDump(bool Dump) { dump = Dump; }
   void Perform(void);
   };
 
 cEpgDataWriter::cEpgDataWriter(void)
 :cThread("epg data writer", true)
 {
+  dump = false;
 }
 
 void cEpgDataWriter::Action(void)
@@ -1169,7 +1172,8 @@
            p->Cleanup(now);
        }
   }
-  cSchedules::Dump();
+  if (dump)
+     cSchedules::Dump();
 }
 
 static cEpgDataWriter EpgDataWriter;
@@ -1203,6 +1207,7 @@
 {
   free(epgDataFileName);
   epgDataFileName = FileName ? strdup(FileName) : NULL;
+  EpgDataWriter.SetDump(epgDataFileName != NULL);
 }
 
 void cSchedules::SetModified(cSchedule *Schedule)
@@ -1217,12 +1222,10 @@
      lastDump = 0;
   time_t now = time(NULL);
   if (now - lastDump > EPGDATAWRITEDELTA) {
-     if (epgDataFileName) {
-        if (Force)
-           EpgDataWriter.Perform();
-        else if (!EpgDataWriter.Active())
-           EpgDataWriter.Start();
-        }
+     if (Force)
+        EpgDataWriter.Perform();
+     else if (!EpgDataWriter.Active())
+        EpgDataWriter.Start();
      lastDump = now;
      }
 }
diff -ruN vdr-2.1.1/menu.c vdr-2.1.2/menu.c
--- vdr-2.1.1/menu.c	2013-08-21 12:45:11.000000000 +0200
+++ vdr-2.1.2/menu.c	2013-10-16 11:15:36.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 3.3 2013/08/21 10:45:11 kls Exp $
+ * $Id: menu.c 3.10 2013/10/16 09:15:36 kls Exp $
  */
 
 #include "menu.h"
@@ -16,7 +16,6 @@
 #include <string.h>
 #include "channels.h"
 #include "config.h"
-#include "cutter.h"
 #include "eitscan.h"
 #include "i18n.h"
 #include "interface.h"
@@ -828,8 +827,7 @@
 
 eOSState cMenuFolder::SetFolder(void)
 {
-  cMenuEditFolder *mef = (cMenuEditFolder *)SubMenu();
-  if (mef) {
+  if (cMenuEditFolder *mef = dynamic_cast<cMenuEditFolder *>(SubMenu())) {
      Set(mef->GetFolder());
      SetHelpKeys();
      Display();
@@ -843,8 +841,7 @@
   if (firstFolder) {
      cMenuFolderItem *Folder = (cMenuFolderItem *)Get(Current());
      if (Folder) {
-        cMenuFolder *mf = (cMenuFolder *)SubMenu();
-        if (mf)
+        if (cMenuFolder *mf = dynamic_cast<cMenuFolder *>(SubMenu()))
            return cString::sprintf("%s%c%s", Folder->Folder()->Text(), FOLDERDELIMCHAR, *mf->GetFolder());
         return Folder->Folder()->Text();
         }
@@ -930,8 +927,7 @@
 
 eOSState cMenuEditTimer::SetFolder(void)
 {
-  cMenuFolder *mf = (cMenuFolder *)SubMenu();
-  if (mf) {
+  if (cMenuFolder *mf = dynamic_cast<cMenuFolder *>(SubMenu())) {
      cString Folder = mf->GetFolder();
      char *p = strrchr(data.file, FOLDERDELIMCHAR);
      if (p)
@@ -2105,30 +2101,350 @@
   return CamMenuIsOpen;
 }
 
+// --- cMenuPathEdit ---------------------------------------------------------
+
+class cMenuPathEdit : public cOsdMenu {
+private:
+  cString path;
+  char folder[PATH_MAX];
+  char name[NAME_MAX];
+  cMenuEditStrItem *folderItem;
+  int pathIsInUse;
+  eOSState SetFolder(void);
+  eOSState Folder(void);
+  eOSState ApplyChanges(void);
+public:
+  cMenuPathEdit(const char *Path);
+  virtual eOSState ProcessKey(eKeys Key);
+  };
+
+cMenuPathEdit::cMenuPathEdit(const char *Path)
+:cOsdMenu(tr("Edit path"), 12)
+{
+  SetMenuCategory(mcRecording);
+  path = Path;
+  *folder = 0;
+  *name = 0;
+  const char *s = strrchr(path, FOLDERDELIMCHAR);
+  if (s) {
+     strn0cpy(folder, cString(path, s), sizeof(folder));
+     s++;
+     }
+  else
+     s = path;
+  strn0cpy(name, s, sizeof(name));
+  pathIsInUse = Recordings.PathIsInUse(path);
+  cOsdItem *p;
+  Add(p = folderItem = new cMenuEditStrItem(tr("Folder"), folder, sizeof(folder)));
+  p->SetSelectable(!pathIsInUse);
+  Add(p = new cMenuEditStrItem(tr("Name"), name, sizeof(name)));
+  p->SetSelectable(!pathIsInUse);
+  if (pathIsInUse) {
+     Add(new cOsdItem("", osUnknown, false));
+     Add(new cOsdItem(tr("This folder is currently in use - no changes are possible!"), osUnknown, false));
+     }
+  Display();
+  if (!pathIsInUse)
+     SetHelp(tr("Button$Folder"));
+}
+
+eOSState cMenuPathEdit::SetFolder(void)
+{
+  if (cMenuFolder *mf = dynamic_cast<cMenuFolder *>(SubMenu())) {
+     strn0cpy(folder, mf->GetFolder(), sizeof(folder));
+     SetCurrent(folderItem);
+     Display();
+     }
+  return CloseSubMenu();
+}
+
+eOSState cMenuPathEdit::Folder(void)
+{
+  return AddSubMenu(new cMenuFolder(tr("Select folder"), &Folders, path));
+}
+
+eOSState cMenuPathEdit::ApplyChanges(void)
+{
+  if (!*name)
+     *name = ' '; // name must not be empty!
+  cString NewPath = *folder ? cString::sprintf("%s%c%s", folder, FOLDERDELIMCHAR, name) : name;
+  NewPath.CompactChars(FOLDERDELIMCHAR);
+  if (strcmp(NewPath, path)) {
+     int NumRecordings = Recordings.GetNumRecordingsInPath(path);
+     if (NumRecordings > 1 && !Interface->Confirm(cString::sprintf(tr("Move entire folder containing %d recordings?"), NumRecordings)))
+        return osContinue;
+     if (!Recordings.MoveRecordings(path, NewPath)) {
+        Skins.Message(mtError, tr("Error while moving folder!"));
+        return osContinue;
+        }
+     cMenuRecordings::SetPath(NewPath); // makes sure the Recordings menu will reposition to the new path
+     return osUser1;
+     }
+  return osBack;
+}
+
+eOSState cMenuPathEdit::ProcessKey(eKeys Key)
+{
+  eOSState state = cOsdMenu::ProcessKey(Key);
+  if (state == osUnknown) {
+     if (!pathIsInUse) {
+        switch (Key) {
+          case kRed: return Folder();
+          case kOk:  return ApplyChanges();
+          default: break;
+          }
+        }
+     else if (Key == kOk)
+        return osBack;
+     }
+  else if (state == osEnd && HasSubMenu())
+     state = SetFolder();
+  return state;
+}
+
+// --- cMenuRecordingEdit ----------------------------------------------------
+
+class cMenuRecordingEdit : public cOsdMenu {
+private:
+  cRecording *recording;
+  cString originalFileName;
+  int recordingsState;
+  char folder[PATH_MAX];
+  char name[NAME_MAX];
+  int priority;
+  int lifetime;
+  cMenuEditStrItem *folderItem;
+  const char *buttonFolder;
+  const char *buttonAction;
+  const char *buttonDeleteMarks;
+  const char *actionCancel;
+  const char *doCut;
+  int recordingIsInUse;
+  void Set(void);
+  void SetHelpKeys(void);
+  bool RefreshRecording(void);
+  eOSState SetFolder(void);
+  eOSState Folder(void);
+  eOSState Action(void);
+  eOSState DeleteMarks(void);
+  eOSState ApplyChanges(void);
+public:
+  cMenuRecordingEdit(cRecording *Recording);
+  virtual eOSState ProcessKey(eKeys Key);
+  };
+
+cMenuRecordingEdit::cMenuRecordingEdit(cRecording *Recording)
+:cOsdMenu(tr("Edit recording"), 12)
+{
+  SetMenuCategory(mcRecording);
+  recording = Recording;
+  originalFileName = recording->FileName();
+  Recordings.StateChanged(recordingsState); // just to get the current state
+  strn0cpy(folder, recording->Folder(), sizeof(folder));
+  strn0cpy(name, recording->BaseName(), sizeof(name));
+  priority = recording->Priority();
+  lifetime = recording->Lifetime();
+  folderItem = NULL;
+  buttonFolder = NULL;
+  buttonAction = NULL;
+  buttonDeleteMarks = NULL;
+  actionCancel = NULL;
+  doCut = NULL;
+  recordingIsInUse = ruNone;
+  Set();
+}
+
+void cMenuRecordingEdit::Set(void)
+{
+  int current = Current();
+  Clear();
+  recordingIsInUse = recording->IsInUse();
+  cOsdItem *p;
+  Add(p = folderItem = new cMenuEditStrItem(tr("Folder"), folder, sizeof(folder)));
+  p->SetSelectable(!recordingIsInUse);
+  Add(p = new cMenuEditStrItem(tr("Name"), name, sizeof(name)));
+  p->SetSelectable(!recordingIsInUse);
+  Add(p = new cMenuEditIntItem(tr("Priority"), &priority, 0, MAXPRIORITY));
+  p->SetSelectable(!recordingIsInUse);
+  Add(p = new cMenuEditIntItem(tr("Lifetime"), &lifetime, 0, MAXLIFETIME));
+  p->SetSelectable(!recordingIsInUse);
+  if (recordingIsInUse) {
+     Add(new cOsdItem("", osUnknown, false));
+     Add(new cOsdItem(tr("This recording is currently in use - no changes are possible!"), osUnknown, false));
+     }
+  SetCurrent(Get(current));
+  Display();
+  SetHelpKeys();
+}
+
+void cMenuRecordingEdit::SetHelpKeys(void)
+{
+  buttonFolder = !recordingIsInUse ? tr("Button$Folder") : NULL;
+  buttonAction = NULL;
+  buttonDeleteMarks = NULL;
+  actionCancel = NULL;
+  doCut = NULL;
+  if ((recordingIsInUse & ruCut) != 0)
+     buttonAction = actionCancel = ((recordingIsInUse & ruPending) != 0) ? tr("Button$Cancel cutting") : tr("Button$Stop cutting");
+  else if ((recordingIsInUse & ruMove) != 0)
+     buttonAction = actionCancel = ((recordingIsInUse & ruPending) != 0) ? tr("Button$Cancel moving") : tr("Button$Stop moving");
+  else if ((recordingIsInUse & ruCopy) != 0)
+     buttonAction = actionCancel = ((recordingIsInUse & ruPending) != 0) ? tr("Button$Cancel copying") : tr("Button$Stop copying");
+  else if (recording->HasMarks()) {
+     buttonAction = doCut = tr("Button$Cut");
+     buttonDeleteMarks = tr("Button$Delete marks");
+     }
+  SetHelp(buttonFolder, buttonAction, buttonDeleteMarks);
+}
+
+bool cMenuRecordingEdit::RefreshRecording(void)
+{
+  if (Recordings.StateChanged(recordingsState)) {
+     if ((recording = Recordings.GetByName(originalFileName)) != NULL)
+        Set();
+     else {
+        Skins.Message(mtWarning, tr("Recording vanished!"));
+        return false;
+        }
+     }
+  return true;
+}
+
+eOSState cMenuRecordingEdit::SetFolder(void)
+{
+  if (cMenuFolder *mf = dynamic_cast<cMenuFolder *>(SubMenu())) {
+     strn0cpy(folder, mf->GetFolder(), sizeof(folder));
+     SetCurrent(folderItem);
+     Display();
+     }
+  return CloseSubMenu();
+}
+
+eOSState cMenuRecordingEdit::Folder(void)
+{
+  return AddSubMenu(new cMenuFolder(tr("Select folder"), &Folders, recording->Name()));
+}
+
+eOSState cMenuRecordingEdit::Action(void)
+{
+  if (actionCancel)
+     RecordingsHandler.Del(recording->FileName());
+  else if (doCut) {
+     if (!RecordingsHandler.Add(ruCut, recording->FileName()))
+        Skins.Message(mtError, tr("Error while queueing recording for cutting!"));
+     }
+  recordingIsInUse = recording->IsInUse();
+  RefreshRecording();
+  SetHelpKeys();
+  return osContinue;
+}
+
+eOSState cMenuRecordingEdit::DeleteMarks(void)
+{
+  if (buttonDeleteMarks && Interface->Confirm(tr("Delete editing marks for this recording?"))) {
+     if (recording->DeleteMarks())
+        SetHelpKeys();
+     else
+        Skins.Message(mtError, tr("Error while deleting editing marks!"));
+     }
+  return osContinue;
+}
+
+eOSState cMenuRecordingEdit::ApplyChanges(void)
+{
+  bool Modified = false;
+  if (priority != recording->Priority() || lifetime != recording->Lifetime()) {
+     if (!recording->ChangePriorityLifetime(priority, lifetime)) {
+        Skins.Message(mtError, tr("Error while changing priority/lifetime!"));
+        return osContinue;
+        }
+     Modified = true;
+     }
+  if (!*name)
+     *name = ' '; // name must not be empty!
+  cString NewName = *folder ? cString::sprintf("%s%c%s", folder, FOLDERDELIMCHAR, name) : name;
+  NewName.CompactChars(FOLDERDELIMCHAR);
+  if (strcmp(NewName, recording->Name())) {
+     if (!recording->ChangeName(NewName)) {
+        Skins.Message(mtError, tr("Error while changing folder/name!"));
+        return osContinue;
+        }
+     Modified = true;
+     }
+  if (Modified) {
+     cMenuRecordings::SetRecording(recording->FileName()); // makes sure the Recordings menu will reposition to the renamed recording
+     return osUser1;
+     }
+  return osBack;
+}
+
+eOSState cMenuRecordingEdit::ProcessKey(eKeys Key)
+{
+  if (!HasSubMenu()) {
+     if (!RefreshRecording())
+        return osBack; // the recording has vanished, so close this menu
+     }
+  eOSState state = cOsdMenu::ProcessKey(Key);
+  if (state == osUnknown) {
+     switch (Key) {
+       case kRed:    return buttonFolder ? Folder() : osContinue;
+       case kGreen:  return buttonAction ? Action() : osContinue;
+       case kYellow: return buttonDeleteMarks ? DeleteMarks() : osContinue;
+       case kOk:     return !recordingIsInUse ? ApplyChanges() : osBack;
+       default: break;
+       }
+     }
+  else if (state == osEnd && HasSubMenu())
+     state = SetFolder();
+  return state;
+}
+
 // --- cMenuRecording --------------------------------------------------------
 
 class cMenuRecording : public cOsdMenu {
 private:
-  const cRecording *recording;
+  cRecording *recording;
+  cString originalFileName;
+  int recordingsState;
   bool withButtons;
+  bool RefreshRecording(void);
 public:
-  cMenuRecording(const cRecording *Recording, bool WithButtons = false);
+  cMenuRecording(cRecording *Recording, bool WithButtons = false);
   virtual void Display(void);
   virtual eOSState ProcessKey(eKeys Key);
 };
 
-cMenuRecording::cMenuRecording(const cRecording *Recording, bool WithButtons)
+cMenuRecording::cMenuRecording(cRecording *Recording, bool WithButtons)
 :cOsdMenu(tr("Recording info"))
 {
   SetMenuCategory(mcRecordingInfo);
   recording = Recording;
+  originalFileName = recording->FileName();
+  Recordings.StateChanged(recordingsState); // just to get the current state
   withButtons = WithButtons;
   if (withButtons)
-     SetHelp(tr("Button$Play"), tr("Button$Rewind"));
+     SetHelp(tr("Button$Play"), tr("Button$Rewind"), NULL, tr("Button$Edit"));
+}
+
+bool cMenuRecording::RefreshRecording(void)
+{
+  if (Recordings.StateChanged(recordingsState)) {
+     if ((recording = Recordings.GetByName(originalFileName)) != NULL)
+        Display();
+     else {
+        Skins.Message(mtWarning, tr("Recording vanished!"));
+        return false;
+        }
+     }
+  return true;
 }
 
 void cMenuRecording::Display(void)
 {
+  if (HasSubMenu()) {
+     SubMenu()->Display();
+     return;
+     }
   cOsdMenu::Display();
   DisplayMenu()->SetRecording(recording);
   if (recording->Info()->Description())
@@ -2137,6 +2453,14 @@
 
 eOSState cMenuRecording::ProcessKey(eKeys Key)
 {
+  if (HasSubMenu()) {
+     eOSState state = cOsdMenu::ProcessKey(Key);
+     if (state == osUser1)
+        CloseSubMenu();
+     return state;
+     }
+  else if (!RefreshRecording())
+     return osBack; // the recording has vanished, so close this menu
   switch (int(Key)) {
     case kUp|k_Repeat:
     case kUp:
@@ -2164,6 +2488,9 @@
                      cRemote::Put(Key, true);
                      // continue with osBack to close the info menu and process the key
        case kOk:     return osBack;
+       case kBlue:   if (withButtons)
+                        return AddSubMenu(new cMenuRecordingEdit(recording));
+                     break;
        default: break;
        }
      }
@@ -2183,6 +2510,7 @@
   ~cMenuRecordingItem();
   void IncrementCounter(bool New);
   const char *Name(void) { return name; }
+  int Level(void) { return level; }
   cRecording *Recording(void) { return recording; }
   bool IsDirectory(void) { return name != NULL; }
   virtual void SetMenuItem(cSkinDisplayMenu *DisplayMenu, int Index, bool Current, bool Selectable);
@@ -2220,6 +2548,9 @@
 
 // --- cMenuRecordings -------------------------------------------------------
 
+cString cMenuRecordings::path;
+cString cMenuRecordings::fileName;
+
 cMenuRecordings::cMenuRecordings(const char *Base, int Level, bool OpenSubMenus)
 :cOsdMenu(Base ? Base : tr("Recordings"), 9, 6, 6)
 {
@@ -2232,8 +2563,12 @@
   Set();
   if (Current() < 0)
      SetCurrent(First());
-  else if (OpenSubMenus && cReplayControl::LastReplayed() && Open(true))
-     return;
+  else if (OpenSubMenus && (cReplayControl::LastReplayed() || *path || *fileName)) {
+     if (!*path || Level < strcountchr(path, FOLDERDELIMCHAR)) {
+        if (Open(true))
+           return;
+        }
+     }
   Display();
   SetHelpKeys();
 }
@@ -2251,18 +2586,14 @@
   if (ri) {
      if (ri->IsDirectory())
         NewHelpKeys = 1;
-     else {
+     else
         NewHelpKeys = 2;
-        if (ri->Recording()->Info()->Title())
-           NewHelpKeys = 3;
-        }
      }
   if (NewHelpKeys != helpKeys) {
      switch (NewHelpKeys) {
        case 0: SetHelp(NULL); break;
-       case 1: SetHelp(tr("Button$Open")); break;
-       case 2:
-       case 3: SetHelp(RecordingCommands.Count() ? tr("Commands") : tr("Button$Play"), tr("Button$Rewind"), tr("Button$Delete"), NewHelpKeys == 3 ? tr("Button$Info") : NULL);
+       case 1: SetHelp(tr("Button$Open"), NULL, NULL, tr("Button$Edit")); break;
+       case 2: SetHelp(RecordingCommands.Count() ? tr("Commands") : tr("Button$Play"), tr("Button$Rewind"), tr("Button$Delete"), tr("Button$Info"));
        default: ;
        }
      helpKeys = NewHelpKeys;
@@ -2271,7 +2602,7 @@
 
 void cMenuRecordings::Set(bool Refresh)
 {
-  const char *CurrentRecording = cReplayControl::LastReplayed();
+  const char *CurrentRecording = *fileName ? *fileName : cReplayControl::LastReplayed();
   cMenuRecordingItem *LastItem = NULL;
   cThreadLock RecordingsLock(&Recordings);
   if (Refresh) {
@@ -2303,7 +2634,11 @@
          else
             delete Item;
          if (LastItem || LastDir) {
-            if (CurrentRecording && strcmp(CurrentRecording, recording->FileName()) == 0)
+            if (*path) {
+               if (strcmp(path, recording->Folder()) == 0)
+                  SetCurrent(LastDir ? LastDir : LastItem);
+               }
+            else if (CurrentRecording && strcmp(CurrentRecording, recording->FileName()) == 0)
                SetCurrent(LastDir ? LastDir : LastItem);
             }
          if (LastDir)
@@ -2314,9 +2649,19 @@
      Display();
 }
 
+void cMenuRecordings::SetPath(const char *Path)
+{
+  path = Path;
+}
+
+void cMenuRecordings::SetRecording(const char *FileName)
+{
+  fileName = FileName;
+}
+
 cString cMenuRecordings::DirectoryName(void)
 {
-  cString d(VideoDirectory);
+  cString d(cVideoDirectory::Name());
   if (base) {
      char *s = ExchangeChars(strdup(base), true);
      d = AddDirectory(d, s);
@@ -2328,11 +2673,11 @@
 bool cMenuRecordings::Open(bool OpenSubMenus)
 {
   cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current());
-  if (ri && ri->IsDirectory()) {
+  if (ri && ri->IsDirectory() && (!*path || strcountchr(path, FOLDERDELIMCHAR) > 0)) {
      const char *t = ri->Name();
      cString buffer;
      if (base) {
-        buffer = cString::sprintf("%s~%s", base, t);
+        buffer = cString::sprintf("%s%c%s", base, FOLDERDELIMCHAR, t);
         t = buffer;
         }
      AddSubMenu(new cMenuRecordings(t, level + 1, OpenSubMenus));
@@ -2395,10 +2740,10 @@
            }
         cRecording *recording = ri->Recording();
         cString FileName = recording->FileName();
-        if (cCutter::Active(ri->Recording()->FileName())) {
+        if (RecordingsHandler.GetUsage(FileName)) {
            if (Interface->Confirm(tr("Recording is being edited - really delete?"))) {
-              cCutter::Stop();
-              recording = Recordings.GetByName(FileName); // cCutter::Stop() might have deleted it if it was the edited version
+              RecordingsHandler.Del(FileName);
+              recording = Recordings.GetByName(FileName); // RecordingsHandler.Del() might have deleted it if it was the edited version
               // we continue with the code below even if recording is NULL,
               // in order to have the menu updated etc.
               }
@@ -2428,9 +2773,12 @@
 {
   if (HasSubMenu() || Count() == 0)
      return osContinue;
-  cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current());
-  if (ri && !ri->IsDirectory() && ri->Recording()->Info()->Title())
-     return AddSubMenu(new cMenuRecording(ri->Recording(), true));
+  if (cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current())) {
+     if (ri->IsDirectory())
+        return AddSubMenu(new cMenuPathEdit(cString(ri->Recording()->Name(), strchrn(ri->Recording()->Name(), FOLDERDELIMCHAR, ri->Level() + 1))));
+     else
+        return AddSubMenu(new cMenuRecording(ri->Recording(), true));
+     }
   return osContinue;
 }
 
@@ -2481,6 +2829,17 @@
        default: break;
        }
      }
+  else if (state == osUser1) {
+     // a recording or path was renamed, so let's refresh the menu
+     CloseSubMenu(false);
+     if (base)
+        return state; // closes all recording menus except for the top one
+     Set(); // this is the top level menu, so we refresh it...
+     Open(true); // ...and open any necessary submenus to show the new name
+     Display();
+     path = NULL;
+     fileName = NULL;
+     }
   if (Key == kYellow && HadSubMenu && !HasSubMenu()) {
      // the last recording in a subdirectory was deleted, so let's go back up
      cOsdMenu::Del(Current());
@@ -3373,7 +3732,7 @@
 
 cOsdObject *cMenuMain::pluginOsdObject = NULL;
 
-cMenuMain::cMenuMain(eOSState State)
+cMenuMain::cMenuMain(eOSState State, bool OpenSubMenus)
 :cOsdMenu("")
 {
   SetMenuCategory(mcMain);
@@ -3390,7 +3749,7 @@
     case osSchedule:   AddSubMenu(new cMenuSchedule); break;
     case osChannels:   AddSubMenu(new cMenuChannels); break;
     case osTimers:     AddSubMenu(new cMenuTimers); break;
-    case osRecordings: AddSubMenu(new cMenuRecordings(NULL, 0, true)); break;
+    case osRecordings: AddSubMenu(new cMenuRecordings(NULL, 0, OpenSubMenus)); break;
     case osSetup:      AddSubMenu(new cMenuSetup); break;
     case osCommands:   AddSubMenu(new cMenuCommands(tr("Commands"), &Commands)); break;
     default: break;
@@ -3457,18 +3816,18 @@
         stopReplayItem = NULL;
         }
      // Color buttons:
-     SetHelp(!replaying ? tr("Button$Record") : NULL, tr("Button$Audio"), replaying ? NULL : tr("Button$Pause"), replaying ? tr("Button$Stop") : cReplayControl::LastReplayed() ? tr("Button$Resume") : tr("Button$Play"));
+     SetHelp(!replaying ? tr("Button$Record") : NULL, tr("Button$Audio"), replaying || !Setup.PauseKeyHandling ? NULL : tr("Button$Pause"), replaying ? tr("Button$Stop") : cReplayControl::LastReplayed() ? tr("Button$Resume") : tr("Button$Play"));
      result = true;
      }
 
   // Editing control:
-  bool CutterActive = cCutter::Active();
-  if (CutterActive && !cancelEditingItem) {
+  bool EditingActive = RecordingsHandler.Active();
+  if (EditingActive && !cancelEditingItem) {
      // TRANSLATORS: note the leading blank!
      Add(cancelEditingItem = new cOsdItem(tr(" Cancel editing"), osCancelEdit));
      result = true;
      }
-  else if (cancelEditingItem && !CutterActive) {
+  else if (cancelEditingItem && !EditingActive) {
      Del(cancelEditingItem->Index());
      cancelEditingItem = NULL;
      result = true;
@@ -3518,7 +3877,7 @@
                           }
                        break;
     case osCancelEdit: if (Interface->Confirm(tr("Cancel editing?"))) {
-                          cCutter::Stop();
+                          RecordingsHandler.DelAll();
                           return osEnd;
                           }
                        break;
@@ -3552,7 +3911,7 @@
                                 }
                              break;
                case kYellow: if (!HadSubMenu)
-                                state = replaying ? osContinue : osPause;
+                                state = replaying || !Setup.PauseKeyHandling ? osContinue : osPause;
                              break;
                case kBlue:   if (!HadSubMenu)
                                 state = replaying ? osStopReplay : cReplayControl::LastReplayed() ? osReplay : osRecordings;
@@ -4342,7 +4701,7 @@
      AssertFreeDiskSpace(Timer->Priority(), !Timer->Pending());
      Timer->SetPending(true);
      }
-  VideoDiskSpace(&FreeMB);
+  cVideoDirectory::VideoDiskSpace(&FreeMB);
   if (FreeMB < MINFREEDISK) {
      if (!Timer || time(NULL) - LastNoDiskSpaceMessage > NODISKSPACEDELTA) {
         isyslog("not enough disk space to start recording%s%s", Timer ? " timer " : "", Timer ? *Timer->ToDescr() : "");
@@ -4853,12 +5212,12 @@
 {
   if (*fileName) {
      Hide();
-     if (!cCutter::Active()) {
+     if (!RecordingsHandler.GetUsage(fileName)) {
         if (!marks.Count())
            Skins.Message(mtError, tr("No editing marks defined!"));
         else if (!marks.GetNumSequences())
            Skins.Message(mtError, tr("No editing sequences defined!"));
-        else if (!cCutter::Start(fileName))
+        else if (!RecordingsHandler.Add(ruCut, fileName))
            Skins.Message(mtError, tr("Can't start editing process!"));
         else
            Skins.Message(mtInfo, tr("Editing process started"));
@@ -4990,10 +5349,8 @@
                            else
                               Show();
                            break;
-            case kBack:    if (Setup.DelTimeshiftRec) {
-                              cRecordControl* rc = cRecordControls::GetRecordControl(fileName);
-                              return rc && rc->InstantId() ? osEnd : osRecordings;
-                              }
+            case kBack:    Hide();
+                           Stop();
                            return osRecordings;
             default:       return osUnknown;
             }
diff -ruN vdr-2.1.1/menu.h vdr-2.1.2/menu.h
--- vdr-2.1.1/menu.h	2013-06-01 15:44:57.000000000 +0200
+++ vdr-2.1.2/menu.h	2013-10-16 11:14:58.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 3.1 2013/06/01 13:44:57 kls Exp $
+ * $Id: menu.h 3.3 2013/10/16 09:14:58 kls Exp $
  */
 
 #ifndef __MENU_H
@@ -107,7 +107,7 @@
   void Set(void);
   bool Update(bool Force = false);
 public:
-  cMenuMain(eOSState State = osUnknown);
+  cMenuMain(eOSState State = osUnknown, bool OpenSubMenus = false);
   virtual eOSState ProcessKey(eKeys Key);
   static cOsdObject *PluginOsdObject(void);
   };
@@ -198,6 +198,8 @@
   int level;
   int recordingsState;
   int helpKeys;
+  static cString path;
+  static cString fileName;
   void SetHelpKeys(void);
   void Set(bool Refresh = false);
   bool Open(bool OpenSubMenus = false);
@@ -213,6 +215,8 @@
   cMenuRecordings(const char *Base = NULL, int Level = 0, bool OpenSubMenus = false);
   ~cMenuRecordings();
   virtual eOSState ProcessKey(eKeys Key);
+  static void SetPath(const char *Path);
+  static void SetRecording(const char *FileName);
   };
 
 class cRecordControl {
diff -ruN vdr-2.1.1/osd.c vdr-2.1.2/osd.c
--- vdr-2.1.1/osd.c	2013-05-18 14:33:16.000000000 +0200
+++ vdr-2.1.2/osd.c	2013-09-03 13:59:17.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: osd.c 3.1 2013/05/18 12:33:16 kls Exp $
+ * $Id: osd.c 3.2 2013/09/03 11:59:17 kls Exp $
  */
 
 #include "osd.h"
@@ -512,6 +512,17 @@
      }
 }
 
+void cBitmap::Fill(tIndex Index)
+{
+  if (bitmap) {
+     memset(bitmap, Index, width * height);
+     dirtyX1 = 0;
+     dirtyY1 = 0;
+     dirtyX2 = width - 1;
+     dirtyY2 = height - 1;
+     }
+}
+
 void cBitmap::DrawPixel(int x, int y, tColor Color)
 {
   x -= x0;
@@ -824,7 +835,7 @@
      }
 }
 
-cBitmap *cBitmap::Scaled(double FactorX, double FactorY, bool AntiAlias)
+cBitmap *cBitmap::Scaled(double FactorX, double FactorY, bool AntiAlias) const
 {
   // Fixed point scaling code based on www.inversereality.org/files/bitmapscaling.pdf
   // by deltener@mindtremors.com
diff -ruN vdr-2.1.1/osd.h vdr-2.1.2/osd.h
--- vdr-2.1.1/osd.h	2013-02-12 14:39:08.000000000 +0100
+++ vdr-2.1.2/osd.h	2013-09-06 14:13:47.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: osd.h 3.0 2013/02/12 13:39:08 kls Exp $
+ * $Id: osd.h 3.2 2013/09/06 12:13:47 kls Exp $
  */
 
 #ifndef __OSD_H
@@ -192,6 +192,8 @@
        ///< contents of the bitmap will be lost. If Width and Height are the same
        ///< as the current values, nothing will happen and the bitmap remains
        ///< unchanged.
+  void SetOffset(int X0, int Y0) { x0 = X0; y0 = Y0; }
+       ///< Sets the offset of this bitmap to the given values.
   bool Contains(int x, int y) const;
        ///< Returns true if this bitmap contains the point (x, y).
   bool Covers(int x1, int y1, int x2, int y2) const;
@@ -221,6 +223,8 @@
   void SetIndex(int x, int y, tIndex Index);
        ///< Sets the index at the given coordinates to Index.
        ///< Coordinates are relative to the bitmap's origin.
+  void Fill(tIndex Index);
+       ///< Fills the bitmap data with the given Index.
   void DrawPixel(int x, int y, tColor Color);
        ///< Sets the pixel at the given coordinates to the given Color, which is
        ///< a full 32 bit ARGB value.
@@ -283,7 +287,7 @@
        ///< the 2^NewBpp most frequently used colors as defined in the current palette.
        ///< If NewBpp is not smaller than the bitmap's current color depth,
        ///< or if it is not one of 4bpp or 2bpp, nothing happens.
-  cBitmap *Scaled(double FactorX, double FactorY, bool AntiAlias = false);
+  cBitmap *Scaled(double FactorX, double FactorY, bool AntiAlias = false) const;
        ///< Creates a copy of this bitmap, scaled by the given factors.
        ///< If AntiAlias is true and either of the factors is greater than 1.0,
        ///< anti-aliasing is applied. This will also set the color depth of the
@@ -657,7 +661,7 @@
        ///< covers the entire view port. This may be of advantage if, e.g.,
        ///< there is a draw port that holds, say, 11 lines of text, while the
        ///< view port displays only 10 lines. By Pan()'ing the draw port up one
-       ///< line, an new bottom line can be written into the draw port (without
+       ///< line, a new bottom line can be written into the draw port (without
        ///< being seen through the view port), and later the draw port can be
        ///< shifted smoothly, resulting in a smooth scrolling.
        ///< It is the caller's responsibility to make sure that Source and Dest
diff -ruN vdr-2.1.1/osdbase.c vdr-2.1.2/osdbase.c
--- vdr-2.1.1/osdbase.c	2013-05-24 12:19:31.000000000 +0200
+++ vdr-2.1.2/osdbase.c	2013-09-22 16:01:17.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: osdbase.c 3.1 2013/05/24 10:19:31 kls Exp $
+ * $Id: osdbase.c 3.2 2013/09/22 14:01:17 kls Exp $
  */
 
 #include "osdbase.h"
@@ -502,12 +502,14 @@
   return osContinue; // convenience return value
 }
 
-eOSState cOsdMenu::CloseSubMenu()
+eOSState cOsdMenu::CloseSubMenu(bool ReDisplay)
 {
   delete subMenu;
   subMenu = NULL;
-  RefreshCurrent();
-  Display();
+  if (ReDisplay) {
+     RefreshCurrent();
+     Display();
+     }
   return osContinue; // convenience return value
 }
 
diff -ruN vdr-2.1.1/osdbase.h vdr-2.1.2/osdbase.h
--- vdr-2.1.1/osdbase.h	2012-12-07 10:49:35.000000000 +0100
+++ vdr-2.1.2/osdbase.h	2013-09-22 16:00:47.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: osdbase.h 3.0 2012/12/07 09:49:35 kls Exp $
+ * $Id: osdbase.h 3.1 2013/09/22 14:00:47 kls Exp $
  */
 
 #ifndef __OSDBASE_H
@@ -119,7 +119,7 @@
   void Mark(void);
   eOSState HotKey(eKeys Key);
   eOSState AddSubMenu(cOsdMenu *SubMenu);
-  eOSState CloseSubMenu();
+  eOSState CloseSubMenu(bool ReDisplay = true);
   bool HasSubMenu(void) { return subMenu; }
   cOsdMenu *SubMenu(void) { return subMenu; }
   void SetStatus(const char *s);
diff -ruN vdr-2.1.1/po/ar.po vdr-2.1.2/po/ar.po
--- vdr-2.1.1/po/ar.po	2013-08-21 11:35:15.000000000 +0200
+++ vdr-2.1.2/po/ar.po	2013-10-14 11:56:31.000000000 +0200
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2013-06-10 12:16+0200\n"
+"POT-Creation-Date: 2013-10-14 11:56+0200\n"
 "PO-Revision-Date: 2008-10-16 11:16-0400\n"
 "Last-Translator: Osama Alrawab <alrawab@hotmail.com>\n"
 "Language-Team: Arabic <ar@li.org>\n"
@@ -727,6 +727,70 @@
 msgid "CAM not responding!"
 msgstr "الكامة لا تستجيب"
 
+msgid "Edit path"
+msgstr ""
+
+msgid "Folder"
+msgstr ""
+
+msgid "This folder is currently in use - no changes are possible!"
+msgstr ""
+
+#, c-format
+msgid "Move entire folder containing %d recordings?"
+msgstr ""
+
+msgid "Error while moving folder!"
+msgstr ""
+
+msgid "Edit recording"
+msgstr ""
+
+msgid "This recording is currently in use - no changes are possible!"
+msgstr ""
+
+msgid "Button$Cancel cutting"
+msgstr ""
+
+msgid "Button$Stop cutting"
+msgstr ""
+
+msgid "Button$Cancel moving"
+msgstr ""
+
+msgid "Button$Stop moving"
+msgstr ""
+
+msgid "Button$Cancel copying"
+msgstr ""
+
+msgid "Button$Stop copying"
+msgstr ""
+
+msgid "Button$Cut"
+msgstr ""
+
+msgid "Button$Delete marks"
+msgstr ""
+
+msgid "Recording vanished!"
+msgstr ""
+
+msgid "Error while queueing recording for cutting!"
+msgstr ""
+
+msgid "Delete editing marks for this recording?"
+msgstr ""
+
+msgid "Error while deleting editing marks!"
+msgstr ""
+
+msgid "Error while changing priority/lifetime!"
+msgstr ""
+
+msgid "Error while changing folder/name!"
+msgstr ""
+
 msgid "Recording info"
 msgstr "معلومات التسجبل"
 
diff -ruN vdr-2.1.1/po/ca_ES.po vdr-2.1.2/po/ca_ES.po
--- vdr-2.1.1/po/ca_ES.po	2013-08-21 11:35:15.000000000 +0200
+++ vdr-2.1.2/po/ca_ES.po	2013-10-14 11:56:30.000000000 +0200
@@ -10,7 +10,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2013-06-10 12:16+0200\n"
+"POT-Creation-Date: 2013-10-14 11:56+0200\n"
 "PO-Revision-Date: 2008-03-02 19:02+0100\n"
 "Last-Translator: Luca Olivetti <luca@ventoso.org>\n"
 "Language-Team: Catalan <vdr@linuxtv.org>\n"
@@ -726,6 +726,70 @@
 msgid "CAM not responding!"
 msgstr "CAM no respon"
 
+msgid "Edit path"
+msgstr ""
+
+msgid "Folder"
+msgstr ""
+
+msgid "This folder is currently in use - no changes are possible!"
+msgstr ""
+
+#, c-format
+msgid "Move entire folder containing %d recordings?"
+msgstr ""
+
+msgid "Error while moving folder!"
+msgstr ""
+
+msgid "Edit recording"
+msgstr ""
+
+msgid "This recording is currently in use - no changes are possible!"
+msgstr ""
+
+msgid "Button$Cancel cutting"
+msgstr ""
+
+msgid "Button$Stop cutting"
+msgstr ""
+
+msgid "Button$Cancel moving"
+msgstr ""
+
+msgid "Button$Stop moving"
+msgstr ""
+
+msgid "Button$Cancel copying"
+msgstr ""
+
+msgid "Button$Stop copying"
+msgstr ""
+
+msgid "Button$Cut"
+msgstr ""
+
+msgid "Button$Delete marks"
+msgstr ""
+
+msgid "Recording vanished!"
+msgstr ""
+
+msgid "Error while queueing recording for cutting!"
+msgstr ""
+
+msgid "Delete editing marks for this recording?"
+msgstr ""
+
+msgid "Error while deleting editing marks!"
+msgstr ""
+
+msgid "Error while changing priority/lifetime!"
+msgstr ""
+
+msgid "Error while changing folder/name!"
+msgstr ""
+
 msgid "Recording info"
 msgstr "Informaci� de la gravaci�"
 
diff -ruN vdr-2.1.1/po/cs_CZ.po vdr-2.1.2/po/cs_CZ.po
--- vdr-2.1.1/po/cs_CZ.po	2013-08-21 11:35:15.000000000 +0200
+++ vdr-2.1.2/po/cs_CZ.po	2013-10-14 11:56:31.000000000 +0200
@@ -10,7 +10,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2013-06-10 12:16+0200\n"
+"POT-Creation-Date: 2013-10-14 11:56+0200\n"
 "PO-Revision-Date: 2010-05-06 11:00+0200\n"
 "Last-Translator: Aleš Juřík <ajurik@quick.cz>\n"
 "Language-Team: Czech <vdr@linuxtv.org>\n"
@@ -726,6 +726,70 @@
 msgid "CAM not responding!"
 msgstr "CAM neodpovídá!"
 
+msgid "Edit path"
+msgstr ""
+
+msgid "Folder"
+msgstr ""
+
+msgid "This folder is currently in use - no changes are possible!"
+msgstr ""
+
+#, c-format
+msgid "Move entire folder containing %d recordings?"
+msgstr ""
+
+msgid "Error while moving folder!"
+msgstr ""
+
+msgid "Edit recording"
+msgstr ""
+
+msgid "This recording is currently in use - no changes are possible!"
+msgstr ""
+
+msgid "Button$Cancel cutting"
+msgstr ""
+
+msgid "Button$Stop cutting"
+msgstr ""
+
+msgid "Button$Cancel moving"
+msgstr ""
+
+msgid "Button$Stop moving"
+msgstr ""
+
+msgid "Button$Cancel copying"
+msgstr ""
+
+msgid "Button$Stop copying"
+msgstr ""
+
+msgid "Button$Cut"
+msgstr ""
+
+msgid "Button$Delete marks"
+msgstr ""
+
+msgid "Recording vanished!"
+msgstr ""
+
+msgid "Error while queueing recording for cutting!"
+msgstr ""
+
+msgid "Delete editing marks for this recording?"
+msgstr ""
+
+msgid "Error while deleting editing marks!"
+msgstr ""
+
+msgid "Error while changing priority/lifetime!"
+msgstr ""
+
+msgid "Error while changing folder/name!"
+msgstr ""
+
 msgid "Recording info"
 msgstr "Detail nahrávky"
 
diff -ruN vdr-2.1.1/po/da_DK.po vdr-2.1.2/po/da_DK.po
--- vdr-2.1.1/po/da_DK.po	2013-08-21 11:35:15.000000000 +0200
+++ vdr-2.1.2/po/da_DK.po	2013-10-14 11:56:30.000000000 +0200
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2013-06-10 12:16+0200\n"
+"POT-Creation-Date: 2013-10-14 11:56+0200\n"
 "PO-Revision-Date: 2007-08-12 14:17+0200\n"
 "Last-Translator: Mogens Elneff <mogens@elneff.dk>\n"
 "Language-Team: Danish <vdr@linuxtv.org>\n"
@@ -723,6 +723,70 @@
 msgid "CAM not responding!"
 msgstr "CAM svarer ikke!"
 
+msgid "Edit path"
+msgstr ""
+
+msgid "Folder"
+msgstr ""
+
+msgid "This folder is currently in use - no changes are possible!"
+msgstr ""
+
+#, c-format
+msgid "Move entire folder containing %d recordings?"
+msgstr ""
+
+msgid "Error while moving folder!"
+msgstr ""
+
+msgid "Edit recording"
+msgstr ""
+
+msgid "This recording is currently in use - no changes are possible!"
+msgstr ""
+
+msgid "Button$Cancel cutting"
+msgstr ""
+
+msgid "Button$Stop cutting"
+msgstr ""
+
+msgid "Button$Cancel moving"
+msgstr ""
+
+msgid "Button$Stop moving"
+msgstr ""
+
+msgid "Button$Cancel copying"
+msgstr ""
+
+msgid "Button$Stop copying"
+msgstr ""
+
+msgid "Button$Cut"
+msgstr ""
+
+msgid "Button$Delete marks"
+msgstr ""
+
+msgid "Recording vanished!"
+msgstr ""
+
+msgid "Error while queueing recording for cutting!"
+msgstr ""
+
+msgid "Delete editing marks for this recording?"
+msgstr ""
+
+msgid "Error while deleting editing marks!"
+msgstr ""
+
+msgid "Error while changing priority/lifetime!"
+msgstr ""
+
+msgid "Error while changing folder/name!"
+msgstr ""
+
 msgid "Recording info"
 msgstr "Optagelses info"
 
diff -ruN vdr-2.1.1/po/de_DE.po vdr-2.1.2/po/de_DE.po
--- vdr-2.1.1/po/de_DE.po	2013-08-21 11:35:15.000000000 +0200
+++ vdr-2.1.2/po/de_DE.po	2013-10-14 11:59:04.000000000 +0200
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2013-06-10 12:16+0200\n"
+"POT-Creation-Date: 2013-10-14 11:56+0200\n"
 "PO-Revision-Date: 2010-01-16 16:46+0100\n"
 "Last-Translator: Klaus Schmidinger <vdr@tvdr.de>\n"
 "Language-Team: German <vdr@linuxtv.org>\n"
@@ -723,6 +723,70 @@
 msgid "CAM not responding!"
 msgstr "CAM antwortet nicht!"
 
+msgid "Edit path"
+msgstr "Pfad editieren"
+
+msgid "Folder"
+msgstr "Ordner"
+
+msgid "This folder is currently in use - no changes are possible!"
+msgstr "Dieser Ordner ist zur Zeit in Verwendung - es sind keine �nderungen m�glich!"
+
+#, c-format
+msgid "Move entire folder containing %d recordings?"
+msgstr "Ganzen Ordner mit %d Aufnahmen verschieben?"
+
+msgid "Error while moving folder!"
+msgstr "Fehler beim Verschieben des Ordners!"
+
+msgid "Edit recording"
+msgstr "Aufnahme editieren"
+
+msgid "This recording is currently in use - no changes are possible!"
+msgstr "Diese Aufnahme ist zur Zeit in Verwendung - es sind keine �nderungen m�glich!"
+
+msgid "Button$Cancel cutting"
+msgstr "Schnitt abbrechen"
+
+msgid "Button$Stop cutting"
+msgstr "Schnitt beenden"
+
+msgid "Button$Cancel moving"
+msgstr "Verschieben abbrechen"
+
+msgid "Button$Stop moving"
+msgstr "Verschieben beenden"
+
+msgid "Button$Cancel copying"
+msgstr "Kopieren abbrechen"
+
+msgid "Button$Stop copying"
+msgstr "Kopieren beenden"
+
+msgid "Button$Cut"
+msgstr "Schneiden"
+
+msgid "Button$Delete marks"
+msgstr "Marken l�schen"
+
+msgid "Recording vanished!"
+msgstr "Aufnahme verschwunden!"
+
+msgid "Error while queueing recording for cutting!"
+msgstr "Fehler beim Hinzuf�gen der Aufnahme zur Schnittwarteschlange"
+
+msgid "Delete editing marks for this recording?"
+msgstr "Schnittmarken f�r diese Aufnahme l�schen?"
+
+msgid "Error while deleting editing marks!"
+msgstr "Fehler beim L�schen der Schnittmarken!"
+
+msgid "Error while changing priority/lifetime!"
+msgstr "Fehler beim �ndern der Priorit�t bzw. Lebensdauer!"
+
+msgid "Error while changing folder/name!"
+msgstr "Fehler beim �ndern des Ordners bzw. Namens!"
+
 msgid "Recording info"
 msgstr "Aufzeichnung"
 
diff -ruN vdr-2.1.1/po/el_GR.po vdr-2.1.2/po/el_GR.po
--- vdr-2.1.1/po/el_GR.po	2013-08-21 11:35:15.000000000 +0200
+++ vdr-2.1.2/po/el_GR.po	2013-10-14 11:56:31.000000000 +0200
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2013-06-10 12:16+0200\n"
+"POT-Creation-Date: 2013-10-14 11:56+0200\n"
 "PO-Revision-Date: 2007-08-12 14:17+0200\n"
 "Last-Translator: Dimitrios Dimitrakos <mail@dimitrios.de>\n"
 "Language-Team: Greek <vdr@linuxtv.org>\n"
@@ -723,6 +723,70 @@
 msgid "CAM not responding!"
 msgstr ""
 
+msgid "Edit path"
+msgstr ""
+
+msgid "Folder"
+msgstr ""
+
+msgid "This folder is currently in use - no changes are possible!"
+msgstr ""
+
+#, c-format
+msgid "Move entire folder containing %d recordings?"
+msgstr ""
+
+msgid "Error while moving folder!"
+msgstr ""
+
+msgid "Edit recording"
+msgstr ""
+
+msgid "This recording is currently in use - no changes are possible!"
+msgstr ""
+
+msgid "Button$Cancel cutting"
+msgstr ""
+
+msgid "Button$Stop cutting"
+msgstr ""
+
+msgid "Button$Cancel moving"
+msgstr ""
+
+msgid "Button$Stop moving"
+msgstr ""
+
+msgid "Button$Cancel copying"
+msgstr ""
+
+msgid "Button$Stop copying"
+msgstr ""
+
+msgid "Button$Cut"
+msgstr ""
+
+msgid "Button$Delete marks"
+msgstr ""
+
+msgid "Recording vanished!"
+msgstr ""
+
+msgid "Error while queueing recording for cutting!"
+msgstr ""
+
+msgid "Delete editing marks for this recording?"
+msgstr ""
+
+msgid "Error while deleting editing marks!"
+msgstr ""
+
+msgid "Error while changing priority/lifetime!"
+msgstr ""
+
+msgid "Error while changing folder/name!"
+msgstr ""
+
 msgid "Recording info"
 msgstr "����������� E�������"
 
diff -ruN vdr-2.1.1/po/es_ES.po vdr-2.1.2/po/es_ES.po
--- vdr-2.1.1/po/es_ES.po	2013-08-21 11:35:15.000000000 +0200
+++ vdr-2.1.2/po/es_ES.po	2013-10-14 11:56:31.000000000 +0200
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2013-06-10 12:16+0200\n"
+"POT-Creation-Date: 2013-10-14 11:56+0200\n"
 "PO-Revision-Date: 2008-03-02 19:02+0100\n"
 "Last-Translator: Luca Olivetti <luca@ventoso.org>\n"
 "Language-Team: Spanish <vdr@linuxtv.org>\n"
@@ -724,6 +724,70 @@
 msgid "CAM not responding!"
 msgstr "�CAM no responde!"
 
+msgid "Edit path"
+msgstr ""
+
+msgid "Folder"
+msgstr ""
+
+msgid "This folder is currently in use - no changes are possible!"
+msgstr ""
+
+#, c-format
+msgid "Move entire folder containing %d recordings?"
+msgstr ""
+
+msgid "Error while moving folder!"
+msgstr ""
+
+msgid "Edit recording"
+msgstr ""
+
+msgid "This recording is currently in use - no changes are possible!"
+msgstr ""
+
+msgid "Button$Cancel cutting"
+msgstr ""
+
+msgid "Button$Stop cutting"
+msgstr ""
+
+msgid "Button$Cancel moving"
+msgstr ""
+
+msgid "Button$Stop moving"
+msgstr ""
+
+msgid "Button$Cancel copying"
+msgstr ""
+
+msgid "Button$Stop copying"
+msgstr ""
+
+msgid "Button$Cut"
+msgstr ""
+
+msgid "Button$Delete marks"
+msgstr ""
+
+msgid "Recording vanished!"
+msgstr ""
+
+msgid "Error while queueing recording for cutting!"
+msgstr ""
+
+msgid "Delete editing marks for this recording?"
+msgstr ""
+
+msgid "Error while deleting editing marks!"
+msgstr ""
+
+msgid "Error while changing priority/lifetime!"
+msgstr ""
+
+msgid "Error while changing folder/name!"
+msgstr ""
+
 msgid "Recording info"
 msgstr "Informaci�n de grabaci�n"
 
diff -ruN vdr-2.1.1/po/et_EE.po vdr-2.1.2/po/et_EE.po
--- vdr-2.1.1/po/et_EE.po	2013-08-21 11:35:15.000000000 +0200
+++ vdr-2.1.2/po/et_EE.po	2013-10-14 11:56:30.000000000 +0200
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2013-06-10 12:16+0200\n"
+"POT-Creation-Date: 2013-10-14 11:56+0200\n"
 "PO-Revision-Date: 2007-08-12 14:17+0200\n"
 "Last-Translator: Arthur Konovalov <artlov@gmail.com>\n"
 "Language-Team: Estonian <vdr@linuxtv.org>\n"
@@ -723,6 +723,70 @@
 msgid "CAM not responding!"
 msgstr "CAM ei vasta"
 
+msgid "Edit path"
+msgstr ""
+
+msgid "Folder"
+msgstr ""
+
+msgid "This folder is currently in use - no changes are possible!"
+msgstr ""
+
+#, c-format
+msgid "Move entire folder containing %d recordings?"
+msgstr ""
+
+msgid "Error while moving folder!"
+msgstr ""
+
+msgid "Edit recording"
+msgstr ""
+
+msgid "This recording is currently in use - no changes are possible!"
+msgstr ""
+
+msgid "Button$Cancel cutting"
+msgstr ""
+
+msgid "Button$Stop cutting"
+msgstr ""
+
+msgid "Button$Cancel moving"
+msgstr ""
+
+msgid "Button$Stop moving"
+msgstr ""
+
+msgid "Button$Cancel copying"
+msgstr ""
+
+msgid "Button$Stop copying"
+msgstr ""
+
+msgid "Button$Cut"
+msgstr ""
+
+msgid "Button$Delete marks"
+msgstr ""
+
+msgid "Recording vanished!"
+msgstr ""
+
+msgid "Error while queueing recording for cutting!"
+msgstr ""
+
+msgid "Delete editing marks for this recording?"
+msgstr ""
+
+msgid "Error while deleting editing marks!"
+msgstr ""
+
+msgid "Error while changing priority/lifetime!"
+msgstr ""
+
+msgid "Error while changing folder/name!"
+msgstr ""
+
 msgid "Recording info"
 msgstr "Salvestuse info"
 
@@ -994,31 +1058,31 @@
 msgstr "oma"
 
 msgid "Setup.LNB$Use dish positioner"
-msgstr ""
+msgstr "Antenni positsioneerija kasutamine"
 
 msgid "Setup.LNB$Site latitude (degrees)"
-msgstr ""
+msgstr "Asukoha laiuskraad (°)"
 
 msgid "South"
-msgstr ""
+msgstr "lõunasse (S)"
 
 msgid "North"
-msgstr ""
+msgstr "põhja (N)"
 
 msgid "Setup.LNB$Site longitude (degrees)"
-msgstr ""
+msgstr "Asukoha pikkuskraad (°)"
 
 msgid "West"
-msgstr ""
+msgstr "läände (W)"
 
 msgid "East"
-msgstr ""
+msgstr "itta (E)"
 
 msgid "Setup.LNB$Max. positioner swing (degrees)"
-msgstr ""
+msgstr "Positsioneerija max pöördeulatus (°)"
 
 msgid "Setup.LNB$Positioner speed (degrees/s)"
-msgstr ""
+msgstr "Positsioneerija kiirus (°/s)"
 
 msgid "CAM reset"
 msgstr "CAM taaskäivitamine"
@@ -1359,7 +1423,7 @@
 
 #, c-format
 msgid "Moving dish to %.1f..."
-msgstr ""
+msgstr "Antenni pööramine positsioonile %.1f..."
 
 msgid "ST:TNG Panels"
 msgstr "ST:TNG Panels"
diff -ruN vdr-2.1.1/po/fi_FI.po vdr-2.1.2/po/fi_FI.po
--- vdr-2.1.1/po/fi_FI.po	2013-08-21 11:35:15.000000000 +0200
+++ vdr-2.1.2/po/fi_FI.po	2013-10-14 11:56:30.000000000 +0200
@@ -11,7 +11,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2013-06-10 12:16+0200\n"
+"POT-Creation-Date: 2013-10-14 11:56+0200\n"
 "PO-Revision-Date: 2007-08-15 15:52+0200\n"
 "Last-Translator: Matti Lehtimäki <matti.lehtimaki@gmail.com>\n"
 "Language-Team: Finnish <vdr@linuxtv.org>\n"
@@ -727,6 +727,70 @@
 msgid "CAM not responding!"
 msgstr "CA-moduuli ei vastaa!"
 
+msgid "Edit path"
+msgstr ""
+
+msgid "Folder"
+msgstr ""
+
+msgid "This folder is currently in use - no changes are possible!"
+msgstr ""
+
+#, c-format
+msgid "Move entire folder containing %d recordings?"
+msgstr ""
+
+msgid "Error while moving folder!"
+msgstr ""
+
+msgid "Edit recording"
+msgstr ""
+
+msgid "This recording is currently in use - no changes are possible!"
+msgstr ""
+
+msgid "Button$Cancel cutting"
+msgstr ""
+
+msgid "Button$Stop cutting"
+msgstr ""
+
+msgid "Button$Cancel moving"
+msgstr ""
+
+msgid "Button$Stop moving"
+msgstr ""
+
+msgid "Button$Cancel copying"
+msgstr ""
+
+msgid "Button$Stop copying"
+msgstr ""
+
+msgid "Button$Cut"
+msgstr ""
+
+msgid "Button$Delete marks"
+msgstr ""
+
+msgid "Recording vanished!"
+msgstr ""
+
+msgid "Error while queueing recording for cutting!"
+msgstr ""
+
+msgid "Delete editing marks for this recording?"
+msgstr ""
+
+msgid "Error while deleting editing marks!"
+msgstr ""
+
+msgid "Error while changing priority/lifetime!"
+msgstr ""
+
+msgid "Error while changing folder/name!"
+msgstr ""
+
 msgid "Recording info"
 msgstr "Tallenteen tiedot"
 
@@ -998,31 +1062,31 @@
 msgstr "oma"
 
 msgid "Setup.LNB$Use dish positioner"
-msgstr ""
+msgstr "Käytä kääntömoottoria lautaselle"
 
 msgid "Setup.LNB$Site latitude (degrees)"
-msgstr ""
+msgstr "Paikkakunnan leveysaste (°)"
 
 msgid "South"
-msgstr ""
+msgstr "etelään"
 
 msgid "North"
-msgstr ""
+msgstr "pohjoiseen"
 
 msgid "Setup.LNB$Site longitude (degrees)"
-msgstr ""
+msgstr "Paikkakunnan pituusaste (°)"
 
 msgid "West"
-msgstr ""
+msgstr "länteen"
 
 msgid "East"
-msgstr ""
+msgstr "itään"
 
 msgid "Setup.LNB$Max. positioner swing (degrees)"
-msgstr ""
+msgstr "Laajin kääntömoottorin pyyhkäisy (°)"
 
 msgid "Setup.LNB$Positioner speed (degrees/s)"
-msgstr ""
+msgstr "Kääntömoottorin nopeus (°/s)"
 
 msgid "CAM reset"
 msgstr "CAM nollaus"
@@ -1363,7 +1427,7 @@
 
 #, c-format
 msgid "Moving dish to %.1f..."
-msgstr ""
+msgstr "Käännetaan lautasta %.1f..."
 
 msgid "ST:TNG Panels"
 msgstr "ST:TNG konsoli"
diff -ruN vdr-2.1.1/po/fr_FR.po vdr-2.1.2/po/fr_FR.po
--- vdr-2.1.1/po/fr_FR.po	2013-08-21 11:35:15.000000000 +0200
+++ vdr-2.1.2/po/fr_FR.po	2013-10-14 11:56:31.000000000 +0200
@@ -17,7 +17,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2013-06-10 12:16+0200\n"
+"POT-Creation-Date: 2013-10-14 11:56+0200\n"
 "PO-Revision-Date: 2013-02-24 12:56+0100\n"
 "Last-Translator: Dominique Plu <dplu@free.fr>\n"
 "Language-Team: French <vdr@linuxtv.org>\n"
@@ -733,6 +733,70 @@
 msgid "CAM not responding!"
 msgstr "Pas de réponse du CAM"
 
+msgid "Edit path"
+msgstr ""
+
+msgid "Folder"
+msgstr ""
+
+msgid "This folder is currently in use - no changes are possible!"
+msgstr ""
+
+#, c-format
+msgid "Move entire folder containing %d recordings?"
+msgstr ""
+
+msgid "Error while moving folder!"
+msgstr ""
+
+msgid "Edit recording"
+msgstr ""
+
+msgid "This recording is currently in use - no changes are possible!"
+msgstr ""
+
+msgid "Button$Cancel cutting"
+msgstr ""
+
+msgid "Button$Stop cutting"
+msgstr ""
+
+msgid "Button$Cancel moving"
+msgstr ""
+
+msgid "Button$Stop moving"
+msgstr ""
+
+msgid "Button$Cancel copying"
+msgstr ""
+
+msgid "Button$Stop copying"
+msgstr ""
+
+msgid "Button$Cut"
+msgstr ""
+
+msgid "Button$Delete marks"
+msgstr ""
+
+msgid "Recording vanished!"
+msgstr ""
+
+msgid "Error while queueing recording for cutting!"
+msgstr ""
+
+msgid "Delete editing marks for this recording?"
+msgstr ""
+
+msgid "Error while deleting editing marks!"
+msgstr ""
+
+msgid "Error while changing priority/lifetime!"
+msgstr ""
+
+msgid "Error while changing folder/name!"
+msgstr ""
+
 msgid "Recording info"
 msgstr "Infos sur l'enregistrement"
 
diff -ruN vdr-2.1.1/po/hr_HR.po vdr-2.1.2/po/hr_HR.po
--- vdr-2.1.1/po/hr_HR.po	2013-08-21 11:35:16.000000000 +0200
+++ vdr-2.1.2/po/hr_HR.po	2013-10-14 11:56:31.000000000 +0200
@@ -9,7 +9,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2013-06-10 12:16+0200\n"
+"POT-Creation-Date: 2013-10-14 11:56+0200\n"
 "PO-Revision-Date: 2008-03-17 19:00+0100\n"
 "Last-Translator: Adrian Caval <anrxc@sysphere.org>\n"
 "Language-Team: Croatian <vdr@linuxtv.org>\n"
@@ -725,6 +725,70 @@
 msgid "CAM not responding!"
 msgstr "CAM ne odgovara!"
 
+msgid "Edit path"
+msgstr ""
+
+msgid "Folder"
+msgstr ""
+
+msgid "This folder is currently in use - no changes are possible!"
+msgstr ""
+
+#, c-format
+msgid "Move entire folder containing %d recordings?"
+msgstr ""
+
+msgid "Error while moving folder!"
+msgstr ""
+
+msgid "Edit recording"
+msgstr ""
+
+msgid "This recording is currently in use - no changes are possible!"
+msgstr ""
+
+msgid "Button$Cancel cutting"
+msgstr ""
+
+msgid "Button$Stop cutting"
+msgstr ""
+
+msgid "Button$Cancel moving"
+msgstr ""
+
+msgid "Button$Stop moving"
+msgstr ""
+
+msgid "Button$Cancel copying"
+msgstr ""
+
+msgid "Button$Stop copying"
+msgstr ""
+
+msgid "Button$Cut"
+msgstr ""
+
+msgid "Button$Delete marks"
+msgstr ""
+
+msgid "Recording vanished!"
+msgstr ""
+
+msgid "Error while queueing recording for cutting!"
+msgstr ""
+
+msgid "Delete editing marks for this recording?"
+msgstr ""
+
+msgid "Error while deleting editing marks!"
+msgstr ""
+
+msgid "Error while changing priority/lifetime!"
+msgstr ""
+
+msgid "Error while changing folder/name!"
+msgstr ""
+
 msgid "Recording info"
 msgstr "Detalji snimanja"
 
diff -ruN vdr-2.1.1/po/hu_HU.po vdr-2.1.2/po/hu_HU.po
--- vdr-2.1.1/po/hu_HU.po	2013-08-21 11:35:15.000000000 +0200
+++ vdr-2.1.2/po/hu_HU.po	2013-10-14 11:56:30.000000000 +0200
@@ -10,7 +10,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2013-06-10 12:16+0200\n"
+"POT-Creation-Date: 2013-10-14 11:56+0200\n"
 "PO-Revision-Date: 2013-03-01 19:22+0200\n"
 "Last-Translator: István Füley <ifuley@tigercomp.ro>\n"
 "Language-Team: Hungarian <vdr@linuxtv.org>\n"
@@ -727,6 +727,70 @@
 msgid "CAM not responding!"
 msgstr "A CAM nem válaszol!"
 
+msgid "Edit path"
+msgstr ""
+
+msgid "Folder"
+msgstr ""
+
+msgid "This folder is currently in use - no changes are possible!"
+msgstr ""
+
+#, c-format
+msgid "Move entire folder containing %d recordings?"
+msgstr ""
+
+msgid "Error while moving folder!"
+msgstr ""
+
+msgid "Edit recording"
+msgstr ""
+
+msgid "This recording is currently in use - no changes are possible!"
+msgstr ""
+
+msgid "Button$Cancel cutting"
+msgstr ""
+
+msgid "Button$Stop cutting"
+msgstr ""
+
+msgid "Button$Cancel moving"
+msgstr ""
+
+msgid "Button$Stop moving"
+msgstr ""
+
+msgid "Button$Cancel copying"
+msgstr ""
+
+msgid "Button$Stop copying"
+msgstr ""
+
+msgid "Button$Cut"
+msgstr ""
+
+msgid "Button$Delete marks"
+msgstr ""
+
+msgid "Recording vanished!"
+msgstr ""
+
+msgid "Error while queueing recording for cutting!"
+msgstr ""
+
+msgid "Delete editing marks for this recording?"
+msgstr ""
+
+msgid "Error while deleting editing marks!"
+msgstr ""
+
+msgid "Error while changing priority/lifetime!"
+msgstr ""
+
+msgid "Error while changing folder/name!"
+msgstr ""
+
 msgid "Recording info"
 msgstr "Felvétel infó"
 
diff -ruN vdr-2.1.1/po/it_IT.po vdr-2.1.2/po/it_IT.po
--- vdr-2.1.1/po/it_IT.po	2013-08-21 11:35:15.000000000 +0200
+++ vdr-2.1.2/po/it_IT.po	2013-10-14 11:56:31.000000000 +0200
@@ -11,8 +11,8 @@
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2013-06-10 12:16+0200\n"
-"PO-Revision-Date: 2013-02-11 23:46+0100\n"
+"POT-Creation-Date: 2013-10-14 11:56+0200\n"
+"PO-Revision-Date: 2013-09-18 23:57+0100\n"
 "Last-Translator: Diego Pierotto <vdr-italian@tiscali.it>\n"
 "Language-Team: Italian <vdr@linuxtv.org>\n"
 "Language: it\n"
@@ -730,6 +730,70 @@
 msgid "CAM not responding!"
 msgstr "La CAM non risponde!"
 
+msgid "Edit path"
+msgstr ""
+
+msgid "Folder"
+msgstr ""
+
+msgid "This folder is currently in use - no changes are possible!"
+msgstr ""
+
+#, c-format
+msgid "Move entire folder containing %d recordings?"
+msgstr ""
+
+msgid "Error while moving folder!"
+msgstr ""
+
+msgid "Edit recording"
+msgstr ""
+
+msgid "This recording is currently in use - no changes are possible!"
+msgstr ""
+
+msgid "Button$Cancel cutting"
+msgstr ""
+
+msgid "Button$Stop cutting"
+msgstr ""
+
+msgid "Button$Cancel moving"
+msgstr ""
+
+msgid "Button$Stop moving"
+msgstr ""
+
+msgid "Button$Cancel copying"
+msgstr ""
+
+msgid "Button$Stop copying"
+msgstr ""
+
+msgid "Button$Cut"
+msgstr ""
+
+msgid "Button$Delete marks"
+msgstr ""
+
+msgid "Recording vanished!"
+msgstr ""
+
+msgid "Error while queueing recording for cutting!"
+msgstr ""
+
+msgid "Delete editing marks for this recording?"
+msgstr ""
+
+msgid "Error while deleting editing marks!"
+msgstr ""
+
+msgid "Error while changing priority/lifetime!"
+msgstr ""
+
+msgid "Error while changing folder/name!"
+msgstr ""
+
 msgid "Recording info"
 msgstr "Info registrazione"
 
@@ -1001,31 +1065,31 @@
 msgstr "propria"
 
 msgid "Setup.LNB$Use dish positioner"
-msgstr ""
+msgstr "Utilizza motore antenna"
 
 msgid "Setup.LNB$Site latitude (degrees)"
-msgstr ""
+msgstr "Latitudine attuale (gradi)"
 
 msgid "South"
-msgstr ""
+msgstr "Sud"
 
 msgid "North"
-msgstr ""
+msgstr "Nord"
 
 msgid "Setup.LNB$Site longitude (degrees)"
-msgstr ""
+msgstr "Longitudine attuale (gradi)"
 
 msgid "West"
-msgstr ""
+msgstr "Ovest"
 
 msgid "East"
-msgstr ""
+msgstr "Est"
 
 msgid "Setup.LNB$Max. positioner swing (degrees)"
-msgstr ""
+msgstr "Rotazione massima motore (gradi)"
 
 msgid "Setup.LNB$Positioner speed (degrees/s)"
-msgstr ""
+msgstr "Velocità motore (gradi/s)"
 
 msgid "CAM reset"
 msgstr "Reimposta la CAM"
@@ -1366,7 +1430,7 @@
 
 #, c-format
 msgid "Moving dish to %.1f..."
-msgstr ""
+msgstr "Muovi antenna verso %.1f..."
 
 msgid "ST:TNG Panels"
 msgstr "Consolle ST:TNG"
diff -ruN vdr-2.1.1/po/lt_LT.po vdr-2.1.2/po/lt_LT.po
--- vdr-2.1.1/po/lt_LT.po	2013-08-21 11:35:15.000000000 +0200
+++ vdr-2.1.2/po/lt_LT.po	2013-10-14 11:56:31.000000000 +0200
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2013-06-10 12:16+0200\n"
+"POT-Creation-Date: 2013-10-14 11:56+0200\n"
 "PO-Revision-Date: 2010-10-30 11:55+0200\n"
 "Last-Translator: Valdemaras Pipiras <varas@ambernet.lt>\n"
 "Language-Team: Lithuanian <vdr@linuxtv.org>\n"
@@ -723,6 +723,70 @@
 msgid "CAM not responding!"
 msgstr "Dekodavimo modulis (CAM) neveikia!"
 
+msgid "Edit path"
+msgstr ""
+
+msgid "Folder"
+msgstr ""
+
+msgid "This folder is currently in use - no changes are possible!"
+msgstr ""
+
+#, c-format
+msgid "Move entire folder containing %d recordings?"
+msgstr ""
+
+msgid "Error while moving folder!"
+msgstr ""
+
+msgid "Edit recording"
+msgstr ""
+
+msgid "This recording is currently in use - no changes are possible!"
+msgstr ""
+
+msgid "Button$Cancel cutting"
+msgstr ""
+
+msgid "Button$Stop cutting"
+msgstr ""
+
+msgid "Button$Cancel moving"
+msgstr ""
+
+msgid "Button$Stop moving"
+msgstr ""
+
+msgid "Button$Cancel copying"
+msgstr ""
+
+msgid "Button$Stop copying"
+msgstr ""
+
+msgid "Button$Cut"
+msgstr ""
+
+msgid "Button$Delete marks"
+msgstr ""
+
+msgid "Recording vanished!"
+msgstr ""
+
+msgid "Error while queueing recording for cutting!"
+msgstr ""
+
+msgid "Delete editing marks for this recording?"
+msgstr ""
+
+msgid "Error while deleting editing marks!"
+msgstr ""
+
+msgid "Error while changing priority/lifetime!"
+msgstr ""
+
+msgid "Error while changing folder/name!"
+msgstr ""
+
 msgid "Recording info"
 msgstr "Informacija apie įrašus"
 
diff -ruN vdr-2.1.1/po/mk_MK.po vdr-2.1.2/po/mk_MK.po
--- vdr-2.1.1/po/mk_MK.po	2013-08-21 11:35:15.000000000 +0200
+++ vdr-2.1.2/po/mk_MK.po	2013-10-14 11:56:31.000000000 +0200
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2013-06-10 12:16+0200\n"
+"POT-Creation-Date: 2013-10-14 11:56+0200\n"
 "PO-Revision-Date: 2012-11-19 15:18+0100\n"
 "Last-Translator: Dimitar Petrovski <dimeptr@gmail.com>\n"
 "Language-Team: Macedonian <en@li.org>\n"
@@ -724,6 +724,70 @@
 msgid "CAM not responding!"
 msgstr "CAM не одговара!"
 
+msgid "Edit path"
+msgstr ""
+
+msgid "Folder"
+msgstr ""
+
+msgid "This folder is currently in use - no changes are possible!"
+msgstr ""
+
+#, c-format
+msgid "Move entire folder containing %d recordings?"
+msgstr ""
+
+msgid "Error while moving folder!"
+msgstr ""
+
+msgid "Edit recording"
+msgstr ""
+
+msgid "This recording is currently in use - no changes are possible!"
+msgstr ""
+
+msgid "Button$Cancel cutting"
+msgstr ""
+
+msgid "Button$Stop cutting"
+msgstr ""
+
+msgid "Button$Cancel moving"
+msgstr ""
+
+msgid "Button$Stop moving"
+msgstr ""
+
+msgid "Button$Cancel copying"
+msgstr ""
+
+msgid "Button$Stop copying"
+msgstr ""
+
+msgid "Button$Cut"
+msgstr ""
+
+msgid "Button$Delete marks"
+msgstr ""
+
+msgid "Recording vanished!"
+msgstr ""
+
+msgid "Error while queueing recording for cutting!"
+msgstr ""
+
+msgid "Delete editing marks for this recording?"
+msgstr ""
+
+msgid "Error while deleting editing marks!"
+msgstr ""
+
+msgid "Error while changing priority/lifetime!"
+msgstr ""
+
+msgid "Error while changing folder/name!"
+msgstr ""
+
 msgid "Recording info"
 msgstr "Детали на снимката"
 
diff -ruN vdr-2.1.1/po/nl_NL.po vdr-2.1.2/po/nl_NL.po
--- vdr-2.1.1/po/nl_NL.po	2013-08-21 11:35:15.000000000 +0200
+++ vdr-2.1.2/po/nl_NL.po	2013-10-14 11:56:31.000000000 +0200
@@ -12,7 +12,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2013-06-10 12:16+0200\n"
+"POT-Creation-Date: 2013-10-14 11:56+0200\n"
 "PO-Revision-Date: 2008-02-26 17:20+0100\n"
 "Last-Translator: Cedric Dewijs <cedric.dewijs@telfort.nl>\n"
 "Language-Team: Dutch <vdr@linuxtv.org>\n"
@@ -728,6 +728,70 @@
 msgid "CAM not responding!"
 msgstr "CAM reageert niet!"
 
+msgid "Edit path"
+msgstr ""
+
+msgid "Folder"
+msgstr ""
+
+msgid "This folder is currently in use - no changes are possible!"
+msgstr ""
+
+#, c-format
+msgid "Move entire folder containing %d recordings?"
+msgstr ""
+
+msgid "Error while moving folder!"
+msgstr ""
+
+msgid "Edit recording"
+msgstr ""
+
+msgid "This recording is currently in use - no changes are possible!"
+msgstr ""
+
+msgid "Button$Cancel cutting"
+msgstr ""
+
+msgid "Button$Stop cutting"
+msgstr ""
+
+msgid "Button$Cancel moving"
+msgstr ""
+
+msgid "Button$Stop moving"
+msgstr ""
+
+msgid "Button$Cancel copying"
+msgstr ""
+
+msgid "Button$Stop copying"
+msgstr ""
+
+msgid "Button$Cut"
+msgstr ""
+
+msgid "Button$Delete marks"
+msgstr ""
+
+msgid "Recording vanished!"
+msgstr ""
+
+msgid "Error while queueing recording for cutting!"
+msgstr ""
+
+msgid "Delete editing marks for this recording?"
+msgstr ""
+
+msgid "Error while deleting editing marks!"
+msgstr ""
+
+msgid "Error while changing priority/lifetime!"
+msgstr ""
+
+msgid "Error while changing folder/name!"
+msgstr ""
+
 msgid "Recording info"
 msgstr "Opname info"
 
diff -ruN vdr-2.1.1/po/nn_NO.po vdr-2.1.2/po/nn_NO.po
--- vdr-2.1.1/po/nn_NO.po	2013-08-21 11:35:16.000000000 +0200
+++ vdr-2.1.2/po/nn_NO.po	2013-10-14 11:56:31.000000000 +0200
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2013-06-10 12:16+0200\n"
+"POT-Creation-Date: 2013-10-14 11:56+0200\n"
 "PO-Revision-Date: 2007-08-12 14:17+0200\n"
 "Last-Translator: Truls Slevigen <truls@slevigen.no>\n"
 "Language-Team: Norwegian Nynorsk <vdr@linuxtv.org>\n"
@@ -724,6 +724,70 @@
 msgid "CAM not responding!"
 msgstr ""
 
+msgid "Edit path"
+msgstr ""
+
+msgid "Folder"
+msgstr ""
+
+msgid "This folder is currently in use - no changes are possible!"
+msgstr ""
+
+#, c-format
+msgid "Move entire folder containing %d recordings?"
+msgstr ""
+
+msgid "Error while moving folder!"
+msgstr ""
+
+msgid "Edit recording"
+msgstr ""
+
+msgid "This recording is currently in use - no changes are possible!"
+msgstr ""
+
+msgid "Button$Cancel cutting"
+msgstr ""
+
+msgid "Button$Stop cutting"
+msgstr ""
+
+msgid "Button$Cancel moving"
+msgstr ""
+
+msgid "Button$Stop moving"
+msgstr ""
+
+msgid "Button$Cancel copying"
+msgstr ""
+
+msgid "Button$Stop copying"
+msgstr ""
+
+msgid "Button$Cut"
+msgstr ""
+
+msgid "Button$Delete marks"
+msgstr ""
+
+msgid "Recording vanished!"
+msgstr ""
+
+msgid "Error while queueing recording for cutting!"
+msgstr ""
+
+msgid "Delete editing marks for this recording?"
+msgstr ""
+
+msgid "Error while deleting editing marks!"
+msgstr ""
+
+msgid "Error while changing priority/lifetime!"
+msgstr ""
+
+msgid "Error while changing folder/name!"
+msgstr ""
+
 msgid "Recording info"
 msgstr ""
 
diff -ruN vdr-2.1.1/po/pl_PL.po vdr-2.1.2/po/pl_PL.po
--- vdr-2.1.1/po/pl_PL.po	2013-08-21 11:35:15.000000000 +0200
+++ vdr-2.1.2/po/pl_PL.po	2013-10-14 11:56:30.000000000 +0200
@@ -9,7 +9,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2013-06-10 12:16+0200\n"
+"POT-Creation-Date: 2013-10-14 11:56+0200\n"
 "PO-Revision-Date: 2008-03-09 12:59+0100\n"
 "Last-Translator: Marek Nazarko <mnazarko@gmail.com>\n"
 "Language-Team: Polish <vdr@linuxtv.org>\n"
@@ -725,6 +725,70 @@
 msgid "CAM not responding!"
 msgstr "CAM nie reaguje!"
 
+msgid "Edit path"
+msgstr ""
+
+msgid "Folder"
+msgstr ""
+
+msgid "This folder is currently in use - no changes are possible!"
+msgstr ""
+
+#, c-format
+msgid "Move entire folder containing %d recordings?"
+msgstr ""
+
+msgid "Error while moving folder!"
+msgstr ""
+
+msgid "Edit recording"
+msgstr ""
+
+msgid "This recording is currently in use - no changes are possible!"
+msgstr ""
+
+msgid "Button$Cancel cutting"
+msgstr ""
+
+msgid "Button$Stop cutting"
+msgstr ""
+
+msgid "Button$Cancel moving"
+msgstr ""
+
+msgid "Button$Stop moving"
+msgstr ""
+
+msgid "Button$Cancel copying"
+msgstr ""
+
+msgid "Button$Stop copying"
+msgstr ""
+
+msgid "Button$Cut"
+msgstr ""
+
+msgid "Button$Delete marks"
+msgstr ""
+
+msgid "Recording vanished!"
+msgstr ""
+
+msgid "Error while queueing recording for cutting!"
+msgstr ""
+
+msgid "Delete editing marks for this recording?"
+msgstr ""
+
+msgid "Error while deleting editing marks!"
+msgstr ""
+
+msgid "Error while changing priority/lifetime!"
+msgstr ""
+
+msgid "Error while changing folder/name!"
+msgstr ""
+
 msgid "Recording info"
 msgstr "Informacje o nagraniu"
 
diff -ruN vdr-2.1.1/po/pt_PT.po vdr-2.1.2/po/pt_PT.po
--- vdr-2.1.1/po/pt_PT.po	2013-08-21 11:35:14.000000000 +0200
+++ vdr-2.1.2/po/pt_PT.po	2013-10-14 11:56:30.000000000 +0200
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2013-06-10 12:16+0200\n"
+"POT-Creation-Date: 2013-10-14 11:56+0200\n"
 "PO-Revision-Date: 2010-03-28 22:49+0100\n"
 "Last-Translator: Cris Silva <hudokkow@gmail.com>\n"
 "Language-Team: Portuguese <vdr@linuxtv.org>\n"
@@ -724,6 +724,70 @@
 msgid "CAM not responding!"
 msgstr "A CAM n�o responde!"
 
+msgid "Edit path"
+msgstr ""
+
+msgid "Folder"
+msgstr ""
+
+msgid "This folder is currently in use - no changes are possible!"
+msgstr ""
+
+#, c-format
+msgid "Move entire folder containing %d recordings?"
+msgstr ""
+
+msgid "Error while moving folder!"
+msgstr ""
+
+msgid "Edit recording"
+msgstr ""
+
+msgid "This recording is currently in use - no changes are possible!"
+msgstr ""
+
+msgid "Button$Cancel cutting"
+msgstr ""
+
+msgid "Button$Stop cutting"
+msgstr ""
+
+msgid "Button$Cancel moving"
+msgstr ""
+
+msgid "Button$Stop moving"
+msgstr ""
+
+msgid "Button$Cancel copying"
+msgstr ""
+
+msgid "Button$Stop copying"
+msgstr ""
+
+msgid "Button$Cut"
+msgstr ""
+
+msgid "Button$Delete marks"
+msgstr ""
+
+msgid "Recording vanished!"
+msgstr ""
+
+msgid "Error while queueing recording for cutting!"
+msgstr ""
+
+msgid "Delete editing marks for this recording?"
+msgstr ""
+
+msgid "Error while deleting editing marks!"
+msgstr ""
+
+msgid "Error while changing priority/lifetime!"
+msgstr ""
+
+msgid "Error while changing folder/name!"
+msgstr ""
+
 msgid "Recording info"
 msgstr "Informa��o da grava��o"
 
diff -ruN vdr-2.1.1/po/ro_RO.po vdr-2.1.2/po/ro_RO.po
--- vdr-2.1.1/po/ro_RO.po	2013-08-21 11:35:15.000000000 +0200
+++ vdr-2.1.2/po/ro_RO.po	2013-10-14 11:56:30.000000000 +0200
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2013-06-10 12:16+0200\n"
+"POT-Creation-Date: 2013-10-14 11:56+0200\n"
 "PO-Revision-Date: 2013-02-09 23:01+0100\n"
 "Last-Translator: Lucian Muresan <lucianm@users.sorceforge.net>\n"
 "Language-Team: Romanian <vdr@linuxtv.org>\n"
@@ -725,6 +725,70 @@
 msgid "CAM not responding!"
 msgstr "CAM-ul nu reacţionează!"
 
+msgid "Edit path"
+msgstr ""
+
+msgid "Folder"
+msgstr ""
+
+msgid "This folder is currently in use - no changes are possible!"
+msgstr ""
+
+#, c-format
+msgid "Move entire folder containing %d recordings?"
+msgstr ""
+
+msgid "Error while moving folder!"
+msgstr ""
+
+msgid "Edit recording"
+msgstr ""
+
+msgid "This recording is currently in use - no changes are possible!"
+msgstr ""
+
+msgid "Button$Cancel cutting"
+msgstr ""
+
+msgid "Button$Stop cutting"
+msgstr ""
+
+msgid "Button$Cancel moving"
+msgstr ""
+
+msgid "Button$Stop moving"
+msgstr ""
+
+msgid "Button$Cancel copying"
+msgstr ""
+
+msgid "Button$Stop copying"
+msgstr ""
+
+msgid "Button$Cut"
+msgstr ""
+
+msgid "Button$Delete marks"
+msgstr ""
+
+msgid "Recording vanished!"
+msgstr ""
+
+msgid "Error while queueing recording for cutting!"
+msgstr ""
+
+msgid "Delete editing marks for this recording?"
+msgstr ""
+
+msgid "Error while deleting editing marks!"
+msgstr ""
+
+msgid "Error while changing priority/lifetime!"
+msgstr ""
+
+msgid "Error while changing folder/name!"
+msgstr ""
+
 msgid "Recording info"
 msgstr "Detaliile înregistrării"
 
diff -ruN vdr-2.1.1/po/ru_RU.po vdr-2.1.2/po/ru_RU.po
--- vdr-2.1.1/po/ru_RU.po	2013-08-21 11:35:16.000000000 +0200
+++ vdr-2.1.2/po/ru_RU.po	2013-10-14 11:56:31.000000000 +0200
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2013-06-10 12:16+0200\n"
+"POT-Creation-Date: 2013-10-14 11:56+0200\n"
 "PO-Revision-Date: 2013-03-10 17:13+0100\n"
 "Last-Translator: Oleg Roitburd <oroitburd@gmail.com>\n"
 "Language-Team: Russian <vdr@linuxtv.org>\n"
@@ -724,6 +724,70 @@
 msgid "CAM not responding!"
 msgstr "CAM �� ��������"
 
+msgid "Edit path"
+msgstr ""
+
+msgid "Folder"
+msgstr ""
+
+msgid "This folder is currently in use - no changes are possible!"
+msgstr ""
+
+#, c-format
+msgid "Move entire folder containing %d recordings?"
+msgstr ""
+
+msgid "Error while moving folder!"
+msgstr ""
+
+msgid "Edit recording"
+msgstr ""
+
+msgid "This recording is currently in use - no changes are possible!"
+msgstr ""
+
+msgid "Button$Cancel cutting"
+msgstr ""
+
+msgid "Button$Stop cutting"
+msgstr ""
+
+msgid "Button$Cancel moving"
+msgstr ""
+
+msgid "Button$Stop moving"
+msgstr ""
+
+msgid "Button$Cancel copying"
+msgstr ""
+
+msgid "Button$Stop copying"
+msgstr ""
+
+msgid "Button$Cut"
+msgstr ""
+
+msgid "Button$Delete marks"
+msgstr ""
+
+msgid "Recording vanished!"
+msgstr ""
+
+msgid "Error while queueing recording for cutting!"
+msgstr ""
+
+msgid "Delete editing marks for this recording?"
+msgstr ""
+
+msgid "Error while deleting editing marks!"
+msgstr ""
+
+msgid "Error while changing priority/lifetime!"
+msgstr ""
+
+msgid "Error while changing folder/name!"
+msgstr ""
+
 msgid "Recording info"
 msgstr "���� � ������"
 
diff -ruN vdr-2.1.1/po/sk_SK.po vdr-2.1.2/po/sk_SK.po
--- vdr-2.1.1/po/sk_SK.po	2013-08-21 11:35:16.000000000 +0200
+++ vdr-2.1.2/po/sk_SK.po	2013-10-14 11:56:31.000000000 +0200
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2013-06-10 12:16+0200\n"
+"POT-Creation-Date: 2013-10-14 11:56+0200\n"
 "PO-Revision-Date: 2013-03-04 21:24+0100\n"
 "Last-Translator: Milan Hrala <hrala.milan@gmail.com>\n"
 "Language-Team: Slovak <vdr@linuxtv.org>\n"
@@ -723,6 +723,70 @@
 msgid "CAM not responding!"
 msgstr "CAM neodpoved�!"
 
+msgid "Edit path"
+msgstr ""
+
+msgid "Folder"
+msgstr ""
+
+msgid "This folder is currently in use - no changes are possible!"
+msgstr ""
+
+#, c-format
+msgid "Move entire folder containing %d recordings?"
+msgstr ""
+
+msgid "Error while moving folder!"
+msgstr ""
+
+msgid "Edit recording"
+msgstr ""
+
+msgid "This recording is currently in use - no changes are possible!"
+msgstr ""
+
+msgid "Button$Cancel cutting"
+msgstr ""
+
+msgid "Button$Stop cutting"
+msgstr ""
+
+msgid "Button$Cancel moving"
+msgstr ""
+
+msgid "Button$Stop moving"
+msgstr ""
+
+msgid "Button$Cancel copying"
+msgstr ""
+
+msgid "Button$Stop copying"
+msgstr ""
+
+msgid "Button$Cut"
+msgstr ""
+
+msgid "Button$Delete marks"
+msgstr ""
+
+msgid "Recording vanished!"
+msgstr ""
+
+msgid "Error while queueing recording for cutting!"
+msgstr ""
+
+msgid "Delete editing marks for this recording?"
+msgstr ""
+
+msgid "Error while deleting editing marks!"
+msgstr ""
+
+msgid "Error while changing priority/lifetime!"
+msgstr ""
+
+msgid "Error while changing folder/name!"
+msgstr ""
+
 msgid "Recording info"
 msgstr "Podrobnosti nahr�vky"
 
diff -ruN vdr-2.1.1/po/sl_SI.po vdr-2.1.2/po/sl_SI.po
--- vdr-2.1.1/po/sl_SI.po	2013-08-21 11:35:16.000000000 +0200
+++ vdr-2.1.2/po/sl_SI.po	2013-10-14 11:56:31.000000000 +0200
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2013-06-10 12:16+0200\n"
+"POT-Creation-Date: 2013-10-14 11:56+0200\n"
 "PO-Revision-Date: 2013-03-04 12:46+0100\n"
 "Last-Translator: Matjaz Thaler <matjaz.thaler@guest.arnes.si>\n"
 "Language-Team: Slovenian <vdr@linuxtv.org>\n"
@@ -724,6 +724,70 @@
 msgid "CAM not responding!"
 msgstr "CAM se ne odziva!"
 
+msgid "Edit path"
+msgstr ""
+
+msgid "Folder"
+msgstr ""
+
+msgid "This folder is currently in use - no changes are possible!"
+msgstr ""
+
+#, c-format
+msgid "Move entire folder containing %d recordings?"
+msgstr ""
+
+msgid "Error while moving folder!"
+msgstr ""
+
+msgid "Edit recording"
+msgstr ""
+
+msgid "This recording is currently in use - no changes are possible!"
+msgstr ""
+
+msgid "Button$Cancel cutting"
+msgstr ""
+
+msgid "Button$Stop cutting"
+msgstr ""
+
+msgid "Button$Cancel moving"
+msgstr ""
+
+msgid "Button$Stop moving"
+msgstr ""
+
+msgid "Button$Cancel copying"
+msgstr ""
+
+msgid "Button$Stop copying"
+msgstr ""
+
+msgid "Button$Cut"
+msgstr ""
+
+msgid "Button$Delete marks"
+msgstr ""
+
+msgid "Recording vanished!"
+msgstr ""
+
+msgid "Error while queueing recording for cutting!"
+msgstr ""
+
+msgid "Delete editing marks for this recording?"
+msgstr ""
+
+msgid "Error while deleting editing marks!"
+msgstr ""
+
+msgid "Error while changing priority/lifetime!"
+msgstr ""
+
+msgid "Error while changing folder/name!"
+msgstr ""
+
 msgid "Recording info"
 msgstr "Podatki o snemanju"
 
diff -ruN vdr-2.1.1/po/sr_RS.po vdr-2.1.2/po/sr_RS.po
--- vdr-2.1.1/po/sr_RS.po	2013-08-21 11:35:15.000000000 +0200
+++ vdr-2.1.2/po/sr_RS.po	2013-10-14 11:56:30.000000000 +0200
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2013-06-10 12:16+0200\n"
+"POT-Creation-Date: 2013-10-14 11:56+0200\n"
 "PO-Revision-Date: 2013-03-16 15:05+0100\n"
 "Last-Translator: Zoran Turalija <zoran.turalija@gmail.com>\n"
 "Language-Team: Serbian <vdr@linuxtv.org>\n"
@@ -724,6 +724,70 @@
 msgid "CAM not responding!"
 msgstr "CAM ne reaguje!"
 
+msgid "Edit path"
+msgstr ""
+
+msgid "Folder"
+msgstr ""
+
+msgid "This folder is currently in use - no changes are possible!"
+msgstr ""
+
+#, c-format
+msgid "Move entire folder containing %d recordings?"
+msgstr ""
+
+msgid "Error while moving folder!"
+msgstr ""
+
+msgid "Edit recording"
+msgstr ""
+
+msgid "This recording is currently in use - no changes are possible!"
+msgstr ""
+
+msgid "Button$Cancel cutting"
+msgstr ""
+
+msgid "Button$Stop cutting"
+msgstr ""
+
+msgid "Button$Cancel moving"
+msgstr ""
+
+msgid "Button$Stop moving"
+msgstr ""
+
+msgid "Button$Cancel copying"
+msgstr ""
+
+msgid "Button$Stop copying"
+msgstr ""
+
+msgid "Button$Cut"
+msgstr ""
+
+msgid "Button$Delete marks"
+msgstr ""
+
+msgid "Recording vanished!"
+msgstr ""
+
+msgid "Error while queueing recording for cutting!"
+msgstr ""
+
+msgid "Delete editing marks for this recording?"
+msgstr ""
+
+msgid "Error while deleting editing marks!"
+msgstr ""
+
+msgid "Error while changing priority/lifetime!"
+msgstr ""
+
+msgid "Error while changing folder/name!"
+msgstr ""
+
 msgid "Recording info"
 msgstr "Detalji snimanja"
 
diff -ruN vdr-2.1.1/po/sv_SE.po vdr-2.1.2/po/sv_SE.po
--- vdr-2.1.1/po/sv_SE.po	2013-08-21 11:35:15.000000000 +0200
+++ vdr-2.1.2/po/sv_SE.po	2013-10-14 11:56:31.000000000 +0200
@@ -11,7 +11,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2013-06-10 12:16+0200\n"
+"POT-Creation-Date: 2013-10-14 11:56+0200\n"
 "PO-Revision-Date: 2013-02-18 17:04+0100\n"
 "Last-Translator: Richard Lithvall <r-vdr@boomer.se>\n"
 "Language-Team: Swedish <vdr@linuxtv.org>\n"
@@ -727,6 +727,70 @@
 msgid "CAM not responding!"
 msgstr "CAM svarar inte!"
 
+msgid "Edit path"
+msgstr ""
+
+msgid "Folder"
+msgstr ""
+
+msgid "This folder is currently in use - no changes are possible!"
+msgstr ""
+
+#, c-format
+msgid "Move entire folder containing %d recordings?"
+msgstr ""
+
+msgid "Error while moving folder!"
+msgstr ""
+
+msgid "Edit recording"
+msgstr ""
+
+msgid "This recording is currently in use - no changes are possible!"
+msgstr ""
+
+msgid "Button$Cancel cutting"
+msgstr ""
+
+msgid "Button$Stop cutting"
+msgstr ""
+
+msgid "Button$Cancel moving"
+msgstr ""
+
+msgid "Button$Stop moving"
+msgstr ""
+
+msgid "Button$Cancel copying"
+msgstr ""
+
+msgid "Button$Stop copying"
+msgstr ""
+
+msgid "Button$Cut"
+msgstr ""
+
+msgid "Button$Delete marks"
+msgstr ""
+
+msgid "Recording vanished!"
+msgstr ""
+
+msgid "Error while queueing recording for cutting!"
+msgstr ""
+
+msgid "Delete editing marks for this recording?"
+msgstr ""
+
+msgid "Error while deleting editing marks!"
+msgstr ""
+
+msgid "Error while changing priority/lifetime!"
+msgstr ""
+
+msgid "Error while changing folder/name!"
+msgstr ""
+
 msgid "Recording info"
 msgstr "Inspelningsinformation"
 
diff -ruN vdr-2.1.1/po/tr_TR.po vdr-2.1.2/po/tr_TR.po
--- vdr-2.1.1/po/tr_TR.po	2013-08-21 11:35:16.000000000 +0200
+++ vdr-2.1.2/po/tr_TR.po	2013-10-14 11:56:30.000000000 +0200
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2013-06-10 12:16+0200\n"
+"POT-Creation-Date: 2013-10-14 11:56+0200\n"
 "PO-Revision-Date: 2008-02-28 00:33+0100\n"
 "Last-Translator: Oktay Yolge�en <oktay_73@yahoo.de>\n"
 "Language-Team: Turkish <vdr@linuxtv.org>\n"
@@ -723,6 +723,70 @@
 msgid "CAM not responding!"
 msgstr "CAM yan�t vermiyor!"
 
+msgid "Edit path"
+msgstr ""
+
+msgid "Folder"
+msgstr ""
+
+msgid "This folder is currently in use - no changes are possible!"
+msgstr ""
+
+#, c-format
+msgid "Move entire folder containing %d recordings?"
+msgstr ""
+
+msgid "Error while moving folder!"
+msgstr ""
+
+msgid "Edit recording"
+msgstr ""
+
+msgid "This recording is currently in use - no changes are possible!"
+msgstr ""
+
+msgid "Button$Cancel cutting"
+msgstr ""
+
+msgid "Button$Stop cutting"
+msgstr ""
+
+msgid "Button$Cancel moving"
+msgstr ""
+
+msgid "Button$Stop moving"
+msgstr ""
+
+msgid "Button$Cancel copying"
+msgstr ""
+
+msgid "Button$Stop copying"
+msgstr ""
+
+msgid "Button$Cut"
+msgstr ""
+
+msgid "Button$Delete marks"
+msgstr ""
+
+msgid "Recording vanished!"
+msgstr ""
+
+msgid "Error while queueing recording for cutting!"
+msgstr ""
+
+msgid "Delete editing marks for this recording?"
+msgstr ""
+
+msgid "Error while deleting editing marks!"
+msgstr ""
+
+msgid "Error while changing priority/lifetime!"
+msgstr ""
+
+msgid "Error while changing folder/name!"
+msgstr ""
+
 msgid "Recording info"
 msgstr "Kay�t bilgisi"
 
diff -ruN vdr-2.1.1/po/uk_UA.po vdr-2.1.2/po/uk_UA.po
--- vdr-2.1.1/po/uk_UA.po	2013-08-21 11:35:15.000000000 +0200
+++ vdr-2.1.2/po/uk_UA.po	2013-10-14 11:56:31.000000000 +0200
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2013-06-10 12:16+0200\n"
+"POT-Creation-Date: 2013-10-14 11:56+0200\n"
 "PO-Revision-Date: 2013-02-09 16:00+0100\n"
 "Last-Translator: Yarema aka Knedlyk <yupadmin@gmail.com>\n"
 "Language-Team: Ukrainian <vdr@linuxtv.org>\n"
@@ -724,6 +724,70 @@
 msgid "CAM not responding!"
 msgstr "CAM не відповідає!"
 
+msgid "Edit path"
+msgstr ""
+
+msgid "Folder"
+msgstr ""
+
+msgid "This folder is currently in use - no changes are possible!"
+msgstr ""
+
+#, c-format
+msgid "Move entire folder containing %d recordings?"
+msgstr ""
+
+msgid "Error while moving folder!"
+msgstr ""
+
+msgid "Edit recording"
+msgstr ""
+
+msgid "This recording is currently in use - no changes are possible!"
+msgstr ""
+
+msgid "Button$Cancel cutting"
+msgstr ""
+
+msgid "Button$Stop cutting"
+msgstr ""
+
+msgid "Button$Cancel moving"
+msgstr ""
+
+msgid "Button$Stop moving"
+msgstr ""
+
+msgid "Button$Cancel copying"
+msgstr ""
+
+msgid "Button$Stop copying"
+msgstr ""
+
+msgid "Button$Cut"
+msgstr ""
+
+msgid "Button$Delete marks"
+msgstr ""
+
+msgid "Recording vanished!"
+msgstr ""
+
+msgid "Error while queueing recording for cutting!"
+msgstr ""
+
+msgid "Delete editing marks for this recording?"
+msgstr ""
+
+msgid "Error while deleting editing marks!"
+msgstr ""
+
+msgid "Error while changing priority/lifetime!"
+msgstr ""
+
+msgid "Error while changing folder/name!"
+msgstr ""
+
 msgid "Recording info"
 msgstr "Про запис"
 
diff -ruN vdr-2.1.1/po/zh_CN.po vdr-2.1.2/po/zh_CN.po
--- vdr-2.1.1/po/zh_CN.po	2013-08-21 11:35:15.000000000 +0200
+++ vdr-2.1.2/po/zh_CN.po	2013-10-14 11:56:31.000000000 +0200
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2013-06-10 12:16+0200\n"
+"POT-Creation-Date: 2013-10-14 11:56+0200\n"
 "PO-Revision-Date: 2013-03-04 14:52+0800\n"
 "Last-Translator: NFVDR <nfvdr@live.com>\n"
 "Language-Team: Chinese (simplified) <nfvdr@live.com>\n"
@@ -725,6 +725,70 @@
 msgid "CAM not responding!"
 msgstr "CAM 没有响应！"
 
+msgid "Edit path"
+msgstr ""
+
+msgid "Folder"
+msgstr ""
+
+msgid "This folder is currently in use - no changes are possible!"
+msgstr ""
+
+#, c-format
+msgid "Move entire folder containing %d recordings?"
+msgstr ""
+
+msgid "Error while moving folder!"
+msgstr ""
+
+msgid "Edit recording"
+msgstr ""
+
+msgid "This recording is currently in use - no changes are possible!"
+msgstr ""
+
+msgid "Button$Cancel cutting"
+msgstr ""
+
+msgid "Button$Stop cutting"
+msgstr ""
+
+msgid "Button$Cancel moving"
+msgstr ""
+
+msgid "Button$Stop moving"
+msgstr ""
+
+msgid "Button$Cancel copying"
+msgstr ""
+
+msgid "Button$Stop copying"
+msgstr ""
+
+msgid "Button$Cut"
+msgstr ""
+
+msgid "Button$Delete marks"
+msgstr ""
+
+msgid "Recording vanished!"
+msgstr ""
+
+msgid "Error while queueing recording for cutting!"
+msgstr ""
+
+msgid "Delete editing marks for this recording?"
+msgstr ""
+
+msgid "Error while deleting editing marks!"
+msgstr ""
+
+msgid "Error while changing priority/lifetime!"
+msgstr ""
+
+msgid "Error while changing folder/name!"
+msgstr ""
+
 msgid "Recording info"
 msgstr "录像信息"
 
diff -ruN vdr-2.1.1/positioner.c vdr-2.1.2/positioner.c
--- vdr-2.1.1/positioner.c	2013-08-21 13:02:52.000000000 +0200
+++ vdr-2.1.2/positioner.c	2013-10-10 16:14:10.000000000 +0200
@@ -7,7 +7,7 @@
  * For an explanation (in German) of the theory behind the calculations see
  * http://www.vdr-portal.de/board17-developer/board97-vdr-core/p1154305-grundlagen-und-winkelberechnungen-f%C3%BCr-h-h-diseqc-motor-antennenanlagen
  *
- * $Id: positioner.c 3.1 2013/08/21 11:02:52 kls Exp $
+ * $Id: positioner.c 3.2 2013/10/10 14:14:10 kls Exp $
  */
 
 #include "positioner.h"
@@ -49,20 +49,20 @@
 
 int cPositioner::CalcHourAngle(int Longitude)
 {
-  double Delta = RAD(Longitude - Setup.SiteLon);
+  double Alpha = RAD(Longitude - Setup.SiteLon);
   double Lat = RAD(Setup.SiteLat);
   int Sign = Setup.SiteLat >= 0 ? -1 : 1; // angles to the right are positive, angles to the left are negative
-  return Sign * round(DEG(atan2(sin(Delta), cos(Delta) - cos(Lat) * SAT_EARTH_RATIO)));
+  return Sign * round(DEG(atan2(sin(Alpha), cos(Alpha) - cos(Lat) * SAT_EARTH_RATIO)));
 }
 
 int cPositioner::CalcLongitude(int HourAngle)
 {
   double Lat = RAD(Setup.SiteLat);
   double Lon = RAD(Setup.SiteLon);
-  double Alpha = RAD(HourAngle);
-  double Delta = Alpha - asin(sin(M_PI - Alpha) * cos(Lat) * SAT_EARTH_RATIO);
+  double Delta = RAD(HourAngle);
+  double Alpha = Delta - asin(sin(M_PI - Delta) * cos(Lat) * SAT_EARTH_RATIO);
   int Sign = Setup.SiteLat >= 0 ? 1 : -1;
-  return NormalizeAngle(round(DEG(Lon - Sign * Delta)));
+  return NormalizeAngle(round(DEG(Lon - Sign * Alpha)));
 }
 
 int cPositioner::HorizonLongitude(ePositionerDirection Direction)
diff -ruN vdr-2.1.1/recorder.c vdr-2.1.2/recorder.c
--- vdr-2.1.1/recorder.c	2012-09-22 13:53:57.000000000 +0200
+++ vdr-2.1.2/recorder.c	2013-10-12 13:49:42.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: recorder.c 3.0 2012/09/22 11:53:57 kls Exp $
+ * $Id: recorder.c 3.1 2013/10/12 11:49:42 kls Exp $
  */
 
 #include "recorder.h"
@@ -14,7 +14,7 @@
 
 // The maximum time we wait before assuming that a recorded video data stream
 // is broken:
-#define MAXBROKENTIMEOUT 30 // seconds
+#define MAXBROKENTIMEOUT 30000 // milliseconds
 
 #define MINFREEDISKSPACE    (512) // MB
 #define DISKCHECKINTERVAL   100 // seconds
@@ -117,7 +117,7 @@
 
 void cRecorder::Action(void)
 {
-  time_t t = time(NULL);
+  cTimeMs t(MAXBROKENTIMEOUT);
   bool InfoWritten = false;
   bool FirstIframeSeen = false;
   while (Running()) {
@@ -160,16 +160,16 @@
                        break;
                        }
                     fileSize += Count;
-                    t = time(NULL);
+                    t.Set(MAXBROKENTIMEOUT);
                     }
                  }
               ringBuffer->Del(Count);
               }
            }
-        if (time(NULL) - t > MAXBROKENTIMEOUT) {
+        if (t.TimedOut()) {
            esyslog("ERROR: video data stream broken");
            ShutdownHandler.RequestEmergencyExit();
-           t = time(NULL);
+           t.Set(MAXBROKENTIMEOUT);
            }
         }
 }
diff -ruN vdr-2.1.1/recording.c vdr-2.1.2/recording.c
--- vdr-2.1.1/recording.c	2013-08-21 15:03:38.000000000 +0200
+++ vdr-2.1.2/recording.c	2013-10-16 12:24:28.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: recording.c 3.2 2013/08/21 13:03:38 kls Exp $
+ * $Id: recording.c 3.7 2013/10/16 10:24:28 kls Exp $
  */
 
 #include "recording.h"
@@ -20,8 +20,10 @@
 #include <sys/stat.h>
 #include <unistd.h>
 #include "channels.h"
+#include "cutter.h"
 #include "i18n.h"
 #include "interface.h"
+#include "menu.h"
 #include "remux.h"
 #include "ringbuffer.h"
 #include "skins.h"
@@ -66,6 +68,8 @@
 
 #define MAX_LINK_LEVEL  6
 
+#define LIMIT_SECS_PER_MB_RADIO 5 // radio recordings typically have more than this
+
 int DirectoryPathMax = PATH_MAX - 1;
 int DirectoryNameMax = NAME_MAX;
 bool DirectoryEncoding = false;
@@ -90,7 +94,7 @@
 void cRemoveDeletedRecordingsThread::Action(void)
 {
   // Make sure only one instance of VDR does this:
-  cLockFile LockFile(VideoDirectory);
+  cLockFile LockFile(cVideoDirectory::Name());
   if (LockFile.Lock()) {
      bool deleted = false;
      cThreadLock DeletedRecordingsLock(&DeletedRecordings);
@@ -109,7 +113,7 @@
          }
      if (deleted) {
         const char *IgnoreFiles[] = { SORTMODEFILE, NULL };
-        RemoveEmptyVideoDirectories(IgnoreFiles);
+        cVideoDirectory::RemoveEmptyVideoDirectories(IgnoreFiles);
         }
      }
 }
@@ -145,9 +149,9 @@
   static time_t LastFreeDiskCheck = 0;
   int Factor = (Priority == -1) ? 10 : 1;
   if (Force || time(NULL) - LastFreeDiskCheck > DISKCHECKDELTA / Factor) {
-     if (!VideoFileSpaceAvailable(MINDISKSPACE)) {
+     if (!cVideoDirectory::VideoFileSpaceAvailable(MINDISKSPACE)) {
         // Make sure only one instance of VDR does this:
-        cLockFile LockFile(VideoDirectory);
+        cLockFile LockFile(cVideoDirectory::Name());
         if (!LockFile.Lock())
            return;
         // Remove the oldest file that has been "deleted":
@@ -422,6 +426,13 @@
   framesPerSecond = FramesPerSecond;
 }
 
+void cRecordingInfo::SetFileName(const char *FileName)
+{
+  bool IsPesRecording = fileName && endswith(fileName, ".vdr");
+  free(fileName);
+  fileName = strdup(cString::sprintf("%s%s", FileName, IsPesRecording ? INFOFILESUFFIX ".vdr" : INFOFILESUFFIX));
+}
+
 bool cRecordingInfo::Read(FILE *f)
 {
   if (ownEvent) {
@@ -769,7 +780,7 @@
   else if (Timer->IsSingleEvent() || !Setup.UseSubtitle)
      name = strdup(Timer->File());
   else
-     name = strdup(cString::sprintf("%s~%s", Timer->File(), Subtitle));
+     name = strdup(cString::sprintf("%s%c%s", Timer->File(), FOLDERDELIMCHAR, Subtitle));
   // substitute characters that would cause problems in file names:
   strreplace(name, '\n', ' ');
   start = Timer->StartTime();
@@ -800,8 +811,8 @@
   FileName = fileName = strdup(FileName);
   if (*(fileName + strlen(fileName) - 1) == '/')
      *(fileName + strlen(fileName) - 1) = 0;
-  if (strstr(FileName, VideoDirectory) == FileName)
-     FileName += strlen(VideoDirectory) + 1;
+  if (strstr(FileName, cVideoDirectory::Name()) == FileName)
+     FileName += strlen(cVideoDirectory::Name()) + 1;
   const char *p = strrchr(FileName, '/');
 
   name = NULL;
@@ -949,7 +960,7 @@
 {
   char **sb = (RecordingsSortMode == rsmName) ? &sortBufferName : &sortBufferTime;
   if (!*sb) {
-     char *s = strdup(FileName() + strlen(VideoDirectory));
+     char *s = strdup(FileName() + strlen(cVideoDirectory::Name()));
      if (RecordingsSortMode != rsmName || Setup.AlwaysSortFoldersFirst)
         s = StripEpisodeName(s, RecordingsSortMode != rsmName);
      strreplace(s, '/', '0'); // some locales ignore '/' when sorting
@@ -963,8 +974,9 @@
 
 void cRecording::ClearSortName(void)
 {
-  DELETENULL(sortBufferName);
-  DELETENULL(sortBufferTime);
+  free(sortBufferName);
+  free(sortBufferTime);
+  sortBufferName = sortBufferTime = NULL;
 }
 
 int cRecording::GetResume(void) const
@@ -982,6 +994,28 @@
   return strcasecmp(SortName(), r->SortName());
 }
 
+bool cRecording::IsInPath(const char *Path)
+{
+  if (isempty(Path))
+     return true;
+  int l = strlen(Path);
+  return strncmp(Path, name, l) == 0 && (name[l] == FOLDERDELIMCHAR);
+}
+
+cString cRecording::Folder(void) const
+{
+  if (char *s = strrchr(name, FOLDERDELIMCHAR))
+     return cString(name, s);
+  return "";
+}
+
+cString cRecording::BaseName(void) const
+{
+  if (char *s = strrchr(name, FOLDERDELIMCHAR))
+     return cString(s + 1);
+  return name;
+}
+
 const char *cRecording::FileName(void) const
 {
   if (!fileName) {
@@ -990,11 +1024,11 @@
      const char *fmt = isPesRecording ? NAMEFORMATPES : NAMEFORMATTS;
      int ch = isPesRecording ? priority : channel;
      int ri = isPesRecording ? lifetime : instanceId;
-     char *Name = LimitNameLengths(strdup(name), DirectoryPathMax - strlen(VideoDirectory) - 1 - 42, DirectoryNameMax); // 42 = length of an actual recording directory name (generated with DATAFORMATTS) plus some reserve
+     char *Name = LimitNameLengths(strdup(name), DirectoryPathMax - strlen(cVideoDirectory::Name()) - 1 - 42, DirectoryNameMax); // 42 = length of an actual recording directory name (generated with DATAFORMATTS) plus some reserve
      if (strcmp(Name, name) != 0)
         dsyslog("recording file name '%s' truncated to '%s'", name, Name);
      Name = ExchangeChars(Name, true);
-     fileName = strdup(cString::sprintf(fmt, VideoDirectory, Name, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, ch, ri));
+     fileName = strdup(cString::sprintf(fmt, cVideoDirectory::Name(), Name, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, ch, ri));
      free(Name);
      }
   return fileName;
@@ -1063,7 +1097,7 @@
 
 const char *cRecording::PrefixFileName(char Prefix)
 {
-  cString p = PrefixVideoFileName(FileName(), Prefix);
+  cString p = cVideoDirectory::PrefixVideoFileName(FileName(), Prefix);
   if (*p) {
      free(fileName);
      fileName = strdup(p);
@@ -1093,10 +1127,26 @@
 bool cRecording::IsOnVideoDirectoryFileSystem(void) const
 {
   if (isOnVideoDirectoryFileSystem < 0)
-     isOnVideoDirectoryFileSystem = ::IsOnVideoDirectoryFileSystem(FileName());
+     isOnVideoDirectoryFileSystem = cVideoDirectory::IsOnVideoDirectoryFileSystem(FileName());
   return isOnVideoDirectoryFileSystem;
 }
 
+bool cRecording::HasMarks(void)
+{
+  return access(cMarks::MarksFileName(this), F_OK) == 0;
+}
+
+bool cRecording::DeleteMarks(void)
+{
+  if (remove(cMarks::MarksFileName(this)) < 0) {
+     if (errno != ENOENT) {
+        LOG_ERROR_STR(fileName);
+        return false;
+        }
+     }
+  return true;
+}
+
 void cRecording::ReadInfo(void)
 {
   info->Read();
@@ -1105,13 +1155,13 @@
   framesPerSecond = info->framesPerSecond;
 }
 
-bool cRecording::WriteInfo(void)
+bool cRecording::WriteInfo(const char *OtherFileName)
 {
-  cString InfoFileName = cString::sprintf("%s%s", fileName, isPesRecording ? INFOFILESUFFIX ".vdr" : INFOFILESUFFIX);
-  FILE *f = fopen(InfoFileName, "w");
-  if (f) {
+  cString InfoFileName = cString::sprintf("%s%s", OtherFileName ? OtherFileName : FileName(), isPesRecording ? INFOFILESUFFIX ".vdr" : INFOFILESUFFIX);
+  cSafeFile f(InfoFileName);
+  if (f.Open()) {
      info->Write(f);
-     fclose(f);
+     f.Close();
      }
   else
      LOG_ERROR_STR(*InfoFileName);
@@ -1125,6 +1175,58 @@
   fileName = NULL;
 }
 
+bool cRecording::ChangePriorityLifetime(int NewPriority, int NewLifetime)
+{
+  if (NewPriority != Priority() || NewLifetime != Lifetime()) {
+     dsyslog("changing priority/lifetime of '%s' to %d/%d", Name(), NewPriority, NewLifetime);
+     if (IsPesRecording()) {
+        cString OldFileName = FileName();
+        priority = NewPriority;
+        lifetime = NewLifetime;
+        free(fileName);
+        fileName = NULL;
+        cString NewFileName = FileName();
+        if (!cVideoDirectory::RenameVideoFile(OldFileName, NewFileName))
+           return false;
+        info->SetFileName(NewFileName);
+        }
+     else {
+        priority = info->priority = NewPriority;
+        lifetime = info->lifetime = NewLifetime;
+        if (!WriteInfo())
+           return false;
+        }
+     Recordings.ChangeState();
+     Recordings.TouchUpdate();
+     }
+  return true;
+}
+
+bool cRecording::ChangeName(const char *NewName)
+{
+  if (strcmp(NewName, Name())) {
+     dsyslog("changing name of '%s' to '%s'", Name(), NewName);
+     cString OldName = Name();
+     cString OldFileName = FileName();
+     free(fileName);
+     fileName = NULL;
+     free(name);
+     name = strdup(NewName);
+     cString NewFileName = FileName();
+     if (!(MakeDirs(NewFileName, true) && cVideoDirectory::MoveVideoFile(OldFileName, NewFileName))) {
+        free(name);
+        name = strdup(OldName);
+        free(fileName);
+        fileName = strdup(OldFileName);
+        return false;
+        }
+     ClearSortName();
+     Recordings.ChangeState();
+     Recordings.TouchUpdate();
+     }
+  return true;
+}
+
 bool cRecording::Delete(void)
 {
   bool result = true;
@@ -1135,11 +1237,11 @@
      if (access(NewName, F_OK) == 0) {
         // the new name already exists, so let's remove that one first:
         isyslog("removing recording '%s'", NewName);
-        RemoveVideoFile(NewName);
+        cVideoDirectory::RemoveVideoFile(NewName);
         }
      isyslog("deleting recording '%s'", FileName());
      if (access(FileName(), F_OK) == 0) {
-        result = RenameVideoFile(FileName(), NewName);
+        result = cVideoDirectory::RenameVideoFile(FileName(), NewName);
         cRecordingUserCommand::InvokeCommand(RUC_DELETERECORDING, NewName);
         }
      else {
@@ -1159,7 +1261,7 @@
      return false;
      }
   isyslog("removing recording %s", FileName());
-  return RemoveVideoFile(FileName());
+  return cVideoDirectory::RemoveVideoFile(FileName());
 }
 
 bool cRecording::Undelete(void)
@@ -1177,7 +1279,7 @@
      else {
         isyslog("undeleting recording '%s'", FileName());
         if (access(FileName(), F_OK) == 0)
-           result = RenameVideoFile(FileName(), NewName);
+           result = cVideoDirectory::RenameVideoFile(FileName(), NewName);
         else {
            isyslog("deleted recording '%s' vanished", FileName());
            result = false;
@@ -1188,6 +1290,17 @@
   return result;
 }
 
+int cRecording::IsInUse(void) const
+{
+  int Use = ruNone;
+  if (cRecordControls::GetRecordControl(FileName()))
+     Use |= ruTimer;
+  if (cReplayControl::NowReplaying() && strcmp(cReplayControl::NowReplaying(), FileName()) == 0)
+     Use |= ruReplay;
+  Use |= RecordingsHandler.GetUsage(FileName());
+  return Use;
+}
+
 void cRecording::ResetResume(void) const
 {
   resume = RESUME_NOT_INITIALIZED;
@@ -1250,7 +1363,7 @@
 const char *cRecordings::UpdateFileName(void)
 {
   if (!updateFileName)
-     updateFileName = strdup(AddDirectory(VideoDirectory, ".update"));
+     updateFileName = strdup(AddDirectory(cVideoDirectory::Name(), ".update"));
   return updateFileName;
 }
 
@@ -1261,7 +1374,7 @@
   Clear();
   ChangeState();
   Unlock();
-  ScanVideoDir(VideoDirectory, Foreground);
+  ScanVideoDir(cVideoDirectory::Name(), Foreground);
 }
 
 void cRecordings::ScanVideoDir(const char *DirName, bool Foreground, int LinkLevel)
@@ -1417,8 +1530,10 @@
          if (FileSizeMB > 0) {
             int LengthInSeconds = recording->LengthInSeconds();
             if (LengthInSeconds > 0) {
-               size += FileSizeMB;
-               length += LengthInSeconds;
+               if (LengthInSeconds / FileSizeMB < LIMIT_SECS_PER_MB_RADIO) { // don't count radio recordings
+                  size += FileSizeMB;
+                  length += LengthInSeconds;
+                  }
                }
             }
          }
@@ -1426,6 +1541,46 @@
   return (size && length) ? double(size) * 60 / length : -1;
 }
 
+int cRecordings::PathIsInUse(const char *Path)
+{
+  LOCK_THREAD;
+  int Use = ruNone;
+  for (cRecording *recording = First(); recording; recording = Next(recording)) {
+      if (recording->IsInPath(Path))
+         Use |= recording->IsInUse();
+      }
+  return Use;
+}
+
+int cRecordings::GetNumRecordingsInPath(const char *Path)
+{
+  LOCK_THREAD;
+  int n = 0;
+  for (cRecording *recording = First(); recording; recording = Next(recording)) {
+      if (recording->IsInPath(Path))
+         n++;
+      }
+  return n;
+}
+
+bool cRecordings::MoveRecordings(const char *OldPath, const char *NewPath)
+{
+  if (OldPath && NewPath && strcmp(OldPath, NewPath)) {
+     LOCK_THREAD;
+     dsyslog("moving '%s' to '%s'", OldPath, NewPath);
+     for (cRecording *recording = First(); recording; recording = Next(recording)) {
+         if (recording->IsInPath(OldPath)) {
+            const char *p = recording->Name() + strlen(OldPath);
+            cString NewName = cString::sprintf("%s%s", NewPath, p);
+            if (!recording->ChangeName(NewName))
+               return false;
+            ChangeState();
+            }
+         }
+     }
+  return true;
+}
+
 void cRecordings::ResetResume(const char *ResumeFileName)
 {
   LOCK_THREAD;
@@ -1443,6 +1598,361 @@
       recording->ClearSortName();
 }
 
+// --- cDirCopier ------------------------------------------------------------
+
+class cDirCopier : public cThread {
+private:
+  cString dirNameSrc;
+  cString dirNameDst;
+  bool error;
+  bool suspensionLogged;
+  bool Throttled(void);
+  virtual void Action(void);
+public:
+  cDirCopier(const char *DirNameSrc, const char *DirNameDst);
+  virtual ~cDirCopier();
+  void Stop(void);
+  bool Error(void) { return error; }
+  };
+
+cDirCopier::cDirCopier(const char *DirNameSrc, const char *DirNameDst)
+:cThread("file copier", true)
+{
+  dirNameSrc = DirNameSrc;
+  dirNameDst = DirNameDst;
+  error = false;
+  suspensionLogged = false;
+}
+
+cDirCopier::~cDirCopier()
+{
+  Stop();
+}
+
+bool cDirCopier::Throttled(void)
+{
+  if (cIoThrottle::Engaged()) {
+     if (!suspensionLogged) {
+        dsyslog("suspending copy thread");
+        suspensionLogged = true;
+        }
+     return true;
+     }
+  else if (suspensionLogged) {
+     dsyslog("resuming copy thread");
+     suspensionLogged = false;
+     }
+  return false;
+}
+
+void cDirCopier::Action(void)
+{
+  if (DirectoryOk(dirNameDst, true)) {
+     cReadDir d(dirNameSrc);
+     if (d.Ok()) {
+        dsyslog("copying directory '%s' to '%s'", *dirNameSrc, *dirNameDst);
+        dirent *e = NULL;
+        cString FileNameSrc;
+        cString FileNameDst;
+        int From = -1;
+        int To = -1;
+        size_t BufferSize = BUFSIZ;
+        while (Running()) {
+              // Suspend cutting if we have severe throughput problems:
+              if (Throttled()) {
+                 cCondWait::SleepMs(100);
+                 continue;
+                 }
+              // Copy all files in the source directory to the destination directory:
+              if (e) {
+                 // We're currently copying a file:
+                 uchar Buffer[BufferSize];
+                 size_t Read = safe_read(From, Buffer, sizeof(Buffer));
+                 if (Read > 0) {
+                    size_t Written = safe_write(To, Buffer, Read);
+                    if (Written != Read) {
+                       esyslog("ERROR: can't write to destination file '%s': %m", *FileNameDst);
+                       break;
+                       }
+                    }
+                 else if (Read == 0) { // EOF on From
+                    e = NULL; // triggers switch to next entry
+                    if (fsync(To) < 0) {
+                       esyslog("ERROR: can't sync destination file '%s': %m", *FileNameDst);
+                       break;
+                       }
+                    if (close(From) < 0) {
+                       esyslog("ERROR: can't close source file '%s': %m", *FileNameSrc);
+                       break;
+                       }
+                    if (close(To) < 0) {
+                       esyslog("ERROR: can't close destination file '%s': %m", *FileNameDst);
+                       break;
+                       }
+                    // Plausibility check:
+                    off_t FileSizeSrc = FileSize(FileNameSrc);
+                    off_t FileSizeDst = FileSize(FileNameDst);
+                    if (FileSizeSrc != FileSizeDst) {
+                       esyslog("ERROR: file size discrepancy: %lld != %lld", FileSizeSrc, FileSizeDst);
+                       break;
+                       }
+                    }
+                 else {
+                    esyslog("ERROR: can't read from source file '%s': %m", *FileNameSrc);
+                    break;
+                    }
+                 }
+              else if ((e = d.Next()) != NULL) {
+                 // We're switching to the next directory entry:
+                 FileNameSrc = AddDirectory(dirNameSrc, e->d_name);
+                 FileNameDst = AddDirectory(dirNameDst, e->d_name);
+                 struct stat st;
+                 if (stat(FileNameSrc, &st) < 0) {
+                    esyslog("ERROR: can't access source file '%s': %m", *FileNameSrc);
+                    break;
+                    }
+                 if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))) {
+                    esyslog("ERROR: source file '%s' is neither a regular file nor a symbolic link", *FileNameSrc);
+                    break;
+                    }
+                 dsyslog("copying file '%s' to '%s'", *FileNameSrc, *FileNameDst);
+                 BufferSize = max(size_t(st.st_blksize * 10), size_t(BUFSIZ));
+                 if (access(FileNameDst, F_OK) == 0) {
+                    esyslog("ERROR: destination file '%s' already exists", *FileNameDst);
+                    break;
+                    }
+                 if ((From = open(FileNameSrc, O_RDONLY)) < 0) {
+                    esyslog("ERROR: can't open source file '%s': %m", *FileNameSrc);
+                    break;
+                    }
+                 if ((To = open(FileNameDst, O_WRONLY | O_CREAT | O_EXCL, DEFFILEMODE)) < 0) {
+                    esyslog("ERROR: can't open destination file '%s': %m", *FileNameDst);
+                    close(From);
+                    break;
+                    }
+                 }
+              else {
+                 // We're done:
+                 dsyslog("done copying directory '%s' to '%s'", *dirNameSrc, *dirNameDst);
+                 return;
+                 }
+              }
+        close(From); // just to be absolutely sure
+        close(To);
+        esyslog("ERROR: copying directory '%s' to '%s' ended prematurely", *dirNameSrc, *dirNameDst);
+        }
+     else
+        esyslog("ERROR: can't open '%s'", *dirNameSrc);
+     }
+  else
+     esyslog("ERROR: can't access '%s'", *dirNameDst);
+  error = true;
+}
+
+void cDirCopier::Stop(void)
+{
+  Cancel(3);
+  if (error) {
+     cVideoDirectory::RemoveVideoFile(dirNameDst);
+     Recordings.AddByName(dirNameSrc);
+     Recordings.DelByName(dirNameDst);
+     }
+}
+
+// --- cRecordingsHandlerEntry -----------------------------------------------
+
+class cRecordingsHandlerEntry : public cListObject {
+private:
+  int usage;
+  cString fileNameSrc;
+  cString fileNameDst;
+  cCutter *cutter;
+  cDirCopier *copier;
+  void ClearPending(void) { usage &= ~ruPending; }
+public:
+  cRecordingsHandlerEntry(int Usage, const char *FileNameSrc, const char *FileNameDst);
+  ~cRecordingsHandlerEntry();
+  int Usage(const char *FileName = NULL) const;
+  const char *FileNameSrc(void) const { return fileNameSrc; }
+  const char *FileNameDst(void) const { return fileNameDst; }
+  bool Active(bool &Error);
+  };
+
+cRecordingsHandlerEntry::cRecordingsHandlerEntry(int Usage, const char *FileNameSrc, const char *FileNameDst)
+{
+  usage = Usage;
+  fileNameSrc = FileNameSrc;
+  fileNameDst = FileNameDst;
+  cutter = NULL;
+  copier = NULL;
+}
+
+cRecordingsHandlerEntry::~cRecordingsHandlerEntry()
+{
+  delete cutter;
+  delete copier;
+}
+
+int cRecordingsHandlerEntry::Usage(const char *FileName) const
+{
+  int u = usage;
+  if (FileName && *FileName) {
+     if (strcmp(FileName, fileNameSrc) == 0)
+        u |= ruSrc;
+     else if (strcmp(FileName, fileNameDst) == 0)
+        u |= ruDst;
+     }
+  return u;
+}
+
+bool cRecordingsHandlerEntry::Active(bool &Error)
+{
+  bool CopierFinishedOk = false;
+  // First test whether there is an ongoing operation:
+  if (cutter) {
+     if (cutter->Active())
+        return true;
+     Error |= cutter->Error();
+     delete cutter;
+     cutter = NULL;
+     }
+  else if (copier) {
+     if (copier->Active())
+        return true;
+     Error |= copier->Error();
+     CopierFinishedOk = !copier->Error();
+     delete copier;
+     copier = NULL;
+     }
+  // Now check if there is something to start:
+  if ((Usage() & ruPending) != 0) {
+     if ((Usage() & ruCut) != 0) {
+        cutter = new cCutter(FileNameSrc());
+        cutter->Start();
+        }
+     else if ((Usage() & (ruMove | ruCopy)) != 0) {
+        copier = new cDirCopier(FileNameSrc(), FileNameDst());
+        copier->Start();
+        }
+     ClearPending();
+     Recordings.ChangeState();
+     return true;
+     }
+  // Clean up:
+  if (CopierFinishedOk && (Usage() & ruMove) != 0) {
+     cRecording Recording(FileNameSrc());
+     Recording.Delete();
+     }
+  Recordings.ChangeState();
+  Recordings.TouchUpdate();
+  return false;
+}
+
+// --- cRecordingsHandler ----------------------------------------------------
+
+cRecordingsHandler RecordingsHandler;
+
+cRecordingsHandler::cRecordingsHandler(void)
+{
+  finished = true;
+  error = false;
+}
+
+cRecordingsHandler::~cRecordingsHandler()
+{
+}
+
+cRecordingsHandlerEntry *cRecordingsHandler::Get(const char *FileName)
+{
+  if (FileName && *FileName) {
+     for (cRecordingsHandlerEntry *r = operations.First(); r; r = operations.Next(r)) {
+         if (strcmp(FileName, r->FileNameSrc()) == 0 || strcmp(FileName, r->FileNameDst()) == 0)
+            return r;
+         }
+     }
+  return NULL;
+}
+
+bool cRecordingsHandler::Add(int Usage, const char *FileNameSrc, const char *FileNameDst)
+{
+  dsyslog("recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
+  cMutexLock MutexLock(&mutex);
+  if (Usage == ruCut || Usage == ruMove || Usage == ruCopy) {
+     if (FileNameSrc && *FileNameSrc) {
+        if (Usage == ruCut || FileNameDst && *FileNameDst) {
+           cString fnd;
+           if (Usage == ruCut && !FileNameDst)
+              FileNameDst = fnd = cCutter::EditedFileName(FileNameSrc);
+           if (!Get(FileNameSrc) && !Get(FileNameDst)) {
+              Usage |= ruPending;
+              operations.Add(new cRecordingsHandlerEntry(Usage, FileNameSrc, FileNameDst));
+              finished = false;
+              Active(); // start it right away if possible
+              Recordings.ChangeState();
+              return true;
+              }
+           else
+              esyslog("ERROR: file name already present in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
+           }
+        else
+           esyslog("ERROR: missing dst file name in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
+        }
+     else
+        esyslog("ERROR: missing src file name in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
+     }
+  else
+     esyslog("ERROR: invalid usage in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
+  return false;
+}
+
+void cRecordingsHandler::Del(const char *FileName)
+{
+  cMutexLock MutexLock(&mutex);
+  if (cRecordingsHandlerEntry *r = Get(FileName)) {
+     operations.Del(r);
+     Recordings.ChangeState();
+     }
+}
+
+void cRecordingsHandler::DelAll(void)
+{
+  cMutexLock MutexLock(&mutex);
+  operations.Clear();
+  Recordings.ChangeState();
+}
+
+int cRecordingsHandler::GetUsage(const char *FileName)
+{
+  cMutexLock MutexLock(&mutex);
+  if (cRecordingsHandlerEntry *r = Get(FileName))
+     return r->Usage(FileName);
+  return ruNone;
+}
+
+bool cRecordingsHandler::Active(void)
+{
+  cMutexLock MutexLock(&mutex);
+  while (cRecordingsHandlerEntry *r = operations.First()) {
+        if (r->Active(error))
+           return true;
+        else
+           operations.Del(r);
+        }
+  return false;
+}
+
+bool cRecordingsHandler::Finished(bool &Error)
+{
+  cMutexLock MutexLock(&mutex);
+  if (!finished && operations.Count() == 0) {
+     finished = true;
+     Error = error;
+     error = false;
+     return true;
+     }
+  return false;
+}
+
 // --- cMark -----------------------------------------------------------------
 
 double MarkFramesPerSecond = DEFAULTFRAMESPERSECOND;
@@ -1485,6 +1995,11 @@
 
 // --- cMarks ----------------------------------------------------------------
 
+cString cMarks::MarksFileName(const cRecording *Recording)
+{
+  return AddDirectory(Recording->FileName(), Recording->IsPesRecording() ? MARKSFILESUFFIX ".vdr" : MARKSFILESUFFIX);
+}
+
 bool cMarks::Load(const char *RecordingFileName, double FramesPerSecond, bool IsPesRecording)
 {
   recordingFileName = RecordingFileName;
@@ -2274,7 +2789,7 @@
      int BlockingFlag = blocking ? 0 : O_NONBLOCK;
      if (record) {
         dsyslog("recording to '%s'", fileName);
-        file = OpenVideoFile(fileName, O_RDWR | O_CREAT | O_LARGEFILE | BlockingFlag);
+        file = cVideoDirectory::OpenVideoFile(fileName, O_RDWR | O_CREAT | O_LARGEFILE | BlockingFlag);
         if (!file)
            LOG_ERROR_STR(fileName);
         }
@@ -2295,8 +2810,9 @@
 void cFileName::Close(void)
 {
   if (file) {
-     if (CloseVideoFile(file) < 0)
+     if (file->Close() < 0)
         LOG_ERROR_STR(fileName);
+     delete file;
      file = NULL;
      }
 }
diff -ruN vdr-2.1.1/recording.h vdr-2.1.2/recording.h
--- vdr-2.1.1/recording.h	2013-03-04 15:01:23.000000000 +0100
+++ vdr-2.1.2/recording.h	2013-10-10 14:08:15.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: recording.h 3.0 2013/03/04 14:01:23 kls Exp $
+ * $Id: recording.h 3.1 2013/10/10 12:08:15 kls Exp $
  */
 
 #ifndef __RECORDING_H
@@ -25,6 +25,21 @@
 extern bool DirectoryEncoding;
 extern int InstanceId;
 
+enum eRecordingUsage {
+  ruNone     = 0x0000, // the recording is currently unused
+  ruTimer    = 0x0001, // the recording is currently written to by a timer
+  ruReplay   = 0x0002, // the recording is being replayed
+  // mutually exclusive:
+  ruCut      = 0x0004, // the recording is being cut
+  ruMove     = 0x0008, // the recording is being moved
+  ruCopy     = 0x0010, // the recording is being copied
+  // mutually exclusive:
+  ruSrc      = 0x0020, // the recording is the source of a cut, move or copy process
+  ruDst      = 0x0040, // the recording is the destination of a cut, move or copy process
+  //
+  ruPending  = 0x0080, // the recording is pending a cut, move or copy process
+  };
+
 void RemoveDeletedRecordings(void);
 void AssertFreeDiskSpace(int Priority = 0, bool Force = false);
      ///< The special Priority value -1 means that we shall get rid of any
@@ -73,6 +88,7 @@
   const char *Aux(void) const { return aux; }
   double FramesPerSecond(void) const { return framesPerSecond; }
   void SetFramesPerSecond(double FramesPerSecond);
+  void SetFileName(const char *FileName);
   bool Write(FILE *f, const char *Prefix = "") const;
   bool Read(void);
   bool Write(void) const;
@@ -114,8 +130,21 @@
   int Lifetime(void) const { return lifetime; }
   time_t Deleted(void) const { return deleted; }
   virtual int Compare(const cListObject &ListObject) const;
+  bool IsInPath(const char *Path);
+       ///< Returns true if this recording is stored anywhere under the given Path.
+       ///< If Path is NULL or an empty string, the entire video directory is checked.
+  cString Folder(void) const;
+       ///< Returns the name of the folder this recording is stored in (without the
+       ///< video directory). For use in menus etc.
+  cString BaseName(void) const;
+       ///< Returns the base name of this recording (without the
+       ///< video directory and folder). For use in menus etc.
   const char *Name(void) const { return name; }
+       ///< Returns the full name of the recording (without the video directory.
+       ///< For use in menus etc.
   const char *FileName(void) const;
+       ///< Returns the full path name to the recording directory, including the
+       ///< video directory and the actual '*.rec'. For disk file access use.
   const char *Title(char Delimiter = ' ', bool NewIndicator = false, int Level = -1) const;
   const cRecordingInfo *Info(void) const { return info; }
   const char *PrefixFileName(char Prefix);
@@ -134,8 +163,17 @@
   bool IsEdited(void) const;
   bool IsPesRecording(void) const { return isPesRecording; }
   bool IsOnVideoDirectoryFileSystem(void) const;
+  bool HasMarks(void);
+       ///< Returns true if this recording has any editing marks.
+  bool DeleteMarks(void);
+       ///< Deletes the editing marks from this recording (if any).
+       ///< Returns true if the operation was successful. If there is no marks file
+       ///< for this recording, it also returns true.
   void ReadInfo(void);
-  bool WriteInfo(void);
+  bool WriteInfo(const char *OtherFileName = NULL);
+       ///< Writes in info file of this recording. If OtherFileName is given, the info
+       ///< file will be written under that recording file name instead of this
+       ///< recording's file name.
   void SetStartTime(time_t Start);
        ///< Sets the start time of this recording to the given value.
        ///< If a filename has already been set for this recording, it will be
@@ -144,6 +182,17 @@
        ///< Use this function with care - it does not check whether a recording with
        ///< this new name already exists, and if there is one, results may be
        ///< unexpected!
+  bool ChangePriorityLifetime(int NewPriority, int NewLifetime);
+       ///< Changes the priority and lifetime of this recording to the given values.
+       ///< If the new values are the same as the old ones, nothing happens.
+       ///< Returns false in case of error.
+  bool ChangeName(const char *NewName);
+       ///< Changes the name of this recording to the given value. NewName is in the
+       ///< same format as the one returned by Name(), i.e. without the video directory
+       ///< and the actual '*.rec' part, and using FOLDERDELIMCHAR as the directory
+       ///< delimiter.
+       ///< If the new name is the same as the old one, nothing happens.
+       ///< Returns false in case of error.
   bool Delete(void);
        ///< Changes the file name so that it will no longer be visible in the "Recordings" menu
        ///< Returns false in case of error
@@ -154,6 +203,14 @@
        ///< Changes the file name so that it will be visible in the "Recordings" menu again and
        ///< not processed by cRemoveDeletedRecordingsThread.
        ///< Returns false in case of error
+  int IsInUse(void) const;
+       ///< Checks whether this recording is currently in use and therefore shall not
+       ///< be tampered with. Returns 0 (ruNone) if the recording is not in use.
+       ///< The return value may consist of several or'd eRecordingUsage flags. If the
+       ///< caller is just interested in whether the recording is in use or not, the
+       ///< return value can be used like a boolean value.
+       ///< A recording may be in use for several reasons (like being recorded and replayed,
+       ///< as in time-shift).
   };
 
 class cRecordings : public cList<cRecording>, public cThread {
@@ -197,11 +254,76 @@
   double MBperMinute(void);
        ///< Returns the average data rate (in MB/min) of all recordings, or -1 if
        ///< this value is unknown.
+  int PathIsInUse(const char *Path);
+       ///< Checks whether any recording in the given Path is currently in use and therefore
+       ///< the whole Path shall not be tampered with. Returns 0 (ruNone) if no recording
+       ///< is in use.
+       ///< See cRecording::IsInUse() for details about the possible non-zero return values.
+       ///< If several recordings in the Path are currently in use, the return value will
+       ///< be the combination of all individual recordings' flags.
+       ///< If Path is NULL or an empty string, the entire video directory is checked.
+  int GetNumRecordingsInPath(const char *Path);
+       ///< Returns the total number of recordings in the given Path, including all
+       ///< sub-folders of Path.
+       ///< If Path is NULL or an empty string, the entire video directory is checked.
+  bool MoveRecordings(const char *OldPath, const char *NewPath);
+       ///< Moves all recordings in OldPath to NewPath.
+       ///< Returns true if all recordings were successfully moved.
+       ///< As soon as the operation fails for one recording, the whole
+       ///< action is aborted and false will be returned. Any recordings that
+       ///< have been successfully moved thus far will keep their new name.
+       ///< If OldPath and NewPath are on different file systems, the recordings
+       ///< will be moved in a background process and this function returns true
+       ///< if all recordings have been successfully added to the RecordingsHandler.
   };
 
 extern cRecordings Recordings;
 extern cRecordings DeletedRecordings;
 
+class cRecordingsHandlerEntry;
+
+class cRecordingsHandler {
+private:
+  cMutex mutex;
+  cList<cRecordingsHandlerEntry> operations;
+  bool finished;
+  bool error;
+  cRecordingsHandlerEntry *Get(const char *FileName);
+public:
+  cRecordingsHandler(void);
+  ~cRecordingsHandler();
+  bool Add(int Usage, const char *FileNameSrc, const char *FileNameDst = NULL);
+       ///< Adds the given FileNameSrc to the recordings handler for (later)
+       ///< processing. Usage can be either ruCut, ruMove or ruCopy. FileNameDst
+       ///< is only applicable for ruMove and ruCopy.
+       ///< At any given time there can be only one operation for any FileNameSrc
+       ///< or FileNameDst in the list. An attempt to add a file name twice will
+       ///< result in an error.
+       ///< Returns true if the operation was successfully added to the list.
+  void Del(const char *FileName);
+       ///< Deletes the given FileName from the list of operations.
+       ///< If an action is already in progress, it will be terminated.
+       ///< FileName can be either the FileNameSrc or FileNameDst (if applicable)
+       ///< that was given when the operation was added with Add().
+  void DelAll(void);
+       ///< Deletes/terminates all operations.
+  int GetUsage(const char *FileName);
+       ///< Returns the usage type for the given FileName.
+  bool Active(void);
+       ///< Checks whether there is currently any operation running and starts
+       ///> the next one form the list if the previous one has finished.
+       ///< This function must be called regularly to trigger switching to the
+       ///< next operation in the list.
+       ///< Returns true if there are any operations in the list.
+  bool Finished(bool &Error);
+       ///< Returns true if all operations in the list have been finished.
+       ///< If there have been any errors, Errors will be set to true.
+       ///< This function will only return true once if the list of operations
+       ///< has actually become empty since the last call.
+  };
+
+extern cRecordingsHandler RecordingsHandler;
+
 #define DEFAULTFRAMESPERSECOND 25.0
 
 class cMark : public cListObject {
@@ -232,6 +354,9 @@
   time_t lastFileTime;
   time_t lastChange;
 public:
+  static cString MarksFileName(const cRecording *Recording);
+       ///< Returns the marks file name for the given Recording (regardless whether such
+       ///< a file actually exists).
   bool Load(const char *RecordingFileName, double FramesPerSecond = DEFAULTFRAMESPERSECOND, bool IsPesRecording = false);
   bool Update(void);
   bool Save(void);
diff -ruN vdr-2.1.1/shutdown.c vdr-2.1.2/shutdown.c
--- vdr-2.1.1/shutdown.c	2013-02-18 11:33:26.000000000 +0100
+++ vdr-2.1.2/shutdown.c	2013-10-02 11:02:01.000000000 +0200
@@ -6,7 +6,7 @@
  *
  * Original version written by Udo Richter <udo_richter@gmx.de>.
  *
- * $Id: shutdown.c 3.0 2013/02/18 10:33:26 kls Exp $
+ * $Id: shutdown.c 3.1 2013/10/02 09:02:01 kls Exp $
  */
 
 #include "shutdown.h"
@@ -16,11 +16,11 @@
 #include <sys/wait.h>
 #include "channels.h"
 #include "config.h"
-#include "cutter.h"
 #include "i18n.h"
 #include "interface.h"
 #include "menu.h"
 #include "plugin.h"
+#include "recording.h"
 #include "timers.h"
 #include "tools.h"
 
@@ -167,7 +167,7 @@
         Skins.Message(mtError, tr("Can't shutdown - option '-s' not given!"));
      return false;
      }
-  if (cCutter::Active()) {
+  if (RecordingsHandler.Active()) {
      if (!Interactive || !Interface->Confirm(tr("Editing - shut down anyway?")))
         return false;
      }
@@ -210,7 +210,7 @@
 
 bool cShutdownHandler::ConfirmRestart(bool Interactive)
 {
-  if (cCutter::Active()) {
+  if (RecordingsHandler.Active()) {
      if (!Interactive || !Interface->Confirm(tr("Editing - restart anyway?")))
         return false;
      }
diff -ruN vdr-2.1.1/svdrp.c vdr-2.1.2/svdrp.c
--- vdr-2.1.1/svdrp.c	2013-02-17 14:18:01.000000000 +0100
+++ vdr-2.1.2/svdrp.c	2013-10-14 11:49:38.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 3.0 2013/02/17 13:18:01 kls Exp $
+ * $Id: svdrp.c 3.3 2013/10/14 09:49:38 kls Exp $
  */
 
 #include "svdrp.h"
@@ -28,7 +28,6 @@
 #include <unistd.h>
 #include "channels.h"
 #include "config.h"
-#include "cutter.h"
 #include "device.h"
 #include "eitscan.h"
 #include "keys.h"
@@ -305,6 +304,11 @@
   "REMO [ on | off ]\n"
   "    Turns the remote control on or off. Without a parameter, the current\n"
   "    status of the remote control is reported.",
+  "RENR <number> <new name>\n"
+  "    Rename the recording with the given number. Before a recording can be\n"
+  "    renamed, an LSTR command must have been executed in order to retrieve\n"
+  "    the recording numbers. The numbers don't change during subsequent RENR\n"
+  "    commands.n",
   "SCAN\n"
   "    Forces an EPG scan. If this is a single DVB device system, the scan\n"
   "    will be done on the primary device unless it is currently recording.",
@@ -659,27 +663,38 @@
      Reply(501, "Missing channel number");
 }
 
+static cString RecordingInUseMessage(int Reason, const char *RecordingId, cRecording *Recording)
+{
+  cRecordControl *rc;
+  if ((Reason & ruTimer) != 0 && (rc = cRecordControls::GetRecordControl(Recording->FileName())) != NULL)
+     return cString::sprintf("Recording \"%s\" is in use by timer %d", RecordingId, rc->Timer()->Index() + 1);
+  else if ((Reason & ruReplay) != 0)
+     return cString::sprintf("Recording \"%s\" is being replayed", RecordingId);
+  else if ((Reason & ruCut) != 0)
+     return cString::sprintf("Recording \"%s\" is being edited", RecordingId);
+  else if ((Reason & (ruMove | ruCopy)) != 0)
+     return cString::sprintf("Recording \"%s\" is being copied/moved", RecordingId);
+  else if (Reason)
+     return cString::sprintf("Recording \"%s\" is in use", RecordingId);
+  return NULL;
+}
+
 void cSVDRP::CmdDELR(const char *Option)
 {
   if (*Option) {
      if (isnumber(Option)) {
         cRecording *recording = recordings.Get(strtol(Option, NULL, 10) - 1);
         if (recording) {
-           cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
-           if (!rc) {
-              if (!cCutter::Active(recording->FileName())) {
-                 if (recording->Delete()) {
-                    Reply(250, "Recording \"%s\" deleted", Option);
-                    Recordings.DelByName(recording->FileName());
-                    }
-                 else
-                    Reply(554, "Error while deleting recording!");
+           if (int RecordingInUse = recording->IsInUse())
+              Reply(550, RecordingInUseMessage(RecordingInUse, Option, recording));
+           else {
+              if (recording->Delete()) {
+                 Reply(250, "Recording \"%s\" deleted", Option);
+                 Recordings.DelByName(recording->FileName());
                  }
               else
-                 Reply(550, "Recording \"%s\" is being edited", Option);
+                 Reply(554, "Error while deleting recording!");
               }
-           else
-              Reply(550, "Recording \"%s\" is in use by timer %d", Option, rc->Timer()->Index() + 1);
            }
         else
            Reply(550, "Recording \"%s\" not found%s", Option, recordings.Count() ? "" : " (use LSTR before deleting)");
@@ -728,14 +743,10 @@
         if (recording) {
            cMarks Marks;
            if (Marks.Load(recording->FileName(), recording->FramesPerSecond(), recording->IsPesRecording()) && 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");
-                 }
+              if (RecordingsHandler.Add(ruCut, recording->FileName()))
+                 Reply(250, "Editing recording \"%s\" [%s]", Option, recording->Title());
               else
-                 Reply(554, "Editing process already active");
+                 Reply(554, "Can't start editing process");
               }
            else
               Reply(554, "No editing marks defined");
@@ -1539,6 +1550,46 @@
      Reply(250, "Remote control is %s", cRemote::Enabled() ? "enabled" : "disabled");
 }
 
+void cSVDRP::CmdRENR(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 (int RecordingInUse = recording->IsInUse())
+              Reply(550, RecordingInUseMessage(RecordingInUse, Option, recording));
+           else {
+              if (c)
+                 option = skipspace(++option);
+              if (*option) {
+                 cString oldName = recording->Name();
+                 if ((recording = Recordings.GetByName(recording->FileName())) != NULL && recording->ChangeName(option))
+                    Reply(250, "Recording \"%s\" renamed to \"%s\"", *oldName, recording->Name());
+                 else
+                    Reply(554, "Error while renaming recording \"%s\" to \"%s\"!", *oldName, option);
+                 }
+              else
+                 Reply(501, "Missing new recording name");
+              }
+           }
+        else
+           Reply(550, "Recording \"%s\" not found%s", num, recordings.Count() ? "" : " (use LSTR before renaming)");
+        }
+     else
+        Reply(501, "Error in recording number \"%s\"", num);
+     free(opt);
+     }
+  else
+     Reply(501, "Missing recording number");
+}
+
 void cSVDRP::CmdSCAN(const char *Option)
 {
   EITScanner.ForceScan();
@@ -1550,7 +1601,7 @@
   if (*Option) {
      if (strcasecmp(Option, "DISK") == 0) {
         int FreeMB, UsedMB;
-        int Percent = VideoDiskSpace(&FreeMB, &UsedMB);
+        int Percent = cVideoDirectory::VideoDiskSpace(&FreeMB, &UsedMB);
         Reply(250, "%dMB %dMB %d%%", FreeMB + UsedMB, FreeMB, Percent);
         }
      else
@@ -1666,6 +1717,7 @@
   else if (CMD("PLUG"))  CmdPLUG(s);
   else if (CMD("PUTE"))  CmdPUTE(s);
   else if (CMD("REMO"))  CmdREMO(s);
+  else if (CMD("RENR"))  CmdRENR(s);
   else if (CMD("SCAN"))  CmdSCAN(s);
   else if (CMD("STAT"))  CmdSTAT(s);
   else if (CMD("UPDR"))  CmdUPDR(s);
diff -ruN vdr-2.1.1/svdrp.h vdr-2.1.2/svdrp.h
--- vdr-2.1.1/svdrp.h	2012-04-26 12:30:06.000000000 +0200
+++ vdr-2.1.2/svdrp.h	2013-09-14 15:24:50.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 3.0 2012/04/26 10:30:06 kls Exp $
+ * $Id: svdrp.h 3.1 2013/09/14 13:24:50 kls Exp $
  */
 
 #ifndef __SVDRP_H
@@ -78,6 +78,7 @@
   void CmdPLUG(const char *Option);
   void CmdPUTE(const char *Option);
   void CmdREMO(const char *Option);
+  void CmdRENR(const char *Option);
   void CmdSCAN(const char *Option);
   void CmdSTAT(const char *Option);
   void CmdUPDT(const char *Option);
diff -ruN vdr-2.1.1/tools.c vdr-2.1.2/tools.c
--- vdr-2.1.1/tools.c	2013-05-07 10:48:00.000000000 +0200
+++ vdr-2.1.2/tools.c	2013-09-22 15:19:19.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: tools.c 3.1 2013/05/07 08:48:00 kls Exp $
+ * $Id: tools.c 3.2 2013/09/22 13:19:19 kls Exp $
  */
 
 #include "tools.h"
@@ -173,6 +173,31 @@
   return s;
 }
 
+const char *strchrn(const char *s, char c, size_t n)
+{
+  if (n == 0)
+     return s;
+  if (s) {
+     for ( ; *s; s++) {
+         if (*s == c && --n == 0)
+            return s;
+         }
+     }
+  return NULL;
+}
+
+int strcountchr(const char *s, char c)
+{
+  int n = 0;
+  if (s && c) {
+     for ( ; *s; s++) {
+         if (*s == c)
+            n++;
+         }
+     }
+  return n;
+}
+
 char *stripspace(char *s)
 {
   if (s && *s) {
@@ -202,6 +227,30 @@
   return s;
 }
 
+char *compactchars(char *s, char c)
+{
+  if (s && *s && c) {
+     char *t = s;
+     char *p = s;
+     int n = 0;
+     while (*p) {
+           if (*p != c) {
+              *t++ = *p;
+              n = 0;
+              }
+           else if (t != s && n == 0) {
+              *t++ = *p;
+              n++;
+              }
+           p++;
+           }
+     if (n)
+        t--; // the last character was c
+     *t = 0;
+     }
+  return s;
+}
+
 cString strescape(const char *s, const char *chars)
 {
   char *buffer;
@@ -970,6 +1019,20 @@
   s = TakePointer ? (char *)S : S ? strdup(S) : NULL;
 }
 
+cString::cString(const char *S, const char *To)
+{
+  if (!S)
+     s = NULL;
+  else if (!To)
+     s = strdup(S);
+  else {
+     int l = To - S;
+     s = MALLOC(char, l + 1);
+     strncpy(s, S, l);
+     s[l] = 0;
+     }
+}
+
 cString::cString(const cString &String)
 {
   s = String.s ? strdup(String.s) : NULL;
@@ -1008,6 +1071,12 @@
   return *this;
 }
 
+cString &cString::CompactChars(char c)
+{
+  compactchars(s, c);
+  return *this;
+}
+
 cString cString::sprintf(const char *fmt, ...)
 {
   va_list ap;
diff -ruN vdr-2.1.1/tools.h vdr-2.1.2/tools.h
--- vdr-2.1.1/tools.h	2013-08-23 12:32:51.000000000 +0200
+++ vdr-2.1.2/tools.h	2013-09-22 15:30:14.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 3.2 2013/08/23 10:32:51 kls Exp $
+ * $Id: tools.h 3.3 2013/09/22 13:30:14 kls Exp $
  */
 
 #ifndef __TOOLS_H
@@ -170,6 +170,7 @@
   char *s;
 public:
   cString(const char *S = NULL, bool TakePointer = false);
+  cString(const char *S, const char *To); ///< Copies S up to To (exclusive). To must be a valid pointer into S. If To is NULL, everything is copied.
   cString(const cString &String);
   virtual ~cString();
   operator const void * () const { return s; } // to catch cases where operator*() should be used
@@ -178,6 +179,7 @@
   cString &operator=(const cString &String);
   cString &operator=(const char *String);
   cString &Truncate(int Index); ///< Truncate the string at the given Index (if Index is < 0 it is counted from the end of the string).
+  cString &CompactChars(char c); ///< Compact any sequence of characters 'c' to a single character, and strip all of them from the beginning and end of this string.
   static cString sprintf(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
   static cString vsprintf(const char *fmt, va_list &ap);
   };
@@ -193,6 +195,8 @@
 char *strn0cpy(char *dest, const char *src, size_t n);
 char *strreplace(char *s, char c1, char c2);
 char *strreplace(char *s, const char *s1, const char *s2); ///< re-allocates 's' and deletes the original string if necessary!
+const char *strchrn(const char *s, char c, size_t n); ///< returns a pointer to the n'th occurrence (counting from 1) of c in s, or NULL if no such character was found. If n is 0, s is returned.
+int strcountchr(const char *s, char c); ///< returns the number of occurrences of 'c' in 's'.
 inline char *skipspace(const char *s)
 {
   if ((uchar)*s > ' ') // most strings don't have any leading space, so handle this case as fast as possible
@@ -203,6 +207,7 @@
 }
 char *stripspace(char *s);
 char *compactspace(char *s);
+char *compactchars(char *s, char c); ///< removes all occurrences of 'c' from the beginning an end of 's' and replaces sequences of multiple 'c's with a single 'c'.
 cString strescape(const char *s, const char *chars);
 bool startswith(const char *s, const char *p);
 bool endswith(const char *s, const char *p);
diff -ruN vdr-2.1.1/vdr.c vdr-2.1.2/vdr.c
--- vdr-2.1.1/vdr.c	2013-06-10 16:28:43.000000000 +0200
+++ vdr-2.1.2/vdr.c	2013-10-16 11:33:58.000000000 +0200
@@ -22,7 +22,7 @@
  *
  * The project's page is at http://www.tvdr.de
  *
- * $Id: vdr.c 3.1 2013/06/10 14:28:43 kls Exp $
+ * $Id: vdr.c 3.4 2013/10/16 09:33:58 kls Exp $
  */
 
 #include <getopt.h>
@@ -663,7 +663,7 @@
 
   // Directories:
 
-  SetVideoDirectory(VideoDirectory);
+  cVideoDirectory::SetName(VideoDirectory);
   if (!ConfigDirectory)
      ConfigDirectory = DEFAULTCONFDIR;
   cPlugin::SetConfigDirectory(ConfigDirectory);
@@ -1239,7 +1239,7 @@
              case osRecordings:
                             DELETE_MENU;
                             cControl::Shutdown();
-                            Menu = new cMenuMain(osRecordings);
+                            Menu = new cMenuMain(osRecordings, true);
                             break;
              case osReplay: DELETE_MENU;
                             cControl::Shutdown();
@@ -1320,8 +1320,9 @@
         if (!Menu) {
            if (!InhibitEpgScan)
               EITScanner.Process();
-           if (!cCutter::Active() && cCutter::Ended()) {
-              if (cCutter::Error())
+           bool Error = false;
+           if (RecordingsHandler.Finished(Error)) {
+              if (Error)
                  Skins.Message(mtError, tr("Editing process failed!"));
               else
                  Skins.Message(mtInfo, tr("Editing process finished"));
@@ -1341,7 +1342,10 @@
               ShutdownHandler.countdown.Cancel();
            }
 
-        if ((Now - LastInteract) > ACTIVITYTIMEOUT && !cRecordControls::Active() && !cCutter::Active() && !Interface->HasSVDRPConnection() && (Now - cRemote::LastActivity()) > ACTIVITYTIMEOUT) {
+        // Keep the recordings handler alive:
+        RecordingsHandler.Active();
+
+        if ((Now - LastInteract) > ACTIVITYTIMEOUT && !cRecordControls::Active() && !RecordingsHandler.Active() && !Interface->HasSVDRPConnection() && (Now - cRemote::LastActivity()) > ACTIVITYTIMEOUT) {
            // Handle housekeeping tasks
 
            // Shutdown:
@@ -1390,7 +1394,7 @@
 
   PluginManager.StopPlugins();
   cRecordControls::Shutdown();
-  cCutter::Stop();
+  RecordingsHandler.DelAll();
   delete Menu;
   cControl::Shutdown();
   delete Interface;
@@ -1406,6 +1410,7 @@
      }
   cDevice::Shutdown();
   cPositioner::DestroyPositioner();
+  cVideoDirectory::Destroy();
   EpgHandlers.Clear();
   PluginManager.Shutdown(true);
   cSchedules::Cleanup(true);
diff -ruN vdr-2.1.1/videodir.c vdr-2.1.2/videodir.c
--- vdr-2.1.1/videodir.c	2013-08-23 14:28:06.000000000 +0200
+++ vdr-2.1.2/videodir.c	2013-10-11 11:38:07.000000000 +0200
@@ -1,10 +1,10 @@
 /*
- * videodir.c: Functions to maintain a distributed video directory
+ * videodir.c: Functions to maintain the video directory
  *
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: videodir.c 3.1 2013/08/23 12:28:06 kls Exp $
+ * $Id: videodir.c 3.4 2013/10/11 09:38:07 kls Exp $
  */
 
 #include "videodir.h"
@@ -19,213 +19,129 @@
 #include "recording.h"
 #include "tools.h"
 
-//#define DEPRECATED_DISTRIBUTED_VIDEODIR // Code enclosed with this macro is deprecated and will be removed in a future version
+cString cVideoDirectory::name;
+cVideoDirectory *cVideoDirectory::current = NULL;
 
-const char *VideoDirectory = VIDEODIR;
-
-void SetVideoDirectory(const char *Directory)
+cVideoDirectory::cVideoDirectory(void)
 {
-  VideoDirectory = strdup(Directory);
+  delete current;
+  current = this;
 }
 
-class cVideoDirectory {
-#ifdef DEPRECATED_DISTRIBUTED_VIDEODIR
-private:
-  char *name, *stored, *adjusted;
-  int length, number, digits;
-#endif
-public:
-  cVideoDirectory(void);
-  ~cVideoDirectory();
-  int FreeMB(int *UsedMB = NULL);
-  const char *Name(void) { return
-#ifdef DEPRECATED_DISTRIBUTED_VIDEODIR
-                                  name ? name :
-#endif
-                                                VideoDirectory; }
-#ifdef DEPRECATED_DISTRIBUTED_VIDEODIR
-  const char *Stored(void) { return stored; }
-  int Length(void) { return length; }
-  bool IsDistributed(void) { return name != NULL; }
-  bool Next(void);
-  void Store(void);
-  const char *Adjust(const char *FileName);
-#endif
-  };
+cVideoDirectory::~cVideoDirectory()
+{
+  current = NULL;
+}
 
-cVideoDirectory::cVideoDirectory(void)
+cVideoDirectory *cVideoDirectory::Current(void)
 {
-#ifdef DEPRECATED_DISTRIBUTED_VIDEODIR
-  length = strlen(VideoDirectory);
-  name = (VideoDirectory[length - 1] == '0') ? strdup(VideoDirectory) : NULL;
-  stored = adjusted = NULL;
-  number = -1;
-  digits = 0;
-#endif
+  if (!current)
+     current = new cVideoDirectory;
+  return current;
 }
 
-cVideoDirectory::~cVideoDirectory()
+void cVideoDirectory::Destroy(void)
 {
-#ifdef DEPRECATED_DISTRIBUTED_VIDEODIR
-  free(name);
-  free(stored);
-  free(adjusted);
-#endif
+  delete current;
 }
 
 int cVideoDirectory::FreeMB(int *UsedMB)
 {
-  return FreeDiskSpaceMB(
-#ifdef DEPRECATED_DISTRIBUTED_VIDEODIR
-                         name ? name :
-#endif
-                                       VideoDirectory, UsedMB);
-}
-
-#ifdef DEPRECATED_DISTRIBUTED_VIDEODIR
-bool cVideoDirectory::Next(void)
-{
-  if (name) {
-     if (number < 0) {
-        int l = length;
-        while (l-- > 0 && isdigit(name[l]))
-              ;
-        l++;
-        digits = length - l;
-        int n = atoi(&name[l]);
-        if (n == 0)
-           number = n;
-        else
-           return false; // base video directory must end with zero
-        }
-     if (++number > 0) {
-        char buf[16];
-        if (sprintf(buf, "%0*d", digits, number) == digits) {
-           strcpy(&name[length - digits], buf);
-           return DirectoryOk(name);
-           }
-        }
-     }
-  return false;
+  return FreeDiskSpaceMB(Name(), UsedMB);
 }
 
-void cVideoDirectory::Store(void)
+const char *cVideoDirectory::Name(void)
 {
-  if (name) {
-     free(stored);
-     stored = strdup(name);
-     }
+  return name;
 }
 
-const char *cVideoDirectory::Adjust(const char *FileName)
+void cVideoDirectory::SetName(const char *Name)
 {
-  if (stored) {
-     free(adjusted);
-     adjusted = strdup(FileName);
-     return strncpy(adjusted, stored, length);
-     }
-  return NULL;
+  name = Name;
 }
-#endif
 
-cUnbufferedFile *OpenVideoFile(const char *FileName, int Flags)
+bool cVideoDirectory::Register(const char *FileName)
 {
-  const char *ActualFileName = FileName;
-
   // Incoming name must be in base video directory:
-  if (strstr(FileName, VideoDirectory) != FileName) {
-     esyslog("ERROR: %s not in %s", FileName, VideoDirectory);
+  if (strstr(FileName, Name()) != FileName) {
+     esyslog("ERROR: %s not in %s", FileName, Name());
      errno = ENOENT; // must set 'errno' - any ideas for a better value?
-     return NULL;
-     }
-#ifdef DEPRECATED_DISTRIBUTED_VIDEODIR
-  // Are we going to create a new file?
-  if ((Flags & O_CREAT) != 0) {
-     cVideoDirectory Dir;
-     if (Dir.IsDistributed()) {
-        // Find the directory with the most free space:
-        int MaxFree = Dir.FreeMB();
-        while (Dir.Next()) {
-              int Free = FreeDiskSpaceMB(Dir.Name());
-              if (Free > MaxFree) {
-                 Dir.Store();
-                 MaxFree = Free;
-                 }
-              }
-        if (Dir.Stored()) {
-           ActualFileName = Dir.Adjust(FileName);
-           if (!MakeDirs(ActualFileName, false))
-              return NULL; // errno has been set by MakeDirs()
-           if (symlink(ActualFileName, FileName) < 0) {
-              LOG_ERROR_STR(FileName);
-              return NULL;
-              }
-           ActualFileName = strdup(ActualFileName); // must survive Dir!
-           }
-        }
+     return false;
      }
-#endif
-  cUnbufferedFile *File = cUnbufferedFile::Create(ActualFileName, Flags, DEFFILEMODE);
-  if (ActualFileName != FileName)
-     free((char *)ActualFileName);
-  return File;
+  return true;
 }
 
-int CloseVideoFile(cUnbufferedFile *File)
+bool cVideoDirectory::Rename(const char *OldName, const char *NewName)
 {
-  int Result = File->Close();
-  delete File;
-  return Result;
+  dsyslog("renaming '%s' to '%s'", OldName, NewName);
+  if (rename(OldName, NewName) == -1) {
+     LOG_ERROR_STR(NewName);
+     return false;
+     }
+  return true;
 }
 
-bool RenameVideoFile(const char *OldName, const char *NewName)
+bool cVideoDirectory::Move(const char *FromName, const char *ToName)
 {
-  // Only the base video directory entry will be renamed, leaving the
-  // possible symlinks untouched. Going through all the symlinks and disks
-  // would be unnecessary work - maybe later...
-  if (rename(OldName, NewName) == -1) {
-     LOG_ERROR_STR(OldName);
-     return false;
+  dsyslog("moving '%s' to '%s'", FromName, ToName);
+  if (EntriesOnSameFileSystem(FromName, ToName)) {
+     if (rename(FromName, ToName) == -1) {
+        LOG_ERROR_STR(ToName);
+        return false;
+        }
      }
+  else
+     return RecordingsHandler.Add(ruMove, FromName, ToName);
   return true;
 }
 
-bool RemoveVideoFile(const char *FileName)
+bool cVideoDirectory::Remove(const char *Name)
 {
-  return RemoveFileOrDir(FileName, true);
+  return RemoveFileOrDir(Name);
 }
 
-bool VideoFileSpaceAvailable(int SizeMB)
+void cVideoDirectory::Cleanup(const char *IgnoreFiles[])
 {
-  cVideoDirectory Dir;
-#ifdef DEPRECATED_DISTRIBUTED_VIDEODIR
-  if (Dir.IsDistributed()) {
-     if (Dir.FreeMB() >= SizeMB * 2) // base directory needs additional space
-        return true;
-     while (Dir.Next()) {
-           if (Dir.FreeMB() >= SizeMB)
-              return true;
-           }
-     return false;
-     }
-#endif
-  return Dir.FreeMB() >= SizeMB;
+  RemoveEmptyDirectories(Name(), false, IgnoreFiles);
+}
+
+bool cVideoDirectory::Contains(const char *Name)
+{
+  return EntriesOnSameFileSystem(this->Name(), Name);
 }
 
-int VideoDiskSpace(int *FreeMB, int *UsedMB)
+cUnbufferedFile *cVideoDirectory::OpenVideoFile(const char *FileName, int Flags)
 {
-  int free = 0, used = 0;
+  if (Current()->Register(FileName))
+     return cUnbufferedFile::Create(FileName, Flags, DEFFILEMODE);
+  return NULL;
+}
+
+bool cVideoDirectory::RenameVideoFile(const char *OldName, const char *NewName)
+{
+  return Current()->Rename(OldName, NewName);
+}
+
+bool cVideoDirectory::MoveVideoFile(const char *FromName, const char *ToName)
+{
+  return Current()->Move(FromName, ToName);
+}
+
+bool cVideoDirectory::RemoveVideoFile(const char *FileName)
+{
+  return Current()->Remove(FileName);
+}
+
+bool cVideoDirectory::VideoFileSpaceAvailable(int SizeMB)
+{
+  return Current()->FreeMB() >= SizeMB;
+}
+
+int cVideoDirectory::VideoDiskSpace(int *FreeMB, int *UsedMB)
+{
+  int used = 0;
+  int free = Current()->FreeMB(&used);
   int deleted = DeletedRecordings.TotalFileSizeMB();
-  cVideoDirectory Dir;
-#ifdef DEPRECATED_DISTRIBUTED_VIDEODIR
-  do {
-#endif
-     int u;
-     free += Dir.FreeMB(&u);
-     used += u;
-#ifdef DEPRECATED_DISTRIBUTED_VIDEODIR
-     } while (Dir.Next());
-#endif
   if (deleted > used)
      deleted = used; // let's not get beyond 100%
   free += deleted;
@@ -237,7 +153,7 @@
   return (free + used) ? used * 100 / (free + used) : 0;
 }
 
-cString PrefixVideoFileName(const char *FileName, char Prefix)
+cString cVideoDirectory::PrefixVideoFileName(const char *FileName, char Prefix)
 {
   char PrefixedName[strlen(FileName) + 2];
 
@@ -257,30 +173,14 @@
   return NULL;
 }
 
-void RemoveEmptyVideoDirectories(const char *IgnoreFiles[])
+void cVideoDirectory::RemoveEmptyVideoDirectories(const char *IgnoreFiles[])
+{
+  Current()->Cleanup(IgnoreFiles);
+}
+
+bool cVideoDirectory::IsOnVideoDirectoryFileSystem(const char *FileName)
 {
-  cVideoDirectory Dir;
-#ifdef DEPRECATED_DISTRIBUTED_VIDEODIR
-  do {
-#endif
-     RemoveEmptyDirectories(Dir.Name(), false, IgnoreFiles);
-#ifdef DEPRECATED_DISTRIBUTED_VIDEODIR
-     } while (Dir.Next());
-#endif
-}
-
-bool IsOnVideoDirectoryFileSystem(const char *FileName)
-{
-  cVideoDirectory Dir;
-#ifdef DEPRECATED_DISTRIBUTED_VIDEODIR
-  do {
-#endif
-     if (EntriesOnSameFileSystem(Dir.Name(), FileName))
-        return true;
-#ifdef DEPRECATED_DISTRIBUTED_VIDEODIR
-     } while (Dir.Next());
-#endif
-  return false;
+  return Current()->Contains(FileName);
 }
 
 // --- cVideoDiskUsage -------------------------------------------------------
@@ -298,7 +198,7 @@
 {
   if (time(NULL) - lastChecked > DISKSPACECHEK) {
      int FreeMB;
-     int UsedPercent = VideoDiskSpace(&FreeMB);
+     int UsedPercent = cVideoDirectory::VideoDiskSpace(&FreeMB);
      if (FreeMB != freeMB) {
         usedPercent = UsedPercent;
         freeMB = FreeMB;
diff -ruN vdr-2.1.1/videodir.h vdr-2.1.2/videodir.h
--- vdr-2.1.1/videodir.h	2012-09-30 13:01:15.000000000 +0200
+++ vdr-2.1.2/videodir.h	2013-10-11 11:37:48.000000000 +0200
@@ -1,10 +1,10 @@
 /*
- * videodir.h: Functions to maintain a distributed video directory
+ * videodir.h: Functions to maintain the video directory
  *
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: videodir.h 3.0 2012/09/30 11:01:15 kls Exp $
+ * $Id: videodir.h 3.2 2013/10/11 09:37:48 kls Exp $
  */
 
 #ifndef __VIDEODIR_H
@@ -13,18 +13,75 @@
 #include <stdlib.h>
 #include "tools.h"
 
-extern const char *VideoDirectory;
-
-void SetVideoDirectory(const char *Directory);
-cUnbufferedFile *OpenVideoFile(const char *FileName, int Flags);
-int CloseVideoFile(cUnbufferedFile *File);
-bool RenameVideoFile(const char *OldName, const char *NewName);
-bool RemoveVideoFile(const char *FileName);
-bool VideoFileSpaceAvailable(int SizeMB);
-int VideoDiskSpace(int *FreeMB = NULL, int *UsedMB = NULL); // returns the used disk space in percent
-cString PrefixVideoFileName(const char *FileName, char Prefix);
-void RemoveEmptyVideoDirectories(const char *IgnoreFiles[] = NULL);
-bool IsOnVideoDirectoryFileSystem(const char *FileName);
+class cVideoDirectory {
+private:
+  static cString name;
+  static cVideoDirectory *current;
+  static cVideoDirectory *Current(void);
+public:
+  cVideoDirectory(void);
+  virtual ~cVideoDirectory();
+  virtual int FreeMB(int *UsedMB = NULL);
+      ///< Returns the total amount (in MB) of free disk space for recording.
+      ///< If UsedMB is given, it returns the amount of disk space in use by
+      ///< existing recordings (or anything else) on that disk.
+  virtual bool Register(const char *FileName);
+      ///< By default VDR assumes that the video directory consists of one large
+      ///< volume, on which it can store its recordings. A derived cVideoDirectory
+      ///< may, for instance, use several separate disks to store recordings.
+      ///< The given FileName is the full path name (including the video directory) of
+      ///< a recording file ('*.ts') that is about to be opened for writing. If the actual
+      ///< file shall be put on an other disk, the derived cVideoDirectory should
+      ///< create a symbolic link from the given FileName to the other location.
+      ///< Returns true if the operation was successful.
+      ///< The default implementation just checks whether the incoming file name really
+      ///< is under the video directory.
+  virtual bool Rename(const char *OldName, const char *NewName);
+      ///< Renames the directory OldName to NewName.
+      ///< OldName and NewName are full path names that begin with the name of the
+      ///< video directory and end with '*.rec' or '*.del'. Only the base name (the
+      ///< rightmost component) of the two names may be different.
+      ///< Returns true if the operation was successful.
+      ///< The default implementation just calls the system's rename() function.
+  virtual bool Move(const char *FromName, const char *ToName);
+      ///< Moves the directory FromName to the location ToName. FromName is the full
+      ///< path name of a recording's '*.rec' directory. ToName has the same '*.rec'
+      ///< part as FromName, but a different directory path above it.
+      ///< Returns true if the operation was successful.
+      ///< The default implementation just calls the system's rename() function.
+  virtual bool Remove(const char *Name);
+      ///< Removes the directory with the given Name and everything it contains.
+      ///< Name is a full path name that begins with the name of the video directory.
+      ///< Returns true if the operation was successful.
+      ///< The default implementation calls RemoveFileOrDir().
+  virtual void Cleanup(const char *IgnoreFiles[] = NULL);
+      ///< Recursively removes all empty directories under the video directory.
+      ///< If IgnoreFiles is given, the file names in this (NULL terminated) array
+      ///< are ignored when checking whether a directory is empty. These are
+      ///< typically "dot files", like e.g. ".sort".
+      ///< The default implementation calls RemoveEmptyDirectories().
+  virtual bool Contains(const char *Name);
+      ///< Checks whether the directory Name is on the same file system as the
+      ///< video directory. Name is the full path name of a recording's '*.rec'
+      ///< directory. This function is usually called when an ongoing recording
+      ///< is about to run out of disk space, and an existing (old) recording needs
+      ///< to be deleted. It shall make sure that deleting this old recording will
+      ///< actually free up space in the video directory, and not on some other
+      ///< device that just happens to be mounted.
+      ///< The default implementation calls EntriesOnSameFileSystem().
+  static const char *Name(void);
+  static void SetName(const char *Name);
+  static void Destroy(void);
+  static cUnbufferedFile *OpenVideoFile(const char *FileName, int Flags);
+  static bool RenameVideoFile(const char *OldName, const char *NewName);
+  static bool MoveVideoFile(const char *FromName, const char *ToName);
+  static bool RemoveVideoFile(const char *FileName);
+  static bool VideoFileSpaceAvailable(int SizeMB);
+  static int VideoDiskSpace(int *FreeMB = NULL, int *UsedMB = NULL); // returns the used disk space in percent
+  static cString PrefixVideoFileName(const char *FileName, char Prefix);
+  static void RemoveEmptyVideoDirectories(const char *IgnoreFiles[] = NULL);
+  static bool IsOnVideoDirectoryFileSystem(const char *FileName);
+  };
 
 class cVideoDiskUsage {
 private:
