#!/usr/bin/perl
#
# gigaset  --  Verwalten und Ansehen der Aufzeichnungen der Gigaset DVB-T Box M740AV
#
# Copyright (c) 2005  Dr. Andy Spiegl <gigaset.andy@spiegl.de>
#  All rights reserved.
#
############################################
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2, or any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# for more details: http://www.gnu.org/copyleft/gpl.html
#
############################################
#
# Mehr Info zur Gigaset DVB-T Box M740AV:
#  http://www.m740.de/
#
# Forumsthread zu diesem Perlscript:
#  http://www.m740.de/forum/showthread.php?p=14433
#
############################################
#
# History:
#
# v0.1  2005-06-19: erste Version
# v0.2  2005-06-21: Umstellung auf OO, Methode view
# v0.3  2005-06-22: Methoden dump, rm, mv, export
# v0.4  2005-06-23: Methoden export, demux, mux, text
# v0.5  2005-06-27: Fehler beim Löschen und Verschieben beseitigt
# v0.6  2005-06-29: fmpg-Datei auslesen, Methode checkFiles
# v0.61 2005-06-30: kleiner Bug bei den Pfadangaben
# v0.62 2005-06-30: Sonderzeichen aus EPG entfernen
# v0.63 2005-06-30: übrige pre-Cuttermaran Dateien nach dem MUXen löschen
# v0.7  2005-06-30: Liste der Sendernamen einlesen und verwenden
# v0.71 2005-06-30: Methode check
# v0.72 2005-06-30: kein Verschieben/Löschen von laufenden Aufnahmen, sonst Warnungen
# v0.73 2005-07-01: mplayer Optionen gegen Audio-Delay und für Deinterlacing
# v0.8  2005-07-01: Methode timeshift, um timeshift-Aufnahmen anzuschauen
# v0.81 2005-07-04: Cuttermarans *.info Datei auch löschen
# v0.82 2005-07-11: "Kurzbeschreibung" in Textexport, Tippfehler in MAN-Page
# v0.83 2005-07-11: text/info exportiert nicht mehr, sondern nun textexport/save
# v0.9  2005-07-11: Code aufgeräumt
# v0.91 2005-07-11: MAN-Page aktualisiert und verbessert
# v0.92 2005-07-11: "manuelle Serie" in REC_FLAG
# v0.93 2005-07-11: projectX nicht mehr über Shell-Skript aufrufen
# v0.94 2005-07-11: demuxen beschleunigt (TS-Export überspringen)
# v0.95 2005-07-11: SCR auswerten (-> weniger mpg-Dateien zu berücksichtigen)
# v0.96 2005-07-15: berücksichtigen, dass SCR_END = 0 während laufender Aufnahme
# v0.97 2005-07-18: Fehler in regexp beim Command-Parsen (textexport wurde als text interpretiert)
# v0.98 2005-08-01: Aufnahmen mit Aufnahmefehler konnten nicht gelöscht werden
# v0.99 2005-08-02: eps_mapping.txt einlesen und parsen
# v1.00 2005-08-14: Mini-Bug in If-Abfrage bei Aufnahmen mit Fehler
# v1.01 2005-08-23: neue Option --yes um Sicherheitsnachfragen zu vermeiden
# v1.02 2005-08-24: neue Methode PS (MPG-Export)
# v1.03 2005-08-24: neue Methode divx (DivX-Export mithilfe von mencoder)
# v1.04 2005-08-25: neue Methode divxfast (mencoder 1-Pass Encoding)
#                    mit Hinweis, wie man wieder in ein PS für die Box wandelt
# v1.05 2005-09-14: neues Default-BASEDIR /data/movies/gigaset/
# v1.06 2005-09-21: Fileextension .crid kann nun auch weggelassen werden
# v1.07 2005-10-24: "play": alle MPEGs auf einmal an mplayer übergeben
# v1.08 2005-10-24: Berücksichtigung von evtl. mehreren Audiospuren
# v1.09 2005-10-25: Hilfeseiten aktualisiert
# v1.10 2005-11-17: neue Option --dontdel um temporäre Dateien NICHT zu löschen
# v1.11 2005-12-20: Dokumentation ergänzt
# v1.12 2005-12-20: mehr Infos zu "seltsame Blocklänge"
# v1.21 2006-01-02: korrektes chronologisches Sortieren der Aufnahmen
# v1.22 2006-01-07: "move" hatte seit v1.06 nicht mehr funktioniert, ups
# v1.23 2006-01-16: Bug beim muxen von mehreren crids beseitigt
# v1.24 2006-01-19: neue Option --nocolor,--nc für nicht-farbige Ausgabe
# v1.25 2006-01-19: neue Methode ids/crids: gibt nur crid-IDs aus
# v1.26 2006-01-20: ls,dir spuckte unsinnige Fehlermeldung aus
# v1.27 2006-01-20: neue Option --force,--overwrite zum Überschreiben vorhandener Dateien
# v1.28 2006-01-23: neue Option --nowarnings zum Unterdrücken der Warnungen
# v1.29 2006-01-23: konsequente Exitcodes
# v1.30 2006-01-23: neue Option --search zum Suchen in den Beschreibungen
# v1.31 2006-01-23: ls gibt auch Kurzbeschreibung aus
# v1.32 2006-02-07: Hinweis in der Manpage auf dvbcut
# v1.33 2006-03-08: Abbruch durch Ctrl-C während Benutzereingabe möglich
# v1.34 2006-03-17: Manpage korrigiert, neue Option --commands (für zsh-Completion)
# v1.35 2006-03-21: Ergänzung der channelnames
# v1.36 2006-03-24: Default-Channelnames, die von eps_mapping.txt nur überschrieben wird
# v1.37 2006-03-24: Hinweis auf eps_mapping.txt in der Man-Page
# v1.4  2006-03-24: Upgrade auf ProjectX_Source_0.90.3.01, Achtung: .mpa -> .mp2
# v1.41 2006-03-24: kleiner Bug bei der Behandlung von mehreren Tonspuren
# v1.42 2006-03-25: Logikänderung: wenn keine crids angegeben sind, immer ALLE behandeln
# v1.43 2006-03-27: neue Methode title/oneliner/short: crid-Infos in nur 1 Zeile
# v1.44 2006-03-28: funktioniert nun super zusammen mit der zsh-Completion
# v1.45 2006-04-07: korrigierte Einstellungen für das neue ProjectX
# v1.46 2006-04-25: neue Methode: copy/cp als Ergänzung zu move/mv
# v1.47 2006-04-25: neue Methode: px/projectX lädt Aufnahme direkt im GUI von ProjectX
# v1.48 2006-04-25: Rewrite von schrecklichem Code
# v1.49 2006-04-25: Sprache von ProjectX auf Deutsch voreingestellt
# v1.50 2006-04-25: Bug nach Abfrage beim Verschieben
# v1.51 2006-04-25: short-Ausgabe passt sich an Terminalbreite an ($COLUMNS)
# v1.52 2006-05-08: keine Warnungen bei falschen "commands" (Danke an Erik Kunze)
# v1.53 2006-05-10: keine Klammern in Dateinamen erlauben (Danke an Josef)
# v1.54 2006-05-10: Anpassungen für Windows (Pfade zu Tools, SIGs, Farben, ...)
# v1.55 2006-06-12: neue Option --neverdie: bei Fehlern weitermachen
# v1.6  2006-09-12: Anpassung an die neue Aufnahmeordner-Struktur
# v1.61 2006-09-14: Anpassung der Blocklänge an die neue Firmware 2.3.x
# v1.62 2007-01-03: verschobenes Unterverzeichnis kann natürlich nicht gelöscht werden
# v1.63 2008-02-03: Patch für UTF-8 von Erik Kunze (danke!)
# v1.64 2008-11-05: wirklich nicht beenden bei --neverdie
# v1.65 2009-09-28: neues Default-BASEDIR /data/video/gigaset/
# v1.66 2009-12-27: bei Blocklänge 10980000 nicht mehr warnen
# v1.67 2009-12-27: "info" um PAUSE_OFFSET erweitert
# v1.68 2009-12-27: Upgrade auf ProjectX_Source_0.90.4, als DEB-Paket
# v1.69 2009-12-27: Hinweis auf A/V-Sync Probleme in der Doku
# v1.70 2009-12-27: Doku zu Command "mpg/ps" korrigiert
# v1.71 2010-01-01: ProjectX mag keine Punkte in Dateinamen
# v1.80 2010-03-17: avoid usage of external perl module Text::Iconv (thanks to Frédéric Devernay)
# v1.81 2010-06-02: special basedir for running on condor
# v1.82 2010-06-18: added "Aufnahme abgebrochen" to @status_msg
#
############################################

my $VERSION = "1.82";

use strict;
use warnings;

# turn off buffering (sinnvoll für Debugging)
$| = 1;

# some self detection
my $self = $0; $self =~ s|.*/||;

my $hostname;
if ($^O ne "MSWin32")
{
  $hostname = `hostname -f`;
  chomp $hostname;
}
else
{
  $hostname = "windows host";        # could do better here :-)
}
$hostname="none" unless $hostname;

############################################
# konfigurierbare VARIABLEN
############################################
# Basis-Arbeitsverzeichnis
my $BASEDIR     = "/data/video/gigaset/";
if ($hostname eq "condor")
{
  $BASEDIR = "/home/andy/tmp/nobackup/gigaset/";
  print "WARNING: running on condor --> Setting special basedir: $BASEDIR\n";
}

# wo liegen die crid-Dateien und Aufnahmen
my $CRIDDIR     = $BASEDIR . "PVR/";

# wohin wird exportiert
my $EXPORTDIR   = $BASEDIR;

# wo sind die fertig geschnittenen (noch nicht gemuxten) Filme
my $CUTDIR      = $BASEDIR . "cut/";

# wo sind die fertig geschnittenen und gemuxten MPG-Filme
my $MPGDIR      = $BASEDIR . "fertig/";

# Pfad und Parameter zu mplayer (und Ausgleich eines kleinen A/V-Versatzes)
my $mplayer = "mplayer -really-quiet -delay -0.200 -vf pp=linblenddeint -fs";

# Pfad zum projectX Verzeichnis
#my $projectXdir = $ENV{HOME} . "/local/ProjectX_Source_0.82.1.00";
#my $projectXdir = $ENV{HOME} . "/local/ProjectX_Source_0.90.3.01";
#my $projectXdir = $ENV{HOME} . "/local/ProjectX_Source_0.90.4";

# Pfad zur Senderliste (Kopie von /var/etc/eps_mapping.txt)
# (Ergänzungen stehen auch in /var/etc/services.txt)
my $channellist = $ENV{HOME} . "/movies/gigaset/eps_mapping.txt";

# Pfad und Parameter zu mplex
my $mplex = "mplex -f 8";

# Pfad und Parameter zu mencoder
my $mencoder = "mencoder";
my $mencoder_VOP="";                   # nicht skalieren
#my $mencoder_VOP="-vop scale=768:576"; # sollte das eigentlich nicht 720x576 sein?
#my $mencoder_VOP="-vop scale=640:480";  # reicht wohl auch
my $mencoder_VBITRATE="1200";   # 2000?  6000?
my $mencoder_divx_opts1="-ovc frameno -o frameno.avi -oac mp3lame -lameopts cbr:br=128 -";
my $mencoder_divx_opts2="-o /dev/null -oac copy -ovc lavc -lavcopts aspect=4/3:vcodec=mpeg4:vbitrate=$mencoder_VBITRATE:vhq:vqmin=2:vqmax=31:vlelim=-4:vcelim=9:lumi_mask=0.05:dark_mask=0.01:vpass=1 $mencoder_VOP -";
my $mencoder_divx_opts3=             "-oac copy -ovc lavc -lavcopts aspect=4/3:vcodec=mpeg4:vbitrate=$mencoder_VBITRATE:vhq:vqmin=2:vqmax=31:vlelim=-4:vcelim=9:lumi_mask=0.05:dark_mask=0.01:vpass=2 $mencoder_VOP -";
my $mencoder_divxfast_opts="-oac mp3lame -lameopts cbr:br=128 -ovc lavc -lavcopts aspect=4/3:vcodec=mpeg4:vbitrate=$mencoder_VBITRATE:vhq:vqmin=2:vqmax=31:vlelim=-4:vcelim=9:lumi_mask=0.05:dark_mask=0.01 -";

# wo sind die fertig geschnittenen und gemuxten MPG-Filme
my $TMPDIR      = $ENV{TMPDIR};
$TMPDIR         = "/tmp" unless $TMPDIR;

# weitere externe Programme
my $ls = "ls -lh";
my $cp = "cp -a";
my $mv = "mv";
my $rm = "rm";
my $rm_r = "rm -r";

if ($^O eq "MSWin32")
{
  $ls = "dir";
  $cp = "copy";
  $mv = "move";
  $rm = "del";
  $rm_r = "deldir";
}

############################################
# text colors
############################################
my ($bold, $underline, $half, $black, $red, $green, $yellow, $blue, $magenta, $cyan, $grey, $norm);

if (-t STDOUT)              # if printing to console (not redirecting to a file)
{
  $bold        = "\x1b[1;1m";
  $underline   = "\x1b[4;4m";
  $half        = "\x1b[2;2m";
  $black       = "\x1b[1;30m";
  $red         = "\x1b[1;31m";
  $green       = "\x1b[1;32m";
  $yellow      = "\x1b[1;33m";
  $blue        = "\x1b[1;34m";
  $magenta     = "\x1b[1;35m";
  $cyan        = "\x1b[1;36m";
  $grey        = "\x1b[1;37m";
  $norm        = "\x1b[0;0m";
}
else
{
 $bold = $underline = $half = $norm = "";
 $black = $red = $green = $yellow = $blue = $magenta = $cyan = $grey ="";
}


############################################
############################################
package crid;
############################################
############################################

use strict;
use warnings;

use Data::Dumper;

my $_DEBUGGING = 0;
my $_NEVERDIE = 0;

# verbose messages
my @status_msg = ("unknown","noch nicht aufgenommen","während der Aufnahme",
                  "fertig aufgenommen","Aufnahmefehler","Aufnahme abgebrochen");
my @lockflag_msg = ("nicht gesperrt","gesperrt");

# Konstante: Länge eines Aufnahmeblocks in 90KHz
my $BLOCKLENGTHOLD = 54090000;     # 10 Minuten pro Block (90150 pro Sekunde)
my $BLOCKLENGTHNEW = 10890000;     # ca. 2 Minuten pro Block
my $BLOCKLENGTHNEW2 = 10980000;    # kommt manchmal vor

# constructor
sub new
{
  my $class = shift;
  my $self  = {};

  bless ($self, $class);

  if (@_) { $self->read_crid(shift) };

  return $self;
}

sub debug
{
  my $self = shift;
  warn "usage: crid->debug(level)"    unless @_ == 1;
  my $level = shift;
  if (ref($self))  {
    $self->{"_DEBUG"} = $level;         # just myself
  } else {
    $_DEBUGGING       = $level;         # whole class
  }
}

sub read_crid
{
  my $self = shift;
  my ($x, $i);

  $self->{FILENAME} = shift;
  print "filename: ". $self->{FILENAME} ."\n"  if $_DEBUGGING;

  $self->{CRIDNAME} = $self->{FILENAME};
  $self->{CRIDNAME} =~ s|.*/||;

  open(CRID, $self->{FILENAME}) or my_die("Aborting: cannot open crid \"". $self->{FILENAME} ."\".\n");
  binmode(CRID);

  # in seine Bestandteile zerlegen (siehe crid-format.txt, BIG-ENDIAN)

  read CRID, $x, 4;
  $self->{VERSIONBYTE}=unpack('N',$x);
  print "versionbyte: ". $self->{VERSIONBYTE} ."\n"  if $_DEBUGGING > 3;
  my_die("Abbruch: unbekannte Dateiversion ($self->{CRIDNAME})\n") unless $self->{VERSIONBYTE} == 2; # "\x00\x00\x00\x02"

  read CRID, $x, 4;
  my $cid1=unpack('N',$x);
  read CRID, $x, 4;
  my $cid2=unpack('N',$x);
  $self->{CID}=$cid1 * 2**32 + $cid2;
  $self->{SENDER_ID}=$self->{CID} % 100000;

  read CRID, $x, 4;
  $self->{STATUS}=unpack('N',$x);

  read CRID, $x, 4;
  $self->{BEGIN_REC}=unpack('N',$x);
  read CRID, $x, 4;
  $self->{END_REC}=unpack('N',$x);

  read CRID, $x, 4;
  $self->{USERACCESSDATA}=unpack('N',$x); # not used, always 0
  read CRID, $x, 4;
  $self->{RECORDINGPREOFFSET}=unpack('N',$x); # not used, always -1
  read CRID, $x, 4;
  $self->{RECORDINGPOSTOFFSET}=unpack('N',$x); # not used, always -1

  read CRID, $x, 4;
  $self->{REC_FLAG}=unpack('N',$x);

  read CRID, $x, 4;
  $self->{SERIES_FLAG}=unpack('N',$x);
  if ($self->{SERIES_FLAG})
  {
    $self->{SERIES_FLAG} = unpack('N',"\xff\xff\xff\xff") - $self->{SERIES_FLAG};
  }

  read CRID, $x, 2;
  $self->{LOCK_FLAG}=unpack('n',$x);

  read CRID, $x, 4;
  $x=unpack('N',$x);
  read CRID, $self->{TITLE}, $x;
  # oder so: $self->{TITLE} = unpack 'L/A*', $crid;
  $self->{TITLE} = &convert_dvbstring($self->{TITLE});

  read CRID, $x, 4;
  $self->{NUM_BLOCKS}=unpack('N',$x);

  my ($h1,$h2);
  for ($i=0; $i<$self->{NUM_BLOCKS}; $i++)
  {
    read CRID, $x, 4;
    $x=unpack('N',$x);
    read CRID, $self->{FILENAME_BASE}[$i], $x;

    read CRID, $x, 4;
    $self->{STARTTIME}[$i]=unpack('N',$x);

    # start-timestamp in 90KHz (90150 pro Sekunde)
    read CRID, $x, 4;
    $h1=unpack('N',$x);
    read CRID, $x, 4;
    $h2=unpack('N',$x);
    $self->{SCR_BEGIN}[$i]=$h1 * 2**32 + $h2;

    # end-timestamp in 90KHz (90150 pro Sekunde)
    read CRID, $x, 4;
    $h1=unpack('N',$x);
    read CRID, $x, 4;
    $h2=unpack('N',$x);
    $self->{SCR_END}[$i]=$h1 * 2**32 + $h2;

    # fmpg-Datei genauer analysieren
    $self->read_fmpg($i);
  }

  read CRID, $x, 4;
  $x=unpack('N',$x);
  read CRID, $self->{SHORTEPG}, $x;
  $self->{SHORTEPG} = &convert_dvbstring($self->{SHORTEPG});

  read CRID, $x, 4;
  $x=unpack('N',$x);
  read CRID, $self->{DESCRIPTION}, $x;
  # Sonderzeichen und Codes entfernen (TODO: besser wäre Umwandeln, aber Info fehlt)
  $self->{DESCRIPTION} = &convert_dvbstring($self->{DESCRIPTION});

  read CRID, $x, 4;
  $self->{PAUSE_OFFSET}=unpack('N',$x);

  while (read CRID, $x, 1)
  {
    print "fill ($self->{CRIDNAME}): \"$x\", ", unpack('C',$x), "\n"  if $_DEBUGGING > 5;
    if ($self->{FILLBYTES})
    {
      $self->{FILLBYTES} .= "," . unpack('C',$x);
    }
    else
    {
      $self->{FILLBYTES} = unpack('C',$x);
    }
  }

  close CRID;
}

sub read_fmpg
{
  my $self = shift;

  my $fmpg = shift;
  my $fmpg_name = $self->{FILENAME_BASE}[$fmpg];
  my $filename = $CRIDDIR . $fmpg_name;
  my $BLOCKLENGTH = $BLOCKLENGTHOLD;

  # auf neue Aufnahmeordner-Struktur mit .rec-Ordner testen
  # (ab Firmware 2.3.x)
  my $new_dir_structure = 0;
  if (-d $CRIDDIR . ".rec/" . $fmpg_name)
  {
    $self->{NEW_DIR_STRUCTURE} = 1;
    $self->{FMPG_DIR} = ".rec/" . $fmpg_name ."/";
    print "neue Aufnahmeordner-Struktur!\n .rec/$fmpg_name/$fmpg_name\n"  if $_DEBUGGING > 1;
    $filename = $CRIDDIR . $self->{FMPG_DIR} . $fmpg_name;
    $BLOCKLENGTH = $BLOCKLENGTHNEW;
  }

  print "basename of fmpgs: $filename\n"  if $_DEBUGGING > 1;

  my $file_length = &main::size_of_file($filename);
  if ($file_length < 0)
  {
    my_die("Abbruch: Fmpg-Datei zu Aufnahme \"". $self->{CRIDNAME} ."\" fehlt.\n");
  }

  if ($file_length % 256 != 0)
  {
    my_die("Abbruch: Fmpg-Datei \"$filename\" zu Aufnahme \"". $self->{CRIDNAME} ."\" hat ungewöhnliche Länge: $file_length.\n");
  }

  # berechne anhand dieser Angaben die Anzahl Blöcke
  # ein eventuell vorhandener unvollständiger Block wird ignoriert
  my $blockCount = $file_length / 256;
  my ($i, $x, $x1, $x2);
  my ($prev_start, $block_length);
  $prev_start = $block_length = 0;

  open(FMPG, $filename) or my_die("Aborting: cannot open fmpg-file \"". $filename ."\".\n");
  binmode(FMPG);

  # in seine Bestandteile zerlegen (siehe fmpg-format.txt, BIG-ENDIAN)
  for ($i=0; $i<$blockCount; $i++)
  {
    # für jeden Block einen neuen Hash anlegen
    my %block;

    read FMPG, $x, 4;
    $x1=unpack('N',$x);
    read FMPG, $x, 4;
    $x2=unpack('N',$x);
    $block{Start} = $x1 * 2**32 + $x2;
    if ($i>0)
    {
      $block_length = $block{Start} - $prev_start;
      if ($block_length != $BLOCKLENGTH and $block_length != $BLOCKLENGTHNEW2)
      {
        my $warnmsg = "seltsame Blocklänge (in Block $i von $blockCount):";
        $warnmsg .= " $block_length (statt $BLOCKLENGTH oder $BLOCKLENGTHNEW2)\n";
        $warnmsg .= " in Datei \"$filename\" von Aufnahme \"". $self->{CRIDNAME} ."\"\n";
        $warnmsg .= "Aufnahmefehler, Gigasetbox-Bug oder vielleicht Gigasetbox-Formatänderung.\n";
        &main::print_warning($warnmsg);
      }
      print "block_length: $block_length\n"  if $_DEBUGGING > 2;
    }
    $prev_start = $block{Start};

    read FMPG, $x, 8;           # Bedeutung noch unbekannt

    read FMPG, $x, 112;
    $x = substr($x, 0, index($x, "\x00"));

    # nur wenn Pfadname nicht leer
    if ($x)
    {
      $block{Name} = $block{PathName} = $x;
      $block{Name} =~ s|.*/||;    # (Gigaset-lokalen) Pfad abschneiden

      print "FMPG_BLOCK[$fmpg,$i]: Start: ", $block{Start}, "\n"  if $_DEBUGGING > 1;
      print "FMPG_BLOCK[$fmpg,$i]: PathName: ", $block{PathName}, "\n"  if $_DEBUGGING > 1;
      print "FMPG_BLOCK[$fmpg,$i]: Name: ", $block{Name}, "\n"  if $_DEBUGGING > 1;

      push @{$self->{FMPG_BLOCKS}[$fmpg]}, \%block;
    }

    read FMPG, $x, 128;         #  Bedeutung noch unbekannt
  }

  close FMPG;
}

###############################################
## methods to access per-object data         ##
##                                           ##
## some basic ones:                          ##
##  With args, they set the value.           ##
##  Without any, they only retrieve it/them. ##
###############################################

sub cridname
{
  my $self = shift;
  if (@_) { $self->{CRIDNAME} = int(shift) }
  return $self->{CRIDNAME};
}

# only numeric values allowed
sub rec_status
{
  my $self = shift;
  if (@_) { $self->{STATUS} = int(shift) }
  return $self->{STATUS};
}

# only numeric values allowed
sub begin_rec
{
  my $self = shift;
  if (@_) { $self->{BEGIN_REC} = int(shift) }
  return $self->{BEGIN_REC};
}

# only numeric values allowed
sub end_rec
{
  my $self = shift;
  if (@_) { $self->{END_REC} = int(shift) }
  return $self->{END_REC};
}

sub rec_flag
{
  my $self = shift;
  if (@_) { $self->{REC_FLAG} = int(shift) }
  return $self->{REC_FLAG};
}

sub series_flag
{
  my $self = shift;
  if (@_) { $self->{SERIES_FLAG} = int(shift) }
  return $self->{SERIES_FLAG};
}

sub lock_flag
{
  my $self = shift;
  if (@_) { $self->{LOCK_FLAG} = int(shift) }
  return $self->{LOCK_FLAG};
}

sub title
{
  my $self = shift;
  if (@_) { $self->{TITLE} = int(shift) }
  return $self->{TITLE};
}

sub description
{
  my $self = shift;
  if (@_) { $self->{DESCRIPTION} = int(shift) }
  return $self->{DESCRIPTION};
}

sub shortepg
{
  my $self = shift;
  if (@_) { $self->{SHORTEPG} = int(shift) }
  return $self->{SHORTEPG};
}

sub pause_offset
{
  my $self = shift;
  if (@_) { $self->{PAUSE_OFFSET} = int(shift) }
  return $self->{PAUSE_OFFSET};
}

sub num_blocks
{
  my $self = shift;
  if (@_) { @{ $self->{NUM_BLOCKS} } = @_ }
  return @{ $self->{NUM_BLOCKS} };
}

sub filename_base
{
  my $self = shift;
  if (@_) { @{ $self->{FILENAME_BASE} } = @_ }
  return @{ $self->{FILENAME_BASE} };
}

sub dumpall
{
  my $self = shift;
  my $i;

  my ($begin_rec_string, $end_rec_string, $series_flag_string, $recflag_string);
  my $sender_id_string;

  $begin_rec_string = &main::timestring($self->{BEGIN_REC});
  $end_rec_string = &main::timestring($self->{END_REC});
  if ($self->{SERIES_FLAG}) {
    $series_flag_string = "Uhrzeit der Programmierung: ";
    $series_flag_string .= &main::timestring($self->{SERIES_FLAG});
  } else {
    $series_flag_string = "keine Serie";
  }

  if ($self->{REC_FLAG}==1) {
    $recflag_string="EPG";
  } elsif ($self->{REC_FLAG}==2) {
    $recflag_string="Timer";
  } elsif ($self->{REC_FLAG}==4) {
    $recflag_string="Serie";
  } elsif ($self->{REC_FLAG}==8) {
    $recflag_string="manuelle Serie";
  } elsif ($self->{REC_FLAG}==32) {
    $recflag_string="manuell";
  } else { $recflag_string="unknown"; }

  $sender_id_string = &main::senderid2txt($self->{SENDER_ID}, 0);

  print << "EOF";
${bold}CRID:$norm $self->{FILENAME}
${bold}versionbyte:$norm $self->{VERSIONBYTE}
${bold}CID:$norm $self->{CID}  (BEGIN_REC * 10000 + SENDER_ID)
${bold}STATUS:$norm $self->{STATUS}  ($status_msg[$self->{STATUS}])
${bold}CHANNEL:$norm $sender_id_string
${bold}BEGIN_REC:$norm $self->{BEGIN_REC}  ($begin_rec_string)
${bold}END_REC:$norm $self->{END_REC}  ($end_rec_string)
${bold}USERACCESSDATA:$norm $self->{USERACCESSDATA}  (immer 0)
${bold}RECORDINGPREOFFSET:$norm $self->{RECORDINGPREOFFSET}  (immer -1)
${bold}RECORDINGPOSTOFFSET:$norm $self->{RECORDINGPOSTOFFSET}  (immer -1)
${bold}REC_FLAG:$norm $self->{REC_FLAG}  ($recflag_string)
${bold}SERIES_FLAG:$norm $self->{SERIES_FLAG}  ($series_flag_string)
${bold}LOCK_FLAG:$norm $self->{LOCK_FLAG}  ($lockflag_msg[$self->{LOCK_FLAG}])
${bold}TITLE:$norm $self->{TITLE}
${bold}SHORTEPG:$norm $self->{SHORTEPG}
${bold}DESCRIPTION:$norm $self->{DESCRIPTION}
EOF

  if ($self->{PAUSE_OFFSET})
  {
    print "${bold}PAUSE_OFFSET:$norm PLAY nach $self->{PAUSE_OFFSET} Sekunden pausiert\n"
  }
  else
  {
    print "${bold}PAUSE_OFFSET:$norm noch nicht angeschaut\n"
  }

  for ($i=0; $i<$self->{NUM_BLOCKS}; $i++)
  {
    print "${bold}FILENAME_BASE[$i]:$norm $self->{FILENAME_BASE}[$i]\n";
    print "${bold}STARTTIME[$i]:$norm $self->{STARTTIME}[$i]\n";
    print "${bold}SCR_BEGIN[$i]:$norm $self->{SCR_BEGIN}[$i]\n";
    print "${bold}SCR_END[$i]:$norm $self->{SCR_END}[$i]\n";
  }

  if (defined $self->{FILLBYTES})
  {
    print "${bold}FILLBYTES:$norm $self->{FILLBYTES}\n";
  }

  print "\n${bold}Zugehörige Dateien:$norm\n ";
  print join("\n ", $self->getAllFiles()), "\n";

  my $missing = $self->checkFiles();
  if ($missing != 0)
  {
    &main::print_error("$missing Datei(en) fehlen.");
  }
  else
  {
    print "Alle nötigen Dateien sind vorhanden.\n";
  }
}

sub checkFiles
{
  my $self = shift;
  my $quiet = shift;            # keine Ausgaben?

  my $i;

  my @files = $self->getAllFiles();
  my $missing=0;

  foreach my $file (@files)
  {
    if (not -f "$CRIDDIR$file")
    {
      &main::print_error("Datei \"$file\" von \"". $self->{CRIDNAME}. "\" fehlt.")  unless $quiet;
      $missing++;
    }
  }

  return $missing;
}

sub terseInfo
{
  my $self = shift;
  my ($begin_rec_string, $end_rec_string, $status_string);

  $begin_rec_string = &main::timestring($self->{BEGIN_REC});
  $end_rec_string = &main::timestring($self->{END_REC});

  $status_string="";
  if ($self->{STATUS} != 3)
  {
    $status_string = "  ($cyan$status_msg[$self->{STATUS}]$norm)";
  }

  if ($self->{LOCK_FLAG})
  {
    $status_string = "  ($red$lockflag_msg[$self->{LOCK_FLAG}]$norm)";
  }

  print "$underline$self->{CRIDNAME}$norm$status_string\n";
  print " $black$begin_rec_string - $end_rec_string$norm, Programm: $cyan". &main::senderid2txt($self->{SENDER_ID}, 1) ."$norm\n";
  print " $green$self->{TITLE}$norm\n";

  if ($self->{SHORTEPG} and $self->{SHORTEPG} ne $self->{TITLE})
  {
    if (length($self->{SHORTEPG}) > 76)
    {
      print " ". substr($self->{SHORTEPG}, 0, 76) . "...\n";
    }
    else
    {
      print " ". $self->{SHORTEPG} . "\n";
    }
  }

  if ($self->{DESCRIPTION})
  {
    if (length($self->{DESCRIPTION}) > 76)
    {
      print " ". substr($self->{DESCRIPTION}, 0, 76) . "...\n";
    }
    else
    {
      print " ". $self->{DESCRIPTION} . "\n";
    }
  }
}

sub oneliner
{
  my ($self, $maxlength, $printcrid) = @_;

  $maxlength ||= 79;            # Default für maximal erlaubte Länge
  $printcrid = 1 unless defined $printcrid; # Default: cridname wird auch gelistet

  my $line = "";
  my $length = 0;
  my $tmpstring;

  # Cridname
  if ($printcrid)
  {
    $line = $self->{CRIDNAME};
    $line =~ s/\.crid$//;       # Extension (.crid) abschneiden
    $line .= " -- ";
    $length = length($line);
  }

  # Channelname am Zeilenende, aber jetzt schon Platz abziehen
  my $senderID = &main::senderid2txt($self->{SENDER_ID}, 1);
  $length += length($senderID) +2;

  # Aufnahmetitel
  $tmpstring = $self->{TITLE};
  $tmpstring =~ s/--/-/g;       # confuses zsh completion
  $tmpstring =~ s/://g;         # confuses zsh completion

  $line .= $green;
  if ($length + length($tmpstring) > $maxlength)
  {
    $line .= substr($tmpstring, 0, $maxlength - $length) ."$norm";
    $length = $maxlength;
  }
  else
  {
    $line .= $tmpstring ."$norm";
    $length += length($tmpstring);

    # noch Platz für SHORTEPG?
    if ($self->{SHORTEPG} and $self->{SHORTEPG} ne $self->{TITLE})
    {
      $tmpstring = $self->{SHORTEPG};
      $tmpstring =~ s/--/-/g;       # confuses zsh completion
      $tmpstring =~ s/://g;         # confuses zsh completion
      if ($length +2 + length($tmpstring) > $maxlength)
      {
        $line .= substr(", " . $tmpstring, 0, $maxlength - $length);
        $length = $maxlength;
      }
      else
      {
        $line .= ", " . $tmpstring;
        $length += 2 + length($tmpstring);
      }
    }
  }

  # Channelname am Zeilenende
  $line .= " " x ($maxlength - $length);
  $line .= " ($cyan". $senderID ."$norm)";

  print $line ."\n";
}

sub getMPEGs
{
  my $self = shift;

  my @mpeglist;
  my ($fmpg, $block);

  my $BLOCKLENGTH = $BLOCKLENGTHOLD;
  my $subdir = "";
  if ($self->{NEW_DIR_STRUCTURE})
  {
    $subdir = $self->{FMPG_DIR};
    $BLOCKLENGTH = $BLOCKLENGTHNEW;
  }

  for ($fmpg=0; $fmpg<$self->{NUM_BLOCKS}; $fmpg++)
  {
    foreach $block (@{$self->{FMPG_BLOCKS}[$fmpg]})
    {
      if ($self->{SCR_BEGIN}[$fmpg] < $block->{Start} + $BLOCKLENGTH and
          ( $self->{SCR_END}[$fmpg] == 0 or $block->{Start} < $self->{SCR_END}[$fmpg])
         )
      {
        print Data::Dumper->Dump([$block], [qw(block)])  if $_DEBUGGING > 1;
        push @mpeglist, $subdir . $block->{Name};
      }
      else
      {
        print "Skipping $subdir$block->{Name}: $self->{SCR_BEGIN}[$fmpg] < ", $block->{Start} + $BLOCKLENGTH, " and $block->{Start} < $self->{SCR_END}[$fmpg]\n"  if $_DEBUGGING > 1;
      }
    }
  }

  return @mpeglist;
}

sub getAllFiles
{
  my $self = shift;

  my @files;
  my ($fmpg, $name, $block);

  push @files, $self->{CRIDNAME};

  my $BLOCKLENGTH = $BLOCKLENGTHOLD;
  my $subdir = "";
  if ($self->{NEW_DIR_STRUCTURE})
  {
    $subdir = $self->{FMPG_DIR};
    $BLOCKLENGTH = $BLOCKLENGTHNEW;
  }

  for ($fmpg=0; $fmpg<$self->{NUM_BLOCKS}; $fmpg++)
  {
    push @files, $subdir . $self->{FILENAME_BASE}[$fmpg];

    foreach $block (@{$self->{FMPG_BLOCKS}[$fmpg]})
    {
      if ($self->{SCR_BEGIN}[$fmpg] < $block->{Start} + $BLOCKLENGTH and
          ( $self->{SCR_END}[$fmpg] == 0 or $block->{Start} < $self->{SCR_END}[$fmpg])
         )
      {
        $name = $subdir . $block->{Name};
        push @files, $name;
        push @files, $name . ".idx";
        push @files, $name . ".midx";
      }
      else
      {
        print "Skipping $subdir.$block->{Name}: $self->{SCR_BEGIN}[$fmpg] < ", $block->{Start} + $BLOCKLENGTH, " and $block->{Start} < $self->{SCR_END}[$fmpg]\n"  if $_DEBUGGING > 1;
      }
    }
  }

  return @files;
}

# schönen eindeutigen Dateinamen zum Exportieren erzeugen
sub createFilename
{
  my $self = shift;

  my $filename = &main::timestring_iso( $self->{BEGIN_REC} );
  $filename .= "_" . $self->{TITLE};
  $filename =~ s/\s+/_/g;                 # bitte keine Leerzeichen in Dateinamen
  $filename =~ s/\./_/g;                  # ProjectX mag keine Punkte in Dateinamen
  $filename =~ s/[<>\/?*'`"|:\\\$&!?\(\)]//g; # ungültige Zeichen entfernen

  return $filename;
}

# liefert die kompletten Aufnahme-Infos als String zurück
sub exportTXT_to_string
{
  my $self = shift;

  my $fullinfo = "";

  my ($begin_rec_string, $end_rec_string, $series_flag_string, $recflag_string);
  $begin_rec_string = &main::timestring($self->{BEGIN_REC});
  $end_rec_string = &main::timestring($self->{END_REC});

  if ($self->{REC_FLAG}==1) {
    $recflag_string="EPG";
  } elsif ($self->{REC_FLAG}==2) {
    $recflag_string="Timer";
  } elsif ($self->{REC_FLAG}==4) {
    $recflag_string="Serienauswahl";
  } elsif ($self->{REC_FLAG}==8) {
    $recflag_string="manuelle Serie";
  } elsif ($self->{REC_FLAG}==32) {
    $recflag_string="manuell";
  } else { $recflag_string="unknown"; }

  $fullinfo .= "Sendungstitel: $self->{TITLE}\n";
  $fullinfo .= "Kurzbeschreibung: $self->{SHORTEPG}\n"  if $self->{SHORTEPG};

  my $sender_id_string = &main::senderid2txt($self->{SENDER_ID}, 0);

  $fullinfo .= "EPG-Beschreibung: $self->{DESCRIPTION}\n"  if $self->{DESCRIPTION};

  $fullinfo .= << "EOF";
Aufnahmebeginn: $begin_rec_string
Aufnahmeende:   $end_rec_string
Programm:       $sender_id_string
Aufnahme programmiert über: $recflag_string
Aufnahmestatus: $status_msg[$self->{STATUS}]
EOF
  chomp $fullinfo;
  if ($self->{PAUSE_OFFSET})
  {
    $fullinfo .= ", PLAY nach $self->{PAUSE_OFFSET} Sekunden pausiert\n"
  }
  else
  {
    $fullinfo .= ", noch nicht angeschaut\n"
  }

  if ($self->{LOCK_FLAG})
  {
    $fullinfo .= "Löschsperre: " . $lockflag_msg[$self->{LOCK_FLAG}] . "\n";
  }

  if ($self->{SERIES_FLAG})
  {
    $fullinfo .= "Uhrzeit der Programmierung: " .
                   &main::timestring($self->{SERIES_FLAG}) . "\n";
  }

  $fullinfo .= "MPG-Dateiname: " . $self->createFilename() . ".mpg\n";

  $self->{FILENAME} =~ m|/([^/]+)$|;
  $fullinfo .= "CRID: $1\n";

  return $fullinfo;
}

# exportiert Aufnahme-Infos als Textdatei
# oder nach STDOUT falls kein Dateiname übergeben wird
sub exportTXT
{
  my $self = shift;
  my $filename = shift;
  my $force = shift;            # overwrite files
  $force = 0 unless defined $force;

  # schon fertig?
  if (not defined $filename)
  {
    $filename="-";              # STDOUT
  }
  else
  {
    if (-s "$filename" and not $force)
    {
      print "Exportdatei ist schon vorhanden. (mit --force überschreiben)\n";
      return 1000;
    }
  }

  print "Export in Datei \"$filename\"\n"  if $_DEBUGGING;

  open(TXTFILE, ">$filename") or my_die("Aborting: cannot write file \"". $filename ."\".$!\n");

  my $fullinfo = $self->exportTXT_to_string();

  print TXTFILE $fullinfo;

  close TXTFILE or do
  {
    print "Beim Exportieren ist ein Fehler ($?) aufgetreten: $!\n";
    return 111;
  };

#  print "fertig.\n";
#  print `$ls '$filename'`;

  return 0;
}

# durchsucht Beschreibung der Aufnahme nach Suchstring
# gibt 1 zurück falls Suchstring gefunden, sonst 0
sub search
{
  my $self = shift;
  my @searchstrings = @_;

  my $fullinfo = $self->exportTXT_to_string();

  # alle Suchstrings müssen vorkommen
  my $found = 1;
  foreach my $str (@searchstrings)
  {
    if (not $fullinfo =~ /$str/mi)
    {
      $found = 0;
    }
  }

  return 1  if $found;
  return 0;
}

sub exportTS
{
  my $self = shift;
  my $filename = shift;
  my $force = shift;            # overwrite files
  $force = 0 unless defined $force;

  # schon fertig?
  if (-s "$filename" and not $force)
  {
    # TODO: Größe der vorhandenen Datei auf Plausibilität testen
    print "Exportdatei ist schon vorhanden. (mit --force überschreiben)\n";
    return 1000;
  }

  my @MPEGs = $self->getMPEGs();
  print "Der Film besteht aus ", $#MPEGs +1, " Teilen.\n";
  my $execute="cat $CRIDDIR" . join(" $CRIDDIR", @MPEGs) ." > '$filename'";
  my $output;
  print $execute ."\n"  if $_DEBUGGING;
  print "Bitte warten...";
  if ($_DEBUGGING)
  {
    print `$execute`;
    $output="";
  }
  else
  {
    $output=`$execute`;
  }

  if ($?)
  {
    print $output ."\n";
    print "Beim Exportieren ist ein Fehler ($?) aufgetreten: $!\n";
    return $?;
  }

  print "fertig.\n";
  print `$ls '$filename'`  if ($^O ne "MSWin32");

  return 0;
}

sub exportPS
{
  my $self = shift;
  my $destfilename = shift;
  my $force = shift;            # overwrite files
  $force = 0 unless defined $force;

  # schon fertig?
  if (-s "$destfilename" and not $force)
  {
    # TODO: Größe der vorhandenen Datei auf Plausibilität testen
    print "PS-Datei ist schon vorhanden. (mit --force überschreiben)\n";
    return 1000;
  }

  my ($filename, $filebasename);

  $filebasename = $self->createFilename(); # Dateiname ohne Erweiterung
  $filename = $MPGDIR . $filebasename;  # Dateiname mit vollem Pfad

  my @MPEGs = $self->getMPEGs();
  foreach (@MPEGs)
  {
    $_ = $CRIDDIR . $_;
  }

#  chdir "$projectXdir" or my_die("Cannot change to dir \"$projectXdir\": $!");

  # Temporäre INI-Datei für ProjectX schreiben
  my $tmp_ini = "$TMPDIR/projectx.ini";
  open(TMPINI, ">$tmp_ini") or my_die("Aborting: cannot write file \"". $tmp_ini ."\".$!\n");
  print TMPINI "Application.Agreement=1\n";
  print TMPINI "Application.Language=de\n";
  print TMPINI "p19*zu M2P\n";  # für altes ProjectX
  print TMPINI "MainPanel.ConversionMode=2\n";
  # keine Logdatei schreiben
  print TMPINI "c21*false\n";   # für altes ProjectX
  print TMPINI "OptionPanel.NormalLog=0\n";
  close TMPINI;
# alternativ so:  $execute="projectx -tom2p";

  my $output;
  my ($err, $errmsg);
#  my $execute="projectx -ini $tmp_ini";
  my $execute="java -Djava.awt.headless=true -jar /usr/share/java/ProjectX.jar -ini $tmp_ini";
  $execute.=" -out '$MPGDIR' -name '$filebasename' " . join(" ", @MPEGs);
  print $execute ."\n"  if $_DEBUGGING;
  print "Bitte warten...";
  if ($_DEBUGGING)
  {
    print `$execute`;
    $err=$?;
    $errmsg=$!  if $err;
    $output="";
  }
  else
  {
    $output=`$execute 2>&1`;
    $err=$?;
    $errmsg=$!  if $err;
  }

  # Aufräumen
  unlink "$tmp_ini" or &main::print_error("cannot delete temporary file \"$tmp_ini\": $!");

  if ($err or not -s "$filename.m2p")
  {
    print $output ."\n";
    print "Beim Exportieren als PS nach \"$filename.m2p\" ist ein Fehler ($err) aufgetreten: $errmsg\n";
    return $err;
  }
  else
  {
    if (not rename("$filename.m2p", "$destfilename"))
    {
      my $rename_error = $!;
      # evtl. verschiedene Partitionen -> Versuch mit Systembefehl
      `$mv "$filename.m2p" "$destfilename"`;
      if ($?)
      {
        &main::print_error("Kann Datei \"$filename.m2p\" nicht in \"$destfilename\" umbenennen.\nFehlercode $?: ");
      }
    }
  }

  print "fertig.\n";
  print `$ls $filename.*`;

  return 0;
}

sub loadInProjectX
{
  my $self = shift;
  my $force = shift;            # overwrite files
  $force = 0 unless defined $force;

  my ($filename, $filebasename);

  $filebasename = $self->createFilename(); # Dateiname ohne Erweiterung
  $filename = $CUTDIR . $filebasename;  # Dateiname mit vollem Pfad

  # schon fertig?
  if (-s "$filename.m2v" and -s "$filename.mp2" and not $force)
  {
    # TODO: Größe der vorhandenen Datei auf Plausibilität testen
    print "Demuxte Dateien sind schon vorhanden. (mit --force überschreiben)\n";
    return 1000;
  }

  my @MPEGs = $self->getMPEGs();
  print "Der Film besteht aus ", $#MPEGs +1, " Teilen.\n";
  foreach (@MPEGs)
  {
    $_ = $CRIDDIR . $_;
  }

#  chdir "$projectXdir" or my_die("Cannot change to dir \"$projectXdir\": $!");

  # Temporäre INI-Datei für ProjectX schreiben
  my $tmp_ini = "$TMPDIR/projectx.ini";
  open(TMPINI, ">$tmp_ini") or my_die("Aborting: cannot write file \"". $tmp_ini ."\".$!\n");
  print TMPINI "Application.Agreement=1\n";
  print TMPINI "Application.Language=de\n";
  print TMPINI "p19*demultiplex\n"; # für altes ProjectX
  print TMPINI "MainPanel.ConversionMode=0\n";
  # keine Logdatei schreiben
  print TMPINI "c21*false\n";   # für altes ProjectX
  print TMPINI "OptionPanel.NormalLog=0\n";
  close TMPINI;

  my $output;
  my ($err, $errmsg);
#  my $execute="java -jar /usr/share/java/ProjectX.jar -ini $tmp_ini -gui ";
  my $execute="projectx -ini $tmp_ini -gui ";

  $execute.=" -out '$CUTDIR' -name '$filebasename' " . join(" ", @MPEGs);
  print $execute ."\n"  if $_DEBUGGING;
  print "Starte ProjectX mit der gewählten Aufnahme.\n";
  print "(Der Button \"Quickstart\" demuxt und speichert die geschnittene Aufnahme.)\n";
  $errmsg="";
  if ($_DEBUGGING)
  {
    print `$execute`;
    $err=$?;
    $errmsg=$!  if $err;
    $output="";
  }
  else
  {
    $output=`$execute 2>&1`;
    $err=$?;
    $errmsg=$!  if $err;
  }

  # Aufräumen
  unlink "$tmp_ini" or &main::print_error("cannot delete temporary file \"$tmp_ini\": $!");

  print "fertig.\n";
  print `$ls $filename.*`;

  return 0;
}

sub convert2divx
{
  my $self = shift;
  my $tsfile = shift;
  my $force = shift;            # overwrite files
  $force = 0 unless defined $force;

  my ($divxfilename, $filebasename);
  my ($err, $errmsg);

  $filebasename = $self->createFilename(); # Dateiname ohne Erweiterung
  $divxfilename = $MPGDIR . $filebasename . "-divx.avi";  # Dateiname mit vollem Pfad

  # schon fertig?
  if (-s "$divxfilename" and not $force)
  {
    # TODO: Größe der vorhandenen Datei auf Plausibilität testen
    print "DivX-Datei \"$divxfilename\" ist schon vorhanden. (mit --force überschreiben)\n";
    return 1000;
  }

  # wg. temporären Dateien für mencoder ohne Pfadangabe
  chdir "$EXPORTDIR" or my_die("Cannot change to dir \"$EXPORTDIR\": $!");

  my $execute;
  my $output;

  print "Bitte warten...";

  # Step 1:
  # extract audio: not necessary - but you have 384 kBit/s then
  # cbr: then you can cut later using avidemux
  print "Schritt 1/3...";
  unlink "frameno.avi";  # falls schon/noch vorhanden

  $execute=$mencoder ." ". $tsfile ." ". $mencoder_divx_opts1;
  print $execute ."\n"  if $_DEBUGGING;
  if ($_DEBUGGING)
  {
    print `$execute | grep -v "^Pos:"`;
    $err=$?;
    $errmsg=$!  if $err;
    $output="";
  }
  else
  {
    $output=`$execute 2>&1 | grep -v "^Pos:"`;
    $err=$?;
    $errmsg=$!  if $err;
  }

  if ($err)
  {
    print $output ."\n";
    print "Beim DivX-Konvertieren (Schritt 1) ist ein Fehler ($err) aufgetreten: $errmsg\n";
    return $err;
  }

  # Step 2:
  # divx 1st pass:
  print "Schritt 2/3...";
  unlink "divx2pass.log";       # falls schon/noch vorhanden

  $execute=$mencoder ." ". $tsfile ." ". $mencoder_divx_opts2;
  print $execute ."\n"  if $_DEBUGGING;
  if ($_DEBUGGING)
  {
    print `$execute | grep -v "^Pos:"`;
    $err=$?;
    $errmsg=$!  if $err;
    $output="";
  }
  else
  {
    $output=`$execute 2>&1 | grep -v "^Pos:"`;
    $err=$?;
    $errmsg=$!  if $err;
  }

  if ($err)
  {
    print $output ."\n";
    print "Beim DivX-Konvertieren (Schritt 2) ist ein Fehler ($err) aufgetreten: $errmsg\n";
    return $err;
  }

  # Step 3:
  # divx 2nd pass:
  print "Schritt 3/3...";
  unlink "$divxfilename";       # falls schon/noch vorhanden

  $execute=$mencoder ." ". $tsfile ." -o ". $divxfilename ." ". $mencoder_divx_opts3;
  print $execute ."\n"  if $_DEBUGGING;
  if ($_DEBUGGING)
  {
    print `$execute | grep -v "^Pos:"`;
    $err=$?;
    $errmsg=$!  if $err;
    $output="";
  }
  else
  {
    $output=`$execute 2>&1 | grep -v "^Pos:"`;
    $err=$?;
    $errmsg=$!  if $err;
  }

  if ($err)
  {
    print $output ."\n";
    print "Beim DivX-Konvertieren (Schritt 3) ist ein Fehler ($err) aufgetreten: $errmsg\n";
    return $err;
  }

  if (not -s "$divxfilename")
  {
    print "Beim DivX-Konvertieren nach \"$divxfilename\" ist ein Fehler aufgetreten.\n";
    return $1001;
  }

  # Aufräumen
  unlink "frameno.avi" or &main::print_error("cannot delete temporary file \"frameno.avi\": $!");
  unlink "divx2pass.log" or &main::print_error("cannot delete temporary file \"divx2pass.log\": $!");

  print "fertig.\n";
  print `$ls $divxfilename`;

  return 0;
}

sub convert2divxfast
{
  my $self = shift;
  my $tsfile = shift;
  my $force = shift;            # overwrite files
  $force = 0 unless defined $force;

  my ($divxfilename, $filebasename);
  my ($err, $errmsg);

  $filebasename = $self->createFilename(); # Dateiname ohne Erweiterung
  $divxfilename = $MPGDIR . $filebasename . "-divx.avi";  # Dateiname mit vollem Pfad

  # schon fertig?
  if (-s "$divxfilename" and not $force)
  {
    # TODO: Größe der vorhandenen Datei auf Plausibilität testen
    print "DivX-Datei \"$divxfilename\" ist schon vorhanden. (mit --force überschreiben)\n";
    return 1000;
  }

  my $execute;
  my $output;

  print "Bitte warten...";

  $execute=$mencoder ." ". $tsfile ." -o ". $divxfilename ." ". $mencoder_divxfast_opts;
  print $execute ."\n"  if $_DEBUGGING;
  if ($_DEBUGGING)
  {
    print `$execute | grep -v "^Pos:"`;
    $err=$?;
    $errmsg=$!  if $err;
    $output="";
  }
  else
  {
    $output=`$execute 2>&1 | grep -v "^Pos:"`;
    $err=$?;
    $errmsg=$!  if $err;
  }

  if ($err)
  {
    print $output ."\n";
    print "Beim DivX-Konvertieren ist ein Fehler ($err) aufgetreten: $errmsg\n";
    return $err;
  }

  print "fertig.\n";
  print `$ls $divxfilename`;

  return 0;
}

sub demux
{
  my $self = shift;
  my $force = shift;            # overwrite files
  $force = 0 unless defined $force;

  my ($filename, $filebasename);

  $filebasename = $self->createFilename(); # Dateiname ohne Erweiterung
  $filename = $EXPORTDIR . $filebasename;  # Dateiname mit vollem Pfad

  # schon fertig?
  if (-s "$filename.m2v" and -s "$filename.mp2" and not $force)
  {
    # TODO: Größe der vorhandenen Datei auf Plausibilität testen
    print "Demuxte Dateien sind schon vorhanden. (mit --force überschreiben)\n";
    return 1000;
  }

  my @MPEGs = $self->getMPEGs();
  print "Der Film besteht aus ", $#MPEGs +1, " Teilen.\n";
  foreach (@MPEGs)
  {
    $_ = $CRIDDIR . $_;
  }

#  chdir "$projectXdir" or my_die("Cannot change to dir \"$projectXdir\": $!");

  # Temporäre INI-Datei für ProjectX schreiben
  my $tmp_ini = "$TMPDIR/projectx.ini";
  open(TMPINI, ">$tmp_ini") or my_die("Aborting: cannot write file \"". $tmp_ini ."\".$!\n");
  print TMPINI "Application.Agreement=1\n";
  print TMPINI "Application.Language=de\n";
  print TMPINI "p19*demultiplex\n"; # für altes ProjectX
  print TMPINI "MainPanel.ConversionMode=0\n";
  # keine Logdatei schreiben
  print TMPINI "c21*false\n";   # für altes ProjectX
  print TMPINI "OptionPanel.NormalLog=0\n";
  close TMPINI;

  my $output;
  my ($err, $errmsg);
#  my $execute="projectx -ini $tmp_ini";
  my $execute="java -Djava.awt.headless=true -jar /usr/share/java/ProjectX.jar -ini $tmp_ini";

  $execute.=" -out '$EXPORTDIR' -name '$filebasename' " . join(" ", @MPEGs);
  print $execute ."\n"  if $_DEBUGGING;
  print "Bitte warten...";
  $errmsg="";
  if ($_DEBUGGING)
  {
    print `$execute`;
    $err=$?;
    $errmsg=$!  if $err;
    $output="";
  }
  else
  {
    $output=`$execute 2>&1`;
    $err=$?;
    $errmsg=$!  if $err;
  }

  # Aufräumen
  unlink "$tmp_ini" or &main::print_error("cannot delete temporary file \"$tmp_ini\": $!");

  if ($err or not -s "$filename.m2v" or not -s "$filename.mp2")
  {
    print $output ."\n";
    print "Beim Demuxen zu \"$filename.m2v\" ist ein Fehler ($err) aufgetreten: $errmsg\n";
    return $err;
  }

  print "fertig.\n";
  print `$ls $filename.*`;

  return 0;
}

sub mux
{
  my $self = shift;
  my ($mpgfile, $videofile, @audiofiles) = @_;

  if (not -s "$videofile")
  {
    print "Demuxte und geschnittene Video-Dateien sind (noch) nicht vorhanden.\n";
    return 1;
  }

  foreach (@audiofiles)
  {
    if (not -s "$_")
    {
      print "Demuxte und geschnittene Audio-Dateien sind (noch) nicht vorhanden.\n";
      return 2;
    }
  }

  my $execute="$mplex -o '$mpgfile' '$videofile' ";
  foreach (@audiofiles)
  {
    $execute .= "'$_' ";
  }

  my $output;
  print $execute ."\n"  if $_DEBUGGING;
  print "Bitte warten...";
  if ($_DEBUGGING)
  {
    print `$execute`;
    $output="";
  }
  else
  {
    $output=`$execute 2>&1`;
  }

  if ($?)
  {
    print $output ."\n";
    print "Beim Muxen ist ein Fehler ($?) aufgetreten: $!\n";
    return $?;
  }

  print "fertig.\n";
  print `$ls '$mpgfile'`;

  return 0;
}

#################
# Hilfsroutinen
#################
# Sonderzeichen und -codes entfernen

# 1st method: with iconv
############################
# use Text::Iconv;
#
# sub convert_dvbstring
# {
#   my $s = shift;
#
#   $s =~ s|\x05||g;              # Umschalten zu alternativem Zeichensatz
#   $s =~ s|[\x00-\x1f]||g;
#   $s =~ s|\x8a|\n|g;
#
#   my $converter = Text::Iconv->new("iso8859-1", "utf-8");
#   $s = $converter->convert($s);
#
#   return $s;
# }

# 2nd method: with builtin (perl >= 5.8) function
#####################################################
use Encode;

sub convert_dvbstring
{
  my $s = shift;

  $s =~ s|\x05||g;              # Umschalten zu alternativem Zeichensatz
#  $s =~ s|[\x00-\x1f]||g;
  $s =~ s|\x8a|\n|g;

#  $s = decode("iso8859-1", $s);
  $s = encode("utf8", decode("iso-8859-1", $s));

  return $s;
}

# wg. Fehler beenden
sub my_die
{
  my $msg = shift;

  die $msg unless $_NEVERDIE;

  # otherwise just print the message
  print $msg;
}

sub set_never_die
{
  warn "setting never_die: auch bei Fehlern weitermachen.\n";
  $_NEVERDIE = 1;
}

sub unset_never_die
{
  warn "unsetting never_die: bei Fehlern abbrechen.\n";
  $_NEVERDIE = 0;
}

1;  # so the require or use succeeds


############################################
############################################
package main;
############################################
############################################

use strict;
use warnings;

use Getopt::Long;
use Pod::Usage;

use POSIX qw(strftime);

#use File::Path;
#use Filesys::DiskSpace;

use Data::Dumper;


# security for shell calls:
$ENV{'PATH'} = '/bin:/usr/bin';
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};

# turn off buffering (sinnvoll für Debugging)
$| = 1;

my ($UID, $EUID, $GID, $EGID);
if ($^O ne "MSWin32")
{
  $UID=(getpwuid( $< ))[0]; $EUID=(getpwuid( $> ))[0];
  $GID=(getgrgid( $( ))[0]; $EGID=(getgrgid( $) ))[0];
}

# globale Variablen
my $debug = 0;
my $errors_occurred = 0;
my $warnings_occurred = 0;

my %crids = ();
my %channelnames = ();


############################################
# Locale auf Deutsch schalten
############################################
#$ENV{'LANGUAGE'} = 'de_DE';
#$ENV{'LANG'} = 'de_DE';
#$ENV{'LC_ALL'} = 'de_DE';


############################################
# parse command line options
############################################
# option defaults
my $showhelp = 0;
my $showmanpage = 0;
my $showversion = 0;
my $always_yes = 0;
my $dontdel = 0;
my $alang = 0;
my $nocolor = 0;
my $force = 0;
my $nowarnings = 0;
my $neverdie = 0;
my @searchstrings = ();
my $onlyCommands = 0;

GetOptions(
   'help|usage'    => \$showhelp,       # show usage
   "manpage"       => \$showmanpage,    # show manpage
   "version"       => \$showversion,    # show programm version
   "debug+"        => \$debug,          # (incremental option)
   "dir|criddir=s" => \$CRIDDIR,        # Verzeichnis mit crid-Dateien
   "exportdir=s"   => \$EXPORTDIR,      # Verzeichnis für Exporte
   "cutdir=s"      => \$CUTDIR,         # Verzeichnis für geschnittene Filme
   "mpgdir=s"      => \$MPGDIR,         # Verzeichnis für gemuxte Filme
   "yes|ja"        => \$always_yes,     # don't ask stupid questions
   "dontdel|nodel" => \$dontdel,        # don't delete files
   "alang|audiolang=s" => \$alang,      # Sprachenauswahl
   "nocolor|nc"    => \$nocolor,        # keine Farben
   "force|overwrite" => \$force,        # overwrite existing files
   "nowarnings"    => \$nowarnings,     # keine Warnungen ausgeben
   "neverdie"      => \$neverdie,       # auch trotz Fehlern nicht abbrechen
   "search=s"      => \@searchstrings,  # Suchstrings (AND-Logik)
   "commands"      => \$onlyCommands,   # nur Liste aller Kommandos ausgeben
          ) or pod2usage(-exitstatus => 1, -verbose => 0);

$| = 1  if $debug;

crid->debug($debug);

crid->set_never_die()  if $neverdie;

pod2usage(-exitstatus => 0, -verbose => 1)  if $showhelp;
pod2usage(-exitstatus => 0, -verbose => 2)  if $showmanpage;

if ($showversion)
{ print "$self - Version: $VERSION\n"; exit; }

if ($onlyCommands)
{
  print <<"EOT";
dir         -- Liste der Aufnahmen
ls          -- Liste der Aufnahmen
title       -- Kurzübersicht (Aufnahme-Titel in nur 1 Zeile)
oneliner    -- Kurzübersicht (Aufnahme-Titel in nur 1 Zeile)
short       -- noch kürzere Kurzübersicht (ohne cridname)
crids       -- Liste der Crid-IDs
ids         -- Liste der Crid-IDs
text        -- Text-Zusammenfassung anzeigen
info        -- Text-Zusammenfassung anzeigen
view        -- Aufnahme anzeigen
play        -- Aufnahme anzeigen
mplayer     -- Aufnahme anzeigen
dump        -- Details anzeigen
details     -- Details anzeigen
mpg         -- Aufnahme mit projectx als PS exportieren
ps          -- Aufnahme mit projectx als PS exportieren
PS          -- Aufnahme mit projectx als PS exportieren
divx        -- Aufnahme ins DivX-Format konvertieren
divxfast    -- Aufnahme (schneller) ins DivX-Format konvertieren
export      -- Aufnahme als TS exportieren (ohne A/V Sync Korrektur)
ts          -- Aufnahme als TS exportieren (ohne A/V Sync Korrektur)
TS          -- Aufnahme als TS exportieren (ohne A/V Sync Korrektur)
px          -- Aufnahme im GUI von ProjectX laden
projectX    -- Aufnahme im GUI von ProjectX laden
demux       -- Aufnahme mit projectX demultiplexen
mux         -- Video+Audio(s) mit mplex wieder zu einer MPG-Datei multiplexen
textexport  -- Text-Zusammenfassung exportieren
save        -- Text-Zusammenfassung exportieren
mv          -- Aufnahme verschieben
move        -- Aufnahme verschieben
cp          -- Aufnahme kopieren
copy        -- Aufnahme kopieren
edit        -- Aufnahme editieren (noch nicht implementiert)
rm          -- Aufnahme löschen
del         -- Aufnahme löschen
timeshift   -- Timeshift-Aufnahme ansehen
check       -- Konsistenz prüfen
test        -- Konsistenz prüfen
EOT
  exit;
}

if ($debug)
{
  print "DEBUG-Modus($debug): schalte $self in Debugmodus.\n";
}

# Farben unterdrücken
if ($nocolor or $^O eq "MSWin32")
{
 $bold = $underline = $half = $norm = "";
 $black = $red = $green = $yellow = $blue = $magenta = $cyan = $grey ="";
}

# Audio-Sprachspur wählen
if ($alang)
{
  $mplayer .= " -alang $alang";
}

# aber die eigentlichen Hauptargumente sind die Befehle
if ($#ARGV < 0)
{
  pod2usage(-message => $red. "ERROR:$norm kein Befehl - nichts zu tun.\n",
            -exitstatus => 2,
            -verbose => 0
           );
}


############################################
# Signal-Handling Routines
############################################
my $exit_signalled = 0;
my $exit_on_ctrl_C = 0;

# trap signals so we don't leave stale lo(g|ck)files
sub signalcatcher
{
  my ($sig) = @_;

  $exit_signalled++;          # wird im Prg mehrfach abgefragt

  # ein paar schlaue Meldungen :-)
  if ($exit_signalled <= 2)
  {
    print "Signal SIG$sig empfangen -- werde möglichst bald beenden.\n\n";
  }
  elsif ($exit_signalled > 2 and $exit_signalled < 5)
  {
    print "\nOkay, okay, ich hab's ja kapiert.\n";
    print "  Ein bißchen Geduld noch...ich bin am Aufräumen!  Schneller kann selbst ich nicht arbeiten.\n";
  }
  elsif ($exit_signalled >= 5)
  {
    print "\nGenug jetzt!  Noch einmal und ich lösche deine Festplatte!  (just kidding)\n";
  }

  # mainly while waiting for user input, e.g. reading from STDIN
  if ($sig eq "INT" and $exit_on_ctrl_C)
  {
    &cleanup_and_exit();
  }
}

sub signalcatcher_kill
{
  my ($sig) = @_;

  print "Signal empfangen SIG$sig -- sofort raus hier.\n";
  print "fertig, chau.\n";
  exit 2;
}

if ($^O ne "MSWin32")
{
  $SIG{'HUP'}  = 'signalcatcher';
  $SIG{'INT'}  = 'signalcatcher';
  $SIG{'QUIT'} = 'signalcatcher';
  $SIG{'KILL'} = 'signalcatcher_kill';
  $SIG{'USR1'} = 'signalcatcher';
  $SIG{'USR2'} = 'signalcatcher';
}


############################################
# jetzt aber los
############################################
#print "Running as UID $UID, EUID $EUID, GID $GID, EGID $EGID.\n"  if $debug;

my $command;

my (@crids, $crid);

# Verzeichnisnamen enden mit Slash
$CRIDDIR .= "/"  unless $CRIDDIR =~ m|/$|;
$EXPORTDIR .= "/"  unless $EXPORTDIR =~ m|/$|;
$CUTDIR .= "/"  unless $CUTDIR =~ m|/$|;
$MPGDIR .= "/"  unless $MPGDIR =~ m|/$|;

# Default-Channelliste (gültig für Bayern)
$channelnames{"10000"} = "Info/3sat";
$channelnames{"10001"} = "BR-Alpha";
$channelnames{"10002"} = "Das Erste";
$channelnames{"10003"} = "arte";
$channelnames{"10004"} = "Bayerisches FS";
$channelnames{"10005"} = "Berlin1";
$channelnames{"10007"} = "DSF";
$channelnames{"10008"} = "EuroNews";
$channelnames{"10009"} = "Eurosport";
$channelnames{"10010"} = "HSE24";
$channelnames{"10011"} = "HR3";
$channelnames{"10012"} = "Kabel1";
$channelnames{"10013"} = "Doku/KiKa";
$channelnames{"10014"} = "MDR";
$channelnames{"10015"} = "MTV";
$channelnames{"10016"} = "N24";
$channelnames{"10017"} = "NDR";
$channelnames{"10018"} = "NTV";
$channelnames{"10019"} = "TM3";
$channelnames{"10020"} = "ORB";
$channelnames{"10021"} = "Phoenix";
$channelnames{"10022"} = "ProSieben";
$channelnames{"10023"} = "CNN";
$channelnames{"10025"} = "RTL2";
$channelnames{"10026"} = "RTL";
$channelnames{"10027"} = "Sat.1";
$channelnames{"10028"} = "SWR";
$channelnames{"10033"} = "Viva2";
$channelnames{"10034"} = "Viva";
$channelnames{"10035"} = "VOX";
$channelnames{"10036"} = "WDR";
$channelnames{"10037"} = "ZDF";
$channelnames{"10100"} = "EinsPlus";
$channelnames{"10101"} = "Super RTL";
$channelnames{"10102"} = "kabel eins";
$channelnames{"10103"} = "Bayerisches FS";
$channelnames{"10104"} = "Südwest BW";
$channelnames{"10105"} = "hr-fernsehen";
$channelnames{"10106"} = "TELE 5";
$channelnames{"10107"} = "HSE24";
$channelnames{"10108"} = "München TV";

# mit Channelliste aus Datei überschreiben, falls vorhanden
if (-s $channellist)
{
  &read_channellist($channellist);
}

# Befehl aus den Kommandozeilen-Argumenten einlesen
$command=shift;

# Liste aller Aufzeichnungen
#############################
if ($command =~ /^(ls|dir|list)$/)
{
  # alle crid-Dateien einlesen
  &read_crids($CRIDDIR);

  if (@ARGV)
  {
    @crids = @ARGV;
    print "Liste crids " . join(", ", @crids) . "\n"  if $debug;
  }
  else
  {
    print "Liste alle vorhandenen crids auf.\n"  if $debug;
    @crids = sort cridsort keys %crids;
  }
  &add_fileextension(@crids);

  # crid-Liste auf Suchstring beschränken
  if (@searchstrings)
  {
    print "Beschränkung auf: \"" . join("\", \"", @searchstrings) . "\"\n";
  }

  foreach $crid (@crids)
  {
    if (not $crids{$crid})
    {
      &print_error("Aufnahme \"$crid\" existiert gar nicht.");
      next;
    }

    next if @searchstrings and not $crids{$crid}->search(@searchstrings);

    print "\n";

    $crids{$crid}->terseInfo();

    last if $exit_signalled;
  }
}

# Kurzübersicht: nur Aufnahme-Titel in 1 Zeile ausgeben
#######################################################
elsif ($command =~ /^(title|oneliner|short)$/)
{
  # alle crid-Dateien einlesen
  &read_crids($CRIDDIR);

  if (@ARGV)
  {
    @crids = @ARGV;
    print "Oneliner für crids " . join(", ", @crids) . "\n"  if $debug;
  }
  else
  {
    print "Oneliner für alle vorhandenen crids.\n"  if $debug;
    @crids = sort cridsort keys %crids;
  }
  &add_fileextension(@crids);

  # crid-Liste auf Suchstring beschränken
  if (@searchstrings)
  {
    print "Beschränkung auf: \"" . join("\", \"", @searchstrings) . "\"\n";
  }

  my $terminal_width = 80;
  if ($ENV{COLUMNS})
  {
    $terminal_width = $ENV{COLUMNS};
  }
#print "terminal_width: $terminal_width\n";
  foreach $crid (@crids)
  {
    if (not $crids{$crid})
    {
      &print_error("Aufnahme \"$crid\" existiert gar nicht.");
      next;
    }

    next if @searchstrings and not $crids{$crid}->search(@searchstrings);

    # ohne cridname
    if ($command eq  "short")
    {
      if ($terminal_width > 38)
      {
        $crids{$crid}->oneliner($terminal_width - 38, 0);
      }
      else
      {
        $crids{$crid}->oneliner(20, 0);
      }
    }

    # mit cridname
    else
    {
      $crids{$crid}->oneliner($terminal_width - 1, 1);
    }

    last if $exit_signalled;
  }
}

# simple Liste aller Crid-IDs
#############################
elsif ($command =~ /^(crids|ids)$/)
{
  # alle crid-Dateien einlesen
  &read_crids($CRIDDIR);

  if (@ARGV)
  {
    @crids = @ARGV;
    print "Liste IDs von crids " . join(", ", @crids) . "\n"  if $debug;
  }
  else
  {
    print "Liste IDs von allen vorhandenen crids auf.\n"  if $debug;
    @crids = sort cridsort keys %crids;
  }
  &add_fileextension(@crids);

  # crid-Liste auf Suchstring beschränken
  if (@searchstrings)
  {
    print "Beschränkung auf: \"" . join("\", \"", @searchstrings) . "\"\n";
  }

  foreach $crid (@crids)
  {
    if (not $crids{$crid})
    {
      &print_error("Aufnahme \"$crid\" existiert gar nicht.");
      next;
    }

    next if @searchstrings and not $crids{$crid}->search(@searchstrings);

    $crid =~ s/\.crid$//;       # Extension abschneiden
    print "$crid\n";

    last if $exit_signalled;
  }
}

# Aufzeichnungen überprüfen
############################
elsif ($command =~ /^(check|test)$/)
{
  # alle crid-Dateien einlesen
  &read_crids($CRIDDIR);

  my $errors_found=0;
  my $warnings_found=0;

  if (@ARGV)
  {
    @crids = @ARGV;
    print "Überprüfe crids " . join(", ", @crids) . "\n"  if $debug;
  }
  else
  {
    print "Überprüfe alle vorhandenen crids.\n"  if $debug;
    @crids = sort cridsort keys %crids;
  }
  &add_fileextension(@crids);

  # crid-Liste auf Suchstring beschränken
  if (@searchstrings)
  {
    print "Beschränkung auf: \"" . join("\", \"", @searchstrings) . "\"\n";
  }

  foreach $crid (@crids)
  {
    if (not $crids{$crid})
    {
      &print_error("Aufnahme \"$crid\" existiert gar nicht.");
      next;
    }

    next if @searchstrings and not $crids{$crid}->search(@searchstrings);

    my $missing = $crids{$crid}->checkFiles(1);   # quiet Modus
    if ($missing == 0)
    {
      print "Alle Dateien von Aufnahme \"". $crids{$crid}->cridname() ."\" sind vorhanden.\n";
    }
    else
    {
      $errors_found++;
      # folgendes nicht nötig, da ja schon beim Einlesen der crids gewarnt wird
#      &print_error("missing Datei(en) von Aufnahme \"". $crids{$crid}->cridname() ."\" fehlen.");
    }

    last if $exit_signalled;
  }


  # auf überflüssige Dateien im CRIDDIR testen:

  # Liste aller benötigten Dateien
  my (@allFiles, $file);
  foreach $crid (keys %crids)
  {
    push @allFiles, $crids{$crid}->getAllFiles();
  }

  # mit vorhandenen vergleichen
  opendir(DIR, $CRIDDIR) or die "Aborting: cannot open dir \"$CRIDDIR\": $!\n";
  foreach $file (readdir(DIR))
  {
    next if $file =~ /^\.$/;
    next if $file =~ /^\.\.$/;

    next if $file =~ /^\.rec$/;

    if (-d "$CRIDDIR$file")
    {
      &print_warning("überflüssiges Unterverzeichnis \"$file\".");
      $warnings_found++;
      next;
    }

    if (not grep /^$file$/, @allFiles)
    {
      &print_warning("Datei \"$file\" gehört zu keiner Aufnahme. (evtl. aber zu Timeshift)");
      $warnings_found++;
      next;
    }
  }
  closedir(DIR);

  if ($errors_found or $warnings_found)
  {
    &error_exit("Überprüfung hat $errors_found Fehler und $warnings_found Warnungen ergeben.\n", 200+$errors_found+$warnings_found);
  }
  else
  {
    print "Alles in Ordnung.\n";
  }
}

# Timeshift-Aufzeichnungen anschauen
#####################################
elsif ($command =~ /^timeshift$/)
{
  if ($#ARGV >= 0)
  {
    &error_exit("zuviele Argumente \"@ARGV\".", 11);
  }

  print "Zeige timeshift-Aufzeichnung (d.h. alle crids, die zu keiner Aufnahme gehören) "  if $debug;

  # alle crid-Dateien einlesen
  &read_crids($CRIDDIR);

  # Liste aller von crids benötigten Dateien
  my (@allFiles, $file, @timeshiftFiles);
  foreach $crid (keys %crids)
  {
    push @allFiles, $crids{$crid}->getAllFiles();
  }

  # mit vorhandenen vergleichen
  opendir(DIR, $CRIDDIR) or die "Aborting: cannot open dir \"$CRIDDIR\": $!\n";
  foreach $file ( sort grep /\.mpg$/, readdir(DIR) )
  {
    next if (-d "$CRIDDIR$file");

    if (not grep /^$file$/, @allFiles)
    {
      push @timeshiftFiles, $file;
    }
  }
  closedir(DIR);

  if (@timeshiftFiles)
  {
    print $#timeshiftFiles, " wahrscheinliche Timeshift-MPEGs gefunden:\n ";
    print join("\n ", @timeshiftFiles) . "\n";

    print "\nTimeshift-Aufnahme abspielen:\n";
    my ($execute, $mpeg);
    foreach $mpeg (@timeshiftFiles)
    {
      print " $mpeg";
      $execute="$mplayer $CRIDDIR$mpeg";
      print "\n". $execute ."\n"  if $debug;
      `$execute 2>/dev/null`;
      print "\n";
      last if $exit_signalled;
    }
  }
  else
  {
    print "Keine Timeshift-MPEGs gefunden.\n";
  }
}

# Text-Zusammenfassung der Aufzeichnung ausgeben
#################################################
elsif ($command =~ /^(text|info)$/)
{
  # alle crid-Dateien einlesen
  &read_crids($CRIDDIR);

  if (@ARGV)
  {
    @crids = @ARGV;
    print "Text-Zusammenfassung der crids " . join(", ", @crids) . "\n"  if $debug;
  }
  else
  {
    print "Text-Zusammenfassung aller vorhandenen crids.\n"  if $debug;
    @crids = sort cridsort keys %crids;
  }
  &add_fileextension(@crids);

  # crid-Liste auf Suchstring beschränken
  if (@searchstrings)
  {
    print "Beschränkung auf: \"" . join("\", \"", @searchstrings) . "\"\n";
  }

  foreach $crid (@crids)
  {
    if (not $crids{$crid})
    {
      &print_error("Aufnahme \"$crid\" existiert gar nicht.");
      next;
    }

    next if @searchstrings and not $crids{$crid}->search(@searchstrings);

    print "\n";

    $crids{$crid}->exportTXT();

    last if $exit_signalled;
  }
}

# Text-Zusammenfassung der Aufzeichnung in Datei schreiben
###########################################################
elsif ($command =~ /^(textexport|save)$/)
{
  # alle crid-Dateien einlesen
  &read_crids($CRIDDIR);

  if (@ARGV)
  {
    @crids = @ARGV;
    print "Text-Export der crids " . join(", ", @crids) . "\n"  if $debug;
  }
  else
  {
    print "Text-Export aller vorhandenen crids.\n"  if $debug;
    @crids = sort cridsort keys %crids;
  }
  &add_fileextension(@crids);

  # crid-Liste auf Suchstring beschränken
  if (@searchstrings)
  {
    print "Beschränkung auf: \"" . join("\", \"", @searchstrings) . "\"\n";
  }

  my @MPEGs;

  foreach $crid (@crids)
  {
    if (not $crids{$crid})
    {
      &print_error("Aufnahme \"$crid\" existiert gar nicht.");
      next;
    }

    next if @searchstrings and not $crids{$crid}->search(@searchstrings);

    my $txtfile = $crids{$crid}->createFilename() .".txt";
    print "Schreibe Text-Zusammenfassung der folgenden Aufnahme nach\n";
    print " \"$MPGDIR$txtfile\":\n";
    $crids{$crid}->terseInfo();

    print "Export nach \"$MPGDIR$txtfile\"\n"  if $debug;

    if ( $crids{$crid}->exportTXT("$MPGDIR$txtfile", $force) == 0)
    {
      print "Aufnahme exportiert.\n";
    }
    else
    {
      print "Aufnahme ${red}NICHT$norm erfolgreich exportiert.\n";
    }

    last if $exit_signalled;
  }
}

# Details der Aufzeichnung auflisten
#####################################
elsif ($command =~ /^(dump|details)$/)
{
  # alle crid-Dateien einlesen
  &read_crids($CRIDDIR);

  if (@ARGV)
  {
    @crids = @ARGV;
    print "Dumpe crids " . join(", ", @crids) . "\n"  if $debug;
  }
  else
  {
    print "Dumpe alle vorhandenen crids.\n"  if $debug;
    @crids = sort cridsort keys %crids;
  }
  &add_fileextension(@crids);

  # crid-Liste auf Suchstring beschränken
  if (@searchstrings)
  {
    print "Beschränkung auf: \"" . join("\", \"", @searchstrings) . "\"\n";
  }

  foreach $crid (@crids)
  {
    if (not $crids{$crid})
    {
      &print_error("Aufnahme \"$crid\" existiert gar nicht.");
      next;
    }

    next if @searchstrings and not $crids{$crid}->search(@searchstrings);

    print "\n";
    $crids{$crid}->dumpall();
    print "\n";

    last if $exit_signalled;
  }
}

# Aufzeichnung(en) löschen
###########################
elsif ($command =~ /^(rm|del)$/)
{
  my $Sicherheitsnachfrage = 0;

  # alle crid-Dateien einlesen
  &read_crids($CRIDDIR);

  if (@ARGV)
  {
    @crids = @ARGV;
    print "Lösche crids " . join(", ", @crids) . "\n"  if $debug;
  }
  else
  {
    $Sicherheitsnachfrage = 1;
    print "Lösche alle vorhandenen crids.\n"  if $debug;
    @crids = sort cridsort keys %crids;
  }
  &add_fileextension(@crids);


  # crid-Liste auf Suchstring beschränken
  if (@searchstrings)
  {
    print "Beschränkung auf: \"" . join("\", \"", @searchstrings) . "\"\n";
  }

  my (@cridsToDelete, @MPEGs, @files);

  # erstmal Liste aussortieren
  foreach $crid (@crids)
  {
    if (not $crids{$crid})
    {
      &print_error("Aufnahme \"$crid\" existiert gar nicht (mehr).");
      next;
    }
    next if @searchstrings and not $crids{$crid}->search(@searchstrings);

    if (($crids{$crid}->rec_status() != 3) and ($crids{$crid}->rec_status() != 4))
    {
      &print_error("Aufnahme \"$crid\" ist noch nicht fertig - lösche nicht!");
      next;
    }

    push @cridsToDelete, $crid;
  }

  # vor dem Löschen lieber zweimal nachfragen!
  if ($Sicherheitsnachfrage)
  {
    print "Wirklich alle diese crids löschen?\n";
    print join(", ", @cridsToDelete) . "\n";

    my $ans;
    print "Wirklich alle löschen? (j/N) ";
    $exit_on_ctrl_C = 1;
    $ans=<STDIN>;
    $exit_on_ctrl_C = 0;
    chomp $ans;

    if ($ans eq "j" or $ans eq "J")
    {
      print "Okay, du hast es so gewollt.  :-)\n";
    }
    else
    {
      print "Besser so, lösche lieber nicht so viel auf einmal.\n";
      @cridsToDelete = ();
    }
  }

  foreach $crid (@cridsToDelete)
  {
    print "Folgende Aufnahme löschen:\n";
    $crids{$crid}->terseInfo;
    @MPEGs = $crids{$crid}->getMPEGs();
    print "Der Film besteht aus ", $#MPEGs +1, " Teilen:\n";
    print " ", join("\n ", @MPEGs), "\n";

    # Sicherheitsabfrage
    my $ans;
    if (not $always_yes)
    {
      print "Wirklich löschen? (j/N) ";
      $exit_on_ctrl_C = 1;
      $ans=<STDIN>;
      $exit_on_ctrl_C = 0;
      chomp $ans;
    }
    else
    {
      $ans="j";
    }

    if ($ans eq "j" or $ans eq "J")
    {
      my ($file, $dontdel, $othercrid, @othersFiles);
      my $dontdelatleastone=0;

      @files = $crids{$crid}->getAllFiles();

      if ($crids{$crid}{NEW_DIR_STRUCTURE})
      {
        my $deldir = $CRIDDIR . $crids{$crid}{FMPG_DIR};

        print `$rm_r $deldir`;
        if ($?)
        {
          &print_error("cannot delete subdirectory \"$deldir\": $!");
        }
      }
      else
      {
        foreach $file (@files)
        {
          next if $file =~ /\.crid$/;   # das crid selber erst zum Schluß löschen

          # überprüfen, ob Datei nicht noch in anderen crids gebraucht wird
          $dontdel=0;
          foreach $othercrid (keys %crids)
          {
            next if $othercrid eq $crid;

            @othersFiles = $crids{$othercrid}->getAllFiles();
            if (grep /^$file$/, @othersFiles)
            {
              print "$file ist auch ein Teil von $othercrid\n -> lösche es nicht!\n";
              $dontdel=1;
              $dontdelatleastone=1;
            }
          }

          unless ($dontdel)
          {
            # dann so löschen:
            #print `rm $CRIDDIR$file`;
            #if ($?)
            #{
            #  &print_error("cannot delete files \"$file\": $!");
            #}

            # oder lieber so:
            unlink "$CRIDDIR$file" or &print_error("cannot delete file \"$CRIDDIR$file\": $!");
          }
        }
      }

      # crid selber sicherheitshalber als letztes löschen
      unlink "$CRIDDIR$crid" or die "Aborting: cannot delete file \"$CRIDDIR$crid\": $!\n";

      delete $crids{$crid};

      unless ($dontdelatleastone)
      {
        print "Aufnahme gelöscht.\n";
      }
      else
      {
        print "Aufnahme teilweise gelöscht.\n";
      }
    }
    else
    {
      print "Okay, lösche also lieber nicht.\n";
    }

    last if $exit_signalled;
  }
}

# Aufzeichnung verschieben oder kopieren
###########################
elsif ($command =~ /^(mv|move|cp|copy)$/)
{
  my $move=0;
  $move=1  if $command =~ /^(mv|move)$/;

  if ($#ARGV < 0)
  {
    &error_exit("zu wenig Argumente.  Wohin sollen die crids denn?", 51);
  }
  my $dest_dir = pop @ARGV;
  if (not -d $dest_dir)
  {
    &error_exit("Ziel \"$dest_dir\" ist kein Verzeichnis.", 52);
  }

  # alle crid-Dateien einlesen
  &read_crids($CRIDDIR);

  if (@ARGV)
  {
    @crids = @ARGV;
    if ($move)
    { print "Verschiebe crids " . join(", ", @crids) . "nach $dest_dir\n"  if $debug; }
    else
    { print "Kopiere crids " . join(", ", @crids) . "nach $dest_dir\n"  if $debug; }
  }
  else
  {
    if ($move)
    { print "Verschiebe alle vorhandenen crids nach $dest_dir\n"; }
    else
    { print "Kopiere alle vorhandenen crids nach $dest_dir\n"; }
    @crids = sort cridsort keys %crids;
  }
  &add_fileextension(@crids);

  # crid-Liste auf Suchstring beschränken
  if (@searchstrings)
  {
    print "Beschränkung auf: \"" . join("\", \"", @searchstrings) . "\"\n";
  }

  my (@MPEGs, @files);

  foreach $crid (@crids)
  {
    if (not $crids{$crid})
    {
      &print_error("Aufnahme \"$crid\" existiert gar nicht (mehr).");
      next;
    }

    next if @searchstrings and not $crids{$crid}->search(@searchstrings);

    print "\n";

    if (($crids{$crid}->rec_status() != 3) and ($crids{$crid}->rec_status() != 4))
    {
      &print_error("Aufnahme \"$crid\" ist noch nicht fertig - überspringe sie!");
      next;
    }

    if ($move)
    { print "Folgende Aufnahme nach \"$dest_dir\" verschieben:\n"; }
    else
    { print "Folgende Aufnahme nach \"$dest_dir\" kopieren:\n"; }

    $crids{$crid}->terseInfo;
    @MPEGs = $crids{$crid}->getMPEGs();
    print "Der Film besteht aus ", $#MPEGs +1, " Teilen:\n";
    print " ", join("\n ", @MPEGs), "\n";

    # Sicherheitsabfrage vor dem Verschieben
    my $ans;
    if ($move)
    {
      if (not $always_yes)
      {
        print "Wirklich verschieben? (j/N) ";
        $exit_on_ctrl_C = 1;
        $ans=<STDIN>;
        $exit_on_ctrl_C = 0;
        chomp $ans;
      }
      else
      {
        $ans="j";
      }
    }
    else
    {
      # Kopieren ohne Nachfrage
      $ans="j";
    }

    if ($ans eq "j" or $ans eq "J")
    {
      my ($file, $dontdel, $othercrid, @othersFiles);
      my $dontdelatleastone = 0;

      @files = $crids{$crid}->getAllFiles();

      if ($crids{$crid}{NEW_DIR_STRUCTURE})
      {
        my $srcdir = $CRIDDIR . $crids{$crid}{FMPG_DIR};

        # Verzeichnisstruktur in Zielverzeichnis erstellen
        if (not -d "$dest_dir/.rec")
        {
          print "INFO: erstelle Verzeichnis \"$dest_dir/.rec\"\n";
          print `mkdir $dest_dir/.rec`;
          if ($?)
          {
            &error_exit("cannot mkdir \"$dest_dir/.rec\": $!", 91);
          }
        }

        if ($move)
        {
          # dann so verschieben
          print `$mv $srcdir $dest_dir/.rec/`;
          if ($?)
          {
            &print_error("cannot move subdirectory \"$srcdir\" to $dest_dir/.rec/: $!");
          }

#          rmdir $srcdir or
#            &print_error("cannot remove subdirectory \"$srcdir\": $!");
        }
        else
        {
          # dann halt nur kopieren
          print `$cp $srcdir $dest_dir/.rec/`;
          if ($?)
          {
            &print_error("cannot copy subdirectory \"$srcdir\" to $dest_dir/.rec/: $!");
          }
        }
      }
      else
      {
        foreach $file (@files)
        {
          next if $file =~ /\.crid$/;   # das crid selber erst zum Schluß

          if ($move)
          {
            # überprüfen, ob Datei nicht noch in anderen crids gebraucht wird
            $dontdel=0;
            foreach $othercrid (keys %crids)
            {
              next if $othercrid eq $crid;

              @othersFiles = $crids{$othercrid}->getAllFiles();
              if (grep /^$file$/, @othersFiles)
              {
                print "$file ist auch ein Teil von $othercrid\n -> kopiere statt zu verschieben!\n";
                $dontdel=1;
                $dontdelatleastone=1;
              }
            }
          }

          if ($move and not $dontdel)
          {
            # dann so verschieben
            print `$mv $CRIDDIR$file $dest_dir`;
            if ($?)
            {
              &print_error("cannot move file \"$CRIDDIR$file\" to $dest_dir: $!");
            }
          }
          else
          {
            # dann halt nur kopieren
            print `$cp $CRIDDIR$file $dest_dir`;
            if ($?)
            {
              &print_error("cannot copy file \"$CRIDDIR$file\" to $dest_dir: $!");
            }
          }
        }
      }

      # crid selber sicherheitshalber als letztes
      if ($move)
      {
        print `$mv $CRIDDIR$crid $dest_dir`;
        if ($?)
        {
          &print_error("cannot move file \"$CRIDDIR$crid\" to $dest_dir: $!");
        }
      }
      else
      {
        print `$cp $CRIDDIR$crid $dest_dir`;
        if ($?)
        {
          &print_error("cannot copy file \"$CRIDDIR$crid\" to $dest_dir: $!");
        }
      }

      delete $crids{$crid};

      if ($move)
      {
        unless ($dontdelatleastone)
        {
          print "Aufnahme komplett nach \"$dest_dir\" verschoben.\n";
        }
        else
        {
          print "Aufnahme nach \"$dest_dir\" tw. verschoben und tw. kopiert.\n";
        }
      }
      else
      {
        print "Aufnahme komplett nach \"$dest_dir\" kopiert.\n";
      }
    }
    else
    {
      print "Okay, verschiebe also lieber nicht.\n";
    }

    last if $exit_signalled;
  }
}

# Aufzeichnung editieren
#########################
elsif ($command =~ /^edit$/)
{
  if ($#ARGV < 0)
  {
    &error_exit("zu wenig Argumente.  Welche Aufnahme soll editiert werden?", 61);
  }

  $crid=shift;

  if ($#ARGV >= 0)
  {
    &error_exit("zuviele Argumente \"@ARGV\".", 62);
  }

  # TODO
  print "crid editieren...not implemented yet...\n";
}

# Aufzeichnung anschauen
#########################
elsif ($command =~ /^(view|play|mplayer)$/)
{
  # alle crid-Dateien einlesen
  &read_crids($CRIDDIR);

  if (@ARGV)
  {
    @crids = @ARGV;
    print "Betrachte crids " . join(", ", @crids) . "\n"  if $debug;
  }
  else
  {
    print "Betrachte alle vorhandenen crids.\n"  if $debug;
    @crids = sort cridsort keys %crids;
  }
  &add_fileextension(@crids);

  # crid-Liste auf Suchstring beschränken
  if (@searchstrings)
  {
    print "Beschränkung auf: \"" . join("\", \"", @searchstrings) . "\"\n";
  }

  my @MPEGs;
  my $execute;

  foreach $crid (@crids)
  {
    if (not $crids{$crid})
    {
      &print_error("Aufnahme \"$crid\" existiert gar nicht.");
      next;
    }

    next if @searchstrings and not $crids{$crid}->search(@searchstrings);

    if (($crids{$crid}->rec_status() != 3) and ($crids{$crid}->rec_status() != 4))
    {
      &print_warning("Aufnahme \"$crid\" ist noch nicht fertig!");
    }

    print "Folgende Aufnahme abspielen:\n";

    $crids{$crid}->terseInfo;
    @MPEGs = $crids{$crid}->getMPEGs();
    print "Der Film besteht aus ", $#MPEGs +1, " Teilen:\n";
    my ($mpegs, $execute, $mpeg);
#     foreach $mpeg (@MPEGs)
#     {
#       print " $mpeg";
#       $execute="$mplayer $CRIDDIR$mpeg";
#       print "\n". $execute ."\n"  if $debug;
#       `$execute 2>/dev/null`;
#       print "\n";
#       last if $exit_signalled;
#     }

    # lieber doch gleich alle an mplayer übergeben
    # (mit < und > kann man springen)
    $mpegs="";
    foreach $mpeg (@MPEGs)
    {
      print " $mpeg\n";
      $mpegs .= "$CRIDDIR/$mpeg "
    }

    $execute="$mplayer $mpegs";
    print "\n". $execute ."\n"  if $debug;
    `$execute 2>/dev/null`;
    print "\n";

    last if $exit_signalled;
  }
}

# Aufzeichnung als PS exportieren
##################################
elsif ($command =~ /^(mpg|ps|PS)$/)
{
  # alle crid-Dateien einlesen
  &read_crids($CRIDDIR);

  if (@ARGV)
  {
    @crids = @ARGV;
    print "Exportiere crids " . join(", ", @crids) . "als PS\n"  if $debug;
  }
  else
  {
    print "Exportiere alle vorhandenen crids als PS.\n"  if $debug;
    @crids = sort cridsort keys %crids;
  }
  &add_fileextension(@crids);

  # crid-Liste auf Suchstring beschränken
  if (@searchstrings)
  {
    print "Beschränkung auf: \"" . join("\", \"", @searchstrings) . "\"\n";
  }

  my @MPEGs;
  my $execute;

  foreach $crid (@crids)
  {
    if (not $crids{$crid})
    {
      &print_error("Aufnahme \"$crid\" existiert gar nicht.");
      next;
    }

    next if @searchstrings and not $crids{$crid}->search(@searchstrings);

    if (($crids{$crid}->rec_status() != 3) and ($crids{$crid}->rec_status() != 4))
    {
      &print_warning("Aufnahme \"$crid\" ist noch nicht fertig!");
    }

    my $psfile = $crids{$crid}->createFilename() .".mpg";
    print "Folgende Aufnahme als PS nach \"$MPGDIR$psfile\" exportieren:\n";
    $crids{$crid}->terseInfo();

    if ( $crids{$crid}->exportPS("$MPGDIR$psfile", $force) == 0)
    {
      print "Aufnahme exportiert.\n";
    }
    else
    {
      print "Aufnahme ${red}NICHT$norm erfolgreich exportiert.\n";
    }

    last if $exit_signalled;
  }
}

# Aufzeichnung als TS exportieren
##################################
elsif ($command =~ /^(export|ts|TS)$/)
{
  # alle crid-Dateien einlesen
  &read_crids($CRIDDIR);

  if (@ARGV)
  {
    @crids = @ARGV;
    print "Exportiere crids " . join(", ", @crids) . "\n"  if $debug;
  }
  else
  {
    print "Exportiere alle vorhandenen crids auf.\n"  if $debug;
    @crids = sort cridsort keys %crids;
  }
  &add_fileextension(@crids);

  # crid-Liste auf Suchstring beschränken
  if (@searchstrings)
  {
    print "Beschränkung auf: \"" . join("\", \"", @searchstrings) . "\"\n";
  }

  my @MPEGs;
  my $execute;

  foreach $crid (@crids)
  {
    if (not $crids{$crid})
    {
      &print_error("Aufnahme \"$crid\" existiert gar nicht.");
      next;
    }

    next if @searchstrings and not $crids{$crid}->search(@searchstrings);

    if (($crids{$crid}->rec_status() != 3) and ($crids{$crid}->rec_status() != 4))
    {
      &print_warning("Aufnahme \"$crid\" ist noch nicht fertig!");
    }

    my $tsfile = $crids{$crid}->createFilename() .".ts";
    print "Folgende Aufnahme als TS nach \"$EXPORTDIR$tsfile\" exportieren:\n";
    $crids{$crid}->terseInfo();

    if ( $crids{$crid}->exportTS("$EXPORTDIR$tsfile", $force) == 0)
    {
      print "Aufnahme exportiert.\n";
    }
    else
    {
      print "Aufnahme ${red}NICHT$norm erfolgreich exportiert.\n";
    }

    last if $exit_signalled;
  }
}

# Aufzeichnung in ProjectX (mit GUI) laden
###########################################
elsif ($command =~ /^(projectX|projectx|px)$/)
{
  # alle crid-Dateien einlesen
  &read_crids($CRIDDIR);

  if (@ARGV)
  {
    @crids = @ARGV;
    print "Lade ProjectX mit crids " . join(", ", @crids) . "\n"  if $debug;
  }
  else
  {
    print "Lade alle vorhandenen crids (nacheinander) in ProjectX.\n"  if $debug;
    @crids = sort cridsort keys %crids;
  }
  &add_fileextension(@crids);

  # crid-Liste auf Suchstring beschränken
  if (@searchstrings)
  {
    print "Beschränkung auf: \"" . join("\", \"", @searchstrings) . "\"\n";
  }

  my @MPEGs;
  my $execute;

  foreach $crid (@crids)
  {
    if (not $crids{$crid})
    {
      &print_error("Aufnahme \"$crid\" existiert gar nicht.");
      next;
    }

    next if @searchstrings and not $crids{$crid}->search(@searchstrings);

    if (($crids{$crid}->rec_status() != 3) and ($crids{$crid}->rec_status() != 4))
    {
      &print_warning("Aufnahme \"$crid\" ist noch nicht fertig!");
    }

    print "Folgende Aufnahme in ProjectX laden:\n";
    $crids{$crid}->terseInfo();

    if ( $crids{$crid}->loadInProjectX($force) == 0)
    {
      print "Aufnahme in ProjectX geladen.\n";

      print "Wenn die geschnittene, demuxte Aufnahme nun im Verzeichnis \"$CUTDIR\" liegt,\n";
      print "kann es mit \"gigaset mux $crid\" wieder DVD-kompatibel zusammengefügt werden.\n";
    }
    else
    {
      print "Aufnahme ${red}NICHT$norm erfolgreich in ProjectX geladen.\n";
    }

    last if $exit_signalled;
  }
}

# Aufzeichnung demuxen (als m2v und mp2)
#  (zuerst als TS exportieren falls nötig)
###########################################
elsif ($command =~ /^(demux)$/)
{
  # alle crid-Dateien einlesen
  &read_crids($CRIDDIR);

  if (@ARGV)
  {
    @crids = @ARGV;
    print "Demuxe crids " . join(", ", @crids) . "\n"  if $debug;
  }
  else
  {
    print "Demuxe alle vorhandenen crids auf.\n"  if $debug;
    @crids = sort cridsort keys %crids;
  }
  &add_fileextension(@crids);

  # crid-Liste auf Suchstring beschränken
  if (@searchstrings)
  {
    print "Beschränkung auf: \"" . join("\", \"", @searchstrings) . "\"\n";
  }

  my @MPEGs;
  my $execute;

  foreach $crid (@crids)
  {
    if (not $crids{$crid})
    {
      &print_error("Aufnahme \"$crid\" existiert gar nicht.");
      next;
    }

    next if @searchstrings and not $crids{$crid}->search(@searchstrings);

    if (($crids{$crid}->rec_status() != 3) and ($crids{$crid}->rec_status() != 4))
    {
      &print_warning("Aufnahme \"$crid\" ist noch nicht fertig!");
    }

    print "Folgende Aufnahme mithilfe von ProjectX demultiplexen:\n";
    $crids{$crid}->terseInfo();

    if ( $crids{$crid}->demux($force) == 0)
    {
      print "Aufnahme demuxt.\n";

      print "Jetzt z.B. mit cuttermaran oder mpeg2schnitt schneiden,\n";
      print "das Ergebnis in das Verzeichnis \"$CUTDIR\" legen\n";
      print "und dann mit \"gigaset mux\" wieder zusammenfügen.\n";
    }
    else
    {
      print "Aufnahme ${red}NICHT$norm erfolgreich demuxt.\n";
    }

    last if $exit_signalled;
  }
}

# Aufzeichnung wieder muxen (m2v + mp2 -> mpg)
###############################################
elsif ($command =~ /^(mux)$/)
{
  # alle crid-Dateien einlesen
  &read_crids($CRIDDIR);

  if (@ARGV)
  {
    @crids = @ARGV;
    print "Muxe crids " . join(", ", @crids) . "\n"  if $debug;
  }
  else
  {
    print "Muxe alle vorhandenen crids auf.\n"  if $debug;
    @crids = sort cridsort keys %crids;
  }
  &add_fileextension(@crids);

  # crid-Liste auf Suchstring beschränken
  if (@searchstrings)
  {
    print "Beschränkung auf: \"" . join("\", \"", @searchstrings) . "\"\n";
  }

  my ($filename_stub, $mpgfile, $videofile);
  my (@audiofiles, @precut_files, @tmp_files);

  foreach $crid (@crids)
  {
    if (not $crids{$crid})
    {
      &print_error("Aufnahme \"$crid\" existiert gar nicht.");
      next;
    }

    next if @searchstrings and not $crids{$crid}->search(@searchstrings);

    if (($crids{$crid}->rec_status() != 3) and ($crids{$crid}->rec_status() != 4))
    {
      &print_warning("Aufnahme \"$crid\" ist noch nicht fertig!");
    }

    # Dateinamen berechnen
    # (NB: cuttermaran hängt "_cut" an den Namen an)
    $filename_stub = $mpgfile = $crids{$crid}->createFilename();
    $mpgfile   = $MPGDIR . $mpgfile .".mpg";

    # demuxte aber noch ungeschnittene Dateien
    @precut_files = ();
    @precut_files = &getFiles($EXPORTDIR, $filename_stub)  unless $CUTDIR eq $EXPORTDIR;

    @tmp_files = &getFiles($CUTDIR, $filename_stub, ('\.m2v') );
    if ($#tmp_files != 0)
    {
      &error_exit("Nicht genau eine Video-Datei zur Aufnahme gefunden ($filename_stub*.m2v in $CUTDIR):\n ".
                  join("\n ", @tmp_files), 71);
    }
    $videofile = $tmp_files[0];
    print "DEBUG: videofile: $videofile\n"  if $debug;

    @audiofiles = &getFiles($CUTDIR, $filename_stub, ('\.mp2', '\.mpa', '\.ac3') );
    if (not @audiofiles)
    {
      &print_error("keine Audio-Dateien zur Aufnahme gefunden.");
      print "Aufnahme ${red}NICHT$norm erfolgreich gemuxt.\n";
    }

    if ($debug)
    {
      print "found audiofile(s): ". join(", ", @audiofiles) ."\n";
    }

    print "Folgende Dateien:\n";
    print " $videofile\n ", join("\n ",@audiofiles), "\n";
    print "von folgender Aufnahme werden in\n \"$mpgfile\"\n gemuxt:\n";

    $crids{$crid}->terseInfo();

    if ( $crids{$crid}->mux($mpgfile, $videofile, @audiofiles) == 0)
    {
      print "Aufnahme gemuxt.\n";

      unless ($dontdel)
      {
        print "Lösche nicht mehr benötigte Video/Audio-Dateien.\n";
        print "unlinking $videofile\n"  if $debug;
        unlink "$videofile"
         or &print_warning("cannot delete video file \"$videofile\" to cleanup.");

        print "unlinking audiofile(s)\n"  if $debug;
        foreach (@audiofiles)
        {
          unlink "$_"
           or &print_warning("cannot delete audio file \"$_\" to cleanup: $!");
        }

        print "unlinking demuxed but uncut media files\n"  if $debug;
        foreach (@precut_files)
        {
          unlink "$_"
           or &print_warning("cannot delete file \"$_\" to cleanup: $!");
        }

        print "Jetzt kann z.B. so die original Gigaset-Aufnahme gelöscht werden:\n";
        print " gigaset del $crid\n";
        print "Vorher evtl. noch die Aufnahme- und EPG-Infos so abspeichern:\n";
        print " gigaset textexport $crid\n";
      }
      else
      {
        print "${bold}NOT$norm unlinking videofile \"$videofile\".\n";

        foreach (@audiofiles)
        {
          print "${bold}NOT$norm unlinking audiofile \"$_\".\n";
        }

        foreach (@precut_files)
        {
          print "${bold}NOT$norm unlinking \"$_\".\n";
        }
      }
    }
    else
    {
      print "Aufnahme ${red}NICHT$norm erfolgreich gemuxt.\n";
    }

    last if $exit_signalled;
  }
}

# Aufzeichnung zu DivX-AVI konvertieren
#  (zuerst als TS exportieren falls nötig)
###########################################
elsif ($command =~ /^(divx|divxfast)$/)
{
  # alle crid-Dateien einlesen
  &read_crids($CRIDDIR);

  if (@ARGV)
  {
    @crids = @ARGV;
    print "Konvertiere crids " . join(", ", @crids) . "\n"  if $debug;
  }
  else
  {
    print "Konvertiere alle vorhandenen crids auf.\n"  if $debug;
    @crids = sort cridsort keys %crids;
  }
  &add_fileextension(@crids);

  # crid-Liste auf Suchstring beschränken
  if (@searchstrings)
  {
    print "Beschränkung auf: \"" . join("\", \"", @searchstrings) . "\"\n";
  }

  my @MPEGs;
  my $execute;
  my $err;

  foreach $crid (@crids)
  {
    if (not $crids{$crid})
    {
      &print_error("Aufnahme \"$crid\" existiert gar nicht.");
      next;
    }

    next if @searchstrings and not $crids{$crid}->search(@searchstrings);

    if (($crids{$crid}->rec_status() != 3) and ($crids{$crid}->rec_status() != 4))
    {
      &print_warning("Aufnahme \"$crid\" ist noch nicht fertig!");
    }

    my $divxfile = $crids{$crid}->createFilename() ."-divx.avi";

    # schon fertig?
    if (-s "$MPGDIR$divxfile" and not $force)
    {
      # TODO: Größe der vorhandenen Datei auf Plausibilität testen
      print "DivX-Datei ist schon vorhanden. (mit --force überschreiben)\n";
      next;
    }

    # ansonsten zuerst als TS exportieren
    my $tsfile = $crids{$crid}->createFilename() .".ts";
    print "Folgende Aufnahme als TS nach \"$EXPORTDIR$tsfile\" exportieren:\n";
    $crids{$crid}->terseInfo();

    $err = $crids{$crid}->exportTS("$EXPORTDIR$tsfile", $force);
    if ( $err == 0)
    {
      print "Aufnahme exportiert.\n";
    }
    elsif ( $err != 1000)       # 1000 bedeutet Datei schon vorhanden
    {
      print "Aufnahme ${red}NICHT$norm erfolgreich exportiert.\n";
      print "Breche wegen vorhergehenden Fehlers ab.\n";
    }

    my $status;
    if ($command =~ /^(divx)$/)
    {
      print "Nun TS \"$EXPORTDIR$tsfile\" mithilfe von mencoder\n",
            "zu DivX-AVI \"$MPGDIR$divxfile\"\n",
            "konvertieren:  (2-Pass Variante, dauert lange!)\n";
      $status = $crids{$crid}->convert2divx("$EXPORTDIR$tsfile", $force)
    }
    else
    {
      print "Nun TS \"$EXPORTDIR$tsfile\" mithilfe von mencoder\n",
            "zu DivX-AVI \"$MPGDIR$divxfile\"\n",
            "konvertieren:  (schnellere 1-Pass Variante)\n";
      $status = $crids{$crid}->convert2divxfast("$EXPORTDIR$tsfile", $force)
    }

    if ( $status == 0)
    {
      print "Aufnahme zu DivX konvertiert.  Lösche nicht mehr benötigte TS-Datei.\n";
      unless ($dontdel)
      {
        unlink "$EXPORTDIR$tsfile"
         or &print_warning("cannot delete TS file \"$EXPORTDIR$tsfile\" to cleanup.");
      }
      else
      {
        print "${bold}NOT$norm deleting TS file \"$EXPORTDIR$tsfile\".\n";
      }

      print "Jetzt z.B. mit avidemux schneiden.\n";

      my $mpgfile = $crids{$crid}->createFilename() .".mpg";
      print "Und wieder zurück in eine PS-Datei (zum Abspielen auf der Box!) geht z.B. so:\n";
      print " ffmpeg -i $divxfile -target dvd -tvstd pal -s 4cif -r 25 -b 4000 -maxrate 4200 -aspect 4:3 -acodec mp2 -ac 2 -ab 224 $mpgfile\n";
    }
    else
    {
      print "Aufnahme ${red}NICHT$norm erfolgreich konvertiert.\n";
    }

    last if $exit_signalled;
  }
}

else
{
  &print_error("unbekanntes Kommando \"$command\".");
  pod2usage(-exitstatus => 0, -verbose => 1);
  &cleanup_and_exit(150);
}

# Ende
&cleanup_and_exit();


############################################
# Hilfsroutinen
############################################
# evtl. der Einfachheit halber weggelassenes ".crid" ergänzen
sub add_fileextension
{
  my $c;

  foreach $c (@_)
  {
    $c .= ".crid"  if ($c !~ /.crid$/)
  }
}

# liefert alle Dateien eines Verzeichnisses mit bestimmtem Dateinamen (und Extension)
# Achtung: Liste der erlaubten Extensions sind reguläre Ausdrücke
#          z.B. müssen also Punkte excapet werden: '\.mp2'
sub getFiles
{
  my ($dirname, $filename_stub, @extensions) = @_;

  my @filelist = ();
  my ($file, $extension);

  if (@extensions)
  {
    print "restrict to extensions: ". join(", ", @extensions) ."\n"  if $debug > 1;
  }

  opendir(DIR, $dirname) or die "Aborting: cannot open dir \"$dirname\": $!\n";
  foreach $file (readdir(DIR))
  {
    next if $file =~ /^\.$/;
    next if $file =~ /^\.\.$/;

    next if -d $file;           # no directories

    if ($file =~ /^$filename_stub(.*)$/)
    {
      $extension = $1;

      # restrict to certain extensions?
      if (@extensions)
      {
        my $found=0;
        foreach (@extensions)
        {
          $found=1  if $extension =~ /$_$/;
        }
        next  unless $found;
      }
      print "$file = ... $extension\n"  if $debug > 1;

      push @filelist, $dirname . $file;
    }
  }
  closedir(DIR);

  return sort @filelist;
}

# zum chronologischen Sortieren von crid-Dateien
sub cridsort
{
  my ($a_tmp, $b_tmp);
  $a =~ /_(.+).crid/;
  $a_tmp = $1;
  $b =~ /_(.+).crid/;
  $b_tmp = $1;
  return $a_tmp <=> $b_tmp;
}

# alle *.crid in übergebenem Verz. einlesen
# Rückgabe: Anzahl der gelesenen crids
sub read_crids
{
  my $directory = shift;
  my $file;
  my $c;
  my $missing;

  opendir(DIR, $directory) or die "Aborting: cannot open dir \"$directory\": $!\n";
  my @files =  readdir(DIR);
  foreach $file (sort @files)   # Dateien sind chronologisch benannt
  {
    next if $file =~ /^\.$/;
    next if $file =~ /^\.\.$/;
    next if (-d "$directory/$file");    # nicht rekursiv


    next unless $file =~ /\.crid$/;
    print "file: $file\n"  if $debug > 3;

    # Datei einlesen
    $c=crid->new("$directory$file");
    if ($debug > 2)
    {
      print "create new ";
      $c->dumpall();
      print "\n\n";
    }

    # und mit neuer eindeutiger ID speichern
    $crids{ $file } = $c;

    $missing = $c->checkFiles();
    if ($missing != 0)
    {
      &print_error("$missing Datei(en) von Aufnahme \"". $c->cridname() ."\" fehlen.");
    }

  } # end of foreach
  closedir(DIR);

  return keys %crids;
}

# Senderliste einlesen und parsen
# Parameter: Pfad zu eps_mapping.txt
# globale Variable: %channelnames
sub read_channellist
{
  my $filename = shift;

  my $line;

  open(EPS, $filename) or die "Aborting: cannot open channellist \"". $filename ."\".\n";
  while ($line = <EPS>)
  {
    # Sendername(n):Sender-ID,Senderabkürzung
    if ($line =~ /^([^,]+)(,.+)?:(\d+),...$/)
    {
      $channelnames{"$3"} = "$1";
      print "channelnames{$3} = $1\n"  if $debug > 2;
    }
    else
    {
      &print_warning("unbekanntes Dateiformat in \"$filename\".");
      print "Problemzeile: $line\n";
    }
  }

  close EPS;
}

sub senderid2txt
{
  my $id = shift;
  my $short = shift;

  my $ans;

  if ($channelnames{$id})
  {
    $ans = $channelnames{$id};
    $ans .= "  (SenderID: ". $id .")"  unless $short;
  }
  else
  {
    $ans = "SenderID: $id";
  }

  return $ans;
}

sub timestring
{
  my $t = shift;
  return strftime "%d.%m.%Y, %H:%M:%S", localtime($t);
}

sub timestring_iso
{
  my $t = shift;
  return strftime "%Y-%m-%d %H-%M", localtime($t);
}

sub size_of_file
{
  my ($filename) = @_;

  if (-f $filename)
  {
    my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
        $atime,$mtime,$ctime,$blksize,$blocks) = lstat($filename);

    return $size;
  }
  else
  {
    &print_error("Datei \"$filename\" existiert nicht.");
    return -1;
  }
}

# $d = 12345;
# $h = &dec2hex($d);
# $b = &hex2bin($h);
# print $d ." -> ". $b ."\n";
sub dec2hex
{
  my ($dec) = @_;
  return sprintf("%x", $dec);
}

# &hex2bin("0Fea");
sub hex2bin
{
  my ($hex) = @_;
  my (@digits, $digit, $bin);
  @digits = split(//, $hex);
  for $digit (@digits)
  {
    $bin .= unpack("B4", pack("H", $digit));
    $bin .= " ";
  }
  return $bin;
}

sub print_error
{
  my ($text) = @_;

  print STDERR $red, "ERROR:$norm ". $text ."\n";

  $errors_occurred++;

  if ($errors_occurred > 10 and not $_NEVERDIE)
  {
    print STDERR $red, "ERROR:$norm Zu viele Fehler ($errors_occurred) aufgetreten -> Abbruch\n";
    &cleanup_and_exit();
  }
}

sub print_warning
{
  my ($text) = @_;

  if (not $nowarnings)
  {
    print "${bold}WARNING:$norm ". $text ."\n";
  }

  $warnings_occurred++;
}

sub error_exit
{
  my ($text, $exitcode) = @_;

  &print_error($text);
  &cleanup_and_exit($exitcode);
}

# nötige Aufräumarbeiten am Ende
sub cleanup
{
  print "done.\n"  if $debug > 1;
}

# Exitcode als optionaler Parameter
sub cleanup_and_exit
{
  my ($exitcode) = @_;
  $exitcode = 0  unless $exitcode;

  &cleanup();

  if ($exit_signalled)
  {
    print "Beende vorzeitig (aber kontrolliert) aufgrund von $exit_signalled Interrupts.\n";
  }

  if ($errors_occurred)
  {
    print "Fertig, aber es ";
    print $errors_occurred == 1 ? "ist" : "sind";
    print " $errors_occurred Fehler aufgetreten.\n";
    $exitcode = $errors_occurred  unless $exitcode;
  }

  if ($warnings_occurred and not $nowarnings)
  {
    print "Fertig, aber es ";
    print $warnings_occurred == 1 ? "ist 1 Warnung" : "sind $warnings_occurred Warnungen";
    print " aufgetreten.\n";
    $exitcode = $warnings_occurred  unless $exitcode;
  }

  exit $exitcode;
}


#----------------------------------------------------------------------------
# Doku
#----------------------------------------------------------------------------

__END__

=head1 NAME

gigaset  --  Bearbeiten/Ansehen der Aufzeichnungen der Gigaset Box

=head1 SYNOPSIS

C<gigaset> [--help|--usage] [--version] [--manpage] [--debug]
          [--dir|--criddir] [--exportdir] [--cutdir] [--mpgdir]
          [--yes|--ja] [--nowarnings] [--neverdie] [--dontdel|--nodel] [--force|--overwrite]
          [--nocolor|--nc] [--alang|--audiolang] [--search] [--commands]
          <COMMAND> [args]

mögliche Kommandos:

dir, ls, list - Liste der Aufnahmen

title, oneliner - Kurzübersicht, 1 Zeile pro Aufnahme

short - wie title und oneliner, aber noch kürzer

crids, ids - Liste der Crid-IDs

text, info - Text-Zusammenfassung anzeigen

view, play, mplayer - Aufnahme anzeigen

dump, details - Details anzeigen

mpg, ps, PS - Aufnahme mit projectx als PS exportieren

divx - Aufnahme ins DivX-Format konvertieren

divxfast - Aufnahme (schneller) ins DivX-Format konvertieren

export, ts, TS - Aufnahme als TS exportieren (ohne A/V Sync Korrektur)

px, projectX - Aufnahme im GUI von ProjectX (zum Schneiden) laden

demux - Aufnahme mit projectX demultiplexen

mux - Video+Audio(s) mit mplex wieder zu einer MPG-Datei multiplexen

textexport, save - Text-Zusammenfassung exportieren

mv, move - Aufnahme verschieben

cp, copy - Aufnahme kopieren

edit - Aufnahme editieren (noch nicht implementiert)

rm, del - Aufnahme löschen

timeshift - Timeshift-Aufnahme ansehen

check, test - Konsistenz prüfen


=head1 DESCRIPTION

B<gigaset> hilft beim Umgang mit Aufzeichnungen der Gigaset DVB-T Box.


=head1 OPTIONS

Alle Optionen können mit einem eindeutigen Anfang abgekürzt werden.

=over 3

=item B<--dir>, B<--criddir>

Verzeichnis mit den Aufnahmen und crid-Dateien.
 (Default: /data/video/gigaset/PVR/)

=item B<--exportdir>

Verzeichnis für den TS-Export der Aufnahmen
 (Default: /data/video/gigaset/)

=item B<--cutdir>

Verzeichnis mit den fertig geschnittenen (noch nicht gemuxten) Filmen.
 (Default: /data/video/gigaset/cut/)

=item B<--mpgdir>

Verzeichnis mit den fertig geschnittenen und gemuxten MPG-Filmen
 (Default: /data/video/gigaset/mpg/)

=item B<--yes>, B<--ja>

Keine Sicherheitsabfragen, sondern immer "ja" annehmen.  (Vorsicht!)
Nützlich für nicht-interaktive Benutzung des Skripts.

=item B<--dontdel>, B<--nodel>

Temporäre Dateien werden nach Programmende nicht gelöscht, sondern nur
aufgelistet.  Hilfreich, wenn man mehrere Schnipsel aus einer Aufnahme nach
dem Schneiden nacheinander wieder muxen will.

=item B<--force>, B<--overwrite>

Vorhandene Dateien werden (nach einer Warnung) Überschrieben.

=item B<--nowarnings>

Warnungen werden unterdrückt.  Nur sinnvoll bei Batchverarbeitung.

=item B<--neverdie>

Fehler werden ausgegeben, aber das Programm läuft weiter.

Diese Option ist nur in Ausnahmen sinnvoll, da sie bei falschem Gebrauch zu
Datenverlusten führen kann.  Eine sinnvolle Anwendung ist z.B. wenn man
mehrere Instanzen von gigaset mit dem Parameter B<--mv> gleichzeitig laufen
lassen will oder wenn man während eines solchen Umschaufelns gigaset mit
dem Parameter B<--ls> starten will.

=item B<--search>

Beschränkung auf Aufnahmen, die den Suchstring in der Beschreibung haben.
Dieser Parameter kann mehrfach angegeben werden, jeweils mit einem
Suchstring.  AND-Logik: Alle Suchstrings müssen in einer
Aufnahmenbeschreibung vorkommen, um berücksichtigt zu werden.

=item B<--alang>, B<--audiolang>

Zum Einstellen der Audio-Sprache.  Dieser Parameter wird direkt an mplayer
weitergegeben.  Siehe auch die mplayer-Doku:

  Gibt eine Prioritätenliste der abzuspielenden Audiospuren an.
  Verschiedene Containerformate verwenden unterschiedliche Ländercodes.
  DVDs benutzen den zweibuchstabigen ISO 639-1 Sprachcode, Matroska und NUT
  benutzen den dreibuchstabigen ISO 639-2 Sprachcode, während OGM einen
  beliebigen Bezeichner verwendet.  MPlayer gibt alle vorhandenen Sprachen
  aus, wenn er im Verbose-Modus (-v) gestartet wird.

  Beispiel: -alang deu,fra
    Wählt die deutsche Sprachspur und wählt die französische,
    wenn deutsch nicht verfügbar ist.

=item B<--nocolor>, B<--nc>

Keine farbige Ausgabe.

=item B<--debug>

Debugmeldungen ausgeben (kann mehrfach angegeben werden, um detailliertere Informationen zu sehen).

=item B<--help>, B<--usage>

Syntax anzeigen

=item B<--manpage>

Die komplette Manpage anzeigen

=item B<--version>

Programmversion anzeigen

=item B<--commands>

Zeigt eine Liste aller möglichen Kommandos.
 (hauptsächlich für die zsh-Completion gedacht)


=head1 COMMANDS

=item B<dir, ls, list>

Kurze Liste aller Aufnahmen bzw. crid-Dateien.

Parameter: keine oder einzelne crid-Datei(en)

=item B<title, oneliner>

Kurzübersicht in nur 1 Zeile pro Aufnahme.

Parameter: crid-Datei(en), sonst alle Aufnahmen

=item B<short>

wie C<title> und C<oneliner>, aber cridname wird nicht gedruckt.
 (hauptsächlich für die zsh-Completion gedacht)

Parameter: crid-Datei(en), sonst alle Aufnahmen

=item B<crids, ids>

Liste der Crid-IDs ohne zusätzliche Informationen

Parameter: crid-Datei(en), sonst alle Aufnahmen

=item B<text, info>

Zusammenfassung der Aufnahme als Text anzeigen.

Parameter: crid-Datei(en), sonst alle Aufnahmen

=item B<view, play, mplayer>

Aufnahmen mit C<mplayer> (oder definiertem Movie-Player) abspielen.

Parameter: crid-Datei(en), sonst alle Aufnahmen

=item B<dump, details>

Kompletten Inhalt der crid-Datei lesbar ausgeben.

Parameter: crid-Datei(en), sonst alle Aufnahmen

=item B<mpg, ps, PS>

Aufnahme mithilfe von ProjectX als PS (Program Stream im MPG-Format)
exportieren, z.B. um sie danach mit der Gigaset wieder anzuschauen.

Ohne A/V Sync Korrektur - Probleme beim Schneiden mit avidemux
vorprogrammiert.
Siehe: http://avidemux.org/admWiki/index.php?title=Editing_MPEG_capture_%28DVB_or_IVTV%29

Zielverzeichnis ist MPGDIR.  (Default: /data/video/gigaset/fertig/)

Parameter: crid-Datei(en), sonst alle Aufnahmen

=item B<divx, divxfast>

Aufnahme ins DivX-Format konvertieren.
Danach kann sie z.B. mit avidemux noch geschnitten werden.

"divxfast" konvertiert schneller (nur 1 mencoder-Durchlauf statt 2,5)

Und wieder zurück in eine PS-Datei (zum Abspielen auf der Box!) geht z.B. so:
 ffmpeg -i $divxfile -target dvd -tvstd pal -s 4cif -r 25 -b 4000 -maxrate 4200 -aspect 4:3 -acodec mp2 -ac 2 -ab 224 $mpgfile

Zielverzeichnis ist MPGDIR.  (Default: /data/video/gigaset/fertig/)

Parameter: crid-Datei(en), sonst alle Aufnahmen

=item B<export, ts, TS>

Aufnahme als TS (Transport Stream) exportieren.

Ohne A/V Sync Korrektur - Probleme beim Schneiden mit avidemux
vorprogrammiert.
Siehe: http://avidemux.org/admWiki/index.php?title=Editing_MPEG_capture_%28DVB_or_IVTV%29

Zielverzeichnis ist EXPORTDIR.  (Default: /data/video/gigaset/)

Danach kann sie z.B. ziemlich bequem mit dvbcut
(http://dvbcut.sourceforge.net/) geschnitten und von dort als MPG
exportiert werden.

Parameter: crid-Datei(en), sonst alle Aufnahmen

=item B<px, projectX>

Aufnahme im GUI von ProjectX laden.  Dort kann sie z.B. geschnitten und
dann als demuxte Dateien exportiert werden.

=item B<demux>

Aufnahme mithilfe von ProjectX demultiplexen (Video/Audio getrennt),
um sie danach mit Schneideprogrammen (z.B. cuttermaran) zu bearbeiten.
Zielverzeichnis ist EXPORTDIR.  (Default: /data/video/gigaset/)

Dort kann sie dann mit Schnittprogrammen wie z.B. cuttermaran oder
mpeg2schnitt ohne Rekodierung geschnitten werden.  Das Ergebnis sollte in
CUTDIR (Default: /data/video/gigaset/cut/) abgelegt werden, wo es
C<gigaset> mit dem Kommando "mux" findet und wieder zu einer MPG-Datei
multiplext ("muxt").

Um mit avidemux zu schneiden: zuerst B<demux>, dann B<mux> verwenden.
Wichtig, da nur so A/V Sync Probleme bei Aufnahmefehlern korrigiert werden.
Siehe: http://avidemux.org/admWiki/index.php?title=Editing_MPEG_capture_%28DVB_or_IVTV%29

Parameter: crid-Datei(en), sonst alle Aufnahmen

=item B<mux>

Das Ergebnis des Schnittprogramms (siehe Abschnitt "demux") wird im
Verzeichnis CUTDIR (Default: /data/video/gigaset/cut/) gesucht und wieder
zu einer MPG-Datei gemultiplext ("gemuxt"), die dann z.B. mit "qdvdauthor"
(Linux) oder "GUI for dvdauthor" (Micky-OS) auf DVD gebrannt werden kann.

Zielverzeichnis ist MPGDIR.  (Default: /data/video/gigaset/fertig/)

Parameter: crid-Datei(en), sonst alle Aufnahmen

=item B<textexport, save>

Zusammenfassung der Aufnahme als Text exportieren.
Zielverzeichnis ist MPGDIR.  (Default: /data/video/gigaset/fertig/)

Parameter: crid-Datei(en), sonst alle Aufnahmen

=item B<mv, move>

Aufnahmen und crid-Datei(en) verschieben.

Parameter: crid-Datei(en) UND als letzten Parameter Ziel-Verzeichnis
     oder: nur Ziel-Verzeichnis -> alle Aufnahmen verschieben.

=item B<cp, copy>

Aufnahmen und crid-Datei(en) kopieren.

Parameter: crid-Datei(en) UND als letzten Parameter Ziel-Verzeichnis
     oder: nur Ziel-Verzeichnis -> alle Aufnahmen kopieren.

=item B<edit>

Inhalt der crid-Datei editieren.  (noch nicht implementiert)

Parameter: eine crid-Datei

=item B<rm, del>

Aufnahmen und crid-Datei(en) löschen.  (unwiderruflich, aber mit Nachfrage)

Parameter: crid-Datei(en), sonst alle Aufnahmen (aber mit Nachfrage!)

=item B<timeshift>

Evtl. vorhandene Timeshift-Aufnahme abspielen.

Parameter: keine

=item B<check, test>

Überprüfen, ob alle notwendigen Dateien der Aufnahme(n) vorhanden sind.

Parameter: keine oder einzelne crid-Datei(en)

=head1 FILES

Es ist ratsam von der Gigaset Box die Datei
 /var/etc/eps_mapping.txt

nach
 $HOME/movies/gigaset/eps_mapping.txt

zu kopieren, damit C<gigaset> die richtigen Namen der TV-Kanäle anzeigt.
Es sind zwar schon viele Sendernamen einprogrammiert, aber es scheint, dass
sie sich oft regional unterscheiden.  Vor allem die Sender mit den Nummern
ab 10100.

Manchmal stehen ein paar weitere Namen noch in /var/etc/services.txt.

=head1 EXITCODES

B<0>  Alles bestens

Alles andere bedeutet nichts Gutes.


=head1 AUTHOR

Dr. Andy Spiegl <A.Spiegl@kascada.com>

