[vdr] 01/09: Version 2.1.2 VDR developer version 2.1.2 is now available at

Tobias Grimm tiber-guest at moszumanska.debian.org
Sun Aug 30 16:18:08 UTC 2015


This is an automated email from the git hooks/post-receive script.

tiber-guest pushed a commit to annotated tag vdr-2.1.10
in repository vdr.

commit 277d66bfe1fc71f253895b0484b3d519b8403a8b
Author: Klaus Schmidinger <Klaus (dot) Schmidinger (at) tvdr (dot) de>
Date:   Sat Oct 19 12:32:00 2013 +0200

    Version 2.1.2
    VDR developer version 2.1.2 is now available at
    
           ftp://ftp.tvdr.de/vdr/Developer/vdr-2.1.2.tar.bz2
    
    A 'diff' against the previous version is available at
    
           ftp://ftp.tvdr.de/vdr/Developer/vdr-2.1.1-2.1.2.diff
    
    MD5 checksums:
    
    4725e9cb26fea8fa3682dd30f5594a50  vdr-2.1.2.tar.bz2
    869d9b298b432a505186328bcf0b53c6  vdr-2.1.1-2.1.2.diff
    
    WARNING:
    ========
    
    This is a *developer* version. Even though *I* use it in my productive
    environment, I strongly recommend that you only use it under controlled
    conditions and for testing and debugging.
    
    From the HISTORY file:
    - 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.
---
 CONTRIBUTORS                          |   33 +
 HISTORY                               |  112 +++-
 INSTALL                               |   20 +-
 MANUAL                                |   24 +
 PLUGINS.html                          |   39 +-
 PLUGINS/src/dvbhddevice/HISTORY       |    4 +
 PLUGINS/src/dvbhddevice/dvbhddevice.c |    2 +-
 PLUGINS/src/dvbhddevice/po/fi_FI.po   |    2 +-
 PLUGINS/src/dvbhddevice/po/it_IT.po   |    4 +-
 channels.c                            |   16 +-
 config.c                              |    4 +-
 config.h                              |   10 +-
 cutter.c                              |  102 ++-
 cutter.h                              |   40 +-
 device.h                              |    4 +-
 dvbdevice.c                           |    4 +-
 dvbsubtitle.c                         | 1180 +++++++++++++++++++--------------
 dvbsubtitle.h                         |    4 +-
 eit.c                                 |    4 +-
 epg.c                                 |   19 +-
 menu.c                                |  451 +++++++++++--
 menu.h                                |    8 +-
 osd.c                                 |   15 +-
 osd.h                                 |   10 +-
 osdbase.c                             |   10 +-
 osdbase.h                             |    4 +-
 po/ar.po                              |   66 +-
 po/ca_ES.po                           |   66 +-
 po/cs_CZ.po                           |   66 +-
 po/da_DK.po                           |   66 +-
 po/de_DE.po                           |   66 +-
 po/el_GR.po                           |   66 +-
 po/es_ES.po                           |   66 +-
 po/et_EE.po                           |   86 ++-
 po/fi_FI.po                           |   86 ++-
 po/fr_FR.po                           |   66 +-
 po/hr_HR.po                           |   66 +-
 po/hu_HU.po                           |   66 +-
 po/it_IT.po                           |   88 ++-
 po/lt_LT.po                           |   66 +-
 po/mk_MK.po                           |   66 +-
 po/nl_NL.po                           |   66 +-
 po/nn_NO.po                           |   66 +-
 po/pl_PL.po                           |   66 +-
 po/pt_PT.po                           |   66 +-
 po/ro_RO.po                           |   66 +-
 po/ru_RU.po                           |   66 +-
 po/sk_SK.po                           |   66 +-
 po/sl_SI.po                           |   66 +-
 po/sr_RS.po                           |   66 +-
 po/sv_SE.po                           |   66 +-
 po/tr_TR.po                           |   66 +-
 po/uk_UA.po                           |   66 +-
 po/zh_CN.po                           |   66 +-
 positioner.c                          |   12 +-
 recorder.c                            |   12 +-
 recording.c                           |  576 +++++++++++++++-
 recording.h                           |  129 +++-
 shutdown.c                            |    8 +-
 svdrp.c                               |   96 ++-
 svdrp.h                               |    3 +-
 tools.c                               |   71 +-
 tools.h                               |    7 +-
 vdr.c                                 |   19 +-
 videodir.c                            |  272 +++-----
 videodir.h                            |   85 ++-
 66 files changed, 4321 insertions(+), 1004 deletions(-)

diff --git a/CONTRIBUTORS b/CONTRIBUTORS
index f162806..3c31e1c 100644
--- a/CONTRIBUTORS
+++ b/CONTRIBUTORS
@@ -618,6 +618,8 @@ Helmut Auer <vdr at helmutauer.de>
  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 at UU.NET>
  for fixing an incomplete initialization of the filter parameters in eit.c
@@ -1169,6 +1171,8 @@ Rolf Ahrenberg <Rolf.Ahrenberg at sci.fi>
  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 at vodafone.com>
  for reporting a bug in cutting a recording if there is only a single editing mark
@@ -1491,6 +1495,8 @@ Thomas Bergwinkl <Thomas.Bergwinkl at vr-web.de>
  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 at free.fr>
  for fixing a typo in libsi/si.h
@@ -1699,6 +1705,7 @@ Arthur Konovalov <artlov at gmail.com>
  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 at cra.cz>
  for suggesting to skip code table info in SI data
@@ -2545,6 +2552,8 @@ Ulf Kiener <webmaster at ulf-kiener.de>
  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 at jwendel.de>
  for reporting that cPlugin::Active() was called too often
@@ -2961,6 +2970,7 @@ Johan Andersson <jna at jna.pp.se>
 Dave Pickles <dave at 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 at gmx.de>
  for making the isnumber() function check the given pointer for NULL
@@ -3009,6 +3019,11 @@ Torsten Lang <info at torstenlang.de>
  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 at gentoo.org>
  for some improvements to the Makefiles
@@ -3169,3 +3184,21 @@ Seppo Ingalsuo <seppo.ingalsuo at iki.fi>
 Manfred V�lkel <mvoelkel at 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 at 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 at martins-kabuff.de>
+ for writing the "extrecmenu" plugin, which inspired the implementation of editing
+ recording properties
+
+Eike Edener <eike at edener.de>
+ for reporting a bug in writing group separators to channels.conf that contain a comma
+
+Harald Koenig <koenig at 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 at tiscali.it>
+ for adding maximum signal strength value for TechniSat SkyStar 2 DVB-S rev 2.3P
diff --git a/HISTORY b/HISTORY
index 4794470..2c03dd4 100644
--- a/HISTORY
+++ b/HISTORY
@@ -7845,7 +7845,8 @@ Video Disk Recorder Revision History
 - 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 @@ Video Disk Recorder Revision History
   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 --git a/INSTALL b/INSTALL
index f3a831f..384ba44 100644
--- a/INSTALL
+++ b/INSTALL
@@ -439,19 +439,17 @@ for a detailed description).
 
 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'
-
-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.
+  0..9                      '0'..'9'
+  Volume+/-                 'PgUp', 'PgDn'
+  Mute                      'F10'
+
+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 --git a/MANUAL b/MANUAL
index e698085..5ba4444 100644
--- a/MANUAL
+++ b/MANUAL
@@ -504,6 +504,30 @@ Version 2.0
   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 --git a/PLUGINS.html b/PLUGINS.html
index 454eae2..71d840e 100644
--- a/PLUGINS.html
+++ b/PLUGINS.html
@@ -104,6 +104,7 @@ structures and allows it to hook itself into specific areas to perform special a
 <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 @@ from <tt>cPositioner</tt>, as in
 #include <vdr/positioner.h>
 
 class cMyPositioner : public cPositioner {
-private:
-  void SendDiseqc(uint8_t *Codes, int NumCodes);
 public:
   cMyPositioner(void);
   virtual void Drive(ePositionerDirection Direction);
@@ -2302,5 +2301,41 @@ to signal VDR that no other EPG handlers shall be queried after this one.
 <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 <vdr/videodir.h>
+
+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 --git a/PLUGINS/src/dvbhddevice/HISTORY b/PLUGINS/src/dvbhddevice/HISTORY
index d22f14d..9db7fd7 100644
--- a/PLUGINS/src/dvbhddevice/HISTORY
+++ b/PLUGINS/src/dvbhddevice/HISTORY
@@ -80,3 +80,7 @@ VDR Plugin 'dvbhddevice' Revision History
 
 - 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 --git a/PLUGINS/src/dvbhddevice/dvbhddevice.c b/PLUGINS/src/dvbhddevice/dvbhddevice.c
index cfd71bb..cff42d0 100644
--- a/PLUGINS/src/dvbhddevice/dvbhddevice.c
+++ b/PLUGINS/src/dvbhddevice/dvbhddevice.c
@@ -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 --git a/PLUGINS/src/dvbhddevice/po/fi_FI.po b/PLUGINS/src/dvbhddevice/po/fi_FI.po
index 9bd9a39..8cb0fde 100644
--- a/PLUGINS/src/dvbhddevice/po/fi_FI.po
+++ b/PLUGINS/src/dvbhddevice/po/fi_FI.po
@@ -26,7 +26,7 @@ msgid "TV on"
 msgstr "TV päälle"
 
 msgid "TV off"
-msgstr ""
+msgstr "TV kiinni"
 
 msgid "Automatic"
 msgstr "automaattinen"
diff --git a/PLUGINS/src/dvbhddevice/po/it_IT.po b/PLUGINS/src/dvbhddevice/po/it_IT.po
index 2d895c6..02cb4a3 100644
--- a/PLUGINS/src/dvbhddevice/po/it_IT.po
+++ b/PLUGINS/src/dvbhddevice/po/it_IT.po
@@ -9,7 +9,7 @@ msgstr ""
 "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 at tiscali.it>\n"
 "Language-Team:  <see README>\n"
 "Language: it\n"
@@ -30,7 +30,7 @@ msgid "TV on"
 msgstr "TV accesa"
 
 msgid "TV off"
-msgstr ""
+msgstr "TV spenta"
 
 msgid "Automatic"
 msgstr "Automatica"
diff --git a/channels.c b/channels.c
index c205f84..7cb7e88 100644
--- a/channels.c
+++ b/channels.c
@@ -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 @@ cString cChannel::ToText(const cChannel *Channel)
   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 --git a/config.c b/config.c
index 591d2ce..657b61c 100644
--- a/config.c
+++ b/config.c
@@ -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 @@ cSetup::cSetup(void)
   SiteLat = 0;
   SiteLon = 0;
   PositionerSpeed = 15;
-  PositionerSwing = 65;
+  PositionerSwing = 650;
   PositionerLastLon = 0;
   SetSystemTime = 0;
   TimeSource = 0;
diff --git a/config.h b/config.h
index 6d1c0da..de3e2aa 100644
--- a/config.h
+++ b/config.h
@@ -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 --git a/cutter.c b/cutter.c
index 5d007c4..9aee66b 100644
--- a/cutter.c
+++ b/cutter.c
@@ -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 @@ void cCuttingThread::Action(void)
 
 // --- 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 @@ bool cCutter::Start(const char *FileName)
 
 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 @@ void cCutter::Stop(void)
         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 @@ bool CutRecording(const char *FileName)
         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 --git a/cutter.h b/cutter.h
index 6821fd4..c67429a 100644
--- a/cutter.h
+++ b/cutter.h
@@ -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 cCuttingThread;
 
 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 --git a/device.h b/device.h
index d4d50d4..aa3a0ee 100644
--- a/device.h
+++ b/device.h
@@ -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 @@ public:
   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 --git a/dvbdevice.c b/dvbdevice.c
index c61fb17..3d01a5a 100644
--- a/dvbdevice.c
+++ b/dvbdevice.c
@@ -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 @@ int cDvbTuner::GetSignalStrength(void) const
     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 --git a/dvbsubtitle.c b/dvbsubtitle.c
index 89abc70..f07267f 100644
--- a/dvbsubtitle.c
+++ b/dvbsubtitle.c
@@ -5,9 +5,9 @@
  * how to reach the author.
  *
  * Original author: Marco Schluessler <marco at lordzodiac.de>
- * With some input from the "subtitle plugin" by Pekka Virtanen <pekka.virtanen at sci.fi>
+ * With some input from the "subtitles plugin" by Pekka Virtanen <pekka.virtanen at 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 @@ cSubtitleClut::cSubtitleClut(int ClutId)
 {
   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 @@ cSubtitleClut::cSubtitleClut(int ClutId)
       }
 }
 
+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 @@ const cPalette *cSubtitleClut::GetPalette(int Bpp)
 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 @@ void cSubtitleObject::DecodeSubBlock(const uchar *Data, int Length, bool Even)
   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 @@ bool cSubtitleObject::Decode2BppCodeString(cBitStream *bs, int &x, int y, const
      }
   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 @@ bool cSubtitleObject::Decode4BppCodeString(cBitStream *bs, int &x, int y, const
      }
   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 @@ bool cSubtitleObject::Decode8BppCodeString(cBitStream *bs, int &x, int y)
      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
+  region8bitPixelCode = 0;
+  region4bitPixelCode = 0;
+  region2bitPixelCode = 0;
 }
 
-void cSubtitleRegion::FillRegion(tIndex Index)
+void cSubtitleRegion::Parse(cBitStream &bs)
 {
-  dbgregions("FillRegion %d\n", Index);
-  for (int y = 0; y < Height(); y++) {
-      for (int x = 0; x < Width(); x++)
-          SetIndex(x, y, Index);
-      }
+  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));
 }
 
-cSubtitleObject *cSubtitleRegion::GetObjectById(int ObjectId, bool New)
+void cSubtitleRegion::Render(cBitmap *Bitmap, cSubtitleObjects *Objects)
 {
-  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);
+  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__);
+       }
      }
-  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;
+  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 ----------------------------------------------------
 
-void cSubtitleRegion::SetDepth(int Depth)
+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; }
+  };
+
+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 @@ void cSubtitleRegion::SetDepth(int Depth)
 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;
+  pageTimeout = 0;
+  pageVersionNumber = -1;
+  pageState = -1;
+  pts = -1;
+  pending = false;
 }
 
-cDvbSubtitlePage::~cDvbSubtitlePage()
+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 @@ void cDvbSubtitleAssembler::Put(const uchar *Data, int Length)
 
 class cDvbSubtitleBitmaps : public cListObject {
 private:
+  int state;
   int64_t pts;
   int timeout;
   tArea *areas;
@@ -722,16 +1025,20 @@ private:
   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 @@ void cDvbSubtitleBitmaps::Draw(cOsd *Osd)
          }
      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::Draw(cOsd *Osd)
      }
 }
 
+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 @@ cDvbSubtitleConverter::cDvbSubtitleConverter(void)
   windowVerticalOffset = 0;
   pages = new cList<cDvbSubtitlePage>;
   bitmaps = new cList<cDvbSubtitleBitmaps>;
+  SD.Reset();
   Start();
 }
 
@@ -818,7 +1157,7 @@ void cDvbSubtitleConverter::SetupChanged(void)
 
 void cDvbSubtitleConverter::Reset(void)
 {
-  dbgconverter("Converter reset -----------------------\n");
+  dbgconverter("converter reset -----------------------<br>\n");
   dvbSubtitleAssembler->Reset();
   Lock();
   pages->Clear();
@@ -848,9 +1187,9 @@ int cDvbSubtitleConverter::ConvertFragments(const uchar *Data, int Length)
         }
 
      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 @@ int cDvbSubtitleConverter::Convert(const uchar *Data, int Length)
   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 @@ int cDvbSubtitleConverter::Convert(const uchar *Data, int Length)
 }
 
 #define LimitTo32Bit(n) ((n) & 0x00000000FFFFFFFFL)
-#define MAXDELTA 40000 // max. reasonable PTS/STC delta in ms
 
 void cDvbSubtitleConverter::Action(void)
 {
@@ -927,55 +1265,47 @@ void cDvbSubtitleConverter::Action(void)
            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 @@ bool cDvbSubtitleConverter::AssertOsd(void)
   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 cDvbSubtitleConverter::ExtractSegment(const uchar *Data, int Length, int64_t
      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 @@ int cDvbSubtitleConverter::ExtractSegment(const uchar *Data, int Length, int64_t
                   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 @@ int cDvbSubtitleConverter::ExtractSegment(const uchar *Data, int Length, int64_t
             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 @@ void cDvbSubtitleConverter::FinishPage(cDvbSubtitlePage *Page)
 {
   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 @@ void cDvbSubtitleConverter::FinishPage(cDvbSubtitlePage *Page)
         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 --git a/dvbsubtitle.h b/dvbsubtitle.h
index 79b1d06..1357433 100644
--- a/dvbsubtitle.h
+++ b/dvbsubtitle.h
@@ -6,7 +6,7 @@
  *
  * Original author: Marco Schluessler <marco at 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 @@ private:
   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 --git a/eit.c b/eit.c
index a3cf341..e5fa301 100644
--- a/eit.c
+++ b/eit.c
@@ -8,7 +8,7 @@
  * Robert Schneider <Robert.Schneider at web.de> and Rolf Hakenes <hakenes at hippomi.de>.
  * Adapted to 'libsi' for VDR 1.3.0 by Marcel Wiesweg <marcel.wiesweg at 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 @@ void cEitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
          }
          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 --git a/epg.c b/epg.c
index e16f978..ee78421 100644
--- a/epg.c
+++ b/epg.c
@@ -7,7 +7,7 @@
  * Original version (as used in VDR before 1.3.0) written by
  * Robert Schneider <Robert.Schneider at web.de> and Rolf Hakenes <hakenes at 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 @@ bool cSchedule::Read(FILE *f, cSchedules *Schedules)
 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 @@ void cEpgDataWriter::Perform(void)
            p->Cleanup(now);
        }
   }
-  cSchedules::Dump();
+  if (dump)
+     cSchedules::Dump();
 }
 
 static cEpgDataWriter EpgDataWriter;
@@ -1203,6 +1207,7 @@ void cSchedules::SetEpgDataFileName(const char *FileName)
 {
   free(epgDataFileName);
   epgDataFileName = FileName ? strdup(FileName) : NULL;
+  EpgDataWriter.SetDump(epgDataFileName != NULL);
 }
 
 void cSchedules::SetModified(cSchedule *Schedule)
@@ -1217,12 +1222,10 @@ void cSchedules::Cleanup(bool Force)
      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 --git a/menu.c b/menu.c
index e466ae0..df78513 100644
--- a/menu.c
+++ b/menu.c
@@ -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::Edit(void)
 
 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 @@ cString cMenuFolder::GetFolder(void)
   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 @@ void cMenuEditTimer::SetFirstDayItem(void)
 
 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 @@ bool CamMenuActive(void)
   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 @@ void cMenuRecording::Display(void)
 
 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 @@ eOSState cMenuRecording::ProcessKey(eKeys Key)
                      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 @@ public:
   ~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 @@ void cMenuRecordingItem::SetMenuItem(cSkinDisplayMenu *DisplayMenu, int Index, b
 
 // --- 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 @@ cMenuRecordings::cMenuRecordings(const char *Base, int Level, bool OpenSubMenus)
   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 @@ void cMenuRecordings::SetHelpKeys(void)
   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::SetHelpKeys(void)
 
 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 @@ void cMenuRecordings::Set(bool Refresh)
          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 @@ void cMenuRecordings::Set(bool Refresh)
      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 @@ cString cMenuRecordings::DirectoryName(void)
 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 @@ eOSState cMenuRecordings::Delete(void)
            }
         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 @@ eOSState cMenuRecordings::Info(void)
 {
   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 @@ eOSState cMenuRecordings::ProcessKey(eKeys Key)
        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 @@ cMenuPluginItem::cMenuPluginItem(const char *Name, int Index)
 
 cOsdObject *cMenuMain::pluginOsdObject = NULL;
 
-cMenuMain::cMenuMain(eOSState State)
+cMenuMain::cMenuMain(eOSState State, bool OpenSubMenus)
 :cOsdMenu("")
 {
   SetMenuCategory(mcMain);
@@ -3390,7 +3749,7 @@ cMenuMain::cMenuMain(eOSState State)
     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 @@ bool cMenuMain::Update(bool Force)
         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 @@ eOSState cMenuMain::ProcessKey(eKeys Key)
                           }
                        break;
     case osCancelEdit: if (Interface->Confirm(tr("Cancel editing?"))) {
-                          cCutter::Stop();
+                          RecordingsHandler.DelAll();
                           return osEnd;
                           }
                        break;
@@ -3552,7 +3911,7 @@ eOSState cMenuMain::ProcessKey(eKeys Key)
                                 }
                              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 @@ bool cRecordControls::Start(cTimer *Timer, bool Pause)
      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 @@ void cReplayControl::EditCut(void)
 {
   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 @@ eOSState cReplayControl::ProcessKey(eKeys Key)
                            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 --git a/menu.h b/menu.h
index db17f98..8915575 100644
--- a/menu.h
+++ b/menu.h
@@ -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 @@ private:
   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 @@ private:
   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 @@ public:
   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 --git a/osd.c b/osd.c
index c266092..7e52782 100644
--- a/osd.c
+++ b/osd.c
@@ -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::SetIndex(int x, int y, tIndex Index)
      }
 }
 
+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 @@ void cBitmap::ShrinkBpp(int NewBpp)
      }
 }
 
-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 at mindtremors.com
diff --git a/osd.h b/osd.h
index 600cc7a..4eaef96 100644
--- a/osd.h
+++ b/osd.h
@@ -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 @@ public:
        ///< 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 @@ public:
   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 @@ public:
        ///< 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 @@ public:
        ///< 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 --git a/osdbase.c b/osdbase.c
index baadc0e..b788edb 100644
--- a/osdbase.c
+++ b/osdbase.c
@@ -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 @@ eOSState cOsdMenu::AddSubMenu(cOsdMenu *SubMenu)
   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 --git a/osdbase.h b/osdbase.h
index 636766a..07dce35 100644
--- a/osdbase.h
+++ b/osdbase.h
@@ -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 @@ protected:
   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 --git a/po/ar.po b/po/ar.po
index 1676dcb..9937682 100644
--- a/po/ar.po
+++ b/po/ar.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs at 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 at hotmail.com>\n"
 "Language-Team: Arabic <ar at li.org>\n"
@@ -727,6 +727,70 @@ msgstr "الرجاء ادخال %d رقم!"
 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 --git a/po/ca_ES.po b/po/ca_ES.po
index 0362cd8..911ce23 100644
--- a/po/ca_ES.po
+++ b/po/ca_ES.po
@@ -10,7 +10,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs at 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 at ventoso.org>\n"
 "Language-Team: Catalan <vdr at linuxtv.org>\n"
@@ -726,6 +726,70 @@ msgstr "Si us plau introdueixi %d digitos"
 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 --git a/po/cs_CZ.po b/po/cs_CZ.po
index 2bd4a8e..3e4855b 100644
--- a/po/cs_CZ.po
+++ b/po/cs_CZ.po
@@ -10,7 +10,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs at 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 at quick.cz>\n"
 "Language-Team: Czech <vdr at linuxtv.org>\n"
@@ -726,6 +726,70 @@ msgstr "Prosím vložte %d znaků!"
 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 --git a/po/da_DK.po b/po/da_DK.po
index edd0f5f..130fc1a 100644
--- a/po/da_DK.po
+++ b/po/da_DK.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs at 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 at elneff.dk>\n"
 "Language-Team: Danish <vdr at linuxtv.org>\n"
@@ -723,6 +723,70 @@ msgstr "Indtast venligst %d cifre!"
 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 --git a/po/de_DE.po b/po/de_DE.po
index 4cc8e84..d31088b 100644
--- a/po/de_DE.po
+++ b/po/de_DE.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs at 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 at tvdr.de>\n"
 "Language-Team: German <vdr at linuxtv.org>\n"
@@ -723,6 +723,70 @@ msgstr "Bitte geben Sie %d Ziffern ein!"
 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 --git a/po/el_GR.po b/po/el_GR.po
index 8df5b78..f03e2a3 100644
--- a/po/el_GR.po
+++ b/po/el_GR.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs at 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 at dimitrios.de>\n"
 "Language-Team: Greek <vdr at linuxtv.org>\n"
@@ -723,6 +723,70 @@ msgstr ""
 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 --git a/po/es_ES.po b/po/es_ES.po
index 023871e..ec6d781 100644
--- a/po/es_ES.po
+++ b/po/es_ES.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs at 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 at ventoso.org>\n"
 "Language-Team: Spanish <vdr at linuxtv.org>\n"
@@ -724,6 +724,70 @@ msgstr "
 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 --git a/po/et_EE.po b/po/et_EE.po
index 8539f17..8b7c913 100644
--- a/po/et_EE.po
+++ b/po/et_EE.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs at 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 at gmail.com>\n"
 "Language-Team: Estonian <vdr at linuxtv.org>\n"
@@ -723,6 +723,70 @@ msgstr "Palun sisestada %d numbrit!"
 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 @@ msgid "Setup.LNB$own"
 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 @@ msgstr "ESITUS"
 
 #, c-format
 msgid "Moving dish to %.1f..."
-msgstr ""
+msgstr "Antenni pööramine positsioonile %.1f..."
 
 msgid "ST:TNG Panels"
 msgstr "ST:TNG Panels"
diff --git a/po/fi_FI.po b/po/fi_FI.po
index 84c50a6..151589d 100644
--- a/po/fi_FI.po
+++ b/po/fi_FI.po
@@ -11,7 +11,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs at 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 at gmail.com>\n"
 "Language-Team: Finnish <vdr at linuxtv.org>\n"
@@ -727,6 +727,70 @@ msgstr "Syötä %d numeroa!"
 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 @@ msgid "Setup.LNB$own"
 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 @@ msgstr "TOISTO"
 
 #, c-format
 msgid "Moving dish to %.1f..."
-msgstr ""
+msgstr "Käännetaan lautasta %.1f..."
 
 msgid "ST:TNG Panels"
 msgstr "ST:TNG konsoli"
diff --git a/po/fr_FR.po b/po/fr_FR.po
index a4eda2f..a6e0e9a 100644
--- a/po/fr_FR.po
+++ b/po/fr_FR.po
@@ -17,7 +17,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs at 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 at free.fr>\n"
 "Language-Team: French <vdr at linuxtv.org>\n"
@@ -733,6 +733,70 @@ msgstr "Veuillez entrer %d chiffres !"
 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 --git a/po/hr_HR.po b/po/hr_HR.po
index baafe9d..23f79a1 100644
--- a/po/hr_HR.po
+++ b/po/hr_HR.po
@@ -9,7 +9,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs at 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 at sysphere.org>\n"
 "Language-Team: Croatian <vdr at linuxtv.org>\n"
@@ -725,6 +725,70 @@ msgstr "Molim unesite %d znamenki!"
 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 --git a/po/hu_HU.po b/po/hu_HU.po
index b3c17b0..1b4ac52 100644
--- a/po/hu_HU.po
+++ b/po/hu_HU.po
@@ -10,7 +10,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs at 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 at tigercomp.ro>\n"
 "Language-Team: Hungarian <vdr at linuxtv.org>\n"
@@ -727,6 +727,70 @@ msgstr "Üssön be %d számot!"
 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 --git a/po/it_IT.po b/po/it_IT.po
index e579885..cc5a567 100644
--- a/po/it_IT.po
+++ b/po/it_IT.po
@@ -11,8 +11,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs at 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 at tiscali.it>\n"
 "Language-Team: Italian <vdr at linuxtv.org>\n"
 "Language: it\n"
@@ -730,6 +730,70 @@ msgstr "Inserisci %d cifre!"
 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 @@ msgid "Setup.LNB$own"
 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 @@ msgstr "RIPRODUCI"
 
 #, c-format
 msgid "Moving dish to %.1f..."
-msgstr ""
+msgstr "Muovi antenna verso %.1f..."
 
 msgid "ST:TNG Panels"
 msgstr "Consolle ST:TNG"
diff --git a/po/lt_LT.po b/po/lt_LT.po
index 34e273c..05eb65c 100644
--- a/po/lt_LT.po
+++ b/po/lt_LT.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs at 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 at ambernet.lt>\n"
 "Language-Team: Lithuanian <vdr at linuxtv.org>\n"
@@ -723,6 +723,70 @@ msgstr "Įveskite %d skaičius!"
 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 --git a/po/mk_MK.po b/po/mk_MK.po
index a398b3c..0bceb1b 100644
--- a/po/mk_MK.po
+++ b/po/mk_MK.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs at 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 at gmail.com>\n"
 "Language-Team: Macedonian <en at li.org>\n"
@@ -724,6 +724,70 @@ msgstr "Внеси %d цифри!"
 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 --git a/po/nl_NL.po b/po/nl_NL.po
index e9b9a00..6b2d122 100644
--- a/po/nl_NL.po
+++ b/po/nl_NL.po
@@ -12,7 +12,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs at 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 at telfort.nl>\n"
 "Language-Team: Dutch <vdr at linuxtv.org>\n"
@@ -728,6 +728,70 @@ msgstr "Vul %d cijfers in!"
 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 --git a/po/nn_NO.po b/po/nn_NO.po
index a05410f..b338ba7 100644
--- a/po/nn_NO.po
+++ b/po/nn_NO.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs at 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 at slevigen.no>\n"
 "Language-Team: Norwegian Nynorsk <vdr at linuxtv.org>\n"
@@ -724,6 +724,70 @@ msgstr ""
 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 --git a/po/pl_PL.po b/po/pl_PL.po
index f240894..28ba68e 100644
--- a/po/pl_PL.po
+++ b/po/pl_PL.po
@@ -9,7 +9,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs at 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 at gmail.com>\n"
 "Language-Team: Polish <vdr at linuxtv.org>\n"
@@ -725,6 +725,70 @@ msgstr "Prosz
 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 --git a/po/pt_PT.po b/po/pt_PT.po
index 7dde8c5..9f04d87 100644
--- a/po/pt_PT.po
+++ b/po/pt_PT.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs at 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 at gmail.com>\n"
 "Language-Team: Portuguese <vdr at linuxtv.org>\n"
@@ -724,6 +724,70 @@ msgstr "Por favor introduza %d d
 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 --git a/po/ro_RO.po b/po/ro_RO.po
index f49c452..f250a41 100644
--- a/po/ro_RO.po
+++ b/po/ro_RO.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs at 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 at users.sorceforge.net>\n"
 "Language-Team: Romanian <vdr at linuxtv.org>\n"
@@ -725,6 +725,70 @@ msgstr "Vă rog introduceţi %d cifre!"
 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 --git a/po/ru_RU.po b/po/ru_RU.po
index 7856560..3b4339f 100644
--- a/po/ru_RU.po
+++ b/po/ru_RU.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs at 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 at gmail.com>\n"
 "Language-Team: Russian <vdr at linuxtv.org>\n"
@@ -724,6 +724,70 @@ msgstr "
 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 --git a/po/sk_SK.po b/po/sk_SK.po
index 6e849b0..bd7fe2b 100644
--- a/po/sk_SK.po
+++ b/po/sk_SK.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs at 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 at gmail.com>\n"
 "Language-Team: Slovak <vdr at linuxtv.org>\n"
@@ -723,6 +723,70 @@ msgstr "Pros
 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 --git a/po/sl_SI.po b/po/sl_SI.po
index 6e9ca32..99f2ec1 100644
--- a/po/sl_SI.po
+++ b/po/sl_SI.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs at 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 at guest.arnes.si>\n"
 "Language-Team: Slovenian <vdr at linuxtv.org>\n"
@@ -724,6 +724,70 @@ msgstr "Prosim vnesite %d 
 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 --git a/po/sr_RS.po b/po/sr_RS.po
index 12586f7..fe5f3cb 100644
--- a/po/sr_RS.po
+++ b/po/sr_RS.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs at 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 at gmail.com>\n"
 "Language-Team: Serbian <vdr at linuxtv.org>\n"
@@ -724,6 +724,70 @@ msgstr "Molimo unesite %d brojeve!"
 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 --git a/po/sv_SE.po b/po/sv_SE.po
index 58492c4..05f6a3b 100644
--- a/po/sv_SE.po
+++ b/po/sv_SE.po
@@ -11,7 +11,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs at 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 at boomer.se>\n"
 "Language-Team: Swedish <vdr at linuxtv.org>\n"
@@ -727,6 +727,70 @@ msgstr "Mata in %d siffror!"
 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 --git a/po/tr_TR.po b/po/tr_TR.po
index d920180..5c9f44e 100644
--- a/po/tr_TR.po
+++ b/po/tr_TR.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs at 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 at yahoo.de>\n"
 "Language-Team: Turkish <vdr at linuxtv.org>\n"
@@ -723,6 +723,70 @@ msgstr "L
 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 --git a/po/uk_UA.po b/po/uk_UA.po
index 829921d..a09fcc7 100644
--- a/po/uk_UA.po
+++ b/po/uk_UA.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs at 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 at gmail.com>\n"
 "Language-Team: Ukrainian <vdr at linuxtv.org>\n"
@@ -724,6 +724,70 @@ msgstr "Введіть %d цифри!"
 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 --git a/po/zh_CN.po b/po/zh_CN.po
index 8ba5e4d..9746e83 100644
--- a/po/zh_CN.po
+++ b/po/zh_CN.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: VDR 2.0.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs at 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 at live.com>\n"
 "Language-Team: Chinese (simplified) <nfvdr at live.com>\n"
@@ -725,6 +725,70 @@ msgstr "请输入 %d 数字!"
 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 --git a/positioner.c b/positioner.c
index 7adcc73..8b9d270 100644
--- a/positioner.c
+++ b/positioner.c
@@ -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::NormalizeAngle(int Angle)
 
 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 --git a/recorder.c b/recorder.c
index 047dd19..7887a88 100644
--- a/recorder.c
+++ b/recorder.c
@@ -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::Receive(uchar *Data, int Length)
 
 void cRecorder::Action(void)
 {
-  time_t t = time(NULL);
+  cTimeMs t(MAXBROKENTIMEOUT);
   bool InfoWritten = false;
   bool FirstIframeSeen = false;
   while (Running()) {
@@ -160,16 +160,16 @@ void cRecorder::Action(void)
                        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 --git a/recording.c b/recording.c
index eb3b466..e41a89e 100644
--- a/recording.c
+++ b/recording.c
@@ -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 @@ cRemoveDeletedRecordingsThread::cRemoveDeletedRecordingsThread(void)
 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 @@ void cRemoveDeletedRecordingsThread::Action(void)
          }
      if (deleted) {
         const char *IgnoreFiles[] = { SORTMODEFILE, NULL };
-        RemoveEmptyVideoDirectories(IgnoreFiles);
+        cVideoDirectory::RemoveEmptyVideoDirectories(IgnoreFiles);
         }
      }
 }
@@ -145,9 +149,9 @@ void AssertFreeDiskSpace(int Priority, bool Force)
   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 @@ void cRecordingInfo::SetFramesPerSecond(double FramesPerSecond)
   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 @@ cRecording::cRecording(cTimer *Timer, const cEvent *Event)
   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 @@ cRecording::cRecording(const char *FileName)
   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 *cRecording::SortName(void) const
 {
   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 @@ char *cRecording::SortName(void) const
 
 void cRecording::ClearSortName(void)
 {
-  DELETENULL(sortBufferName);
-  DELETENULL(sortBufferTime);
+  free(sortBufferName);
+  free(sortBufferTime);
+  sortBufferName = sortBufferTime = NULL;
 }
 
 int cRecording::GetResume(void) const
@@ -982,6 +994,28 @@ int cRecording::Compare(const cListObject &ListObject) const
   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 *cRecording::FileName(void) const
      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::Title(char Delimiter, bool NewIndicator, int Level) cons
 
 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::IsEdited(void) const
 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 @@ void cRecording::ReadInfo(void)
   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 @@ void cRecording::SetStartTime(time_t Start)
   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 @@ bool cRecording::Delete(void)
      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 @@ bool cRecording::Remove(void)
      return false;
      }
   isyslog("removing recording %s", FileName());
-  return RemoveVideoFile(FileName());
+  return cVideoDirectory::RemoveVideoFile(FileName());
 }
 
 bool cRecording::Undelete(void)
@@ -1177,7 +1279,7 @@ bool cRecording::Undelete(void)
      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 @@ bool cRecording::Undelete(void)
   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 @@ void cRecordings::Action(void)
 const char *cRecordings::UpdateFileName(void)
 {
   if (!updateFileName)
-     updateFileName = strdup(AddDirectory(VideoDirectory, ".update"));
+     updateFileName = strdup(AddDirectory(cVideoDirectory::Name(), ".update"));
   return updateFileName;
 }
 
@@ -1261,7 +1374,7 @@ void cRecordings::Refresh(bool Foreground)
   Clear();
   ChangeState();
   Unlock();
-  ScanVideoDir(VideoDirectory, Foreground);
+  ScanVideoDir(cVideoDirectory::Name(), Foreground);
 }
 
 void cRecordings::ScanVideoDir(const char *DirName, bool Foreground, int LinkLevel)
@@ -1417,8 +1530,10 @@ double cRecordings::MBperMinute(void)
          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 @@ double cRecordings::MBperMinute(void)
   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 @@ void cRecordings::ClearSortNames(void)
       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 @@ bool cMark::Save(FILE *f)
 
 // --- 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 @@ cUnbufferedFile *cFileName::Open(void)
      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 @@ cUnbufferedFile *cFileName::Open(void)
 void cFileName::Close(void)
 {
   if (file) {
-     if (CloseVideoFile(file) < 0)
+     if (file->Close() < 0)
         LOG_ERROR_STR(fileName);
+     delete file;
      file = NULL;
      }
 }
diff --git a/recording.h b/recording.h
index 2b76762..3b00c71 100644
--- a/recording.h
+++ b/recording.h
@@ -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 int DirectoryNameMax;
 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 @@ public:
   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 @@ public:
   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 @@ public:
   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 @@ public:
        ///< 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 @@ public:
        ///< 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 @@ public:
   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 @@ private:
   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 --git a/shutdown.c b/shutdown.c
index 0a36a15..97d056e 100644
--- a/shutdown.c
+++ b/shutdown.c
@@ -6,7 +6,7 @@
  *
  * Original version written by Udo Richter <udo_richter at 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 @@ bool cShutdownHandler::ConfirmShutdown(bool Interactive)
         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::ConfirmShutdown(bool Interactive)
 
 bool cShutdownHandler::ConfirmRestart(bool Interactive)
 {
-  if (cCutter::Active()) {
+  if (RecordingsHandler.Active()) {
      if (!Interactive || !Interface->Confirm(tr("Editing - restart anyway?")))
         return false;
      }
diff --git a/svdrp.c b/svdrp.c
index 1cb87e4..7f91479 100644
--- a/svdrp.c
+++ b/svdrp.c
@@ -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 @@ const char *HelpPages[] = {
   "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 @@ void cSVDRP::CmdDELC(const char *Option)
      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 @@ void cSVDRP::CmdEDIT(const char *Option)
         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 @@ void cSVDRP::CmdREMO(const char *Option)
      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 @@ void cSVDRP::CmdSTAT(const char *Option)
   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 @@ void cSVDRP::Execute(char *Cmd)
   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 --git a/svdrp.h b/svdrp.h
index fcba1fb..96247e5 100644
--- a/svdrp.h
+++ b/svdrp.h
@@ -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 @@ private:
   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 --git a/tools.c b/tools.c
index d881827..a2055ec 100644
--- a/tools.c
+++ b/tools.c
@@ -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 @@ char *strreplace(char *s, const char *s1, const char *s2)
   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 @@ char *compactspace(char *s)
   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 @@ cString::cString(const char *S, bool TakePointer)
   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 @@ cString &cString::Truncate(int Index)
   return *this;
 }
 
+cString &cString::CompactChars(char c)
+{
+  compactchars(s, c);
+  return *this;
+}
+
 cString cString::sprintf(const char *fmt, ...)
 {
   va_list ap;
diff --git a/tools.h b/tools.h
index 9cfd152..358f75e 100644
--- a/tools.h
+++ b/tools.h
@@ -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 @@ private:
   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 @@ public:
   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 *strcpyrealloc(char *dest, const char *src);
 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 @@ inline char *skipspace(const char *s)
 }
 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 --git a/vdr.c b/vdr.c
index bf581a6..083d838 100644
--- a/vdr.c
+++ b/vdr.c
@@ -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 @@ int main(int argc, char *argv[])
 
   // Directories:
 
-  SetVideoDirectory(VideoDirectory);
+  cVideoDirectory::SetName(VideoDirectory);
   if (!ConfigDirectory)
      ConfigDirectory = DEFAULTCONFDIR;
   cPlugin::SetConfigDirectory(ConfigDirectory);
@@ -1239,7 +1239,7 @@ int main(int argc, char *argv[])
              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 @@ int main(int argc, char *argv[])
         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 @@ int main(int argc, char *argv[])
               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 @@ Exit:
 
   PluginManager.StopPlugins();
   cRecordControls::Shutdown();
-  cCutter::Stop();
+  RecordingsHandler.DelAll();
   delete Menu;
   cControl::Shutdown();
   delete Interface;
@@ -1406,6 +1410,7 @@ Exit:
      }
   cDevice::Shutdown();
   cPositioner::DestroyPositioner();
+  cVideoDirectory::Destroy();
   EpgHandlers.Clear();
   PluginManager.Shutdown(true);
   cSchedules::Cleanup(true);
diff --git a/videodir.c b/videodir.c
index 9ad31b6..932f8ce 100644
--- a/videodir.c
+++ b/videodir.c
@@ -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);
+  return FreeDiskSpaceMB(Name(), UsedMB);
 }
 
-#ifdef DEPRECATED_DISTRIBUTED_VIDEODIR
-bool cVideoDirectory::Next(void)
+const char *cVideoDirectory::Name(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 name;
 }
 
-void cVideoDirectory::Store(void)
+void cVideoDirectory::SetName(const char *Name)
 {
-  if (name) {
-     free(stored);
-     stored = strdup(name);
-     }
+  name = Name;
 }
 
-const char *cVideoDirectory::Adjust(const char *FileName)
+bool cVideoDirectory::Register(const char *FileName)
 {
-  if (stored) {
-     free(adjusted);
-     adjusted = strdup(FileName);
-     return strncpy(adjusted, stored, length);
+  // Incoming name must be in base video directory:
+  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 false;
      }
-  return NULL;
+  return true;
 }
-#endif
 
-cUnbufferedFile *OpenVideoFile(const char *FileName, int Flags)
+bool cVideoDirectory::Rename(const char *OldName, const char *NewName)
 {
-  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);
-     errno = ENOENT; // must set 'errno' - any ideas for a better value?
-     return NULL;
+  dsyslog("renaming '%s' to '%s'", OldName, NewName);
+  if (rename(OldName, NewName) == -1) {
+     LOG_ERROR_STR(NewName);
+     return false;
      }
-#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 true;
+}
+
+bool cVideoDirectory::Move(const char *FromName, const char *ToName)
+{
+  dsyslog("moving '%s' to '%s'", FromName, ToName);
+  if (EntriesOnSameFileSystem(FromName, ToName)) {
+     if (rename(FromName, ToName) == -1) {
+        LOG_ERROR_STR(ToName);
+        return false;
         }
      }
-#endif
-  cUnbufferedFile *File = cUnbufferedFile::Create(ActualFileName, Flags, DEFFILEMODE);
-  if (ActualFileName != FileName)
-     free((char *)ActualFileName);
-  return File;
+  else
+     return RecordingsHandler.Add(ruMove, FromName, ToName);
+  return true;
 }
 
-int CloseVideoFile(cUnbufferedFile *File)
+bool cVideoDirectory::Remove(const char *Name)
 {
-  int Result = File->Close();
-  delete File;
-  return Result;
+  return RemoveFileOrDir(Name);
 }
 
-bool RenameVideoFile(const char *OldName, const char *NewName)
+void cVideoDirectory::Cleanup(const char *IgnoreFiles[])
 {
-  // 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;
-     }
-  return true;
+  RemoveEmptyDirectories(Name(), false, IgnoreFiles);
 }
 
-bool RemoveVideoFile(const char *FileName)
+bool cVideoDirectory::Contains(const char *Name)
 {
-  return RemoveFileOrDir(FileName, true);
+  return EntriesOnSameFileSystem(this->Name(), Name);
 }
 
-bool VideoFileSpaceAvailable(int SizeMB)
+cUnbufferedFile *cVideoDirectory::OpenVideoFile(const char *FileName, int Flags)
 {
-  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;
+  if (Current()->Register(FileName))
+     return cUnbufferedFile::Create(FileName, Flags, DEFFILEMODE);
+  return NULL;
 }
 
-int VideoDiskSpace(int *FreeMB, int *UsedMB)
+bool cVideoDirectory::RenameVideoFile(const char *OldName, const char *NewName)
 {
-  int free = 0, used = 0;
+  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 @@ int VideoDiskSpace(int *FreeMB, int *UsedMB)
   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 @@ cString PrefixVideoFileName(const char *FileName, char Prefix)
   return NULL;
 }
 
-void RemoveEmptyVideoDirectories(const char *IgnoreFiles[])
+void cVideoDirectory::RemoveEmptyVideoDirectories(const char *IgnoreFiles[])
 {
-  cVideoDirectory Dir;
-#ifdef DEPRECATED_DISTRIBUTED_VIDEODIR
-  do {
-#endif
-     RemoveEmptyDirectories(Dir.Name(), false, IgnoreFiles);
-#ifdef DEPRECATED_DISTRIBUTED_VIDEODIR
-     } while (Dir.Next());
-#endif
+  Current()->Cleanup(IgnoreFiles);
 }
 
-bool IsOnVideoDirectoryFileSystem(const char *FileName)
+bool cVideoDirectory::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 @@ bool cVideoDiskUsage::HasChanged(int &State)
 {
   if (time(NULL) - lastChecked > DISKSPACECHEK) {
      int FreeMB;
-     int UsedPercent = VideoDiskSpace(&FreeMB);
+     int UsedPercent = cVideoDirectory::VideoDiskSpace(&FreeMB);
      if (FreeMB != freeMB) {
         usedPercent = UsedPercent;
         freeMB = FreeMB;
diff --git a/videodir.h b/videodir.h
index 35c1a55..f520d77 100644
--- a/videodir.h
+++ b/videodir.h
@@ -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:

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-vdr-dvb/vdr.git



More information about the pkg-vdr-dvb-changes mailing list